@pasko70/pibo 3.3.0 → 3.4.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 (237) hide show
  1. package/dist/agent-runtime/capabilities.js +5 -0
  2. package/dist/agent-runtime/contract.js +2 -0
  3. package/dist/agent-runtime/routed-session.js +68 -11
  4. package/dist/agent-runtimes/codex-native/adapter.js +3 -0
  5. package/dist/agent-runtimes/codex-native/thread.js +26 -4
  6. package/dist/agent-runtimes/omp/adapter.js +1 -0
  7. package/dist/agent-runtimes/pi/adapter.js +4 -0
  8. package/dist/agent-runtimes/pi/routed-session.js +53 -5
  9. package/dist/apps/chat/chat-api-routes.js +11 -37
  10. package/dist/apps/chat/chat-request-normalizers.js +8 -48
  11. package/dist/apps/chat/data/legacy-project-migration.js +492 -0
  12. package/dist/apps/chat/data/room-service.js +2 -2
  13. package/dist/apps/chat/data/workflow-session-model.js +1 -0
  14. package/dist/apps/chat/data/workflow-session-service.js +352 -0
  15. package/dist/apps/chat/rooms.js +1 -1
  16. package/dist/apps/chat/types/rooms.js +30 -0
  17. package/dist/apps/chat/web-app.js +264 -647
  18. package/dist/apps/chat/workflow-catalog.js +18 -18
  19. package/dist/apps/chat/{project-workflow-human-actions.js → workflow-human-actions.js} +14 -14
  20. package/dist/apps/chat/workflow-manual-trigger-runtime.js +5 -1
  21. package/dist/apps/chat/workflow-persistence.js +20 -28
  22. package/dist/apps/chat/{project-workflow-sessions.js → workflow-sessions.js} +18 -17
  23. package/dist/apps/chat-ui/assets/{dist-Kc3zbvQi.js → dist-AhtdAETR.js} +1 -1
  24. package/dist/apps/chat-ui/assets/{dist-CcAkJ8iv.js → dist-Bk_L1qbJ.js} +1 -1
  25. package/dist/apps/chat-ui/assets/{dist-BbCjOLiw.js → dist-Dyp6sLQn.js} +1 -1
  26. package/dist/apps/chat-ui/assets/{dist-Bd9gWCbk.js → dist-jVaiGXFD.js} +1 -1
  27. package/dist/apps/chat-ui/assets/{dist-C9a0_446.js → dist-t25moyJS.js} +1 -1
  28. package/dist/apps/chat-ui/assets/index-CZjNw-aK.js +228 -0
  29. package/dist/apps/chat-ui/assets/index-CcEjFITM.css +1 -0
  30. package/dist/apps/chat-ui/index.html +2 -2
  31. package/dist/apps/chat-vscode-web/assets/index-Spj6M0tn.js +43 -0
  32. package/dist/apps/chat-vscode-web/index.html +1 -1
  33. package/dist/compute/pool/cli.js +1 -1
  34. package/dist/compute/pool/seeds.js +2 -3
  35. package/dist/config/config.js +1 -1
  36. package/dist/core/session-router.js +7 -4
  37. package/dist/previews/cli.js +154 -79
  38. package/dist/previews/compute-worker.js +37 -0
  39. package/dist/previews/config.js +2 -1
  40. package/dist/previews/migrations/session-ownership.js +7 -0
  41. package/dist/previews/proxy.js +4 -2
  42. package/dist/previews/public-setup.js +108 -0
  43. package/dist/previews/store.js +20 -13
  44. package/dist/previews/web-app.js +12 -2
  45. package/dist/sessions/workflow-session-kind.js +0 -3
  46. package/npm-shrinkwrap.json +7 -6
  47. package/package.json +6 -2
  48. package/packages/workflows/README.md +21 -0
  49. package/packages/workflows/dist/api/index.d.ts +48 -0
  50. package/packages/workflows/dist/api/index.d.ts.map +1 -0
  51. package/packages/workflows/dist/api/index.js +96 -0
  52. package/packages/workflows/dist/api/index.js.map +1 -0
  53. package/packages/workflows/dist/definition-hash.d.ts +4 -0
  54. package/packages/workflows/dist/definition-hash.d.ts.map +1 -0
  55. package/packages/workflows/dist/definition-hash.js +27 -0
  56. package/packages/workflows/dist/definition-hash.js.map +1 -0
  57. package/packages/workflows/dist/fixtures/index.d.ts +39 -0
  58. package/packages/workflows/dist/fixtures/index.d.ts.map +1 -0
  59. package/packages/workflows/dist/fixtures/index.js +608 -0
  60. package/packages/workflows/dist/fixtures/index.js.map +1 -0
  61. package/packages/workflows/dist/index.d.ts +16 -0
  62. package/packages/workflows/dist/index.d.ts.map +1 -0
  63. package/packages/workflows/dist/index.js +10 -0
  64. package/packages/workflows/dist/index.js.map +1 -0
  65. package/packages/workflows/dist/inspection/index.d.ts +47 -0
  66. package/packages/workflows/dist/inspection/index.d.ts.map +1 -0
  67. package/packages/workflows/dist/inspection/index.js +131 -0
  68. package/packages/workflows/dist/inspection/index.js.map +1 -0
  69. package/packages/workflows/dist/registry/index.d.ts +30 -0
  70. package/packages/workflows/dist/registry/index.d.ts.map +1 -0
  71. package/packages/workflows/dist/registry/index.js +161 -0
  72. package/packages/workflows/dist/registry/index.js.map +1 -0
  73. package/packages/workflows/dist/runtime/adapter-node.d.ts +28 -0
  74. package/packages/workflows/dist/runtime/adapter-node.d.ts.map +1 -0
  75. package/packages/workflows/dist/runtime/adapter-node.js +172 -0
  76. package/packages/workflows/dist/runtime/adapter-node.js.map +1 -0
  77. package/packages/workflows/dist/runtime/agent-node.d.ts +32 -0
  78. package/packages/workflows/dist/runtime/agent-node.d.ts.map +1 -0
  79. package/packages/workflows/dist/runtime/agent-node.js +231 -0
  80. package/packages/workflows/dist/runtime/agent-node.js.map +1 -0
  81. package/packages/workflows/dist/runtime/agent-runtime.d.ts +25 -0
  82. package/packages/workflows/dist/runtime/agent-runtime.d.ts.map +1 -0
  83. package/packages/workflows/dist/runtime/agent-runtime.js +111 -0
  84. package/packages/workflows/dist/runtime/agent-runtime.js.map +1 -0
  85. package/packages/workflows/dist/runtime/code-node.d.ts +31 -0
  86. package/packages/workflows/dist/runtime/code-node.d.ts.map +1 -0
  87. package/packages/workflows/dist/runtime/code-node.js +223 -0
  88. package/packages/workflows/dist/runtime/code-node.js.map +1 -0
  89. package/packages/workflows/dist/runtime/dispatch-failures.d.ts +36 -0
  90. package/packages/workflows/dist/runtime/dispatch-failures.d.ts.map +1 -0
  91. package/packages/workflows/dist/runtime/dispatch-failures.js +80 -0
  92. package/packages/workflows/dist/runtime/dispatch-failures.js.map +1 -0
  93. package/packages/workflows/dist/runtime/edge-payloads.d.ts +3 -0
  94. package/packages/workflows/dist/runtime/edge-payloads.d.ts.map +1 -0
  95. package/packages/workflows/dist/runtime/edge-payloads.js +11 -0
  96. package/packages/workflows/dist/runtime/edge-payloads.js.map +1 -0
  97. package/packages/workflows/dist/runtime/edge-transfer.d.ts +39 -0
  98. package/packages/workflows/dist/runtime/edge-transfer.d.ts.map +1 -0
  99. package/packages/workflows/dist/runtime/edge-transfer.js +356 -0
  100. package/packages/workflows/dist/runtime/edge-transfer.js.map +1 -0
  101. package/packages/workflows/dist/runtime/human-action.d.ts +43 -0
  102. package/packages/workflows/dist/runtime/human-action.d.ts.map +1 -0
  103. package/packages/workflows/dist/runtime/human-action.js +231 -0
  104. package/packages/workflows/dist/runtime/human-action.js.map +1 -0
  105. package/packages/workflows/dist/runtime/human-node.d.ts +27 -0
  106. package/packages/workflows/dist/runtime/human-node.d.ts.map +1 -0
  107. package/packages/workflows/dist/runtime/human-node.js +126 -0
  108. package/packages/workflows/dist/runtime/human-node.js.map +1 -0
  109. package/packages/workflows/dist/runtime/ids.d.ts +2 -0
  110. package/packages/workflows/dist/runtime/ids.d.ts.map +1 -0
  111. package/packages/workflows/dist/runtime/ids.js +4 -0
  112. package/packages/workflows/dist/runtime/ids.js.map +1 -0
  113. package/packages/workflows/dist/runtime/index.d.ts +23 -0
  114. package/packages/workflows/dist/runtime/index.d.ts.map +1 -0
  115. package/packages/workflows/dist/runtime/index.js +12 -0
  116. package/packages/workflows/dist/runtime/index.js.map +1 -0
  117. package/packages/workflows/dist/runtime/manual-trigger.d.ts +51 -0
  118. package/packages/workflows/dist/runtime/manual-trigger.d.ts.map +1 -0
  119. package/packages/workflows/dist/runtime/manual-trigger.js +403 -0
  120. package/packages/workflows/dist/runtime/manual-trigger.js.map +1 -0
  121. package/packages/workflows/dist/runtime/nested-workflow-node.d.ts +48 -0
  122. package/packages/workflows/dist/runtime/nested-workflow-node.d.ts.map +1 -0
  123. package/packages/workflows/dist/runtime/nested-workflow-node.js +246 -0
  124. package/packages/workflows/dist/runtime/nested-workflow-node.js.map +1 -0
  125. package/packages/workflows/dist/runtime/one-node-agent.d.ts +34 -0
  126. package/packages/workflows/dist/runtime/one-node-agent.d.ts.map +1 -0
  127. package/packages/workflows/dist/runtime/one-node-agent.js +334 -0
  128. package/packages/workflows/dist/runtime/one-node-agent.js.map +1 -0
  129. package/packages/workflows/dist/runtime/persistence.d.ts +9 -0
  130. package/packages/workflows/dist/runtime/persistence.d.ts.map +1 -0
  131. package/packages/workflows/dist/runtime/persistence.js +67 -0
  132. package/packages/workflows/dist/runtime/persistence.js.map +1 -0
  133. package/packages/workflows/dist/runtime/pibo-routing.d.ts +126 -0
  134. package/packages/workflows/dist/runtime/pibo-routing.d.ts.map +1 -0
  135. package/packages/workflows/dist/runtime/pibo-routing.js +96 -0
  136. package/packages/workflows/dist/runtime/pibo-routing.js.map +1 -0
  137. package/packages/workflows/dist/runtime/prompts.d.ts +20 -0
  138. package/packages/workflows/dist/runtime/prompts.d.ts.map +1 -0
  139. package/packages/workflows/dist/runtime/prompts.js +235 -0
  140. package/packages/workflows/dist/runtime/prompts.js.map +1 -0
  141. package/packages/workflows/dist/runtime/retry.d.ts +35 -0
  142. package/packages/workflows/dist/runtime/retry.d.ts.map +1 -0
  143. package/packages/workflows/dist/runtime/retry.js +80 -0
  144. package/packages/workflows/dist/runtime/retry.js.map +1 -0
  145. package/packages/workflows/dist/runtime/state.d.ts +14 -0
  146. package/packages/workflows/dist/runtime/state.d.ts.map +1 -0
  147. package/packages/workflows/dist/runtime/state.js +139 -0
  148. package/packages/workflows/dist/runtime/state.js.map +1 -0
  149. package/packages/workflows/dist/runtime/time.d.ts +6 -0
  150. package/packages/workflows/dist/runtime/time.d.ts.map +1 -0
  151. package/packages/workflows/dist/runtime/time.js +42 -0
  152. package/packages/workflows/dist/runtime/time.js.map +1 -0
  153. package/packages/workflows/dist/store/catalog-records.d.ts +18 -0
  154. package/packages/workflows/dist/store/catalog-records.d.ts.map +1 -0
  155. package/packages/workflows/dist/store/catalog-records.js +59 -0
  156. package/packages/workflows/dist/store/catalog-records.js.map +1 -0
  157. package/packages/workflows/dist/store/contracts.d.ts +149 -0
  158. package/packages/workflows/dist/store/contracts.d.ts.map +1 -0
  159. package/packages/workflows/dist/store/contracts.js +2 -0
  160. package/packages/workflows/dist/store/contracts.js.map +1 -0
  161. package/packages/workflows/dist/store/draft-writes.d.ts +7 -0
  162. package/packages/workflows/dist/store/draft-writes.d.ts.map +1 -0
  163. package/packages/workflows/dist/store/draft-writes.js +18 -0
  164. package/packages/workflows/dist/store/draft-writes.js.map +1 -0
  165. package/packages/workflows/dist/store/index.d.ts +55 -0
  166. package/packages/workflows/dist/store/index.d.ts.map +1 -0
  167. package/packages/workflows/dist/store/index.js +650 -0
  168. package/packages/workflows/dist/store/index.js.map +1 -0
  169. package/packages/workflows/dist/store/list-query.d.ts +13 -0
  170. package/packages/workflows/dist/store/list-query.d.ts.map +1 -0
  171. package/packages/workflows/dist/store/list-query.js +20 -0
  172. package/packages/workflows/dist/store/list-query.js.map +1 -0
  173. package/packages/workflows/dist/store/row-mappers.d.ts +206 -0
  174. package/packages/workflows/dist/store/row-mappers.d.ts.map +1 -0
  175. package/packages/workflows/dist/store/row-mappers.js +262 -0
  176. package/packages/workflows/dist/store/row-mappers.js.map +1 -0
  177. package/packages/workflows/dist/store/schema.d.ts +13 -0
  178. package/packages/workflows/dist/store/schema.d.ts.map +1 -0
  179. package/packages/workflows/dist/store/schema.js +458 -0
  180. package/packages/workflows/dist/store/schema.js.map +1 -0
  181. package/packages/workflows/dist/store/write-values.d.ts +18 -0
  182. package/packages/workflows/dist/store/write-values.d.ts.map +1 -0
  183. package/packages/workflows/dist/store/write-values.js +227 -0
  184. package/packages/workflows/dist/store/write-values.js.map +1 -0
  185. package/packages/workflows/dist/types/index.d.ts +1202 -0
  186. package/packages/workflows/dist/types/index.d.ts.map +1 -0
  187. package/packages/workflows/dist/types/index.js +8 -0
  188. package/packages/workflows/dist/types/index.js.map +1 -0
  189. package/packages/workflows/dist/validation/diagnostics.d.ts +27 -0
  190. package/packages/workflows/dist/validation/diagnostics.d.ts.map +1 -0
  191. package/packages/workflows/dist/validation/diagnostics.js +104 -0
  192. package/packages/workflows/dist/validation/diagnostics.js.map +1 -0
  193. package/packages/workflows/dist/validation/graph-cycles.d.ts +4 -0
  194. package/packages/workflows/dist/validation/graph-cycles.d.ts.map +1 -0
  195. package/packages/workflows/dist/validation/graph-cycles.js +130 -0
  196. package/packages/workflows/dist/validation/graph-cycles.js.map +1 -0
  197. package/packages/workflows/dist/validation/graph-edges.d.ts +5 -0
  198. package/packages/workflows/dist/validation/graph-edges.d.ts.map +1 -0
  199. package/packages/workflows/dist/validation/graph-edges.js +48 -0
  200. package/packages/workflows/dist/validation/graph-edges.js.map +1 -0
  201. package/packages/workflows/dist/validation/graph-ports.d.ts +5 -0
  202. package/packages/workflows/dist/validation/graph-ports.d.ts.map +1 -0
  203. package/packages/workflows/dist/validation/graph-ports.js +55 -0
  204. package/packages/workflows/dist/validation/graph-ports.js.map +1 -0
  205. package/packages/workflows/dist/validation/index.d.ts +18 -0
  206. package/packages/workflows/dist/validation/index.d.ts.map +1 -0
  207. package/packages/workflows/dist/validation/index.js +175 -0
  208. package/packages/workflows/dist/validation/index.js.map +1 -0
  209. package/packages/workflows/dist/validation/json-schema.d.ts +12 -0
  210. package/packages/workflows/dist/validation/json-schema.d.ts.map +1 -0
  211. package/packages/workflows/dist/validation/json-schema.js +556 -0
  212. package/packages/workflows/dist/validation/json-schema.js.map +1 -0
  213. package/packages/workflows/dist/validation/registry-refs.d.ts +21 -0
  214. package/packages/workflows/dist/validation/registry-refs.d.ts.map +1 -0
  215. package/packages/workflows/dist/validation/registry-refs.js +283 -0
  216. package/packages/workflows/dist/validation/registry-refs.js.map +1 -0
  217. package/packages/workflows/dist/validation/retry-policy.d.ts +3 -0
  218. package/packages/workflows/dist/validation/retry-policy.d.ts.map +1 -0
  219. package/packages/workflows/dist/validation/retry-policy.js +114 -0
  220. package/packages/workflows/dist/validation/retry-policy.js.map +1 -0
  221. package/packages/workflows/dist/validation/schema-declarations.d.ts +5 -0
  222. package/packages/workflows/dist/validation/schema-declarations.d.ts.map +1 -0
  223. package/packages/workflows/dist/validation/schema-declarations.js +36 -0
  224. package/packages/workflows/dist/validation/schema-declarations.js.map +1 -0
  225. package/packages/workflows/dist/validation/state-access.d.ts +4 -0
  226. package/packages/workflows/dist/validation/state-access.d.ts.map +1 -0
  227. package/packages/workflows/dist/validation/state-access.js +121 -0
  228. package/packages/workflows/dist/validation/state-access.js.map +1 -0
  229. package/packages/workflows/dist/xstate/index.d.ts +73 -0
  230. package/packages/workflows/dist/xstate/index.d.ts.map +1 -0
  231. package/packages/workflows/dist/xstate/index.js +728 -0
  232. package/packages/workflows/dist/xstate/index.js.map +1 -0
  233. package/packages/workflows/package.json +23 -0
  234. package/dist/apps/chat/data/project-service.js +0 -1029
  235. package/dist/apps/chat-ui/assets/index-DWJnUye8.css +0 -1
  236. package/dist/apps/chat-ui/assets/index-exohEOUE.js +0 -228
  237. package/dist/apps/chat-vscode-web/assets/index-D1bpAUkR.js +0 -43
@@ -5,7 +5,7 @@
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
6
  <meta name="theme-color" content="#101d22" />
7
7
  <title>Pibo</title>
8
- <script type="module" crossorigin src="/apps/chat-vscode/assets/index-D1bpAUkR.js"></script>
8
+ <script type="module" crossorigin src="/apps/chat-vscode/assets/index-Spj6M0tn.js"></script>
9
9
  <link rel="stylesheet" crossorigin href="/apps/chat-vscode/assets/index-b18ZkEo0.css">
10
10
  </head>
11
11
  <body>
@@ -149,7 +149,7 @@ Provide exactly one source:
149
149
  program.command("seed")
150
150
  .action(() => console.log(`Deployment seed modes:
151
151
  full Nearly complete Pibo home plus configured workspace; excludes active runtime, lock, browser, debug, and pool state.
152
- medium Operational config, selected product databases, projects, contexts, agents, and user skills; excludes heavy payload/tool/browser/debug state.
152
+ medium Operational config, selected product and workflow databases, contexts, agents, and user skills; excludes heavy payload/tool/browser/debug state.
153
153
  fresh Operational config, Google/Machine auth configuration, model defaults, contexts, and user skills; no existing product databases.
154
154
  `));
155
155
  await program.parseAsync(argv);
@@ -35,11 +35,10 @@ const MEDIUM_ALLOWED_TOP_LEVEL = new Set([
35
35
  "chat-agents.sqlite",
36
36
  "pibo.sqlite",
37
37
  "pibo-events.sqlite",
38
- "web-projects.sqlite",
38
+ "pibo-workflows.sqlite",
39
39
  "web-annotations.sqlite",
40
40
  "context-files",
41
41
  "user-skills",
42
- "projects",
43
42
  ]);
44
43
  const FRESH_ALLOWED_TOP_LEVEL = new Set([
45
44
  "config.json",
@@ -137,7 +136,7 @@ async function backupSeedDatabases(sourceHome, destinationHome, mode) {
137
136
  if (mode === "fresh")
138
137
  return [];
139
138
  const allowed = mode === "medium"
140
- ? new Set(["chat-agents.sqlite", "pibo.sqlite", "pibo-events.sqlite", "web-projects.sqlite", "web-annotations.sqlite"])
139
+ ? new Set(["chat-agents.sqlite", "pibo.sqlite", "pibo-events.sqlite", "pibo-workflows.sqlite", "web-annotations.sqlite"])
141
140
  : undefined;
142
141
  const copied = [];
143
142
  for (const entry of await readdir(sourceHome, { withFileTypes: true })) {
@@ -59,7 +59,7 @@ export const PIBO_CONFIG_KEYS = [
59
59
  {
60
60
  key: "preview.baseURL",
61
61
  type: "string",
62
- description: "Public preview base URL. Preview ids become subdomains, for example https://preview.example.com.",
62
+ description: "Public Preview base URL. Run `pibo preview setup --base-url https://preview.example.com` before enabling it.",
63
63
  },
64
64
  {
65
65
  key: "preview.databasePath",
@@ -231,7 +231,6 @@ function derivedSessionMetadata(value) {
231
231
  for (const key of [
232
232
  DERIVED_SESSION_RECONCILIATION_METADATA_KEY,
233
233
  "workflowSessionKind",
234
- "projectSessionKind",
235
234
  "subagentName",
236
235
  "subagentToolName",
237
236
  "agentStatus",
@@ -1692,9 +1691,13 @@ export class PiboSessionRouter {
1692
1691
  catch (error) {
1693
1692
  transitionError = error;
1694
1693
  }
1695
- // Always discard the source live handle after derivation. Bound native
1696
- // branches move that handle to the derived session; first-message Codex
1697
- // branches intentionally leave it on the source and persist unbound.
1694
+ if (result.sourceSessionUnchanged) {
1695
+ if (transitionError)
1696
+ throw transitionError;
1697
+ return;
1698
+ }
1699
+ // Identity-moving forks and clones transfer the live native handle to the
1700
+ // derived session, so the source must be reopened from its persisted binding.
1698
1701
  try {
1699
1702
  await this.resetCachedSession(event.piboSessionId);
1700
1703
  }
@@ -2,11 +2,13 @@ import { randomBytes } from "node:crypto";
2
2
  import { resolve } from "node:path";
3
3
  import { Command } from "commander";
4
4
  import { createDefaultPiboDataSessionStore } from "../sessions/pibo-data-store.js";
5
+ import { resolvePreviewComputeWorkerTarget } from "./compute-worker.js";
5
6
  import { DEFAULT_PREVIEW_TTL_MINUTES, loadEffectivePreviewServerSettings, loadPreviewConfig, previewPublicURL, requirePreviewBaseURL } from "./config.js";
6
7
  import { reconcileManagedPreviews, startManagedPreview, stopManagedPreview, validatePreviewStartCommand, } from "./manager.js";
7
8
  import { findPreviewTargetProcess, isPiboControlProcess, isPiboYieldedProcess, isPreviewTargetProcessCurrent, probePreviewTarget, validatePreviewPort, } from "./network.js";
8
9
  import { createDefaultPreviewStore, previewExposureState } from "./store.js";
9
10
  import { DEFAULT_MAX_PREVIEW_PROXY_CONNECTIONS, DEFAULT_MAX_PREVIEW_PROXY_CONNECTIONS_PER_PREVIEW, } from "./proxy.js";
11
+ import { createPreviewProductionSetupPlan, inspectPreviewPublicRoute } from "./public-setup.js";
10
12
  function printJson(value) {
11
13
  console.log(JSON.stringify(value, null, 2));
12
14
  }
@@ -19,6 +21,12 @@ function parsePositiveInteger(value) {
19
21
  throw new Error("Value must be a positive integer");
20
22
  return parsed;
21
23
  }
24
+ function parseGatewayPort(value) {
25
+ const parsed = Number(value);
26
+ if (!Number.isInteger(parsed) || parsed < 1 || parsed > 65535)
27
+ throw new Error("Gateway port must be an integer between 1 and 65535");
28
+ return parsed;
29
+ }
22
30
  function parsePreviewTtlMinutes(value) {
23
31
  const parsed = parsePositiveInteger(value);
24
32
  if (parsed > 7 * 24 * 60)
@@ -28,12 +36,6 @@ function parsePreviewTtlMinutes(value) {
28
36
  function createPreviewId() {
29
37
  return `pv-${randomBytes(9).toString("hex")}`;
30
38
  }
31
- function inferredProjectId(metadata) {
32
- if (!metadata || typeof metadata !== "object" || Array.isArray(metadata))
33
- return undefined;
34
- const projectId = metadata.projectId;
35
- return typeof projectId === "string" && projectId.trim() ? projectId.trim() : undefined;
36
- }
37
39
  async function previewHealth(exposure) {
38
40
  const state = previewExposureState(exposure);
39
41
  if (state !== "active")
@@ -64,23 +66,117 @@ async function exposureView(exposure) {
64
66
  ...(target ? { latencyMs: target.latencyMs } : {}),
65
67
  };
66
68
  }
69
+ async function createPreviewExposure(port, options, defaults = {}) {
70
+ const baseURL = requirePreviewBaseURL();
71
+ const piboSessionId = options.session.trim();
72
+ if (!piboSessionId)
73
+ throw new Error("--session must contain a Pibo Session ID");
74
+ const sessionStore = createDefaultPiboDataSessionStore();
75
+ let session;
76
+ try {
77
+ session = sessionStore.get(piboSessionId);
78
+ }
79
+ finally {
80
+ sessionStore.close();
81
+ }
82
+ if (!session)
83
+ throw new Error(`Pibo Session "${piboSessionId}" was not found`);
84
+ const command = options.command === undefined ? undefined : validatePreviewStartCommand(options.command);
85
+ const target = command ? undefined : await probePreviewTarget(port);
86
+ if (!command && !target)
87
+ throw new Error(`No development server is reachable on loopback port ${port}`);
88
+ if (command && await probePreviewTarget(port, { timeoutMs: 150 }))
89
+ throw new Error(`Preview port ${port} is already occupied`);
90
+ const now = new Date();
91
+ const id = createPreviewId();
92
+ const targetProcess = target ? findPreviewTargetProcess(target.host, port) : undefined;
93
+ if (!command && process.platform === "linux" && !targetProcess) {
94
+ throw new Error(`Preview port ${port} is not bound exclusively to the selected loopback address`);
95
+ }
96
+ if (!command && targetProcess && isPiboControlProcess(targetProcess.pid)) {
97
+ throw new Error(`Preview port ${port} belongs to a Pibo control process and cannot be exposed`);
98
+ }
99
+ if (!command && process.env.NODE_ENV !== "test" && targetProcess && isPiboYieldedProcess(targetProcess.pid)) {
100
+ throw new Error("Preview servers cannot run as yielded agent resources; stop that process and use --command so Preview owns its lifecycle");
101
+ }
102
+ const store = createDefaultPreviewStore();
103
+ let exposure;
104
+ try {
105
+ exposure = store.createExposure({
106
+ id,
107
+ piboSessionId,
108
+ label: options.name?.trim() || defaults.label || `Preview ${port}`,
109
+ targetHost: target?.host ?? "127.0.0.1",
110
+ targetPort: port,
111
+ targetProcessId: targetProcess?.pid,
112
+ targetProcessStartTicks: targetProcess?.startTicks,
113
+ workspace: resolve(options.workspace ?? defaults.workspace ?? session.workspace ?? process.cwd()),
114
+ managementMode: command ? "managed" : "external",
115
+ proxyMode: defaults.proxyMode,
116
+ startCommand: command,
117
+ serverState: command ? "stopped" : undefined,
118
+ createdAt: now.toISOString(),
119
+ expiresAt: new Date(now.getTime() + options.ttlMinutes * 60_000).toISOString(),
120
+ });
121
+ if (command)
122
+ exposure = await startManagedPreview(store, id);
123
+ }
124
+ finally {
125
+ store.close();
126
+ }
127
+ return { ...await exposureView(exposure), publicUrl: previewPublicURL(id, baseURL).toString() };
128
+ }
129
+ function printCreatedPreview(result, json = false) {
130
+ if (json) {
131
+ printJson(result);
132
+ return;
133
+ }
134
+ console.log(`${result.id}\t${result.health}\t${result.label}`);
135
+ console.log(`url\t${result.publicUrl}`);
136
+ console.log(`session\t${result.piboSessionId}`);
137
+ console.log(`managed\t${result.managed}`);
138
+ console.log(`proxyMode\t${result.proxyMode}`);
139
+ if (result.serverStopAt)
140
+ console.log(`autoStop\t${result.serverStopAt}`);
141
+ console.log(`expires\t${result.expiresAt}`);
142
+ }
67
143
  function printPreviewDiscovery() {
68
144
  console.log(`pibo preview - Session-linked live development previews
69
145
 
70
146
  Commands:
147
+ setup Print production DNS, TLS, proxy, and restart instructions
71
148
  expose <port> Register an external port or start a managed server with --command
149
+ expose-worker <id> Register a running Pibo compute worker with local dev auth
72
150
  list List preview registrations
73
151
  show <preview-id> Inspect one preview
74
152
  start <preview-id> Start a saved managed Preview server
75
153
  stop <preview-id> Stop a managed Preview server without removing it
76
- doctor [preview-id] Check preview configuration and reachability
154
+ doctor [preview-id] Check local state; add --public to verify DNS, TLS, and routing
77
155
  remove <preview-id> Stop and remove a preview definition
78
156
  close <preview-id> Alias for remove
79
157
 
80
158
  Next:
159
+ pibo preview setup --help
81
160
  pibo preview expose --help
161
+ pibo preview expose-worker --help
82
162
  `);
83
163
  }
164
+ function printProductionSetupPlan(plan) {
165
+ console.log("Preview production setup");
166
+ console.log(`baseURL\t${plan.baseURL}`);
167
+ console.log(`DNS\t${plan.dnsRecord.type} ${plan.dnsRecord.name} -> ${plan.dnsRecord.value}`);
168
+ console.log("\nCaddy global options (merge into an existing global block):");
169
+ console.log(plan.caddy.globalOptions);
170
+ console.log("\nCaddy site block:");
171
+ console.log(plan.caddy.siteBlock);
172
+ console.log("\nCommands:");
173
+ console.log(`configure\t${plan.commands.configure}`);
174
+ console.log(`validate proxy\t${plan.commands.validateCaddy}`);
175
+ console.log(`restart gateway\t${plan.commands.restartGateway}`);
176
+ console.log(`verify\t${plan.commands.verify}`);
177
+ for (const warning of plan.warnings)
178
+ console.log(`warning\t${warning}`);
179
+ }
84
180
  export async function runPreviewCli(argv = process.argv) {
85
181
  if (argv.length <= 2 || argv[2] === "--help" || argv[2] === "-h") {
86
182
  printPreviewDiscovery();
@@ -88,86 +184,51 @@ export async function runPreviewCli(argv = process.argv) {
88
184
  }
89
185
  const program = new Command();
90
186
  program.name("pibo preview").description("Manage session-linked live development previews");
187
+ program
188
+ .command("setup")
189
+ .requiredOption("--base-url <url>", "Dedicated HTTPS base URL whose subdomains host Previews")
190
+ .option("--gateway-port <port>", "Loopback Pibo Web Gateway port", parseGatewayPort, 4788)
191
+ .option("--public-ip <address>", "Public IPv4 or IPv6 address for the exact DNS record")
192
+ .option("--json", "Print JSON")
193
+ .action((options) => {
194
+ const plan = createPreviewProductionSetupPlan({
195
+ baseURL: options.baseUrl,
196
+ gatewayPort: options.gatewayPort,
197
+ publicIp: options.publicIp,
198
+ });
199
+ if (options.json)
200
+ printJson(plan);
201
+ else
202
+ printProductionSetupPlan(plan);
203
+ });
91
204
  program
92
205
  .command("expose")
93
206
  .argument("<port>", "Reachable loopback development port", parsePort)
94
207
  .requiredOption("--session <pibo-session-id>", "Pibo Session that owns the preview")
95
- .option("--project <project-id>", "Optional Project association")
96
208
  .option("--name <label>", "Preview label")
97
209
  .option("--workspace <path>", "Workspace used by the managed command and recorded for diagnostics")
98
210
  .option("--command <shell-command>", "Save and start this command as a Preview-managed server")
99
211
  .option("--ttl-minutes <minutes>", "Automatic preview-definition lifetime", parsePreviewTtlMinutes, DEFAULT_PREVIEW_TTL_MINUTES)
100
212
  .option("--json", "Print JSON")
101
213
  .action(async (port, options) => {
102
- const baseURL = requirePreviewBaseURL();
103
- const piboSessionId = options.session.trim();
104
- if (!piboSessionId)
105
- throw new Error("--session must contain a Pibo Session ID");
106
- const sessionStore = createDefaultPiboDataSessionStore();
107
- let session;
108
- try {
109
- session = sessionStore.get(piboSessionId);
110
- }
111
- finally {
112
- sessionStore.close();
113
- }
114
- if (!session)
115
- throw new Error(`Pibo Session "${piboSessionId}" was not found`);
116
- const command = options.command === undefined ? undefined : validatePreviewStartCommand(options.command);
117
- let target = command ? undefined : await probePreviewTarget(port);
118
- if (!command && !target)
119
- throw new Error(`No development server is reachable on loopback port ${port}`);
120
- if (command && await probePreviewTarget(port, { timeoutMs: 150 }))
121
- throw new Error(`Preview port ${port} is already occupied`);
122
- const now = new Date();
123
- const id = createPreviewId();
124
- const targetProcess = target ? findPreviewTargetProcess(target.host, port) : undefined;
125
- if (!command && process.platform === "linux" && !targetProcess) {
126
- throw new Error(`Preview port ${port} is not bound exclusively to the selected loopback address`);
127
- }
128
- if (!command && targetProcess && isPiboControlProcess(targetProcess.pid)) {
129
- throw new Error(`Preview port ${port} belongs to a Pibo control process and cannot be exposed`);
130
- }
131
- if (!command && process.env.NODE_ENV !== "test" && targetProcess && isPiboYieldedProcess(targetProcess.pid)) {
132
- throw new Error("Preview servers cannot run as yielded agent resources; stop that process and use --command so Preview owns its lifecycle");
133
- }
134
- const store = createDefaultPreviewStore();
135
- let exposure;
136
- try {
137
- exposure = store.createExposure({
138
- id,
139
- piboSessionId,
140
- projectId: options.project?.trim() || inferredProjectId(session.metadata),
141
- label: options.name?.trim() || `Preview ${port}`,
142
- targetHost: target?.host ?? "127.0.0.1",
143
- targetPort: port,
144
- targetProcessId: targetProcess?.pid,
145
- targetProcessStartTicks: targetProcess?.startTicks,
146
- workspace: resolve(options.workspace ?? session.workspace ?? process.cwd()),
147
- managementMode: command ? "managed" : "external",
148
- startCommand: command,
149
- serverState: command ? "stopped" : undefined,
150
- createdAt: now.toISOString(),
151
- expiresAt: new Date(now.getTime() + options.ttlMinutes * 60_000).toISOString(),
152
- });
153
- if (command)
154
- exposure = await startManagedPreview(store, id);
155
- }
156
- finally {
157
- store.close();
158
- }
159
- const result = { ...await exposureView(exposure), publicUrl: previewPublicURL(id, baseURL).toString() };
160
- if (options.json)
161
- printJson(result);
162
- else {
163
- console.log(`${result.id}\t${result.health}\t${result.label}`);
164
- console.log(`url\t${result.publicUrl}`);
165
- console.log(`session\t${result.piboSessionId}`);
166
- console.log(`managed\t${result.managed}`);
167
- if (result.serverStopAt)
168
- console.log(`autoStop\t${result.serverStopAt}`);
169
- console.log(`expires\t${result.expiresAt}`);
170
- }
214
+ printCreatedPreview(await createPreviewExposure(port, options), options.json);
215
+ });
216
+ program
217
+ .command("expose-worker")
218
+ .argument("<worker>", "Running Pibo compute worker name or id")
219
+ .requiredOption("--session <pibo-session-id>", "Pibo Session that owns the preview")
220
+ .option("--name <label>", "Preview label")
221
+ .option("--workspace <path>", "Workspace recorded for diagnostics")
222
+ .option("--ttl-minutes <minutes>", "Automatic preview-definition lifetime", parsePreviewTtlMinutes, DEFAULT_PREVIEW_TTL_MINUTES)
223
+ .option("--json", "Print JSON")
224
+ .action(async (workerSelector, options) => {
225
+ const worker = await resolvePreviewComputeWorkerTarget(workerSelector);
226
+ const result = await createPreviewExposure(worker.webPort, options, {
227
+ label: worker.name,
228
+ workspace: worker.worktreePath,
229
+ proxyMode: "pibo-compute-dev-auth",
230
+ });
231
+ printCreatedPreview(result, options.json);
171
232
  });
172
233
  program
173
234
  .command("list")
@@ -258,9 +319,12 @@ export async function runPreviewCli(argv = process.argv) {
258
319
  program
259
320
  .command("doctor")
260
321
  .argument("[preview-id]")
322
+ .option("--public", "Verify public DNS, trusted TLS, and Preview-host routing")
261
323
  .option("--json", "Print JSON")
262
324
  .action(async (id, options) => {
263
325
  const baseURL = requirePreviewBaseURL();
326
+ if (options.public && !id)
327
+ throw new Error("--public requires a preview id. Create an active Preview, then run `pibo preview doctor <preview-id> --public`.");
264
328
  if (!id) {
265
329
  const config = loadPreviewConfig();
266
330
  const serverLimits = loadEffectivePreviewServerSettings();
@@ -277,6 +341,7 @@ export async function runPreviewCli(argv = process.argv) {
277
341
  const result = {
278
342
  configured: true,
279
343
  baseURL: baseURL.toString(),
344
+ productionSetup: createPreviewProductionSetupPlan({ baseURL: baseURL.toString() }),
280
345
  limits: {
281
346
  ...serverLimits,
282
347
  maxProxyConnections: config.maxProxyConnections ?? DEFAULT_MAX_PREVIEW_PROXY_CONNECTIONS,
@@ -292,6 +357,8 @@ export async function runPreviewCli(argv = process.argv) {
292
357
  console.log(`baseURL\t${result.baseURL}`);
293
358
  console.log(`active\t${diagnostics.activeExposures}`);
294
359
  console.log(`managed\t${diagnostics.managedStartingOrRunning}`);
360
+ console.log(`DNS required\t${result.productionSetup.dnsRecord.name}`);
361
+ console.log(`production guide\tpibo preview setup --base-url ${baseURL.origin}`);
295
362
  }
296
363
  return;
297
364
  }
@@ -304,15 +371,23 @@ export async function runPreviewCli(argv = process.argv) {
304
371
  finally {
305
372
  store.close();
306
373
  }
307
- const result = await exposureView(exposure);
374
+ const publicChecks = options.public ? await inspectPreviewPublicRoute(baseURL, id) : undefined;
375
+ const result = {
376
+ ...await exposureView(exposure),
377
+ ...(publicChecks ? { publicChecks } : {}),
378
+ };
308
379
  if (options.json)
309
380
  printJson(result);
310
381
  else {
311
382
  console.log(`${result.id}\t${result.health}`);
312
383
  console.log(`target\t${result.targetHost}:${result.targetPort}`);
313
384
  console.log(`url\t${result.publicUrl}`);
385
+ if (publicChecks) {
386
+ console.log(`DNS\t${publicChecks.dns.status}\t${publicChecks.dns.detail}`);
387
+ console.log(`TLS and routing\t${publicChecks.tlsAndRouting.status}\t${publicChecks.tlsAndRouting.detail}`);
388
+ }
314
389
  }
315
- if (result.health !== "online")
390
+ if (result.health !== "online" || publicChecks && (publicChecks.dns.status !== "ok" || publicChecks.tlsAndRouting.status !== "ok"))
316
391
  process.exitCode = 1;
317
392
  });
318
393
  const removeAction = async (id, options) => {
@@ -0,0 +1,37 @@
1
+ import { listWorkers } from "../compute/docker.js";
2
+ import { validatePreviewPort } from "./network.js";
3
+ export function selectPreviewComputeWorkerTarget(workers, selector) {
4
+ const normalized = selector.trim();
5
+ if (!normalized)
6
+ throw new Error("Compute worker name or id is required");
7
+ const worker = workers.find((candidate) => candidate.name === normalized || candidate.id === normalized);
8
+ if (!worker)
9
+ throw new Error(`Pibo compute worker "${normalized}" was not found`);
10
+ if (worker.role !== "worker" && worker.role !== "dev")
11
+ throw new Error(`Pibo compute target "${worker.name}" is not a managed worker`);
12
+ if (worker.state !== "running")
13
+ throw new Error(`Pibo compute worker "${worker.name}" is not running`);
14
+ const webPort = Number(worker.portMap.web);
15
+ if (!Number.isInteger(webPort))
16
+ throw new Error(`Pibo compute worker "${worker.name}" does not publish a labeled Web port`);
17
+ const bindings = worker.portMap["4788/tcp"]?.split(",") ?? [];
18
+ if (!bindings.includes(`127.0.0.1:${webPort}`)) {
19
+ throw new Error(`Pibo compute worker "${worker.name}" Web port is not published on host loopback`);
20
+ }
21
+ return {
22
+ id: worker.id,
23
+ name: worker.name,
24
+ webPort: validatePreviewPort(webPort),
25
+ worktreePath: worker.worktreePath,
26
+ };
27
+ }
28
+ export async function resolvePreviewComputeWorkerTarget(selector) {
29
+ let workers;
30
+ try {
31
+ workers = await listWorkers({ all: true });
32
+ }
33
+ catch (error) {
34
+ throw new Error(`Unable to inspect Pibo compute workers: ${error instanceof Error ? error.message : String(error)}`);
35
+ }
36
+ return selectPreviewComputeWorkerTarget(workers, selector);
37
+ }
@@ -5,6 +5,7 @@ import { parsePreviewBaseURL } from "./base-url.js";
5
5
  export const DEFAULT_PREVIEW_TTL_MINUTES = 8 * 60;
6
6
  export const DEFAULT_PREVIEW_TICKET_TTL_SECONDS = 60;
7
7
  export const DEFAULT_PREVIEW_SESSION_TTL_MINUTES = 8 * 60;
8
+ export const PREVIEW_TLS_AUTHORIZATION_PATH = "/api/previews/tls-authorize";
8
9
  export function loadPreviewConfig() {
9
10
  return loadPiboConfig().preview ?? {};
10
11
  }
@@ -18,7 +19,7 @@ export function loadEffectivePreviewServerSettings() {
18
19
  }
19
20
  export function requirePreviewBaseURL(value = loadPreviewConfig().baseURL) {
20
21
  if (!value)
21
- throw new Error("preview.baseURL is required. Set it with `pibo config set preview.baseURL https://preview.example.com`.");
22
+ throw new Error("preview.baseURL is required. Run `pibo preview setup --base-url https://preview.example.com` for DNS, TLS, proxy, and restart instructions.");
22
23
  return parsePreviewBaseURL(value);
23
24
  }
24
25
  export function previewPublicURL(previewId, baseURL = requirePreviewBaseURL()) {
@@ -0,0 +1,7 @@
1
+ /** One-time v4 upgrade: session identity already owns every exposure and authorization. */
2
+ export function migratePreviewSessionOwnership(db) {
3
+ const columns = db.prepare("PRAGMA table_info(preview_exposures)").all();
4
+ if (columns.some((column) => column.name === "project_id")) {
5
+ db.exec("ALTER TABLE preview_exposures DROP COLUMN project_id");
6
+ }
7
+ }
@@ -154,8 +154,10 @@ function requestHeaders(incoming, exposure, requestURL, websocket) {
154
154
  }
155
155
  const targetHost = exposure.targetHost === "::1" ? `[::1]` : exposure.targetHost;
156
156
  headers.host = `${targetHost}:${exposure.targetPort}`;
157
- headers["x-forwarded-host"] = requestURL.host;
158
- headers["x-forwarded-proto"] = requestURL.protocol.slice(0, -1);
157
+ if (exposure.proxyMode !== "pibo-compute-dev-auth") {
158
+ headers["x-forwarded-host"] = requestURL.host;
159
+ headers["x-forwarded-proto"] = requestURL.protocol.slice(0, -1);
160
+ }
159
161
  if (websocket) {
160
162
  headers.connection = "Upgrade";
161
163
  headers.upgrade = incoming.upgrade ?? "websocket";
@@ -0,0 +1,108 @@
1
+ import { lookup } from "node:dns/promises";
2
+ import { isIP } from "node:net";
3
+ import { PREVIEW_TLS_AUTHORIZATION_PATH, previewPublicURL, requirePreviewBaseURL } from "./config.js";
4
+ function shellQuote(value) {
5
+ return `'${value.replace(/'/g, `'\\''`)}'`;
6
+ }
7
+ function validateGatewayPort(port) {
8
+ if (!Number.isInteger(port) || port < 1 || port > 65535)
9
+ throw new Error("Gateway port must be an integer between 1 and 65535");
10
+ return port;
11
+ }
12
+ function validatePublicIp(value) {
13
+ if (!value)
14
+ return { type: "A or AAAA", value: "<public-gateway-ip>" };
15
+ const family = isIP(value);
16
+ if (family === 0)
17
+ throw new Error("Public IP must be a valid IPv4 or IPv6 address");
18
+ return { type: family === 4 ? "A" : "AAAA", value };
19
+ }
20
+ export function createPreviewProductionSetupPlan(input) {
21
+ const baseURL = requirePreviewBaseURL(input.baseURL);
22
+ const gatewayPort = validateGatewayPort(input.gatewayPort ?? 4788);
23
+ const publicIp = validatePublicIp(input.publicIp);
24
+ const wildcardHostname = `*.${baseURL.hostname}`;
25
+ const caddySiteAddress = baseURL.port ? `${wildcardHostname}:${baseURL.port}` : wildcardHostname;
26
+ const tlsAuthorizationURL = `http://127.0.0.1:${gatewayPort}${PREVIEW_TLS_AUTHORIZATION_PATH}`;
27
+ return {
28
+ baseURL: baseURL.toString(),
29
+ wildcardHostname,
30
+ dnsRecord: {
31
+ type: publicIp.type,
32
+ name: wildcardHostname,
33
+ value: publicIp.value,
34
+ },
35
+ gateway: {
36
+ host: "127.0.0.1",
37
+ port: gatewayPort,
38
+ tlsAuthorizationURL,
39
+ },
40
+ caddy: {
41
+ globalOptions: `on_demand_tls {\n\task ${tlsAuthorizationURL}\n}`,
42
+ siteBlock: `${caddySiteAddress} {\n\ttls {\n\t\ton_demand\n\t}\n\treverse_proxy 127.0.0.1:${gatewayPort}\n}`,
43
+ },
44
+ commands: {
45
+ configure: `pibo config set preview.baseURL ${shellQuote(baseURL.origin)}`,
46
+ restartGateway: "pibo gateway web restart",
47
+ validateCaddy: "caddy validate --config /etc/caddy/Caddyfile && systemctl reload caddy",
48
+ verify: "pibo preview doctor <preview-id> --public",
49
+ },
50
+ warnings: [
51
+ "Merge the on_demand_tls stanza into an existing Caddy global options block; a Caddyfile may contain only one global options block.",
52
+ "Create the wildcard DNS record before Caddy requests the first Preview certificate.",
53
+ "Restart the Pibo gateway after setting preview.baseURL; the safe restart may wait for active sessions.",
54
+ ],
55
+ };
56
+ }
57
+ async function resolveHostname(hostname) {
58
+ return (await lookup(hostname, { all: true })).map((entry) => entry.address);
59
+ }
60
+ async function requestPreview(url) {
61
+ const response = await fetch(url, {
62
+ method: "GET",
63
+ redirect: "manual",
64
+ signal: AbortSignal.timeout(10_000),
65
+ });
66
+ return { status: response.status, location: response.headers.get("location") ?? undefined };
67
+ }
68
+ function errorDetail(error) {
69
+ return error instanceof Error ? error.message : String(error);
70
+ }
71
+ export async function inspectPreviewPublicRoute(baseURLValue, previewId, options = {}) {
72
+ const baseURL = requirePreviewBaseURL(baseURLValue instanceof URL ? baseURLValue.toString() : baseURLValue);
73
+ const publicURL = previewPublicURL(previewId, baseURL);
74
+ let addresses = [];
75
+ let dns;
76
+ try {
77
+ addresses = await (options.resolveHostname ?? resolveHostname)(publicURL.hostname);
78
+ dns = addresses.length > 0
79
+ ? { status: "ok", detail: `Resolved ${publicURL.hostname}`, addresses }
80
+ : { status: "fail", detail: `${publicURL.hostname} returned no addresses`, addresses };
81
+ }
82
+ catch (error) {
83
+ dns = { status: "fail", detail: `DNS lookup failed: ${errorDetail(error)}`, addresses };
84
+ }
85
+ let tlsAndRouting;
86
+ try {
87
+ const response = await (options.request ?? requestPreview)(publicURL);
88
+ if (response.status === 401) {
89
+ tlsAndRouting = {
90
+ status: "ok",
91
+ detail: "HTTPS reached the Preview gateway and anonymous access was rejected",
92
+ httpStatus: response.status,
93
+ };
94
+ }
95
+ else {
96
+ tlsAndRouting = {
97
+ status: "fail",
98
+ detail: `Expected HTTP 401 from the Preview gateway, received HTTP ${response.status}`,
99
+ httpStatus: response.status,
100
+ ...(response.location ? { location: response.location } : {}),
101
+ };
102
+ }
103
+ }
104
+ catch (error) {
105
+ tlsAndRouting = { status: "fail", detail: `HTTPS request failed: ${errorDetail(error)}` };
106
+ }
107
+ return { publicURL: publicURL.toString(), dns, tlsAndRouting };
108
+ }