@lowdefy/server-dev 6.0.0 → 7.0.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 (625) hide show
  1. package/client/App.jsx +52 -24
  2. package/client/Inspector.jsx +73 -30
  3. package/client/JourneyObserver.jsx +47 -0
  4. package/client/Page.jsx +54 -22
  5. package/client/Recorder.jsx +73 -0
  6. package/client/Routing.jsx +37 -13
  7. package/client/clientBuildImports.test.mjs +63 -0
  8. package/client/devStreamConnections.test.mjs +40 -0
  9. package/client/feedback/FeedbackMount.jsx +2 -2
  10. package/client/feedback/FeedbackOverlay.jsx +7 -4
  11. package/client/feedback/feedbackStyles.js +3 -0
  12. package/client/journeyObserver/startJourneyObserver.js +101 -0
  13. package/client/journeyObserver/startJourneyObserver.test.mjs +149 -0
  14. package/client/openInEditor/OpenInEditorListener.jsx +2 -0
  15. package/client/recorder/buildEngineRecord.js +37 -0
  16. package/client/recorder/buildInteractionRecord.js +64 -0
  17. package/client/recorder/buildTraceEvent.js +57 -0
  18. package/client/recorder/buildTraceEvent.test.mjs +81 -0
  19. package/client/recorder/chunkRecords.js +69 -0
  20. package/client/recorder/chunkRecords.test.mjs +59 -0
  21. package/client/recorder/collectEventActions.js +30 -0
  22. package/client/recorder/collectEventEndpoints.js +40 -0
  23. package/client/recorder/collectEventRequests.js +39 -0
  24. package/client/recorder/createPairingBuffer.js +187 -0
  25. package/client/recorder/createPairingBuffer.test.mjs +405 -0
  26. package/client/recorder/createPasswordRedactor.js +84 -0
  27. package/client/recorder/createRecordBatcher.js +54 -0
  28. package/client/recorder/createRecordBatcher.test.mjs +76 -0
  29. package/client/recorder/createRecorder.js +216 -0
  30. package/client/recorder/createRecorder.test.mjs +318 -0
  31. package/client/recorder/createRecordingSession.js +61 -0
  32. package/client/recorder/createRecordingSession.test.mjs +72 -0
  33. package/client/recorder/diffStateWrites.js +77 -0
  34. package/client/recorder/diffStateWrites.test.mjs +66 -0
  35. package/client/recorder/isEventCall.js +30 -0
  36. package/client/recorder/jsonType.js +39 -0
  37. package/client/recorder/keyChord.js +47 -0
  38. package/client/recorder/keyChord.test.mjs +50 -0
  39. package/client/recorder/sendRecords.js +40 -0
  40. package/client/recorder/toJsonSafe.js +29 -0
  41. package/lib/client/BuildingPage.jsx +3 -1
  42. package/lib/client/ErrorBar.jsx +17 -13
  43. package/lib/client/RedirectingPage.jsx +56 -0
  44. package/lib/client/auth/Auth.jsx +11 -6
  45. package/lib/client/auth/AuthConfigured.jsx +224 -39
  46. package/lib/client/auth/AuthNotConfigured.js +45 -4
  47. package/lib/client/auth/authNotConfiguredParity.test.mjs +61 -0
  48. package/lib/client/auth/authTemplateSync.test.mjs +40 -0
  49. package/lib/client/auth/betterAuthSessionContract.test.mjs +181 -0
  50. package/lib/client/auth/createFetchCheckingBuild.js +45 -0
  51. package/lib/client/bundleBuildId.js +25 -0
  52. package/lib/client/collectVisibleBlockIds.js +51 -0
  53. package/lib/client/getCspNonce.js +25 -0
  54. package/lib/client/getPagePath.js +36 -0
  55. package/lib/client/getPagePath.test.mjs +51 -0
  56. package/lib/client/utils/formatErrorsForCopy.js +30 -0
  57. package/lib/client/utils/formatErrorsForCopy.test.mjs +39 -0
  58. package/lib/client/utils/getErrorBarColor.js +25 -0
  59. package/lib/client/utils/getErrorBarColor.test.mjs +34 -0
  60. package/lib/client/utils/request.js +8 -1
  61. package/lib/client/utils/usePageConfig.js +89 -25
  62. package/lib/client/utils/usePageConfig.test.mjs +198 -13
  63. package/lib/configTree/buildConfigTree.mjs +58 -0
  64. package/lib/configTree/buildConfigTree.test.mjs +77 -0
  65. package/lib/configTree/collectConfigText.mjs +101 -0
  66. package/lib/configTree/collectConfigText.test.mjs +116 -0
  67. package/lib/configTree/prepareScratchServer.mjs +46 -0
  68. package/lib/configTree/runConfigTreeBuild.mjs +88 -0
  69. package/lib/configTree/runConfigTreeBuild.skipPluginChecks.test.mjs +74 -0
  70. package/lib/configTree/runConfigTreeBuild.test.mjs +202 -0
  71. package/lib/docs/JourneyStepError.js +30 -0
  72. package/lib/docs/acquireBrowserSlot.js +30 -0
  73. package/lib/docs/addNestedEndpoints.js +54 -0
  74. package/lib/docs/addNestedEndpoints.test.mjs +185 -0
  75. package/lib/docs/addWriteFlags.js +77 -0
  76. package/lib/docs/buildEditedPages.js +50 -0
  77. package/lib/docs/buildWaitHeader.js +21 -0
  78. package/lib/docs/captureAnnotatedScreenshot.js +23 -12
  79. package/lib/docs/checkAgentFiles.js +148 -0
  80. package/lib/docs/checkAgentFiles.test.mjs +225 -0
  81. package/lib/docs/checkStepReferences.js +239 -0
  82. package/lib/docs/checkStepReferences.test.mjs +173 -0
  83. package/lib/docs/checkpointStore.js +3 -0
  84. package/lib/docs/clientErrorStore.js +18 -1
  85. package/lib/docs/clientErrorStore.test.mjs +46 -0
  86. package/lib/docs/collectExercised.js +52 -0
  87. package/lib/docs/createBrowserLifecycle.js +102 -0
  88. package/lib/docs/createBrowserLifecycle.test.mjs +212 -0
  89. package/lib/docs/createDocsMcpServer.js +345 -388
  90. package/lib/docs/createDocsMcpServer.test.mjs +644 -4
  91. package/lib/docs/createDocsMcpServerRunJourney.test.mjs +148 -0
  92. package/lib/docs/createJourneyActors.chromium.test.mjs +227 -0
  93. package/lib/docs/createJourneyActors.js +213 -0
  94. package/lib/docs/createJourneyActors.test.mjs +79 -0
  95. package/lib/docs/createJourneyObservations.js +80 -0
  96. package/lib/docs/createLeftOriginError.js +26 -0
  97. package/lib/docs/createNetworkCounter.js +101 -0
  98. package/lib/docs/dataSets/checkSharedCollectionIds.js +67 -0
  99. package/lib/docs/dataSets/dataSessionRegistry.js +25 -0
  100. package/lib/docs/dataSets/dataSetJourneys.chromium.mongodb.test.mjs +679 -0
  101. package/lib/docs/dataSets/describeDataSetResult.js +25 -0
  102. package/lib/docs/dataSets/describeDatabaseTarget.js +46 -0
  103. package/lib/docs/dataSets/deserializeFixture.js +34 -0
  104. package/lib/docs/dataSets/drainSessionWork.js +37 -0
  105. package/lib/docs/dataSets/drainSessionWork.test.mjs +64 -0
  106. package/lib/docs/dataSets/duplicateIndexName.js +24 -0
  107. package/lib/docs/dataSets/findCollectionCollisions.js +52 -0
  108. package/lib/docs/dataSets/getDataStore.js +106 -0
  109. package/lib/docs/dataSets/getDataStore.mongodb.test.mjs +63 -0
  110. package/lib/docs/dataSets/getDataStore.test.mjs +195 -0
  111. package/lib/docs/dataSets/groupDataSetByCollection.js +44 -0
  112. package/lib/docs/dataSets/insertFixture.js +57 -0
  113. package/lib/docs/dataSets/insertGeneratedDocuments.js +41 -0
  114. package/lib/docs/dataSets/isDataStoreGone.js +29 -0
  115. package/lib/docs/dataSets/loadDataSetCollection.js +42 -0
  116. package/lib/docs/dataSets/mergeCollectionIndexes.js +52 -0
  117. package/lib/docs/dataSets/mergeCollectionIndexes.test.mjs +59 -0
  118. package/lib/docs/dataSets/openDataSession.js +119 -0
  119. package/lib/docs/dataSets/openDataSession.mongodb.test.mjs +343 -0
  120. package/lib/docs/dataSets/pickDataStorePort.js +58 -0
  121. package/lib/docs/dataSets/pickDataStorePort.test.mjs +63 -0
  122. package/lib/docs/dataSets/readConnectionArtifacts.js +46 -0
  123. package/lib/docs/dataSets/readDataSession.js +41 -0
  124. package/lib/docs/dataSets/readDataSession.test.mjs +57 -0
  125. package/lib/docs/dataSets/readDataSet.js +68 -0
  126. package/lib/docs/dataSets/readDataSet.test.mjs +220 -0
  127. package/lib/docs/dataSets/registerDataStoreShutdown.js +47 -0
  128. package/lib/docs/dataSets/removeDataStoreFiles.js +32 -0
  129. package/lib/docs/dataSets/resolveDataSetCollection.js +46 -0
  130. package/lib/docs/dataSets/resolveJourneyDataSet.js +106 -0
  131. package/lib/docs/dataSets/resolveJourneyDataSet.test.mjs +130 -0
  132. package/lib/docs/dataSets/sweepOrphanDatabases.js +33 -0
  133. package/lib/docs/dataSets/test/app/lowdefy.yaml +203 -0
  134. package/lib/docs/dataSets/test/app/modules/archive/module.lowdefy.yaml +30 -0
  135. package/lib/docs/dataSets/test/app/tests/data/shop.yaml +8 -0
  136. package/lib/docs/dataSets/test/buildTestApp.mjs +43 -0
  137. package/lib/docs/devEventBus.js +136 -0
  138. package/lib/docs/devEventBus.test.mjs +269 -0
  139. package/lib/docs/devToolDefinitions.js +529 -0
  140. package/lib/docs/docSources.js +21 -0
  141. package/lib/docs/docs.test.mjs +171 -15
  142. package/lib/docs/docsIndex.test.mjs +377 -0
  143. package/lib/docs/evalOperator.js +5 -3
  144. package/lib/docs/evalOperatorHeadless.js +84 -12
  145. package/lib/docs/evalOperatorHeadless.test.mjs +7 -1
  146. package/lib/docs/evalOperatorInTab.js +2 -1
  147. package/lib/docs/findConfig.js +95 -49
  148. package/lib/docs/fitResponse.js +81 -0
  149. package/lib/docs/fitResponse.test.mjs +128 -0
  150. package/lib/docs/formatFeedback.js +26 -5
  151. package/lib/docs/formatFeedback.test.mjs +48 -0
  152. package/lib/docs/getAppMap.js +7 -1
  153. package/lib/docs/getBrowser.js +150 -80
  154. package/lib/docs/getBrowser.test.mjs +407 -16
  155. package/lib/docs/getBuildId.js +61 -0
  156. package/lib/docs/getBuildId.test.mjs +84 -0
  157. package/lib/docs/getBuildStatus.js +54 -6
  158. package/lib/docs/getBuildStatus.test.mjs +133 -0
  159. package/lib/docs/getBuildStatusAfterEdits.js +35 -0
  160. package/lib/docs/getBuildStatusAfterEdits.test.mjs +76 -0
  161. package/lib/docs/getCoreDoc.js +9 -1
  162. package/lib/docs/getDoc.js +100 -0
  163. package/lib/docs/getDocsIndex.js +152 -0
  164. package/lib/docs/getHazards.js +115 -0
  165. package/lib/docs/getHazards.test.mjs +175 -0
  166. package/lib/docs/getMailOutboxDirectory.js +26 -0
  167. package/lib/docs/getOverview.js +79 -13
  168. package/lib/docs/getOverviewBasePath.test.mjs +37 -0
  169. package/lib/docs/getPageBuildStatus.js +46 -0
  170. package/lib/docs/getPluginDoc.js +21 -23
  171. package/lib/docs/getSchema.js +33 -1
  172. package/lib/docs/getStaleStatus.js +36 -0
  173. package/lib/docs/getStaleStatus.test.mjs +74 -0
  174. package/lib/docs/guardJourneyOrigin.js +39 -0
  175. package/lib/docs/inspectState.js +5 -3
  176. package/lib/docs/inspectStateFromTab.js +2 -2
  177. package/lib/docs/inspectStateHeadless.js +89 -27
  178. package/lib/docs/inspectStateHeadless.test.mjs +20 -1
  179. package/lib/docs/installHeadlessShell.failure.test.mjs +45 -0
  180. package/lib/docs/installHeadlessShell.js +123 -0
  181. package/lib/docs/installHeadlessShell.test.mjs +70 -0
  182. package/lib/docs/installHeadlessShell.timeout.test.mjs +72 -0
  183. package/lib/docs/isHeadlessShellIncomplete.js +34 -0
  184. package/lib/docs/isHeadlessShellIncomplete.test.mjs +63 -0
  185. package/lib/docs/isPageReady.js +77 -0
  186. package/lib/docs/isPageReady.test.mjs +235 -0
  187. package/lib/docs/isRunErrorEntry.js +25 -0
  188. package/lib/docs/journeyMutantCookie.chromium.test.mjs +150 -0
  189. package/lib/docs/journeyRunRecording.js +35 -0
  190. package/lib/docs/journeyRunRecording.test.mjs +39 -0
  191. package/lib/docs/keepEventStreamAlive.js +68 -0
  192. package/lib/docs/keepEventStreamAlive.test.mjs +77 -0
  193. package/lib/docs/launchBrowser.js +83 -0
  194. package/lib/docs/launchBrowser.test.mjs +175 -0
  195. package/lib/docs/listModuleDocEntries.js +69 -0
  196. package/lib/docs/listPackageDocFiles.js +67 -0
  197. package/lib/docs/listPluginDocEntries.js +111 -0
  198. package/lib/docs/listPlugins.js +9 -58
  199. package/lib/docs/listTypes.js +14 -6
  200. package/lib/docs/loadFrameworkHazards.js +40 -0
  201. package/lib/docs/loadFrameworkHazards.test.mjs +43 -0
  202. package/lib/docs/loadState.js +103 -35
  203. package/lib/docs/makeModuleManifestDoc.js +174 -0
  204. package/lib/docs/matchExercisedPages.js +35 -0
  205. package/lib/docs/matchJourneyRoute.js +69 -0
  206. package/lib/docs/matchJourneyRoute.test.mjs +178 -0
  207. package/lib/docs/mergeNetworkSnapshots.js +49 -0
  208. package/lib/docs/nextJourneySequence.js +30 -0
  209. package/lib/docs/noBrowserError.js +22 -0
  210. package/lib/docs/observe/TRANSIENT_SELECTOR.js +21 -0
  211. package/lib/docs/observe/claimExpectedErrors.js +67 -0
  212. package/lib/docs/observe/createJourneyAppErrors.js +236 -0
  213. package/lib/docs/observe/createJourneyAppErrors.test.mjs +385 -0
  214. package/lib/docs/observe/evaluateInvariants.js +183 -0
  215. package/lib/docs/observe/evaluateInvariants.test.mjs +299 -0
  216. package/lib/docs/observe/findingKey.js +29 -0
  217. package/lib/docs/observe/hasNoEffect.js +31 -0
  218. package/lib/docs/observe/hasNoEffect.test.mjs +37 -0
  219. package/lib/docs/observe/installStepObserver.js +72 -0
  220. package/lib/docs/observe/isAppApiUrl.js +33 -0
  221. package/lib/docs/observe/parseAppApiUrl.js +37 -0
  222. package/lib/docs/observe/readStepObserver.js +42 -0
  223. package/lib/docs/observe/relativeSource.js +29 -0
  224. package/lib/docs/observe/resolveConfigKeySource.js +33 -0
  225. package/lib/docs/observe/takeJourneyEvents.js +36 -0
  226. package/lib/docs/observe/toAppErrorFailure.js +61 -0
  227. package/lib/docs/observe/usesSearchStage.js +45 -0
  228. package/lib/docs/observe/waitForClientErrorReports.js +31 -0
  229. package/lib/docs/observe/watchJourneyContext.js +75 -0
  230. package/lib/docs/observe/withAppErrors.js +31 -0
  231. package/lib/docs/observe/withAppErrors.test.mjs +101 -0
  232. package/lib/docs/openJourney.js +94 -0
  233. package/lib/docs/openJourneyEmail.js +41 -0
  234. package/lib/docs/openJourneyEmail.test.mjs +187 -0
  235. package/lib/docs/pageBlocks.js +47 -0
  236. package/lib/docs/pageBuildStatus.test.mjs +103 -0
  237. package/lib/{client/setPageId.js → docs/parseModuleSource.js} +11 -12
  238. package/lib/docs/rails/authEngineActions.js +53 -0
  239. package/lib/docs/rails/authEngineActions.test.mjs +69 -0
  240. package/lib/docs/rails/authEngineMethods.js +52 -0
  241. package/lib/docs/rails/authEngineMethods.test.mjs +36 -0
  242. package/lib/docs/rails/checkAuthActionsRan.js +66 -0
  243. package/lib/docs/rails/checkAuthActionsRan.test.mjs +149 -0
  244. package/lib/docs/rails/checkClickRails.js +56 -0
  245. package/lib/docs/rails/createDataSetRails.js +82 -0
  246. package/lib/docs/rails/createDataSetRails.test.mjs +172 -0
  247. package/lib/docs/rails/findExternalBlocks.js +147 -0
  248. package/lib/docs/rails/findExternalBlocks.test.mjs +254 -0
  249. package/lib/docs/rails/installAuthActionRecorder.js +56 -0
  250. package/lib/docs/rails/readAuthActionRecorder.js +32 -0
  251. package/lib/docs/rails/readBuiltArtifact.js +30 -0
  252. package/lib/docs/readConnectionPlugins.js +26 -0
  253. package/lib/docs/readDevAuthMode.js +32 -0
  254. package/lib/docs/readDocEntry.js +40 -0
  255. package/lib/docs/readDocTitle.js +26 -0
  256. package/lib/docs/readJourneyEmailMatch.js +57 -0
  257. package/lib/docs/readJourneyEmailMatch.test.mjs +110 -0
  258. package/lib/docs/readJourneySession.js +56 -0
  259. package/lib/docs/readMailOutbox.js +37 -0
  260. package/lib/docs/readPagePath.js +27 -0
  261. package/lib/docs/readPluginPackages.js +99 -0
  262. package/lib/docs/readProxyBuildWait.js +38 -0
  263. package/lib/docs/readRoutineSteps.js +45 -0
  264. package/lib/docs/registerShownTextEngine.js +118 -0
  265. package/lib/docs/requestRestart.js +30 -0
  266. package/lib/docs/requestRestart.test.mjs +49 -0
  267. package/lib/docs/resolvePageInstance.js +46 -0
  268. package/lib/docs/resolvePageInstance.test.mjs +81 -0
  269. package/lib/docs/resolveSource.js +11 -7
  270. package/lib/docs/resolveSource.test.mjs +1 -1
  271. package/lib/docs/resolveToolCaller.js +62 -0
  272. package/lib/docs/reviewPage.js +51 -0
  273. package/lib/docs/reviewPageBuilds.js +56 -0
  274. package/lib/docs/reviewPageBuilds.test.mjs +207 -0
  275. package/lib/docs/runCheck.js +75 -0
  276. package/lib/docs/runCheck.test.mjs +91 -0
  277. package/lib/docs/runCheckChild.js +49 -0
  278. package/lib/docs/runEndpoint.js +185 -0
  279. package/lib/docs/runEndpoint.test.mjs +509 -0
  280. package/lib/docs/runErrorBuffers.js +99 -0
  281. package/lib/docs/runErrorBuffers.test.mjs +95 -0
  282. package/lib/docs/runJourney.js +347 -0
  283. package/lib/docs/runJourney.test.mjs +2383 -0
  284. package/lib/docs/runJourneyAsUsers.js +44 -0
  285. package/lib/docs/runJourneyDataSet.test.mjs +274 -0
  286. package/lib/docs/runJourneySteps.js +1320 -0
  287. package/lib/docs/runRequest.js +100 -59
  288. package/lib/docs/runRequest.test.mjs +190 -0
  289. package/lib/docs/saveAnnotatedScreenshot.js +1 -1
  290. package/lib/docs/scaffoldPage.js +3 -1
  291. package/lib/docs/screenshotPage.js +163 -11
  292. package/lib/docs/screenshotPage.test.mjs +263 -1
  293. package/lib/docs/screenshotPageClip.test.mjs +4 -0
  294. package/lib/docs/searchDocs.js +82 -30
  295. package/lib/docs/searchDocs.test.mjs +100 -0
  296. package/lib/docs/searchIcons.js +90 -0
  297. package/lib/docs/searchIcons.test.mjs +127 -0
  298. package/lib/docs/selectExercised.js +39 -0
  299. package/lib/docs/selectFinalState.js +66 -0
  300. package/lib/docs/selectFinalState.test.mjs +51 -0
  301. package/lib/docs/serverErrorStore.js +54 -0
  302. package/lib/docs/serverErrorStore.test.mjs +50 -0
  303. package/lib/docs/setupTestFixtures.mjs +133 -39
  304. package/lib/docs/shownTextSelector.js +28 -0
  305. package/lib/docs/snapshotState.js +13 -5
  306. package/lib/docs/splitSearchTerms.js +62 -0
  307. package/lib/docs/splitSearchTerms.test.mjs +50 -0
  308. package/lib/docs/stateCheckpoints.test.mjs +77 -0
  309. package/lib/docs/tabAvailable.js +6 -5
  310. package/lib/docs/tabChannel.js +127 -21
  311. package/lib/docs/tabChannel.test.mjs +187 -29
  312. package/lib/docs/unsettledPageNote.js +24 -0
  313. package/lib/docs/validateJourneyMail.js +36 -0
  314. package/lib/docs/validateJourneyTimeout.js +38 -0
  315. package/lib/docs/validateJourneyTimeout.test.mjs +29 -0
  316. package/lib/docs/validateRecordingParam.js +42 -0
  317. package/lib/docs/validateStateSelection.js +33 -0
  318. package/lib/docs/validateStateSelection.test.mjs +33 -0
  319. package/lib/docs/validateViewport.js +51 -0
  320. package/lib/docs/validateViewport.test.mjs +54 -0
  321. package/lib/docs/waitForBuild.js +50 -0
  322. package/lib/docs/waitForBuild.test.mjs +75 -0
  323. package/lib/docs/waitForJourneyEmail.js +77 -0
  324. package/lib/docs/withBrowserSlot.js +45 -0
  325. package/lib/docs/withBrowserSlot.test.mjs +75 -0
  326. package/lib/docs/withDataSession.js +53 -0
  327. package/lib/server/applyDataSetRedirect.js +83 -0
  328. package/lib/server/applyDataSetRedirect.test.mjs +340 -0
  329. package/lib/server/applyDataSetRedirectTenantReach.test.mjs +204 -0
  330. package/lib/server/applyDataSetRedirectWalled.test.mjs +131 -0
  331. package/lib/server/auth/createSystemContext.js +73 -0
  332. package/lib/server/auth/getAuth.js +46 -0
  333. package/lib/server/auth/getHeadlessUser.js +9 -8
  334. package/lib/server/auth/getHeadlessUser.test.mjs +41 -0
  335. package/lib/server/auth/getMockUser.js +18 -14
  336. package/lib/server/auth/{getAuthConfig.js → getStrategies.js} +12 -10
  337. package/lib/server/auth/headlessUser.js +3 -2
  338. package/lib/server/auth/journeyActor.js +35 -0
  339. package/lib/server/auth/resolveHeadlessUser.test.mjs +0 -4
  340. package/lib/server/checkPageRecord.js +44 -0
  341. package/lib/server/contextMapBudget.js +24 -0
  342. package/lib/server/contextMapBudget.test.mjs +98 -0
  343. package/lib/server/continueJitKeys.js +28 -0
  344. package/lib/server/continueJitKeys.test.mjs +43 -0
  345. package/lib/server/createDataSessionEndedError.js +33 -0
  346. package/lib/server/createDataSetRefusal.js +26 -0
  347. package/lib/server/createLowdefyContext.js +174 -41
  348. package/lib/server/createLowdefyContext.test.mjs +437 -0
  349. package/lib/server/getClientAddress.js +51 -0
  350. package/lib/server/getClientAddress.test.mjs +56 -0
  351. package/lib/server/getPageJitEnrichment.test.mjs +29 -11
  352. package/lib/server/hashConfigContent.js +26 -0
  353. package/lib/server/importAppCode.test.mjs +57 -0
  354. package/lib/server/isRoutineStep.js +27 -0
  355. package/lib/server/jitPageBuilder.js +309 -126
  356. package/lib/server/jitPageBuilder.skipPluginChecks.test.mjs +78 -0
  357. package/lib/server/jitPageBuilder.test.mjs +218 -0
  358. package/lib/server/journeyCookies/JOURNEY_COOKIES.js +37 -0
  359. package/lib/server/journeyCookies/forwardJourneyCookies.js +46 -0
  360. package/lib/server/journeyCookies/readJourneyCookie.js +52 -0
  361. package/lib/server/journeyCookies/writeJourneyCookie.js +46 -0
  362. package/lib/server/journeyCookies.js +22 -0
  363. package/lib/server/journeyCookies.test.mjs +145 -0
  364. package/lib/server/loadDynamicJsMap.js +47 -0
  365. package/lib/server/log/createHandleError.js +38 -2
  366. package/lib/server/log/createHandleError.test.mjs +125 -2
  367. package/lib/server/log/createLogger.js +3 -0
  368. package/lib/server/log/createLogger.test.mjs +77 -0
  369. package/lib/server/log/logRequest.js +2 -3
  370. package/lib/server/mutantDataSetWrappers.test.mjs +112 -0
  371. package/lib/server/mutants/applyMutant.js +39 -0
  372. package/lib/server/mutants/artifactPatterns.js +25 -0
  373. package/lib/server/mutants/buildAnchor.js +45 -0
  374. package/lib/server/mutants/collectSharedIds.js +41 -0
  375. package/lib/server/mutants/configPathSegments.js +38 -0
  376. package/lib/server/mutants/createMutantReadConfigFile.js +42 -0
  377. package/lib/server/mutants/describeEvent.js +22 -0
  378. package/lib/server/mutants/enumerateArtifact.js +56 -0
  379. package/lib/server/mutants/findNodeByKey.js +45 -0
  380. package/lib/server/mutants/findNodeByPointer.js +49 -0
  381. package/lib/server/mutants/getArtifactKind.js +32 -0
  382. package/lib/server/mutants/groupMutantCopies.js +56 -0
  383. package/lib/server/mutants/hashArtifact.js +25 -0
  384. package/lib/server/mutants/hashMutantTarget.js +34 -0
  385. package/lib/server/mutants/listMutants.js +122 -0
  386. package/lib/server/mutants/listMutants.test.mjs +502 -0
  387. package/lib/server/mutants/mutantId.js +30 -0
  388. package/lib/server/mutants/mutantRuns.js +57 -0
  389. package/lib/server/mutants/mutantRuns.test.mjs +136 -0
  390. package/lib/server/mutants/operators/dropAction.js +47 -0
  391. package/lib/server/mutants/operators/dropBlock.js +44 -0
  392. package/lib/server/mutants/operators/dropPayload.js +52 -0
  393. package/lib/server/mutants/operators/dropStep.js +44 -0
  394. package/lib/server/mutants/operators/flipVisible.js +53 -0
  395. package/lib/server/mutants/operators/index.js +37 -0
  396. package/lib/server/mutants/operators/retargetLink.js +72 -0
  397. package/lib/server/mutants/operators/skipValidate.js +77 -0
  398. package/lib/server/mutants/operators/swapIf.js +112 -0
  399. package/lib/server/mutants/operators.test.mjs +396 -0
  400. package/lib/server/mutants/positionFreePath.js +38 -0
  401. package/lib/server/mutants/removeFromParent.js +28 -0
  402. package/lib/server/mutants/test/fixtures.mjs +173 -0
  403. package/lib/server/mutants/test/keyArtifact.mjs +63 -0
  404. package/lib/server/mutants/validateMutantParam.js +69 -0
  405. package/lib/server/mutants/validateMutantsBody.js +88 -0
  406. package/lib/server/mutants/walkArtifact.js +179 -0
  407. package/lib/server/pageBuildRecords.js +131 -0
  408. package/lib/server/pageBuildRecords.test.mjs +258 -0
  409. package/lib/server/pageCache.mjs +22 -7
  410. package/lib/server/pageCache.test.mjs +22 -7
  411. package/lib/server/pageRebuilds.test.mjs +543 -0
  412. package/lib/server/pruneJitMaps.js +36 -0
  413. package/lib/server/pruneJitMaps.test.mjs +55 -0
  414. package/lib/server/readInstanceRecord.js +33 -0
  415. package/lib/server/readInstanceRecord.test.mjs +99 -0
  416. package/lib/server/readMergedMaps.js +86 -0
  417. package/lib/server/readMergedMaps.test.mjs +79 -0
  418. package/lib/server/recording/appendRecords.js +33 -0
  419. package/lib/server/recording/errorRecordingStamp.js +34 -0
  420. package/lib/server/recording/errorRecordingStamp.test.mjs +31 -0
  421. package/lib/server/recording/isRecordingEnabled.js +31 -0
  422. package/lib/server/recording/readRecordingCookie.js +71 -0
  423. package/lib/server/recording/readRecordingCookie.test.mjs +73 -0
  424. package/lib/server/recording/recordingCookiePayload.js +33 -0
  425. package/lib/server/recording/servedBuilds.js +46 -0
  426. package/lib/server/recording/servedBuilds.test.mjs +42 -0
  427. package/lib/server/scrubSecrets.js +23 -0
  428. package/lib/server/sinkExternalConnections.js +95 -0
  429. package/lib/server/sinkExternalConnections.test.mjs +135 -0
  430. package/lib/server/skipStaleMapWrites.js +62 -0
  431. package/lib/server/skipStaleMapWrites.test.mjs +113 -0
  432. package/lib/server/startIdleGc.js +67 -0
  433. package/lib/server/startIdleGc.test.mjs +138 -0
  434. package/lib/server/testFixtures/importAppCodeTwice.mjs +70 -0
  435. package/lib/server/trackSessionWork.js +30 -0
  436. package/lib/vite/createDevServerExclude.js +40 -0
  437. package/lib/vite/createDevServerExclude.test.mjs +79 -0
  438. package/manager/getContext.mjs +24 -2
  439. package/manager/processes/initialBuild.mjs +14 -0
  440. package/manager/processes/initialBuild.test.mjs +101 -0
  441. package/manager/processes/lowdefyBuild.mjs +30 -7
  442. package/manager/processes/lowdefyBuild.skipPluginChecks.test.mjs +62 -0
  443. package/manager/processes/lowdefyBuild.test.mjs +136 -1
  444. package/manager/processes/optimizeDependencies.mjs +59 -0
  445. package/manager/processes/restartServer.mjs +34 -6
  446. package/manager/processes/restartServer.test.mjs +162 -0
  447. package/manager/processes/shutdownServer.mjs +11 -0
  448. package/manager/processes/shutdownServer.test.mjs +45 -0
  449. package/manager/processes/startFirstServer.mjs +46 -0
  450. package/manager/processes/startFirstServer.test.mjs +326 -0
  451. package/manager/processes/startMailSink.mjs +135 -0
  452. package/manager/processes/startMailSink.test.mjs +146 -0
  453. package/manager/processes/startProxy.mjs +129 -12
  454. package/manager/processes/startProxy.test.mjs +366 -0
  455. package/manager/processes/startServer.mjs +47 -6
  456. package/manager/processes/startServer.test.mjs +155 -0
  457. package/manager/processes/startWatchers.mjs +6 -4
  458. package/manager/processes/syncServer.mjs +69 -0
  459. package/manager/processes/syncServer.test.mjs +141 -0
  460. package/manager/processes/waitForDevServer.mjs +48 -0
  461. package/manager/processes/waitForDevServer.test.mjs +112 -0
  462. package/manager/run.mjs +128 -43
  463. package/manager/utils/BatchChanges.mjs +21 -5
  464. package/manager/utils/BatchChanges.test.mjs +77 -0
  465. package/manager/utils/acquireDevInstance.mjs +99 -0
  466. package/manager/utils/acquireDevInstance.test.mjs +160 -0
  467. package/manager/utils/createBuildActivity.mjs +67 -0
  468. package/manager/utils/createBuildActivity.test.mjs +58 -0
  469. package/manager/utils/createChangeSignal.mjs +39 -0
  470. package/manager/utils/createChangeSignal.test.mjs +62 -0
  471. package/manager/utils/createCustomPluginMessagesMap.mjs +23 -33
  472. package/manager/utils/createCustomPluginTypesMap.mjs +24 -30
  473. package/manager/utils/createCustomPluginTypesMap.test.mjs +94 -0
  474. package/manager/utils/createDotPathIgnore.mjs +44 -0
  475. package/manager/utils/createDotPathIgnore.test.mjs +65 -0
  476. package/manager/utils/createIdleGc.mjs +51 -0
  477. package/manager/utils/createIdleGc.test.mjs +77 -0
  478. package/manager/utils/createRequestActivity.mjs +100 -0
  479. package/manager/utils/createRequestActivity.test.mjs +121 -0
  480. package/manager/utils/createServerArtifactTracker.mjs +83 -0
  481. package/manager/utils/createServerArtifactTracker.test.mjs +78 -0
  482. package/manager/utils/createServerEnv.mjs +39 -0
  483. package/manager/utils/filesHaveSameBytes.mjs +41 -0
  484. package/manager/utils/findBuildFilesOutsideWatch.mjs +59 -0
  485. package/manager/utils/findBuildFilesOutsideWatch.test.mjs +111 -0
  486. package/manager/utils/importFresh.mjs +46 -0
  487. package/manager/utils/importFresh.test.mjs +72 -0
  488. package/manager/utils/importFreshWorker.mjs +20 -0
  489. package/manager/utils/killTaggedBrowser.mjs +35 -0
  490. package/manager/utils/pagePathChanged.mjs +56 -0
  491. package/manager/utils/pruneRecordings.mjs +85 -0
  492. package/manager/utils/pruneRecordings.test.mjs +117 -0
  493. package/manager/utils/publishBuildDirectory.mjs +121 -0
  494. package/manager/utils/publishBuildDirectory.order.test.mjs +74 -0
  495. package/manager/utils/publishBuildDirectory.test.mjs +262 -0
  496. package/manager/utils/readBasePath.mjs +27 -0
  497. package/manager/utils/readBasePath.test.mjs +43 -0
  498. package/manager/utils/readBuildStatusWait.mjs +67 -0
  499. package/manager/utils/readManagerVersion.mjs +30 -0
  500. package/manager/utils/readManagerVersion.test.mjs +35 -0
  501. package/manager/utils/readPluginDefinitions.mjs +36 -0
  502. package/manager/utils/resolveDevAuthUrl.mjs +58 -0
  503. package/manager/utils/resolveDevAuthUrl.test.mjs +107 -0
  504. package/manager/utils/resolvePorts.mjs +49 -0
  505. package/manager/utils/setupWatcher.mjs +19 -11
  506. package/manager/utils/startRecordingPruner.mjs +45 -0
  507. package/manager/utils/startRecordingPruner.test.mjs +66 -0
  508. package/manager/utils/waitForChildExit.mjs +53 -0
  509. package/manager/utils/waitForChildExit.test.mjs +70 -0
  510. package/manager/utils/waitForServer.mjs +44 -0
  511. package/manager/utils/writeBuildStatus.mjs +1 -1
  512. package/manager/watchers/createPluginSourceIgnore.mjs +77 -0
  513. package/manager/watchers/envWatcher.mjs +9 -2
  514. package/manager/watchers/envWatcher.test.mjs +87 -0
  515. package/manager/watchers/listServerSidePackages.mjs +47 -0
  516. package/manager/watchers/listServerSidePackages.test.mjs +38 -0
  517. package/manager/watchers/lowdefyBuildWatcher.mjs +106 -22
  518. package/manager/watchers/lowdefyBuildWatcher.test.mjs +309 -0
  519. package/manager/watchers/pluginSourceWatcher.mjs +79 -0
  520. package/manager/watchers/pluginSourceWatcher.test.mjs +222 -0
  521. package/manager/watchers/restartRequestWatcher.mjs +71 -0
  522. package/manager/watchers/restartRequestWatcher.test.mjs +111 -0
  523. package/manager/watchers/selectLocalPluginPackages.mjs +48 -0
  524. package/manager/watchers/selectLocalPluginPackages.test.mjs +73 -0
  525. package/manager/watchers/serverPackageWatcher.mjs +38 -0
  526. package/package.json +61 -49
  527. package/package.original.json +61 -49
  528. package/src/app.js +131 -20
  529. package/src/html/renderDevPage.js +63 -11
  530. package/src/html/renderDevPage.test.mjs +138 -0
  531. package/src/lib/getRequestPath.js +29 -0
  532. package/src/middleware/createSameOriginGuard.js +37 -0
  533. package/src/middleware/createSameOriginGuard.test.mjs +102 -0
  534. package/src/middleware/errorHandler.js +51 -1
  535. package/src/middleware/errorHandler.test.mjs +196 -14
  536. package/src/middleware/isRebindingSafeHost.js +44 -0
  537. package/src/middleware/isRebindingSafeHost.test.mjs +32 -0
  538. package/src/middleware/localDevToolsOnly.js +38 -0
  539. package/src/middleware/localDevToolsOnly.test.mjs +61 -0
  540. package/src/middleware/noStore.js +27 -0
  541. package/src/middleware/noStore.test.mjs +48 -0
  542. package/src/middleware/requestTimeout.js +38 -0
  543. package/src/middleware/staleFlag.js +76 -0
  544. package/src/middleware/staleFlag.test.mjs +127 -0
  545. package/src/middleware/stampCaller.js +38 -0
  546. package/src/middleware/stampCaller.test.mjs +64 -0
  547. package/src/routes/agent.js +15 -3
  548. package/src/routes/auth.js +29 -20
  549. package/src/routes/auth.test.mjs +30 -56
  550. package/src/routes/clientError.js +13 -13
  551. package/src/routes/cron.js +3 -1
  552. package/src/routes/cronForward.js +4 -2
  553. package/src/routes/detached.js +22 -9
  554. package/src/routes/devInspect.js +17 -8
  555. package/src/routes/devInspect.test.mjs +44 -7
  556. package/src/routes/devRecording.js +113 -0
  557. package/src/routes/devRecording.test.mjs +253 -0
  558. package/src/routes/docs/buildStatus.js +10 -2
  559. package/src/routes/docs/buildStatus.test.mjs +46 -0
  560. package/src/routes/docs/content.js +5 -3
  561. package/src/routes/docs/evalOperator.js +15 -1
  562. package/src/routes/docs/events.js +50 -0
  563. package/src/routes/docs/events.test.mjs +117 -0
  564. package/src/routes/docs/find.js +9 -1
  565. package/src/routes/docs/icons.js +30 -0
  566. package/src/routes/docs/inspectState.js +16 -1
  567. package/src/routes/docs/journey.js +194 -0
  568. package/src/routes/docs/journey.test.mjs +401 -0
  569. package/src/routes/docs/journeyRefusals.test.mjs +71 -0
  570. package/src/routes/docs/loadState.js +4 -2
  571. package/src/routes/docs/mcp.js +34 -3
  572. package/src/routes/docs/mcp.test.mjs +130 -0
  573. package/src/routes/docs/mutants.js +78 -0
  574. package/src/routes/docs/mutants.test.mjs +180 -0
  575. package/src/routes/docs/parsePathParamsParam.js +41 -0
  576. package/src/routes/docs/parsePathParamsParam.test.mjs +42 -0
  577. package/src/routes/docs/parseUserParam.js +27 -21
  578. package/src/routes/docs/parseUserParam.test.mjs +17 -5
  579. package/src/routes/docs/restart.js +35 -0
  580. package/src/routes/docs/restart.test.mjs +64 -0
  581. package/src/routes/docs/runEndpoint.js +54 -0
  582. package/src/routes/docs/runEndpoint.test.mjs +165 -0
  583. package/src/routes/docs/runRequest.js +25 -8
  584. package/src/routes/docs/runRequest.test.mjs +92 -0
  585. package/src/routes/docs/screenshot.js +50 -2
  586. package/src/routes/docs/screenshot.test.mjs +117 -0
  587. package/src/routes/docs/search.js +15 -2
  588. package/src/routes/docs/snapshotState.js +17 -3
  589. package/src/routes/endpoints.js +27 -26
  590. package/src/routes/endpointsTextAnswer.test.mjs +77 -0
  591. package/src/routes/feedback.js +10 -13
  592. package/src/routes/jitPage.js +99 -35
  593. package/src/routes/jitPage.test.mjs +248 -0
  594. package/src/routes/mcp.js +82 -5
  595. package/src/routes/mcp.test.mjs +215 -0
  596. package/src/routes/mcpMemberToken.test.mjs +125 -0
  597. package/src/routes/mcpProtectedResourceMetadata.js +34 -0
  598. package/src/routes/mcpProtectedResourceMetadata.test.mjs +65 -0
  599. package/src/routes/mountDevPageRoutes.js +25 -0
  600. package/src/routes/mountDevPageRoutes.test.mjs +30 -0
  601. package/src/routes/mountOauthDiscovery.js +44 -0
  602. package/src/routes/mountOauthDiscovery.test.mjs +79 -0
  603. package/src/routes/normalizeMcpProtocolVersionHeader.js +46 -0
  604. package/src/routes/normalizeMcpProtocolVersionHeader.test.mjs +62 -0
  605. package/src/routes/reload.js +16 -0
  606. package/src/routes/request.js +6 -4
  607. package/src/routes/request.test.mjs +9 -5
  608. package/src/routes/requestBody.test.mjs +159 -0
  609. package/src/routes/usage.js +6 -2
  610. package/src/routes/user.js +27 -0
  611. package/src/routes/websocket.js +29 -8
  612. package/src/routes/websocket.test.mjs +65 -0
  613. package/src/routes/wellKnownFallback.js +30 -0
  614. package/src/routes/wellKnownFallback.test.mjs +32 -0
  615. package/src/websocket/devWebSocket.js +41 -26
  616. package/src/websocket/devWebSocket.test.mjs +157 -0
  617. package/vite.config.js +53 -23
  618. package/lib/server/auth/getDevSession.js +0 -70
  619. package/lib/server/auth/getDevSession.test.mjs +0 -138
  620. package/lib/server/auth/session.js +0 -39
  621. package/manager/processes/warnAuthUrlPortMismatch.mjs +0 -47
  622. package/manager/processes/warnAuthUrlPortMismatch.test.mjs +0 -93
  623. package/manager/watchers/moduleBuildWatcher.mjs +0 -79
  624. package/manager/watchers/serverArtifactWatcher.mjs +0 -120
  625. package/src/routes/apiPage.js +0 -37
@@ -14,15 +14,20 @@
14
14
  limitations under the License.
15
15
  */
16
16
 
17
- import { type } from '@lowdefy/helpers';
17
+ import path from 'node:path';
18
+
18
19
  import { callRequest } from '@lowdefy/api';
20
+ import { ConfigError } from '@lowdefy/errors';
21
+ import { type } from '@lowdefy/helpers';
19
22
 
23
+ import buildPageIfNeeded, { syncBuildSignals } from '../server/jitPageBuilder.js';
24
+ import fitResponse from './fitResponse.js';
20
25
  import isWriteRequestsAllowed from './isWriteRequestsAllowed.js';
26
+ import mapPageBuildErrors from './mapPageBuildErrors.js';
21
27
  import readBuildArtifact from './readBuildArtifact.js';
22
-
23
- // Cap the serialized response so a large request result can't blow out an
24
- // agent's context window.
25
- const MAX_RESPONSE_CHARS = 100_000;
28
+ import resolveToolCaller from './resolveToolCaller.js';
29
+ import reviewPage from './reviewPage.js';
30
+ import withDataSession from './withDataSession.js';
26
31
 
27
32
  function getRequestType({ pageId, requestId }) {
28
33
  // The request's `type` is stripped from build/pages/<pageId>.json by the
@@ -37,54 +42,63 @@ function getRequestType({ pageId, requestId }) {
37
42
  return requestConfig?.type ?? null;
38
43
  }
39
44
 
40
- function truncateResponse(result) {
41
- const json = JSON.stringify(result.response);
42
- if (json.length <= MAX_RESPONSE_CHARS) {
43
- return result;
44
- }
45
- return {
46
- ...result,
47
- response: json.slice(0, MAX_RESPONSE_CHARS),
48
- truncated: true,
49
- note: `Response truncated to ${MAX_RESPONSE_CHARS} characters (original serialized size: ${json.length} characters).`,
50
- };
51
- }
52
-
53
45
  // Executes a page request the same way POST /api/request/<pageId>/<requestId>
54
46
  // does (src/routes/request.js), but gated for agent use: requests whose type
55
47
  // is not declared read-only (checkWrite: false in requestSchemas.json meta)
56
48
  // are refused unless the app opts in via lowdefy.yaml's
57
49
  // cli.agentTools.allowWriteRequests. Never throws — errors and refusals are
58
- // returned as data so an agent can reason about them.
59
- async function runRequest({ pageId, requestId, payload = {}, honoContext }) {
50
+ // returned as data so an agent can reason about them. `user` and `data` name
51
+ // the caller as a journey's do (see resolveToolCaller); a data set runs the
52
+ // request on that data set's own database.
53
+ async function runRequest({
54
+ pageId,
55
+ requestId,
56
+ payload = {},
57
+ user,
58
+ data,
59
+ saveResponse = false,
60
+ honoContext,
61
+ }) {
60
62
  if (type.isUndefined(pageId) || !type.isString(pageId)) {
61
- throw new Error(`run_request requires a "pageId" string. Received ${JSON.stringify(pageId)}.`);
63
+ throw new ConfigError(
64
+ `run_request requires a "pageId" string. Received ${JSON.stringify(pageId)}.`
65
+ );
62
66
  }
63
67
  if (type.isUndefined(requestId) || !type.isString(requestId)) {
64
- throw new Error(
68
+ throw new ConfigError(
65
69
  `run_request requires a "requestId" string. Received ${JSON.stringify(requestId)}.`
66
70
  );
67
71
  }
68
72
 
69
- let requestType = getRequestType({ pageId, requestId });
70
- if (type.isNone(requestType)) {
71
- // In dev, page content (including per-request artifacts) is built JIT on
72
- // first page request — trigger the same build GET /api/page/* runs so a
73
- // freshly added request is found without the page ever being opened.
74
- try {
75
- const { default: buildPageIfNeeded } = await import('../server/jitPageBuilder.js');
76
- const path = await import('node:path');
77
- await buildPageIfNeeded({
78
- pageId,
79
- buildDirectory: path.join(process.cwd(), 'build'),
80
- configDirectory: process.env.LOWDEFY_DIRECTORY_CONFIG || process.cwd(),
81
- });
82
- requestType = getRequestType({ pageId, requestId });
83
- } catch {
84
- // JIT build failure surfaces through lowdefy_build_status — fall
85
- // through to the not-found refusal below.
86
- }
73
+ const caller = await resolveToolCaller({ user, data });
74
+ if (!type.isUndefined(caller.error)) {
75
+ throw new ConfigError(`run_request: ${caller.error}`);
76
+ }
77
+
78
+ if (!type.isBoolean(saveResponse)) {
79
+ throw new ConfigError(
80
+ `run_request "saveResponse" must be a boolean. Received ${JSON.stringify(saveResponse)}.`
81
+ );
87
82
  }
83
+
84
+ // Page content, including the per-request artifacts, is built JIT, and a
85
+ // page edit only marks pages for rebuilding: the previous build's artifacts
86
+ // stay on disk until something requests the page. Run the build the page
87
+ // route runs (a no-op while the page is current) so the request that runs is
88
+ // the one in the config now.
89
+ const configDirectory = process.env.LOWDEFY_DIRECTORY_CONFIG || process.cwd();
90
+ const buildDirectory = path.join(process.cwd(), 'build');
91
+ try {
92
+ await buildPageIfNeeded({ pageId, buildDirectory, configDirectory });
93
+ } catch (error) {
94
+ return {
95
+ refused: true,
96
+ reason: `Page "${pageId}" fails to build, so its requests cannot run until the errors are fixed.`,
97
+ buildErrors: mapPageBuildErrors(error),
98
+ };
99
+ }
100
+
101
+ const requestType = getRequestType({ pageId, requestId });
88
102
  if (type.isNone(requestType)) {
89
103
  return {
90
104
  refused: true,
@@ -116,26 +130,53 @@ async function runRequest({ pageId, requestId, payload = {}, honoContext }) {
116
130
  // at module load would break every consumer of this module (e.g. the MCP
117
131
  // server) in environments without a full build.
118
132
  const { default: createLowdefyContext } = await import('../server/createLowdefyContext.js');
119
- const context = await createLowdefyContext({ c: honoContext });
120
- context.logger.info({ event: 'agent_run_request', pageId, requestId });
121
-
122
- try {
123
- const result = await callRequest(context, {
124
- blockId: undefined,
125
- pageId,
126
- payload,
127
- requestId,
128
- });
129
- return { refused: false, ...truncateResponse(result) };
130
- } catch (error) {
131
- return {
132
- refused: false,
133
- error: {
134
- name: error.name,
135
- message: error.message,
136
- },
137
- };
133
+ const ran = await withDataSession({
134
+ dataSet: caller.dataSet,
135
+ task: async ({ dataSession }) => {
136
+ // "none" injects no caller: the app's own auth resolves this call, which
137
+ // carries no session, so it runs signed out.
138
+ const context = await createLowdefyContext({
139
+ c: honoContext,
140
+ user: caller.user === 'none' ? undefined : caller.user,
141
+ dataSession,
142
+ });
143
+ context.logger.info({ event: 'agent_run_request', pageId, requestId, user, data });
144
+ try {
145
+ const result = await callRequest(context, {
146
+ blockId: undefined,
147
+ pageId,
148
+ payload,
149
+ requestId,
150
+ });
151
+ return {
152
+ refused: false,
153
+ ...fitResponse({ result, name: `${pageId}.${requestId}`, saveResponse }),
154
+ };
155
+ } catch (error) {
156
+ return {
157
+ refused: false,
158
+ error: {
159
+ name: error.name,
160
+ message: error.message,
161
+ },
162
+ };
163
+ }
164
+ },
165
+ });
166
+ if (!type.isUndefined(ran.error) && type.isString(ran.error)) {
167
+ return { refused: true, reason: ran.error };
168
+ }
169
+ const signals = syncBuildSignals({ buildDirectory, configDirectory });
170
+ const review = await reviewPage({
171
+ pageId,
172
+ entry: signals.registry?.[pageId],
173
+ configDirectory,
174
+ signals,
175
+ });
176
+ if (review === 'edited') {
177
+ ran.staleConfig = `Page "${pageId}" changed on disk after its last build, but the dev server has not rebuilt it yet, so this ran the previous config. Call lowdefy_build_status with wait: true, then run the request again.`;
138
178
  }
179
+ return ran;
139
180
  }
140
181
 
141
182
  export default runRequest;
@@ -0,0 +1,190 @@
1
+ /*
2
+ Copyright 2020-2026 Lowdefy, Inc
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */
16
+
17
+ import { jest } from '@jest/globals';
18
+
19
+ // runRequest reads build artifacts, builds a Lowdefy context and calls
20
+ // callRequest - all three only exist in a running server directory, so mock
21
+ // them and leave only runRequest's own validation and pass-through under test.
22
+ const mockCallRequest = jest.fn();
23
+ const mockReadBuildArtifact = jest.fn();
24
+ const mockIsWriteRequestsAllowed = jest.fn();
25
+ const mockCreateLowdefyContext = jest.fn();
26
+ const mockLoggerInfo = jest.fn();
27
+
28
+ jest.unstable_mockModule('@lowdefy/api', () => ({
29
+ callRequest: mockCallRequest,
30
+ }));
31
+ jest.unstable_mockModule('./readBuildArtifact.js', () => ({
32
+ default: mockReadBuildArtifact,
33
+ }));
34
+ jest.unstable_mockModule('./isWriteRequestsAllowed.js', () => ({
35
+ default: mockIsWriteRequestsAllowed,
36
+ }));
37
+ jest.unstable_mockModule('../server/createLowdefyContext.js', () => ({
38
+ default: mockCreateLowdefyContext,
39
+ }));
40
+ const mockBuildPageIfNeeded = jest.fn();
41
+ const mockSyncBuildSignals = jest.fn(() => ({
42
+ registry: { home: { pageId: 'home', refPath: 'pages/home.yaml' } },
43
+ eventCounter: 0,
44
+ generation: 1,
45
+ }));
46
+ jest.unstable_mockModule('../server/jitPageBuilder.js', () => ({
47
+ default: mockBuildPageIfNeeded,
48
+ syncBuildSignals: mockSyncBuildSignals,
49
+ }));
50
+ const mockReviewPage = jest.fn();
51
+ jest.unstable_mockModule('./reviewPage.js', () => ({
52
+ default: mockReviewPage,
53
+ }));
54
+
55
+ const { ConfigError } = await import('@lowdefy/errors');
56
+ const { default: runRequest } = await import('./runRequest.js');
57
+
58
+ const honoContext = { req: { path: '/lowdefy-docs/run-request' } };
59
+
60
+ beforeEach(() => {
61
+ jest.clearAllMocks();
62
+ mockReadBuildArtifact.mockImplementation(({ name }) => {
63
+ if (name === 'plugins/requestSchemas.json') {
64
+ return { MongoDBFind: { meta: { checkRead: true, checkWrite: false } } };
65
+ }
66
+ if (name === 'pageRegistry.json') {
67
+ return { home: { pageId: 'home', refPath: 'pages/home.yaml' }, other: { pageId: 'other' } };
68
+ }
69
+ return { type: 'MongoDBFind' };
70
+ });
71
+ mockIsWriteRequestsAllowed.mockResolvedValue(false);
72
+ mockCreateLowdefyContext.mockResolvedValue({ logger: { info: mockLoggerInfo } });
73
+ mockCallRequest.mockResolvedValue({ id: 'requests', response: [{ _id: 1 }] });
74
+ mockBuildPageIfNeeded.mockResolvedValue(true);
75
+ mockReviewPage.mockResolvedValue('current');
76
+ });
77
+
78
+ test('runRequest passes a user object to createLowdefyContext', async () => {
79
+ const user = { roles: ['admin'], organization_id: 'org_1' };
80
+
81
+ const result = await runRequest({
82
+ pageId: 'home',
83
+ requestId: 'get_rows',
84
+ user,
85
+ honoContext,
86
+ });
87
+
88
+ expect(mockCreateLowdefyContext).toHaveBeenCalledWith({ c: honoContext, user });
89
+ expect(result.refused).toBe(false);
90
+ expect(result.response).toEqual([{ _id: 1 }]);
91
+ });
92
+
93
+ test('runRequest logs the user it ran the request as', async () => {
94
+ const user = { roles: ['admin'] };
95
+
96
+ await runRequest({ pageId: 'home', requestId: 'get_rows', user, honoContext });
97
+
98
+ expect(mockLoggerInfo).toHaveBeenCalledWith({
99
+ event: 'agent_run_request',
100
+ pageId: 'home',
101
+ requestId: 'get_rows',
102
+ user,
103
+ });
104
+ });
105
+
106
+ test('runRequest throws a ConfigError when user is neither an object nor a data set user', async () => {
107
+ await expect(
108
+ runRequest({ pageId: 'home', requestId: 'get_rows', user: 'admin', honoContext })
109
+ ).rejects.toThrow(ConfigError);
110
+ await expect(
111
+ runRequest({ pageId: 'home', requestId: 'get_rows', user: ['admin'], honoContext })
112
+ ).rejects.toThrow(/run_request: "user" should be a user object/);
113
+ expect(mockCreateLowdefyContext).not.toHaveBeenCalled();
114
+ });
115
+
116
+ test('runRequest builds a context with an undefined user when user is omitted', async () => {
117
+ const result = await runRequest({ pageId: 'home', requestId: 'get_rows', honoContext });
118
+
119
+ expect(mockCreateLowdefyContext).toHaveBeenCalledWith({ c: honoContext, user: undefined });
120
+ expect(mockLoggerInfo).toHaveBeenCalledWith({
121
+ event: 'agent_run_request',
122
+ pageId: 'home',
123
+ requestId: 'get_rows',
124
+ user: undefined,
125
+ });
126
+ expect(result).toEqual({ refused: false, id: 'requests', response: [{ _id: 1 }] });
127
+ });
128
+
129
+ test('runRequest refuses a write request for an impersonated caller too', async () => {
130
+ mockReadBuildArtifact.mockImplementation(({ name }) => {
131
+ if (name === 'plugins/requestSchemas.json') {
132
+ return { MongoDBInsertOne: { meta: { checkWrite: true } } };
133
+ }
134
+ return { type: 'MongoDBInsertOne' };
135
+ });
136
+
137
+ const result = await runRequest({
138
+ pageId: 'home',
139
+ requestId: 'insert_row',
140
+ user: { roles: ['admin'] },
141
+ honoContext,
142
+ });
143
+
144
+ expect(result.refused).toBe(true);
145
+ expect(mockCreateLowdefyContext).not.toHaveBeenCalled();
146
+ });
147
+
148
+ test('runRequest notes stale config when the page changed but the dev server has not rebuilt it', async () => {
149
+ mockReviewPage.mockResolvedValue('edited');
150
+
151
+ const result = await runRequest({ pageId: 'home', requestId: 'get_rows', honoContext });
152
+
153
+ expect(result.response).toEqual([{ _id: 1 }]);
154
+ expect(result.staleConfig).toContain('has not rebuilt it yet');
155
+ });
156
+
157
+ test('runRequest does not note stale config for a page that is current', async () => {
158
+ const result = await runRequest({ pageId: 'home', requestId: 'get_rows', honoContext });
159
+
160
+ expect(result.staleConfig).toBeUndefined();
161
+ });
162
+
163
+ test('runRequest throws a ConfigError when saveResponse is not a boolean', async () => {
164
+ await expect(
165
+ runRequest({ pageId: 'home', requestId: 'get_rows', saveResponse: 1, honoContext })
166
+ ).rejects.toThrow(ConfigError);
167
+ expect(mockBuildPageIfNeeded).not.toHaveBeenCalled();
168
+ });
169
+
170
+ test('runRequest notes stale config on a request that fails', async () => {
171
+ mockReviewPage.mockResolvedValue('edited');
172
+ mockCallRequest.mockRejectedValue(new Error('connect ECONNREFUSED'));
173
+
174
+ const result = await runRequest({ pageId: 'home', requestId: 'get_rows', honoContext });
175
+
176
+ expect(result.error.message).toBe('connect ECONNREFUSED');
177
+ expect(result.staleConfig).toContain('ran the previous config');
178
+ });
179
+
180
+ test('runRequest reviews only the page it ran', async () => {
181
+ await runRequest({ pageId: 'home', requestId: 'get_rows', honoContext });
182
+
183
+ expect(mockReviewPage).toHaveBeenCalledTimes(1);
184
+ expect(mockReviewPage).toHaveBeenCalledWith(
185
+ expect.objectContaining({
186
+ pageId: 'home',
187
+ entry: { pageId: 'home', refPath: 'pages/home.yaml' },
188
+ })
189
+ );
190
+ });
@@ -34,7 +34,7 @@ function saveAnnotatedScreenshot({ dataUrl, fileName }) {
34
34
  const dir = path.join(configDirectory, '.lowdefy', 'annotations');
35
35
  fs.mkdirSync(dir, { recursive: true });
36
36
  fs.writeFileSync(path.join(dir, fileName), buffer);
37
- return { path: path.join('.lowdefy', 'annotations', fileName) };
37
+ return { path: path.posix.join('.lowdefy', 'annotations', fileName) };
38
38
  } catch (error) {
39
39
  return { error: `Failed to save annotated screenshot: ${error.message}` };
40
40
  }
@@ -49,6 +49,8 @@ function scaffoldPage({ pageId, title }) {
49
49
 
50
50
  const configDirectory = process.env.LOWDEFY_DIRECTORY_CONFIG || process.cwd();
51
51
  const pageRelativePath = path.join('pages', `${pageId}.yaml`);
52
+ // _ref paths in config use forward slashes on every platform.
53
+ const pageRefPath = path.posix.join('pages', `${pageId}.yaml`);
52
54
  const pageFilePath = path.join(configDirectory, pageRelativePath);
53
55
 
54
56
  if (fs.existsSync(pageFilePath)) {
@@ -68,7 +70,7 @@ function scaffoldPage({ pageId, title }) {
68
70
 
69
71
  return {
70
72
  created: pageRelativePath,
71
- next: `Add \`- _ref: ${pageRelativePath}\` under \`pages:\` in lowdefy.yaml (or the relevant pages file), then check lowdefy_build_status.`,
73
+ next: `Add \`- _ref: ${pageRefPath}\` under \`pages:\` in lowdefy.yaml (or the relevant pages file), then check lowdefy_build_status.`,
72
74
  };
73
75
  }
74
76
 
@@ -15,8 +15,19 @@
15
15
  */
16
16
 
17
17
  import { type } from '@lowdefy/helpers';
18
+ import { normaliseJourneySteps, validateJourneySteps } from '@lowdefy/node-utils';
18
19
 
19
20
  import { getBrowser, openPage, buildPageUrl } from './getBrowser.js';
21
+ import noBrowserError from './noBrowserError.js';
22
+ import openJourney from './openJourney.js';
23
+ import resolvePageInstance from './resolvePageInstance.js';
24
+ import resolveToolCaller from './resolveToolCaller.js';
25
+ import runJourneySteps from './runJourneySteps.js';
26
+ import unsettledPageNote from './unsettledPageNote.js';
27
+ import validateJourneyMail from './validateJourneyMail.js';
28
+ import validateViewport from './validateViewport.js';
29
+ import withBrowserSlot from './withBrowserSlot.js';
30
+ import withDataSession from './withDataSession.js';
20
31
 
21
32
  // A feedback annotation's elementRect/shapes are captured in the developer's
22
33
  // live tab, viewport-relative at whatever scroll position they were at
@@ -51,18 +62,59 @@ async function resolveClip({ page, clip, scrollX, scrollY }) {
51
62
  };
52
63
  }
53
64
 
65
+ // Opens the page and, when `steps` are given, runs them before the capture —
66
+ // the same step engine lowdefy_run_journey uses, so an agent can capture an
67
+ // open dropdown, a picker's calendar or a modal. Returns the page, a cleanup
68
+ // function, the page-open timeout, any captures `screenshot` steps took and any
69
+ // step failure; the capture happens on whatever state the steps left (also
70
+ // after a failed step, which is what the agent needs to see).
71
+ async function openAndRunSteps({ browser, steps, stepTimeout, users, ...pageOptions }) {
72
+ if (steps.length === 0) {
73
+ const opened = await openPage({ browser, ...pageOptions });
74
+ return {
75
+ opened,
76
+ close: () => opened.context.close(),
77
+ openTimeout: pageOptions.timeout,
78
+ screenshots: [],
79
+ };
80
+ }
81
+ const { journey, main } = await openJourney({ browser, ...pageOptions, stepTimeout, users });
82
+ const { screenshots, failure } = await runJourneySteps({ journey, steps });
83
+ return {
84
+ opened: { ...journey.actors.current(), ready: main.ready },
85
+ close: journey.actors.closeAll,
86
+ openTimeout: journey.openTimeout,
87
+ screenshots,
88
+ failure,
89
+ };
90
+ }
91
+
54
92
  // screenshotPage lets an agent visually verify a page rendered by the
55
- // running dev server.
93
+ // running dev server, at a given viewport size and colour scheme. `pathParams`
94
+ // fill a patterned page's placeholders, `urlQuery` opens the page at a query
95
+ // string, and `steps` (see validateJourneySteps)
96
+ // drive it first — open a dropdown, click a button — so the capture shows
97
+ // that state; captures `screenshot` steps take come back as `screenshots`.
98
+ // Popups antd renders in a portal at the end of <body> are part of the
99
+ // document, so a fullPage or clip capture includes them. `user` and `data`
100
+ // name the caller as a journey's do (see resolveToolCaller): a data set user
101
+ // renders the page on that data set's own database.
56
102
  async function screenshotPage({
57
103
  origin,
58
104
  pageId,
105
+ pathParams,
106
+ urlQuery,
107
+ steps: inputSteps = [],
108
+ stepTimeout = 5000,
59
109
  fullPage = false,
60
110
  clip,
61
111
  scrollX = 0,
62
112
  scrollY = 0,
63
113
  user,
114
+ data,
64
115
  width = 1280,
65
116
  height = 800,
117
+ colorScheme = 'light',
66
118
  timeout = 15000,
67
119
  }) {
68
120
  if (type.isNone(origin) || !type.isString(origin)) {
@@ -75,34 +127,134 @@ async function screenshotPage({
75
127
  error: `screenshotPage requires a "pageId" string. Received ${JSON.stringify(pageId)}.`,
76
128
  };
77
129
  }
130
+ if (!type.isNone(urlQuery) && !type.isObject(urlQuery)) {
131
+ return {
132
+ error: `screenshotPage requires "urlQuery" to be an object. Received ${JSON.stringify(
133
+ urlQuery
134
+ )}.`,
135
+ };
136
+ }
137
+ const instance = resolvePageInstance({ pageId, pathParams });
138
+ if (!type.isUndefined(instance.error)) {
139
+ return { error: instance.error, invalidInput: true };
140
+ }
141
+ const { error: stepsError } = validateJourneySteps({ steps: inputSteps });
142
+ if (!type.isUndefined(stepsError)) {
143
+ return { error: stepsError };
144
+ }
145
+ const steps = normaliseJourneySteps({ steps: inputSteps });
146
+ const viewportError = validateViewport({ width, height, colorScheme });
147
+ if (!type.isUndefined(viewportError)) {
148
+ return { error: viewportError };
149
+ }
150
+ const mailError = validateJourneyMail({ steps });
151
+ if (!type.isUndefined(mailError)) {
152
+ return { error: mailError };
153
+ }
154
+ const caller = await resolveToolCaller({ user, data });
155
+ if (!type.isUndefined(caller.error)) {
156
+ return caller;
157
+ }
158
+
159
+ return withBrowserSlot({
160
+ task: () =>
161
+ withDataSession({
162
+ dataSet: caller.dataSet,
163
+ task: ({ dataCookie }) =>
164
+ screenshotInBrowser({
165
+ origin,
166
+ pageId,
167
+ path: instance.path,
168
+ pathParams,
169
+ urlQuery,
170
+ user: caller.user,
171
+ dataCookie,
172
+ users: caller.dataSet?.users,
173
+ width,
174
+ height,
175
+ colorScheme,
176
+ timeout,
177
+ steps,
178
+ stepTimeout,
179
+ clip,
180
+ scrollX,
181
+ scrollY,
182
+ fullPage,
183
+ }),
184
+ }),
185
+ });
186
+ }
78
187
 
188
+ // The part of screenshotPage that runs in the browser, inside a browser slot.
189
+ async function screenshotInBrowser({
190
+ origin,
191
+ pageId,
192
+ path,
193
+ pathParams,
194
+ urlQuery,
195
+ user,
196
+ dataCookie,
197
+ users,
198
+ width,
199
+ height,
200
+ colorScheme,
201
+ timeout,
202
+ steps,
203
+ stepTimeout,
204
+ clip,
205
+ scrollX,
206
+ scrollY,
207
+ fullPage,
208
+ }) {
79
209
  let browser;
80
210
  try {
81
211
  browser = await getBrowser();
82
212
  } catch (error) {
83
- return {
84
- error: `No Chromium available. Run: npx playwright install chromium (${error.message})`,
85
- };
213
+ return { error: noBrowserError(error) };
86
214
  }
87
215
 
88
- const url = buildPageUrl({ origin, pageId });
216
+ const url = buildPageUrl({ origin, pageId, path, pathParams, urlQuery });
89
217
 
90
- let context;
218
+ let close;
91
219
  try {
92
- const opened = await openPage({ browser, origin, pageId, user, width, height, timeout });
93
- context = opened.context;
220
+ const run = await openAndRunSteps({
221
+ browser,
222
+ origin,
223
+ pageId,
224
+ path,
225
+ pathParams,
226
+ urlQuery,
227
+ user,
228
+ dataCookie,
229
+ users,
230
+ width,
231
+ height,
232
+ colorScheme,
233
+ timeout,
234
+ steps,
235
+ stepTimeout,
236
+ });
237
+ const { opened, openTimeout, screenshots, failure } = run;
238
+ close = run.close;
94
239
  // Let post-load rendering (fonts, transitions, client-side state) settle.
95
240
  await opened.page.waitForTimeout(300);
96
241
 
97
242
  const docClip = await resolveClip({ page: opened.page, clip, scrollX, scrollY });
98
243
  const screenshotOptions = docClip ? { type: 'png', clip: docClip } : { type: 'png', fullPage };
99
244
  const buffer = await opened.page.screenshot(screenshotOptions);
100
- return { data: buffer.toString('base64'), mimeType: 'image/png' };
245
+ const result = { data: buffer.toString('base64'), mimeType: 'image/png', screenshots };
246
+ if (!type.isUndefined(failure)) {
247
+ result.failure = failure;
248
+ }
249
+ if (!opened.ready) {
250
+ return { ...result, ready: false, note: unsettledPageNote({ timeout: openTimeout }) };
251
+ }
252
+ return result;
101
253
  } catch (error) {
102
254
  return { error: `Failed to screenshot "${url}": ${error.message}` };
103
255
  } finally {
104
- if (context) {
105
- await context.close();
256
+ if (close) {
257
+ await close();
106
258
  }
107
259
  }
108
260
  }