@lowdefy/server-dev 6.1.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 (623) 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 +2 -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 +222 -36
  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/{getAuthConfig.js → getAuth.js} +15 -9
  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/{session.js → getStrategies.js} +17 -16
  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 +168 -41
  348. package/lib/server/createLowdefyContext.test.mjs +328 -8
  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 +15 -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/sinkExternalConnections.js +95 -0
  428. package/lib/server/sinkExternalConnections.test.mjs +135 -0
  429. package/lib/server/skipStaleMapWrites.js +62 -0
  430. package/lib/server/skipStaleMapWrites.test.mjs +113 -0
  431. package/lib/server/startIdleGc.js +67 -0
  432. package/lib/server/startIdleGc.test.mjs +138 -0
  433. package/lib/server/testFixtures/importAppCodeTwice.mjs +70 -0
  434. package/lib/server/trackSessionWork.js +30 -0
  435. package/lib/vite/createDevServerExclude.js +40 -0
  436. package/lib/vite/createDevServerExclude.test.mjs +79 -0
  437. package/manager/getContext.mjs +24 -2
  438. package/manager/processes/initialBuild.mjs +14 -0
  439. package/manager/processes/initialBuild.test.mjs +101 -0
  440. package/manager/processes/lowdefyBuild.mjs +30 -7
  441. package/manager/processes/lowdefyBuild.skipPluginChecks.test.mjs +62 -0
  442. package/manager/processes/lowdefyBuild.test.mjs +136 -1
  443. package/manager/processes/optimizeDependencies.mjs +59 -0
  444. package/manager/processes/restartServer.mjs +34 -6
  445. package/manager/processes/restartServer.test.mjs +162 -0
  446. package/manager/processes/shutdownServer.mjs +11 -0
  447. package/manager/processes/shutdownServer.test.mjs +45 -0
  448. package/manager/processes/startFirstServer.mjs +46 -0
  449. package/manager/processes/startFirstServer.test.mjs +326 -0
  450. package/manager/processes/startMailSink.mjs +135 -0
  451. package/manager/processes/startMailSink.test.mjs +146 -0
  452. package/manager/processes/startProxy.mjs +129 -12
  453. package/manager/processes/startProxy.test.mjs +366 -0
  454. package/manager/processes/startServer.mjs +47 -6
  455. package/manager/processes/startServer.test.mjs +155 -0
  456. package/manager/processes/startWatchers.mjs +6 -4
  457. package/manager/processes/syncServer.mjs +69 -0
  458. package/manager/processes/syncServer.test.mjs +141 -0
  459. package/manager/processes/waitForDevServer.mjs +48 -0
  460. package/manager/processes/waitForDevServer.test.mjs +112 -0
  461. package/manager/run.mjs +128 -43
  462. package/manager/utils/BatchChanges.mjs +21 -5
  463. package/manager/utils/BatchChanges.test.mjs +77 -0
  464. package/manager/utils/acquireDevInstance.mjs +99 -0
  465. package/manager/utils/acquireDevInstance.test.mjs +160 -0
  466. package/manager/utils/createBuildActivity.mjs +67 -0
  467. package/manager/utils/createBuildActivity.test.mjs +58 -0
  468. package/manager/utils/createChangeSignal.mjs +39 -0
  469. package/manager/utils/createChangeSignal.test.mjs +62 -0
  470. package/manager/utils/createCustomPluginMessagesMap.mjs +23 -33
  471. package/manager/utils/createCustomPluginTypesMap.mjs +24 -30
  472. package/manager/utils/createCustomPluginTypesMap.test.mjs +94 -0
  473. package/manager/utils/createDotPathIgnore.mjs +44 -0
  474. package/manager/utils/createDotPathIgnore.test.mjs +65 -0
  475. package/manager/utils/createIdleGc.mjs +51 -0
  476. package/manager/utils/createIdleGc.test.mjs +77 -0
  477. package/manager/utils/createRequestActivity.mjs +100 -0
  478. package/manager/utils/createRequestActivity.test.mjs +121 -0
  479. package/manager/utils/createServerArtifactTracker.mjs +83 -0
  480. package/manager/utils/createServerArtifactTracker.test.mjs +78 -0
  481. package/manager/utils/createServerEnv.mjs +39 -0
  482. package/manager/utils/filesHaveSameBytes.mjs +41 -0
  483. package/manager/utils/findBuildFilesOutsideWatch.mjs +59 -0
  484. package/manager/utils/findBuildFilesOutsideWatch.test.mjs +111 -0
  485. package/manager/utils/importFresh.mjs +46 -0
  486. package/manager/utils/importFresh.test.mjs +72 -0
  487. package/manager/utils/importFreshWorker.mjs +20 -0
  488. package/manager/utils/killTaggedBrowser.mjs +35 -0
  489. package/manager/utils/pagePathChanged.mjs +56 -0
  490. package/manager/utils/pruneRecordings.mjs +85 -0
  491. package/manager/utils/pruneRecordings.test.mjs +117 -0
  492. package/manager/utils/publishBuildDirectory.mjs +121 -0
  493. package/manager/utils/publishBuildDirectory.order.test.mjs +74 -0
  494. package/manager/utils/publishBuildDirectory.test.mjs +262 -0
  495. package/manager/utils/readBasePath.mjs +27 -0
  496. package/manager/utils/readBasePath.test.mjs +43 -0
  497. package/manager/utils/readBuildStatusWait.mjs +67 -0
  498. package/manager/utils/readManagerVersion.mjs +30 -0
  499. package/manager/utils/readManagerVersion.test.mjs +35 -0
  500. package/manager/utils/readPluginDefinitions.mjs +36 -0
  501. package/manager/utils/resolveDevAuthUrl.mjs +58 -0
  502. package/manager/utils/resolveDevAuthUrl.test.mjs +107 -0
  503. package/manager/utils/resolvePorts.mjs +49 -0
  504. package/manager/utils/setupWatcher.mjs +19 -11
  505. package/manager/utils/startRecordingPruner.mjs +45 -0
  506. package/manager/utils/startRecordingPruner.test.mjs +66 -0
  507. package/manager/utils/waitForChildExit.mjs +53 -0
  508. package/manager/utils/waitForChildExit.test.mjs +70 -0
  509. package/manager/utils/waitForServer.mjs +44 -0
  510. package/manager/utils/writeBuildStatus.mjs +1 -1
  511. package/manager/watchers/createPluginSourceIgnore.mjs +77 -0
  512. package/manager/watchers/envWatcher.mjs +9 -2
  513. package/manager/watchers/envWatcher.test.mjs +87 -0
  514. package/manager/watchers/listServerSidePackages.mjs +47 -0
  515. package/manager/watchers/listServerSidePackages.test.mjs +38 -0
  516. package/manager/watchers/lowdefyBuildWatcher.mjs +106 -22
  517. package/manager/watchers/lowdefyBuildWatcher.test.mjs +309 -0
  518. package/manager/watchers/pluginSourceWatcher.mjs +79 -0
  519. package/manager/watchers/pluginSourceWatcher.test.mjs +222 -0
  520. package/manager/watchers/restartRequestWatcher.mjs +71 -0
  521. package/manager/watchers/restartRequestWatcher.test.mjs +111 -0
  522. package/manager/watchers/selectLocalPluginPackages.mjs +48 -0
  523. package/manager/watchers/selectLocalPluginPackages.test.mjs +73 -0
  524. package/manager/watchers/serverPackageWatcher.mjs +38 -0
  525. package/package.json +60 -47
  526. package/package.original.json +60 -47
  527. package/src/app.js +131 -20
  528. package/src/html/renderDevPage.js +63 -11
  529. package/src/html/renderDevPage.test.mjs +138 -0
  530. package/src/lib/getRequestPath.js +29 -0
  531. package/src/middleware/createSameOriginGuard.js +37 -0
  532. package/src/middleware/createSameOriginGuard.test.mjs +102 -0
  533. package/src/middleware/errorHandler.js +51 -1
  534. package/src/middleware/errorHandler.test.mjs +173 -3
  535. package/src/middleware/isRebindingSafeHost.js +44 -0
  536. package/src/middleware/isRebindingSafeHost.test.mjs +32 -0
  537. package/src/middleware/localDevToolsOnly.js +38 -0
  538. package/src/middleware/localDevToolsOnly.test.mjs +61 -0
  539. package/src/middleware/noStore.js +27 -0
  540. package/src/middleware/noStore.test.mjs +48 -0
  541. package/src/middleware/requestTimeout.js +38 -0
  542. package/src/middleware/staleFlag.js +76 -0
  543. package/src/middleware/staleFlag.test.mjs +127 -0
  544. package/src/middleware/stampCaller.js +38 -0
  545. package/src/middleware/stampCaller.test.mjs +64 -0
  546. package/src/routes/agent.js +15 -3
  547. package/src/routes/auth.js +29 -20
  548. package/src/routes/auth.test.mjs +30 -56
  549. package/src/routes/clientError.js +13 -13
  550. package/src/routes/cron.js +3 -1
  551. package/src/routes/cronForward.js +3 -1
  552. package/src/routes/detached.js +22 -9
  553. package/src/routes/devInspect.js +17 -8
  554. package/src/routes/devInspect.test.mjs +44 -7
  555. package/src/routes/devRecording.js +113 -0
  556. package/src/routes/devRecording.test.mjs +253 -0
  557. package/src/routes/docs/buildStatus.js +10 -2
  558. package/src/routes/docs/buildStatus.test.mjs +46 -0
  559. package/src/routes/docs/content.js +5 -3
  560. package/src/routes/docs/evalOperator.js +15 -1
  561. package/src/routes/docs/events.js +50 -0
  562. package/src/routes/docs/events.test.mjs +117 -0
  563. package/src/routes/docs/find.js +9 -1
  564. package/src/routes/docs/icons.js +30 -0
  565. package/src/routes/docs/inspectState.js +16 -1
  566. package/src/routes/docs/journey.js +194 -0
  567. package/src/routes/docs/journey.test.mjs +401 -0
  568. package/src/routes/docs/journeyRefusals.test.mjs +71 -0
  569. package/src/routes/docs/loadState.js +4 -2
  570. package/src/routes/docs/mcp.js +34 -3
  571. package/src/routes/docs/mcp.test.mjs +130 -0
  572. package/src/routes/docs/mutants.js +78 -0
  573. package/src/routes/docs/mutants.test.mjs +180 -0
  574. package/src/routes/docs/parsePathParamsParam.js +41 -0
  575. package/src/routes/docs/parsePathParamsParam.test.mjs +42 -0
  576. package/src/routes/docs/parseUserParam.js +27 -21
  577. package/src/routes/docs/parseUserParam.test.mjs +17 -5
  578. package/src/routes/docs/restart.js +35 -0
  579. package/src/routes/docs/restart.test.mjs +64 -0
  580. package/src/routes/docs/runEndpoint.js +54 -0
  581. package/src/routes/docs/runEndpoint.test.mjs +165 -0
  582. package/src/routes/docs/runRequest.js +25 -8
  583. package/src/routes/docs/runRequest.test.mjs +92 -0
  584. package/src/routes/docs/screenshot.js +50 -2
  585. package/src/routes/docs/screenshot.test.mjs +117 -0
  586. package/src/routes/docs/search.js +15 -2
  587. package/src/routes/docs/snapshotState.js +17 -3
  588. package/src/routes/endpoints.js +27 -26
  589. package/src/routes/endpointsTextAnswer.test.mjs +77 -0
  590. package/src/routes/feedback.js +10 -13
  591. package/src/routes/jitPage.js +99 -35
  592. package/src/routes/jitPage.test.mjs +248 -0
  593. package/src/routes/mcp.js +82 -5
  594. package/src/routes/mcp.test.mjs +215 -0
  595. package/src/routes/mcpMemberToken.test.mjs +125 -0
  596. package/src/routes/mcpProtectedResourceMetadata.js +34 -0
  597. package/src/routes/mcpProtectedResourceMetadata.test.mjs +65 -0
  598. package/src/routes/mountDevPageRoutes.js +25 -0
  599. package/src/routes/mountDevPageRoutes.test.mjs +30 -0
  600. package/src/routes/mountOauthDiscovery.js +44 -0
  601. package/src/routes/mountOauthDiscovery.test.mjs +79 -0
  602. package/src/routes/normalizeMcpProtocolVersionHeader.js +46 -0
  603. package/src/routes/normalizeMcpProtocolVersionHeader.test.mjs +62 -0
  604. package/src/routes/reload.js +16 -0
  605. package/src/routes/request.js +6 -4
  606. package/src/routes/request.test.mjs +9 -5
  607. package/src/routes/requestBody.test.mjs +159 -0
  608. package/src/routes/usage.js +6 -2
  609. package/src/routes/user.js +27 -0
  610. package/src/routes/websocket.js +29 -8
  611. package/src/routes/websocket.test.mjs +65 -0
  612. package/src/routes/wellKnownFallback.js +30 -0
  613. package/src/routes/wellKnownFallback.test.mjs +32 -0
  614. package/src/websocket/devWebSocket.js +41 -26
  615. package/src/websocket/devWebSocket.test.mjs +157 -0
  616. package/vite.config.js +53 -23
  617. package/lib/server/auth/getDevSession.js +0 -70
  618. package/lib/server/auth/getDevSession.test.mjs +0 -138
  619. package/manager/processes/warnAuthUrlPortMismatch.mjs +0 -47
  620. package/manager/processes/warnAuthUrlPortMismatch.test.mjs +0 -93
  621. package/manager/watchers/moduleBuildWatcher.mjs +0 -79
  622. package/manager/watchers/serverArtifactWatcher.mjs +0 -120
  623. package/src/routes/apiPage.js +0 -37
@@ -14,8 +14,15 @@
14
14
  limitations under the License.
15
15
  */
16
16
 
17
+ import fs from 'fs';
18
+ import os from 'os';
19
+ import path from 'path';
20
+
17
21
  import { Client } from '@modelcontextprotocol/sdk/client/index.js';
18
22
  import { InMemoryTransport } from '@modelcontextprotocol/sdk/inMemory.js';
23
+ import { LoggingMessageNotificationSchema } from '@modelcontextprotocol/sdk/types.js';
24
+
25
+ import { jest } from '@jest/globals';
19
26
 
20
27
  import setupTestFixtures from './setupTestFixtures.mjs';
21
28
 
@@ -24,7 +31,21 @@ import setupTestFixtures from './setupTestFixtures.mjs';
24
31
  const fixtureDir = setupTestFixtures();
25
32
  process.chdir(fixtureDir);
26
33
 
27
- const { default: createDocsMcpServer } = await import('./createDocsMcpServer.js');
34
+ // runJourney needs a browser; mocked so the tool's result shaping (JSON text
35
+ // followed by image blocks) can be asserted without one.
36
+ const mockRunJourney = jest.fn();
37
+ jest.unstable_mockModule('./runJourney.js', () => ({
38
+ default: mockRunJourney,
39
+ runSteps: jest.fn(),
40
+ MAIN_ACTOR: 'main',
41
+ }));
42
+ const mockScreenshotPage = jest.fn();
43
+ jest.unstable_mockModule('./screenshotPage.js', () => ({ default: mockScreenshotPage }));
44
+
45
+ const { default: createDocsMcpServer, subscribeMcpServerToDevEvents } = await import(
46
+ './createDocsMcpServer.js'
47
+ );
48
+ const { publish } = await import('./devEventBus.js');
28
49
 
29
50
  const EXPECTED_TOOLS = [
30
51
  'lowdefy_overview',
@@ -34,15 +55,20 @@ const EXPECTED_TOOLS = [
34
55
  'lowdefy_get_examples',
35
56
  'lowdefy_get_doc',
36
57
  'lowdefy_search_docs',
58
+ 'lowdefy_search_icons',
37
59
  'lowdefy_get_plugin_doc',
38
60
  'lowdefy_build_status',
61
+ 'lowdefy_check',
39
62
  'lowdefy_get_page_config',
40
63
  'lowdefy_find_config',
41
64
  'lowdefy_screenshot_page',
65
+ 'lowdefy_run_journey',
42
66
  'lowdefy_scaffold_page',
43
67
  'lowdefy_inspect_state',
44
68
  'lowdefy_eval_operator',
45
69
  'lowdefy_run_request',
70
+ 'lowdefy_run_endpoint',
71
+ 'lowdefy_restart',
46
72
  'lowdefy_app_map',
47
73
  'lowdefy_checkpoint',
48
74
  'lowdefy_revert_checkpoint',
@@ -50,17 +76,82 @@ const EXPECTED_TOOLS = [
50
76
  'lowdefy_load_state',
51
77
  'lowdefy_list_state_checkpoints',
52
78
  'lowdefy_checkpoint_to_mocks',
79
+ 'lowdefy_journey_session',
53
80
  ];
54
81
 
55
82
  async function connectClient() {
56
- const server = createDocsMcpServer({ origin: 'http://localhost:3000' });
83
+ const { client } = await connectPair();
84
+ return client;
85
+ }
86
+
87
+ async function connectPair() {
88
+ const server = createDocsMcpServer({ origin: 'http://localhost:3000', version: '7.2.0' });
57
89
  const client = new Client({ name: 'test-client', version: '1.0.0' });
58
90
  const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
59
91
  await server.connect(serverTransport);
60
92
  await client.connect(clientTransport);
61
- return client;
93
+ return { client, server };
62
94
  }
63
95
 
96
+ test('MCP server reports the Lowdefy version it is given as its server version', async () => {
97
+ const client = await connectClient();
98
+ expect(client.getServerVersion()).toEqual({ name: 'lowdefy', version: '7.2.0' });
99
+ await client.close();
100
+ });
101
+
102
+ test('MCP server declares the logging capability and teaches the push channel', async () => {
103
+ const client = await connectClient();
104
+ expect(client.getServerCapabilities().logging).toEqual({});
105
+ const instructions = client.getInstructions();
106
+ expect(instructions).toContain('notifications/message');
107
+ expect(instructions).toContain('logger "lowdefy"');
108
+ await client.close();
109
+ });
110
+
111
+ test('subscribeMcpServerToDevEvents forwards bus events as notifications/message from logger lowdefy', async () => {
112
+ const { client, server } = await connectPair();
113
+ const received = [];
114
+ client.setNotificationHandler(
115
+ LoggingMessageNotificationSchema,
116
+ (notification) => void received.push(notification.params)
117
+ );
118
+ const unsubscribe = subscribeMcpServerToDevEvents(server);
119
+
120
+ publish({ type: 'client_error', timestamp: '2026-01-01T00:00:00.000Z', message: 'boom' });
121
+ publish({ type: 'build', timestamp: '2026-01-01T00:00:01.000Z', status: 'error', errorCount: 1 });
122
+ publish({ type: 'build', timestamp: '2026-01-01T00:00:02.000Z', status: 'ok', errorCount: 0 });
123
+ await new Promise((resolve) => setTimeout(resolve, 20));
124
+
125
+ expect(received).toEqual([
126
+ {
127
+ level: 'info',
128
+ logger: 'lowdefy',
129
+ data: { type: 'client_error', timestamp: '2026-01-01T00:00:00.000Z', message: 'boom' },
130
+ },
131
+ {
132
+ level: 'error',
133
+ logger: 'lowdefy',
134
+ data: {
135
+ type: 'build',
136
+ timestamp: '2026-01-01T00:00:01.000Z',
137
+ status: 'error',
138
+ errorCount: 1,
139
+ },
140
+ },
141
+ {
142
+ level: 'info',
143
+ logger: 'lowdefy',
144
+ data: { type: 'build', timestamp: '2026-01-01T00:00:02.000Z', status: 'ok', errorCount: 0 },
145
+ },
146
+ ]);
147
+
148
+ unsubscribe();
149
+ publish({ type: 'client_error', message: 'after unsubscribe' });
150
+ await new Promise((resolve) => setTimeout(resolve, 20));
151
+ expect(received).toHaveLength(3);
152
+ await client.close();
153
+ });
154
+
64
155
  test('MCP server exposes instructions teaching the feedback loop', async () => {
65
156
  const client = await connectClient();
66
157
  const instructions = client.getInstructions();
@@ -77,15 +168,26 @@ test('MCP tools/list returns all lowdefy tools', async () => {
77
168
  await client.close();
78
169
  });
79
170
 
171
+ test('MCP tools/list serves exactly the tools in devToolDefinitions, which the stdio shim lists', async () => {
172
+ const { default: devToolDefinitions } = await import('./devToolDefinitions.js');
173
+ const client = await connectClient();
174
+ const { tools } = await client.listTools();
175
+ expect(tools.map((tool) => tool.name).sort()).toEqual(Object.keys(devToolDefinitions).sort());
176
+ await client.close();
177
+ });
178
+
80
179
  test('MCP tools that render a page headless advertise an optional user parameter', async () => {
81
180
  const client = await connectClient();
82
181
  const { tools } = await client.listTools();
83
182
 
84
183
  [
85
184
  'lowdefy_screenshot_page',
185
+ 'lowdefy_run_journey',
86
186
  'lowdefy_inspect_state',
87
187
  'lowdefy_eval_operator',
88
188
  'lowdefy_load_state',
189
+ 'lowdefy_run_request',
190
+ 'lowdefy_run_endpoint',
89
191
  ].forEach((name) => {
90
192
  const tool = tools.find((candidate) => candidate.name === name);
91
193
  expect(tool.inputSchema.properties.user).toBeDefined();
@@ -95,6 +197,137 @@ test('MCP tools that render a page headless advertise an optional user parameter
95
197
  await client.close();
96
198
  });
97
199
 
200
+ test('MCP tools that act as someone take one user option and share one sentence explaining it', async () => {
201
+ const client = await connectClient();
202
+ const { tools } = await client.listTools();
203
+ const sentence =
204
+ 'Pass user to act as someone: a user object such as {"roles":["admin"]}, "none" to act signed out, or the name of a user in the data set that data names';
205
+
206
+ [
207
+ 'lowdefy_screenshot_page',
208
+ 'lowdefy_run_journey',
209
+ 'lowdefy_inspect_state',
210
+ 'lowdefy_eval_operator',
211
+ 'lowdefy_load_state',
212
+ 'lowdefy_run_request',
213
+ 'lowdefy_run_endpoint',
214
+ ].forEach((name) => {
215
+ const tool = tools.find((candidate) => candidate.name === name);
216
+ expect(tool.description).toContain(sentence);
217
+ expect(tool.inputSchema.properties.data.type).toEqual('string');
218
+ const forms = JSON.stringify(tool.inputSchema.properties.user);
219
+ expect(forms).toContain('"const":"none"');
220
+ expect(forms).toContain('"type":"string"');
221
+ expect(forms).toContain('"type":"object"');
222
+ });
223
+
224
+ await client.close();
225
+ });
226
+
227
+ test('MCP tools/call lowdefy_screenshot_page passes data and a data set user name through', async () => {
228
+ mockScreenshotPage.mockResolvedValue({ data: 'AAAA', mimeType: 'image/png', screenshots: [] });
229
+ const client = await connectClient();
230
+
231
+ await client.callTool({
232
+ name: 'lowdefy_screenshot_page',
233
+ arguments: { pageId: 'tickets', user: 'member', data: 'crm' },
234
+ });
235
+
236
+ expect(mockScreenshotPage).toHaveBeenCalledWith(
237
+ expect.objectContaining({ pageId: 'tickets', user: 'member', data: 'crm' })
238
+ );
239
+ await client.close();
240
+ });
241
+
242
+ test('MCP lowdefy_screenshot_page advertises width and height up to 4096, and colorScheme', async () => {
243
+ const client = await connectClient();
244
+ const { tools } = await client.listTools();
245
+ const { properties } = tools.find((tool) => tool.name === 'lowdefy_screenshot_page').inputSchema;
246
+
247
+ expect(properties.width).toMatchObject({ type: 'integer', exclusiveMinimum: 0, maximum: 4096 });
248
+ expect(properties.height).toMatchObject({ type: 'integer', exclusiveMinimum: 0, maximum: 4096 });
249
+ expect(properties.colorScheme).toMatchObject({ enum: ['light', 'dark'] });
250
+ await client.close();
251
+ });
252
+
253
+ test('MCP lowdefy_screenshot_page advertises steps and urlQuery, and passes them on', async () => {
254
+ mockScreenshotPage.mockResolvedValue({ data: 'AAAA', mimeType: 'image/png', screenshots: [] });
255
+ const client = await connectClient();
256
+ const { tools } = await client.listTools();
257
+ const { properties } = tools.find((tool) => tool.name === 'lowdefy_screenshot_page').inputSchema;
258
+ expect(properties.steps).toMatchObject({ type: 'array' });
259
+ expect(properties.urlQuery).toBeDefined();
260
+
261
+ const result = await client.callTool({
262
+ name: 'lowdefy_screenshot_page',
263
+ arguments: { pageId: 'gates', urlQuery: { framework: 'popia' }, steps: [{ open: 'status' }] },
264
+ });
265
+ expect(mockScreenshotPage).toHaveBeenCalledWith(
266
+ expect.objectContaining({
267
+ pageId: 'gates',
268
+ urlQuery: { framework: 'popia' },
269
+ steps: [{ open: 'status' }],
270
+ })
271
+ );
272
+ expect(result.content).toEqual([{ type: 'image', data: 'AAAA', mimeType: 'image/png' }]);
273
+ await client.close();
274
+ });
275
+
276
+ test('MCP lowdefy_screenshot_page returns a failed step beside the image', async () => {
277
+ mockScreenshotPage.mockResolvedValue({
278
+ data: 'AAAA',
279
+ mimeType: 'image/png',
280
+ screenshots: [],
281
+ failure: { index: 0, step: { open: 'x' }, message: 'm' },
282
+ });
283
+ const client = await connectClient();
284
+ const result = await client.callTool({
285
+ name: 'lowdefy_screenshot_page',
286
+ arguments: { pageId: 'gates', steps: [{ open: 'x' }] },
287
+ });
288
+ expect(result.content).toHaveLength(2);
289
+ expect(JSON.parse(result.content[0].text).failure.index).toBe(0);
290
+ expect(result.content[1].type).toBe('image');
291
+ await client.close();
292
+ });
293
+
294
+ test('MCP lowdefy_screenshot_page returns screenshot step captures after the final capture', async () => {
295
+ mockScreenshotPage.mockResolvedValue({
296
+ data: 'AAAA',
297
+ mimeType: 'image/png',
298
+ screenshots: [{ name: 'before', data: 'BBBB', mimeType: 'image/png' }],
299
+ });
300
+ const client = await connectClient();
301
+ const result = await client.callTool({
302
+ name: 'lowdefy_screenshot_page',
303
+ arguments: { pageId: 'gates', steps: [{ screenshot: 'before' }, { open: 'status' }] },
304
+ });
305
+ expect(JSON.parse(result.content[0].text)).toEqual({
306
+ passed: true,
307
+ screenshots: [{ name: 'before' }],
308
+ });
309
+ expect(result.content.slice(1)).toEqual([
310
+ { type: 'image', data: 'AAAA', mimeType: 'image/png' },
311
+ { type: 'image', data: 'BBBB', mimeType: 'image/png' },
312
+ ]);
313
+ await client.close();
314
+ });
315
+
316
+ test('MCP lowdefy_run_journey advertises a state option of true, false or state paths', async () => {
317
+ const client = await connectClient();
318
+ const { tools } = await client.listTools();
319
+ const { properties, required } = tools.find(
320
+ (tool) => tool.name === 'lowdefy_run_journey'
321
+ ).inputSchema;
322
+
323
+ expect(properties.state.anyOf).toEqual([
324
+ { type: 'boolean' },
325
+ { type: 'array', items: { type: 'string', minLength: 1 } },
326
+ ]);
327
+ expect(required).not.toContain('state');
328
+ await client.close();
329
+ });
330
+
98
331
  test('MCP tools/call lowdefy_get_schema returns a block schema', async () => {
99
332
  const client = await connectClient();
100
333
  const result = await client.callTool({
@@ -106,6 +339,40 @@ test('MCP tools/call lowdefy_get_schema returns a block schema', async () => {
106
339
  await client.close();
107
340
  });
108
341
 
342
+ test('MCP get_schema, get_doc and find_config describe and return hazards', async () => {
343
+ const client = await connectClient();
344
+ const { tools } = await client.listTools();
345
+ for (const name of ['lowdefy_get_schema', 'lowdefy_get_doc', 'lowdefy_find_config']) {
346
+ expect(tools.find((tool) => tool.name === name).description).toContain(
347
+ 'Results include `hazards`'
348
+ );
349
+ }
350
+
351
+ const schema = await client.callTool({
352
+ name: 'lowdefy_get_schema',
353
+ arguments: { kind: 'blocks', type: 'TestBlock' },
354
+ });
355
+ expect(JSON.parse(schema.content[0].text).hazards[0].id).toEqual('test-block-hazard');
356
+
357
+ const doc = await client.callTool({
358
+ name: 'lowdefy_get_doc',
359
+ arguments: { kind: 'operator', type: '_get' },
360
+ });
361
+ expect(doc.content[0].text).toContain('## Hazards');
362
+ expect(doc.content[0].text).toContain(
363
+ '**get-fixture-hazard**: _get fixture hazard. (see `operators/_get`)'
364
+ );
365
+
366
+ const found = await client.callTool({
367
+ name: 'lowdefy_find_config',
368
+ arguments: { id: 'my_button' },
369
+ });
370
+ expect(JSON.parse(found.content[0].text).matches[0].hazards[0].id).toEqual(
371
+ 'visible-false-prunes-state'
372
+ );
373
+ await client.close();
374
+ });
375
+
109
376
  test('MCP tools/call lowdefy_build_status returns build status with client errors', async () => {
110
377
  const client = await connectClient();
111
378
  const result = await client.callTool({ name: 'lowdefy_build_status', arguments: {} });
@@ -125,7 +392,23 @@ test('MCP tools/call lowdefy_find_config locates a block by id', async () => {
125
392
  expect(parsed.kind).toEqual('page');
126
393
  expect(parsed.pageId).toEqual('home');
127
394
  expect(parsed.file).toEqual('pages/home.yaml');
128
- expect(parsed.matches[0].location.source).toContain('pages/home.yaml');
395
+ expect(parsed.matches[0].location.source).toContain(path.join('pages', 'home.yaml'));
396
+ await client.close();
397
+ });
398
+
399
+ test('MCP tools/call lowdefy_restart writes the restart sentinel and tells the caller to poll', async () => {
400
+ const client = await connectClient();
401
+ const result = await client.callTool({
402
+ name: 'lowdefy_restart',
403
+ arguments: { reason: 'edited a request plugin' },
404
+ });
405
+ const body = JSON.parse(result.content[0].text);
406
+ expect(body.requested).toBe(true);
407
+ expect(body.reason).toBe('edited a request plugin');
408
+ expect(body.note).toContain('build-status');
409
+ const sentinelPath = path.join(fixtureDir, 'build', '.restart');
410
+ expect(JSON.parse(fs.readFileSync(sentinelPath, 'utf8')).reason).toBe('edited a request plugin');
411
+ fs.rmSync(sentinelPath, { force: true });
129
412
  await client.close();
130
413
  });
131
414
 
@@ -139,3 +422,360 @@ test('MCP tools/call with an unknown type returns isError with guidance', async
139
422
  expect(result.content[0].text).toContain('lowdefy_list_types');
140
423
  await client.close();
141
424
  });
425
+
426
+ test('MCP lowdefy_get_schema returns the journey step grammar for kind journey-step without a type', async () => {
427
+ const client = await connectClient();
428
+ const { tools } = await client.listTools();
429
+ const getSchemaTool = tools.find((tool) => tool.name === 'lowdefy_get_schema');
430
+ expect(getSchemaTool.inputSchema.properties.kind.enum).toContain('journey-step');
431
+ expect(getSchemaTool.inputSchema.required).toEqual(['kind']);
432
+ const runJourneyTool = tools.find((tool) => tool.name === 'lowdefy_run_journey');
433
+ expect(runJourneyTool.inputSchema.properties.steps.description).toContain(
434
+ 'lowdefy_get_schema with kind "journey-step"'
435
+ );
436
+
437
+ const all = await client.callTool({
438
+ name: 'lowdefy_get_schema',
439
+ arguments: { kind: 'journey-step' },
440
+ });
441
+ expect(all.isError).toBeUndefined();
442
+ expect(JSON.parse(all.content[0].text).schema.title).toEqual('Journey step');
443
+
444
+ const wait = await client.callTool({
445
+ name: 'lowdefy_get_schema',
446
+ arguments: { kind: 'journey-step', type: 'wait' },
447
+ });
448
+ expect(JSON.parse(wait.content[0].text)).toMatchObject({ kind: 'journey-step', type: 'wait' });
449
+
450
+ const noType = await client.callTool({
451
+ name: 'lowdefy_get_schema',
452
+ arguments: { kind: 'blocks' },
453
+ });
454
+ expect(noType.isError).toBe(true);
455
+ expect(noType.content[0].text).toContain('requires a "type"');
456
+ await client.close();
457
+ });
458
+
459
+ // The dev server keeps serving the previous build when a rebuild fails, so
460
+ // every tool result must announce that its answer predates the caller's edits.
461
+ test('MCP tools/call prepends a STALE notice while the last build failed', async () => {
462
+ const statusPath = path.join(fixtureDir, 'build', 'buildStatus.json');
463
+ const okStatus = fs.readFileSync(statusPath, 'utf8');
464
+ fs.writeFileSync(
465
+ statusPath,
466
+ JSON.stringify({
467
+ status: 'error',
468
+ timestamp: '2026-02-03T04:05:06.000Z',
469
+ errors: [{ message: 'Block type "Buton" not found.' }],
470
+ warnings: [],
471
+ })
472
+ );
473
+ try {
474
+ const client = await connectClient();
475
+ const result = await client.callTool({
476
+ name: 'lowdefy_get_schema',
477
+ arguments: { kind: 'blocks', type: 'Button' },
478
+ });
479
+ expect(result.content[0].text.startsWith('STALE: ')).toBe(true);
480
+ expect(result.content[0].text).toContain('"staleSince":"2026-02-03T04:05:06.000Z"');
481
+ // The tool's own payload is untouched, just no longer first.
482
+ const parsed = JSON.parse(result.content[1].text);
483
+ expect(parsed.schema.properties.title).toBeDefined();
484
+ await client.close();
485
+ } finally {
486
+ fs.writeFileSync(statusPath, okStatus);
487
+ }
488
+ });
489
+
490
+ test('MCP tools/call lowdefy_run_journey returns the JSON result followed by one image per screenshot', async () => {
491
+ mockRunJourney.mockResolvedValue({
492
+ pageId: 'form',
493
+ passed: false,
494
+ steps: [{ index: 0, step: { screenshot: 'before' }, status: 'ok', durationMs: 3 }],
495
+ failure: { index: 1, step: { click: 'nope' }, expected: 'x', actual: 'y', message: 'm' },
496
+ screenshots: [
497
+ { name: 'before', data: 'AAAA', mimeType: 'image/png' },
498
+ { name: 'after', data: 'BBBB', mimeType: 'image/png' },
499
+ ],
500
+ state: { saved: false },
501
+ });
502
+ const client = await connectClient();
503
+
504
+ const result = await client.callTool({
505
+ name: 'lowdefy_run_journey',
506
+ arguments: {
507
+ pageId: 'form',
508
+ steps: [{ screenshot: 'before' }, { click: 'nope' }],
509
+ user: { roles: ['admin'] },
510
+ urlQuery: { id: '1' },
511
+ state: ['saved'],
512
+ },
513
+ });
514
+
515
+ expect(mockRunJourney).toHaveBeenCalledWith({
516
+ origin: 'http://localhost:3000',
517
+ pageId: 'form',
518
+ steps: [{ screenshot: 'before' }, { click: 'nope' }],
519
+ user: { roles: ['admin'] },
520
+ urlQuery: { id: '1' },
521
+ state: ['saved'],
522
+ basePath: '',
523
+ recording: {
524
+ source: 'journey',
525
+ run: { id: expect.stringMatching(/^\d{8}T\d{6}Z-[a-z0-9]{6}$/), by: 'agent', journey: null },
526
+ },
527
+ });
528
+ expect(result.content).toHaveLength(3);
529
+ const summary = JSON.parse(result.content[0].text);
530
+ expect(summary.passed).toBe(false);
531
+ expect(summary.failure.index).toBe(1);
532
+ expect(summary.state).toEqual({ saved: false });
533
+ expect(summary.screenshots).toEqual([{ name: 'before' }, { name: 'after' }]);
534
+ expect(result.content[1]).toEqual({ type: 'image', data: 'AAAA', mimeType: 'image/png' });
535
+ expect(result.content[2]).toEqual({ type: 'image', data: 'BBBB', mimeType: 'image/png' });
536
+ await client.close();
537
+ });
538
+
539
+ test('MCP tools/call lowdefy_run_journey passes data and a data set user name through', async () => {
540
+ mockRunJourney.mockResolvedValue({
541
+ pageId: 'tickets',
542
+ passed: true,
543
+ steps: [],
544
+ screenshots: [],
545
+ data: { name: 'staging-sample', loadMs: 12, documents: 0 },
546
+ warnings: [],
547
+ });
548
+ const client = await connectClient();
549
+
550
+ const result = await client.callTool({
551
+ name: 'lowdefy_run_journey',
552
+ arguments: { pageId: 'tickets', steps: [], data: 'staging-sample', user: 'member' },
553
+ });
554
+
555
+ expect(mockRunJourney).toHaveBeenCalledWith(
556
+ expect.objectContaining({ pageId: 'tickets', data: 'staging-sample', user: 'member' })
557
+ );
558
+ expect(JSON.parse(result.content[0].text).data.name).toEqual('staging-sample');
559
+ await client.close();
560
+ });
561
+
562
+ test('MCP lowdefy_run_journey advertises data and the name form of user', async () => {
563
+ const client = await connectClient();
564
+ const { tools } = await client.listTools();
565
+ const { properties } = tools.find((tool) => tool.name === 'lowdefy_run_journey').inputSchema;
566
+ expect(properties.data.type).toEqual('string');
567
+ expect(properties.user.description).toMatch('the name of a user in the journey');
568
+ await client.close();
569
+ });
570
+
571
+ test('MCP tools/call lowdefy_run_journey runs a list of users once each and reports every run', async () => {
572
+ mockRunJourney.mockImplementation(async ({ user }) => ({
573
+ pageId: 'tickets',
574
+ passed: user === 'admin',
575
+ steps: [],
576
+ screenshots: [{ name: 'end', data: user === 'admin' ? 'AAAA' : 'BBBB', mimeType: 'image/png' }],
577
+ ...(user === 'admin'
578
+ ? {}
579
+ : { failure: { index: 0, step: { click: 'edit' }, message: 'edit is hidden' } }),
580
+ }));
581
+ const client = await connectClient();
582
+
583
+ const result = await client.callTool({
584
+ name: 'lowdefy_run_journey',
585
+ arguments: { pageId: 'tickets', steps: [], data: 'crm', user: ['admin', 'member'] },
586
+ });
587
+
588
+ expect(mockRunJourney).toHaveBeenCalledTimes(2);
589
+ expect(mockRunJourney.mock.calls.map(([params]) => params.user)).toEqual(['admin', 'member']);
590
+ expect(mockRunJourney.mock.calls[0][0]).toEqual(
591
+ expect.objectContaining({ pageId: 'tickets', data: 'crm' })
592
+ );
593
+ const [first, second] = mockRunJourney.mock.calls.map(([params]) => params.recording.run.id);
594
+ expect(first).not.toEqual(second);
595
+ const summary = JSON.parse(result.content[0].text);
596
+ expect(summary.passed).toBe(false);
597
+ expect(summary.runs.map(({ user, passed }) => ({ user, passed }))).toEqual([
598
+ { user: 'admin', passed: true },
599
+ { user: 'member', passed: false },
600
+ ]);
601
+ expect(summary.runs[1].failure.message).toBe('edit is hidden');
602
+ expect(summary.runs[0].screenshots).toEqual([{ name: 'end' }]);
603
+ expect(result.content.slice(1)).toEqual([
604
+ { type: 'image', data: 'AAAA', mimeType: 'image/png' },
605
+ { type: 'image', data: 'BBBB', mimeType: 'image/png' },
606
+ ]);
607
+ await client.close();
608
+ });
609
+
610
+ test('MCP tools/call lowdefy_run_journey reports a run the runner could not do in its run', async () => {
611
+ mockRunJourney.mockImplementation(async ({ user }) =>
612
+ user === 'ghost'
613
+ ? { error: 'Data set "crm" declares no user "ghost".', refused: true }
614
+ : { pageId: 'tickets', passed: true, steps: [], screenshots: [] }
615
+ );
616
+ const client = await connectClient();
617
+
618
+ const result = await client.callTool({
619
+ name: 'lowdefy_run_journey',
620
+ arguments: { pageId: 'tickets', steps: [], data: 'crm', user: ['admin', 'ghost'] },
621
+ });
622
+
623
+ const summary = JSON.parse(result.content[0].text);
624
+ expect(summary.passed).toBe(false);
625
+ expect(summary.runs[1]).toEqual({
626
+ user: 'ghost',
627
+ passed: false,
628
+ error: 'Data set "crm" declares no user "ghost".',
629
+ screenshots: [],
630
+ });
631
+ await client.close();
632
+ });
633
+
634
+ test.each([
635
+ [['admin'], undefined, 'declares no "data"'],
636
+ [['admin', 'admin'], 'crm', 'names "admin" more than once'],
637
+ [['none'], 'crm', 'Received "none".'],
638
+ ])(
639
+ 'MCP tools/call lowdefy_run_journey refuses user list %j with data %j before running',
640
+ async (user, data, message) => {
641
+ const client = await connectClient();
642
+
643
+ const result = await client.callTool({
644
+ name: 'lowdefy_run_journey',
645
+ arguments: { pageId: 'tickets', steps: [], data, user },
646
+ });
647
+
648
+ expect(result.isError).toBe(true);
649
+ expect(result.content[0].text).toContain(message);
650
+ expect(mockRunJourney).not.toHaveBeenCalled();
651
+ await client.close();
652
+ }
653
+ );
654
+
655
+ test('MCP tools/call lowdefy_run_journey reports a runner error as a tool error', async () => {
656
+ mockRunJourney.mockResolvedValue({ error: 'Step 0: Unknown journey step "hover".' });
657
+ const client = await connectClient();
658
+
659
+ const result = await client.callTool({
660
+ name: 'lowdefy_run_journey',
661
+ arguments: { pageId: 'form', steps: [{ hover: 'a' }] },
662
+ });
663
+
664
+ expect(result.isError).toBe(true);
665
+ expect(result.content[0].text).toEqual('Step 0: Unknown journey step "hover".');
666
+ await client.close();
667
+ });
668
+
669
+ test('MCP instructions teach lowdefy_run_journey as the way to verify behaviour', async () => {
670
+ const client = await connectClient();
671
+ const instructions = client.getInstructions();
672
+ expect(instructions).toContain('lowdefy_run_journey');
673
+ expect(instructions).toContain('verify behaviour');
674
+ await client.close();
675
+ });
676
+
677
+ function writeDevSession({ configDirectory, id, records }) {
678
+ const directory = path.join(configDirectory, '.lowdefy', 'traces', 'dev', '2026-10-06');
679
+ fs.mkdirSync(directory, { recursive: true });
680
+ fs.writeFileSync(
681
+ path.join(directory, `${id}.jsonl`),
682
+ records.map((record) => JSON.stringify(record)).join('\n')
683
+ );
684
+ }
685
+
686
+ function devRecord({ id, t, kind, page, ...rest }) {
687
+ return {
688
+ v: 1,
689
+ source: 'dev',
690
+ session: id,
691
+ t,
692
+ scope: 'page',
693
+ kind,
694
+ page_id: page,
695
+ roles: [],
696
+ person: null,
697
+ org: null,
698
+ build: null,
699
+ target: null,
700
+ event: null,
701
+ ...rest,
702
+ };
703
+ }
704
+
705
+ test('MCP tools/call lowdefy_journey_session lists dev sessions and prints one as a log', async () => {
706
+ const configDirectory = fs.mkdtempSync(path.join(os.tmpdir(), 'lowdefy-mcp-session-'));
707
+ const previous = process.env.LOWDEFY_DIRECTORY_CONFIG;
708
+ process.env.LOWDEFY_DIRECTORY_CONFIG = configDirectory;
709
+ const id = '20261006T100000Z-abcdef';
710
+ writeDevSession({
711
+ configDirectory,
712
+ id,
713
+ records: [
714
+ devRecord({
715
+ id,
716
+ t: '2026-10-06T10:00:00.000Z',
717
+ kind: 'pageview',
718
+ page: 'tickets',
719
+ url: '/tickets',
720
+ }),
721
+ devRecord({
722
+ id,
723
+ t: '2026-10-06T10:00:02.000Z',
724
+ kind: 'click',
725
+ page: 'tickets',
726
+ target: {
727
+ block_id: 'sync',
728
+ block_type: 'Button',
729
+ row: null,
730
+ column: null,
731
+ text: null,
732
+ nth: null,
733
+ option: false,
734
+ },
735
+ event: {
736
+ name: 'onClick',
737
+ block_id: 'sync',
738
+ success: true,
739
+ actions: ['CallAPI'],
740
+ requests: [],
741
+ endpoints: [{ id: 'syncTickets', ok: true, ms: 10 }],
742
+ state_writes: [],
743
+ url_after: '/tickets',
744
+ },
745
+ }),
746
+ ],
747
+ });
748
+ try {
749
+ const client = await connectClient();
750
+ const list = await client.callTool({ name: 'lowdefy_journey_session', arguments: {} });
751
+ expect(list.isError).toBeUndefined();
752
+ expect(list.content[0].text).toContain(`${id} `);
753
+ expect(list.content[0].text).toContain('tickets 1 interaction');
754
+
755
+ const log = await client.callTool({ name: 'lowdefy_journey_session', arguments: { id } });
756
+ expect(log.content[0].text).toBe(
757
+ [
758
+ `Session ${id}, 2026-10-06T10:00:00.000Z to 2026-10-06T10:00:02.000Z:`,
759
+ 'page tickets',
760
+ 'click sync → endpoint syncTickets ok',
761
+ ].join('\n')
762
+ );
763
+
764
+ const unknown = await client.callTool({
765
+ name: 'lowdefy_journey_session',
766
+ arguments: { id: 'missing' },
767
+ });
768
+ expect(unknown.isError).toBe(true);
769
+ expect(unknown.content[0].text).toBe(
770
+ `No session "missing" in this window. The newest sessions are ${id}.`
771
+ );
772
+ await client.close();
773
+ } finally {
774
+ if (previous === undefined) {
775
+ delete process.env.LOWDEFY_DIRECTORY_CONFIG;
776
+ } else {
777
+ process.env.LOWDEFY_DIRECTORY_CONFIG = previous;
778
+ }
779
+ fs.rmSync(configDirectory, { recursive: true, force: true });
780
+ }
781
+ });