@abide/abide 0.48.0 → 0.50.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 (360) hide show
  1. package/AGENTS.md +85 -59
  2. package/CHANGELOG.md +216 -0
  3. package/README.md +21 -10
  4. package/package.json +15 -4
  5. package/src/abideLsp.ts +5 -6
  6. package/src/abideResolverPlugin.ts +197 -169
  7. package/src/build.ts +79 -56
  8. package/src/buildCli.ts +3 -5
  9. package/src/buildDisconnected.ts +2 -5
  10. package/src/bundleApp.ts +2 -3
  11. package/src/checkAbide.ts +12 -2
  12. package/src/compile.ts +2 -4
  13. package/src/devEntry.ts +113 -28
  14. package/src/discoveryEntry.ts +3 -2
  15. package/src/lib/bundle/disconnected.abide +69 -73
  16. package/src/lib/bundle/infoPlist.ts +13 -7
  17. package/src/lib/bundle/installDownloads.ts +13 -3
  18. package/src/lib/bundle/installMacMenu.ts +13 -3
  19. package/src/lib/bundle/spawnEmbeddedServer.ts +13 -4
  20. package/src/lib/bundle/waitForServer.ts +6 -1
  21. package/src/lib/cli/parseArgvForRpc.ts +36 -9
  22. package/src/lib/cli/tokenizeArgvFlags.ts +4 -2
  23. package/src/lib/mcp/createMcpResourceServer.ts +13 -2
  24. package/src/lib/mcp/mcpTools.ts +15 -6
  25. package/src/lib/mcp/toolResultFromResponse.ts +40 -12
  26. package/src/lib/server/error.ts +6 -6
  27. package/src/lib/server/json.ts +17 -3
  28. package/src/lib/server/rpc/defineRpc.ts +58 -24
  29. package/src/lib/server/rpc/parseArgs.ts +113 -14
  30. package/src/lib/server/rpc/resolveRpcJsonSchema.ts +28 -0
  31. package/src/lib/server/rpc/runWithRpcTimeout.ts +12 -1
  32. package/src/lib/server/rpc/types/RpcHelper.ts +75 -105
  33. package/src/lib/server/rpc/types/RpcRegistryEntry.ts +24 -9
  34. package/src/lib/server/rpc/validationError.ts +1 -1
  35. package/src/lib/server/runtime/DEV_RELOAD_CLIENT_SCRIPT.ts +0 -15
  36. package/src/lib/server/runtime/buildCacheSnapshot.ts +10 -9
  37. package/src/lib/server/runtime/buildInspectorSurface.ts +6 -4
  38. package/src/lib/server/runtime/buildOpenApiSpec.ts +55 -11
  39. package/src/lib/server/runtime/buildPreloadManifest.ts +12 -10
  40. package/src/lib/server/runtime/createAppAssetServer.ts +18 -15
  41. package/src/lib/server/runtime/createAppRouteResolver.ts +145 -0
  42. package/src/lib/server/runtime/createPlumbingRouter.ts +212 -0
  43. package/src/lib/server/runtime/createRouteDispatcher.ts +4 -10
  44. package/src/lib/server/runtime/createServer.ts +120 -324
  45. package/src/lib/server/runtime/createUiPageRenderer.ts +137 -26
  46. package/src/lib/server/runtime/devClientFingerprint.ts +19 -34
  47. package/src/lib/server/runtime/finalizeResponse.ts +11 -1
  48. package/src/lib/server/runtime/installAmbientScopeStore.ts +30 -9
  49. package/src/lib/server/runtime/logExposedSurfaces.ts +8 -7
  50. package/src/lib/server/runtime/pathStore.ts +15 -0
  51. package/src/lib/server/runtime/registryManifests.ts +1 -1
  52. package/src/lib/server/runtime/renderCellBarrierStore.ts +15 -0
  53. package/src/lib/server/runtime/runWithRequestScope.ts +3 -0
  54. package/src/lib/server/runtime/serializeCacheSnapshot.ts +6 -4
  55. package/src/lib/server/runtime/snapshotEntryFromCache.ts +24 -16
  56. package/src/lib/server/runtime/streamCacheResolutions.ts +14 -7
  57. package/src/lib/server/runtime/streamFromIterator.ts +30 -2
  58. package/src/lib/server/runtime/textResponse.ts +23 -0
  59. package/src/lib/server/runtime/types/DevReloadStamp.ts +6 -10
  60. package/src/lib/server/runtime/types/InspectorCacheEntry.ts +1 -1
  61. package/src/lib/server/runtime/types/RequestStore.ts +27 -0
  62. package/src/lib/server/runtime/warnUnguardedMcp.ts +15 -5
  63. package/src/lib/server/sockets/createSocketDispatcher.ts +9 -11
  64. package/src/lib/server/sockets/defineSocket.ts +5 -4
  65. package/src/lib/server/sse.ts +5 -1
  66. package/src/lib/shared/ASYNC_CELL.ts +5 -0
  67. package/src/lib/shared/DEV_REBUILD_PATH.ts +6 -0
  68. package/src/lib/shared/HTTP_METHODS.ts +6 -0
  69. package/src/lib/shared/HttpError.ts +1 -5
  70. package/src/lib/shared/MCP_PATH.ts +6 -0
  71. package/src/lib/shared/PROXIED_SERVER_SUBDIRS.ts +15 -0
  72. package/src/lib/shared/REF_JSON_TAGS.ts +13 -1
  73. package/src/lib/shared/RPC_SHIM_GLOBALS.ts +9 -0
  74. package/src/lib/shared/activeCacheStore.ts +1 -0
  75. package/src/lib/shared/activePage.ts +1 -0
  76. package/src/lib/shared/buildArtifact.ts +20 -0
  77. package/src/lib/shared/buildRpcRequest.ts +6 -2
  78. package/src/lib/shared/buildSocketOverChannel.ts +4 -3
  79. package/src/lib/shared/bundleGraphFromMetafile.ts +68 -0
  80. package/src/lib/shared/cache.ts +156 -131
  81. package/src/lib/shared/canonicalJson.ts +24 -1
  82. package/src/lib/shared/changeAffectsClient.ts +12 -7
  83. package/src/lib/shared/createCacheStore.ts +31 -8
  84. package/src/lib/shared/createChannelLog.ts +24 -0
  85. package/src/lib/shared/createLifecycleChannel.ts +7 -1
  86. package/src/lib/shared/createReachable.ts +47 -78
  87. package/src/lib/shared/createRemoteFunction.ts +52 -30
  88. package/src/lib/shared/createRpcServerProgram.ts +820 -0
  89. package/src/lib/shared/decodeRefJson.ts +4 -4
  90. package/src/lib/shared/decodeResponse.ts +2 -2
  91. package/src/lib/shared/decodeWireBody.ts +35 -0
  92. package/src/lib/shared/detectRpcMethod.ts +8 -1
  93. package/src/lib/shared/done.ts +8 -2
  94. package/src/lib/shared/encodeRefJson.ts +4 -4
  95. package/src/lib/shared/encodeWireBody.ts +18 -0
  96. package/src/lib/shared/exitOnBuildFailure.ts +4 -3
  97. package/src/lib/{server/rpc → shared}/fieldErrorsFromIssues.ts +5 -1
  98. package/src/lib/shared/generateDeclarations.ts +72 -0
  99. package/src/lib/shared/hasSeedableRequest.ts +25 -0
  100. package/src/lib/shared/hydrationWindow.ts +53 -0
  101. package/src/lib/shared/isAsyncCell.ts +11 -0
  102. package/src/lib/shared/isAsyncIterable.ts +8 -0
  103. package/src/lib/shared/isSubscribable.ts +3 -3
  104. package/src/lib/shared/isThenable.ts +9 -0
  105. package/src/lib/shared/jsonSchemaForType.ts +258 -0
  106. package/src/lib/shared/lenientDecode.ts +15 -0
  107. package/src/lib/shared/loadProjectTsConfig.ts +28 -0
  108. package/src/lib/shared/markFrameworkSourcesIgnored.ts +1 -1
  109. package/src/lib/shared/matchRoute.ts +4 -14
  110. package/src/lib/shared/parseEnv.ts +17 -6
  111. package/src/lib/shared/peek.ts +14 -0
  112. package/src/lib/shared/pending.ts +9 -6
  113. package/src/lib/shared/pendingAsyncCellsSlot.ts +13 -0
  114. package/src/lib/shared/prepareRpcModule.ts +91 -93
  115. package/src/lib/shared/prepareSocketModule.ts +3 -2
  116. package/src/lib/shared/probeRegistries.ts +5 -18
  117. package/src/lib/shared/reachable.ts +7 -10
  118. package/src/lib/shared/refresh.ts +12 -2
  119. package/src/lib/shared/refreshing.ts +8 -2
  120. package/src/lib/shared/resolvedCellsSlot.ts +13 -0
  121. package/src/lib/shared/reviveWireField.ts +49 -0
  122. package/src/lib/shared/reviveWireOutput.ts +36 -0
  123. package/src/lib/shared/rpcServerForRoot.ts +25 -0
  124. package/src/lib/shared/scanPages.ts +25 -0
  125. package/src/lib/shared/serializeEnv.ts +18 -6
  126. package/src/lib/shared/snapshotShippable.ts +8 -7
  127. package/src/lib/shared/snippet.ts +11 -6
  128. package/src/lib/shared/streamedCellsSlot.ts +14 -0
  129. package/src/lib/shared/subscribableFromResponse.ts +4 -4
  130. package/src/lib/shared/subscribableProbes.ts +1 -1
  131. package/src/lib/shared/tailProbeSlot.ts +1 -1
  132. package/src/lib/shared/types/AsyncComputed.ts +20 -0
  133. package/src/lib/shared/types/AsyncState.ts +13 -0
  134. package/src/lib/shared/types/CacheEntry.ts +7 -7
  135. package/src/lib/shared/types/CacheOptions.ts +23 -37
  136. package/src/lib/shared/types/CachePolicy.ts +25 -0
  137. package/src/lib/shared/types/CacheSnapshotEntry.ts +6 -5
  138. package/src/lib/shared/types/ErrorJsonSchemas.ts +8 -0
  139. package/src/lib/shared/types/HttpMethod.ts +3 -1
  140. package/src/lib/shared/types/InputCoercion.ts +17 -0
  141. package/src/lib/shared/types/{Subscribable.ts → NamedAsyncIterable.ts} +1 -1
  142. package/src/lib/shared/types/OutputWirePlan.ts +17 -0
  143. package/src/lib/shared/types/PagesScan.ts +7 -0
  144. package/src/lib/shared/types/PendingAsyncCells.ts +10 -0
  145. package/src/lib/shared/types/RawRemoteFunction.ts +6 -0
  146. package/src/lib/shared/types/RemoteCallable.ts +8 -7
  147. package/src/lib/shared/types/RemoteFunction.ts +16 -15
  148. package/src/lib/shared/types/ResolvedCells.ts +11 -0
  149. package/src/lib/shared/types/ReturnBody.ts +15 -0
  150. package/src/lib/shared/types/RpcBuildStamps.ts +23 -0
  151. package/src/lib/shared/types/RpcErrorGuard.ts +3 -8
  152. package/src/lib/shared/types/Socket.ts +4 -4
  153. package/src/lib/shared/types/SsrPayload.ts +5 -1
  154. package/src/lib/shared/types/StreamPolicy.ts +11 -0
  155. package/src/lib/shared/types/StreamedCells.ts +19 -0
  156. package/src/lib/shared/types/StreamedResolution.ts +24 -6
  157. package/src/lib/shared/types/TailHooks.ts +1 -1
  158. package/src/lib/shared/types/WireKind.ts +11 -0
  159. package/src/lib/shared/url.ts +5 -0
  160. package/src/lib/shared/validationHttpError.ts +33 -0
  161. package/src/lib/shared/warmSeedKey.ts +16 -0
  162. package/src/lib/shared/wireJsonReplacer.ts +30 -0
  163. package/src/lib/shared/writeRpcDts.ts +11 -1
  164. package/src/lib/shared/writeTestSocketsDts.ts +1 -1
  165. package/src/lib/test/createTestApp.ts +19 -1
  166. package/src/lib/ui/README.md +1 -1
  167. package/src/lib/ui/activePendingCells.ts +14 -0
  168. package/src/lib/ui/compile/BLOCK_KEYWORDS.ts +21 -0
  169. package/src/lib/ui/compile/SSR_ESCAPE.ts +4 -1
  170. package/src/lib/ui/compile/UI_RUNTIME_IMPORTS.ts +35 -7
  171. package/src/lib/ui/compile/abideUiPlugin.ts +19 -2
  172. package/src/lib/ui/compile/analyzeComponent.ts +89 -6
  173. package/src/lib/ui/compile/assignmentTargetNames.ts +54 -0
  174. package/src/lib/ui/compile/asyncInterpolationFields.ts +152 -0
  175. package/src/lib/ui/compile/asyncValuePositionError.ts +21 -0
  176. package/src/lib/ui/compile/asyncValuePositionInterpolations.ts +64 -0
  177. package/src/lib/ui/compile/attrLiftPosition.ts +18 -0
  178. package/src/lib/ui/compile/bindListenEvent.ts +5 -6
  179. package/src/lib/ui/compile/cachedSourceFile.ts +40 -0
  180. package/src/lib/ui/compile/catchBinding.ts +7 -5
  181. package/src/lib/ui/compile/classifyInterpolationType.ts +46 -0
  182. package/src/lib/ui/compile/collectAbideDiagnostics.ts +96 -0
  183. package/src/lib/ui/compile/compileComponent.ts +19 -3
  184. package/src/lib/ui/compile/compileModule.ts +27 -58
  185. package/src/lib/ui/compile/compileSSR.ts +52 -13
  186. package/src/lib/ui/compile/compileShadow.ts +265 -42
  187. package/src/lib/ui/compile/composeProps.ts +15 -4
  188. package/src/lib/ui/compile/createShadowLanguageService.ts +116 -68
  189. package/src/lib/ui/compile/createShadowProgram.ts +35 -6
  190. package/src/lib/ui/compile/declaredNames.ts +36 -0
  191. package/src/lib/ui/compile/desugarSignals.ts +419 -37
  192. package/src/lib/ui/compile/expressionIsPrefixEvaluable.ts +19 -0
  193. package/src/lib/ui/compile/generateBuild.ts +74 -17
  194. package/src/lib/ui/compile/generateSSR.ts +286 -75
  195. package/src/lib/ui/compile/hoistableAwaits.ts +193 -0
  196. package/src/lib/ui/compile/hoistableChildRenders.ts +112 -0
  197. package/src/lib/ui/compile/interpolatedTemplateLiteral.ts +3 -1
  198. package/src/lib/ui/compile/interpolationClassifierForRoot.ts +37 -0
  199. package/src/lib/ui/compile/isSpuriousAsyncReadDiagnostic.ts +174 -0
  200. package/src/lib/ui/compile/isWhitespaceText.ts +10 -2
  201. package/src/lib/ui/compile/liftAsyncSubExpressions.ts +166 -0
  202. package/src/lib/ui/compile/lowerAsyncInterpolations.ts +92 -0
  203. package/src/lib/ui/compile/lowerContext.ts +10 -0
  204. package/src/lib/ui/compile/lowerDocAccess.ts +51 -10
  205. package/src/lib/ui/compile/lowerScript.ts +40 -4
  206. package/src/lib/ui/compile/nodeAtShadowOffset.ts +28 -0
  207. package/src/lib/ui/compile/parseTemplate.ts +18 -1090
  208. package/src/lib/ui/compile/parseTemplateRecovering.ts +1385 -0
  209. package/src/lib/ui/compile/referencedIdentifiers.ts +35 -0
  210. package/src/lib/ui/compile/renameSignalRefs.ts +26 -40
  211. package/src/lib/ui/compile/resolveReactiveExport.ts +4 -7
  212. package/src/lib/ui/compile/scopeCss.ts +27 -4
  213. package/src/lib/ui/compile/seedTypeClassifierForRoot.ts +65 -0
  214. package/src/lib/ui/compile/shadowInterpolationClassifier.ts +47 -0
  215. package/src/lib/ui/compile/sourceFileOptionsSignature.ts +20 -0
  216. package/src/lib/ui/compile/structuralHeadTokens.ts +107 -0
  217. package/src/lib/ui/compile/templateSemanticTokens.ts +21 -0
  218. package/src/lib/ui/compile/templateStartOffset.ts +15 -0
  219. package/src/lib/ui/compile/tryPlan.ts +4 -3
  220. package/src/lib/ui/compile/types/AnalyzedComponent.ts +4 -0
  221. package/src/lib/ui/compile/types/AsyncInterpolationField.ts +19 -0
  222. package/src/lib/ui/compile/types/InterpolationClassifier.ts +12 -0
  223. package/src/lib/ui/compile/types/InterpolationKind.ts +7 -0
  224. package/src/lib/ui/compile/types/ParseDiagnostic.ts +8 -0
  225. package/src/lib/ui/compile/types/SeedTypeClassifier.ts +15 -0
  226. package/src/lib/ui/compile/types/TemplateNode.ts +36 -4
  227. package/src/lib/ui/compile/types/ValuePositionInterpolation.ts +13 -0
  228. package/src/lib/ui/compile/writtenTemplateNames.ts +84 -0
  229. package/src/lib/ui/computed.ts +28 -1
  230. package/src/lib/ui/createScope.ts +35 -68
  231. package/src/lib/ui/dom/anchoredBranch.ts +142 -0
  232. package/src/lib/ui/dom/appendText.ts +8 -3
  233. package/src/lib/ui/dom/awaitBlock.ts +62 -75
  234. package/src/lib/ui/dom/bindProp.ts +22 -0
  235. package/src/lib/ui/dom/bindableProp.ts +47 -0
  236. package/src/lib/ui/dom/cellPending.ts +24 -0
  237. package/src/lib/ui/dom/discardBoundary.ts +24 -6
  238. package/src/lib/ui/dom/disposeRange.ts +2 -1
  239. package/src/lib/ui/dom/each.ts +41 -12
  240. package/src/lib/ui/dom/eachAsync.ts +39 -3
  241. package/src/lib/ui/dom/fillBefore.ts +6 -7
  242. package/src/lib/ui/dom/fillBoundary.ts +2 -3
  243. package/src/lib/ui/dom/fillRange.ts +4 -4
  244. package/src/lib/ui/dom/hydrate.ts +6 -12
  245. package/src/lib/ui/dom/isComment.ts +1 -1
  246. package/src/lib/ui/dom/matchingRangeClose.ts +29 -0
  247. package/src/lib/ui/dom/mergeProps.ts +1 -1
  248. package/src/lib/ui/dom/mount.ts +1 -2
  249. package/src/lib/ui/dom/mountChild.ts +9 -40
  250. package/src/lib/ui/dom/mountRange.ts +2 -3
  251. package/src/lib/ui/dom/mountSlot.ts +1 -1
  252. package/src/lib/ui/dom/mountStreamedChild.ts +84 -0
  253. package/src/lib/ui/dom/mountSwappableRange.ts +46 -4
  254. package/src/lib/ui/dom/mutateDocContainer.ts +65 -0
  255. package/src/lib/ui/dom/on.ts +2 -2
  256. package/src/lib/ui/dom/readCell.ts +40 -0
  257. package/src/lib/ui/dom/restProps.ts +2 -2
  258. package/src/lib/ui/dom/spreadProps.ts +3 -4
  259. package/src/lib/ui/dom/switchBlock.ts +30 -7
  260. package/src/lib/ui/dom/tryBlock.ts +233 -70
  261. package/src/lib/ui/dom/types/SwitchCase.ts +5 -1
  262. package/src/lib/ui/dom/when.ts +12 -2
  263. package/src/lib/ui/dom/withScope.ts +9 -2
  264. package/src/lib/ui/finalizeStreamedChildren.ts +89 -0
  265. package/src/lib/ui/flight.ts +56 -0
  266. package/src/lib/ui/html.ts +12 -1
  267. package/src/lib/ui/isolateCellBarrier.ts +17 -0
  268. package/src/lib/ui/linked.ts +48 -2
  269. package/src/lib/ui/props.ts +17 -0
  270. package/src/lib/ui/remoteProxy.ts +84 -159
  271. package/src/lib/ui/renderChain.ts +53 -13
  272. package/src/lib/ui/renderToStream.ts +22 -18
  273. package/src/lib/ui/resumeSeedScript.ts +1 -1
  274. package/src/lib/ui/router.ts +86 -53
  275. package/src/lib/ui/runtime/AsyncCellError.ts +20 -0
  276. package/src/lib/ui/runtime/CELL_SEED.ts +14 -0
  277. package/src/lib/ui/runtime/CHILD_PRESENT.ts +2 -2
  278. package/src/lib/ui/runtime/CURRENT_BOUNDARY.ts +11 -0
  279. package/src/lib/ui/runtime/CURRENT_PATH.ts +29 -0
  280. package/src/lib/ui/runtime/RENDER.ts +10 -7
  281. package/src/lib/ui/runtime/RESUME.ts +4 -4
  282. package/src/lib/ui/runtime/STREAMED_CELLS.ts +57 -0
  283. package/src/lib/ui/runtime/ambientPathBacking.ts +32 -0
  284. package/src/lib/ui/runtime/applyPatchToTree.ts +17 -4
  285. package/src/lib/ui/runtime/blockId.ts +31 -0
  286. package/src/lib/ui/runtime/boundaryFor.ts +11 -0
  287. package/src/lib/ui/runtime/cellBarrierBacking.ts +30 -0
  288. package/src/lib/ui/runtime/createAsyncCell.ts +300 -0
  289. package/src/lib/ui/runtime/createDoc.ts +31 -54
  290. package/src/lib/ui/runtime/createEffectNode.ts +9 -0
  291. package/src/lib/ui/runtime/enterRenderPass.ts +5 -4
  292. package/src/lib/ui/runtime/flushEffects.ts +33 -1
  293. package/src/lib/ui/runtime/isAsyncFunction.ts +14 -0
  294. package/src/lib/ui/runtime/nextBlockId.ts +8 -7
  295. package/src/lib/ui/runtime/renderPath.ts +16 -0
  296. package/src/lib/ui/runtime/scope.ts +11 -0
  297. package/src/lib/ui/runtime/toTeardown.ts +10 -3
  298. package/src/lib/ui/runtime/types/Boundary.ts +9 -0
  299. package/src/lib/ui/runtime/types/RenderContext.ts +9 -7
  300. package/src/lib/ui/runtime/types/SsrRender.ts +9 -2
  301. package/src/lib/ui/runtime/types/UiComponent.ts +1 -5
  302. package/src/lib/ui/runtime/types/UiProps.ts +6 -5
  303. package/src/lib/ui/runtime/withOptionalPath.ts +8 -0
  304. package/src/lib/ui/runtime/withPath.ts +16 -0
  305. package/src/lib/ui/runtime/withPathFrom.ts +20 -0
  306. package/src/lib/ui/runtime/withoutHydration.ts +22 -0
  307. package/src/lib/ui/seedStreamedResolution.ts +31 -6
  308. package/src/lib/ui/settleAsyncCells.ts +24 -0
  309. package/src/lib/ui/socketProxy.ts +1 -1
  310. package/src/lib/ui/startClient.ts +16 -31
  311. package/src/lib/ui/state.ts +9 -3
  312. package/src/lib/ui/trackedComputed.ts +68 -0
  313. package/src/lib/ui/types/Scope.ts +8 -24
  314. package/src/lib/ui/watch.ts +3 -3
  315. package/src/serverEntry.ts +14 -0
  316. package/template/src/server/rpc/getHello.ts +15 -13
  317. package/template/test/app.test.ts +1 -1
  318. package/src/lib/server/runtime/devHotModuleResponse.ts +0 -41
  319. package/src/lib/shared/DEV_HOT_PREFIX.ts +0 -7
  320. package/src/lib/shared/UNREACHABLE_STATUSES.ts +0 -13
  321. package/src/lib/shared/hasReplayableRequest.ts +0 -17
  322. package/src/lib/shared/hydratingSlot.ts +0 -12
  323. package/src/lib/shared/outboxProbeSlot.ts +0 -20
  324. package/src/lib/shared/types/Outbox.ts +0 -9
  325. package/src/lib/shared/types/OutboxEntry.ts +0 -27
  326. package/src/lib/shared/types/SmartReadOptions.ts +0 -36
  327. package/src/lib/shared/wakeHydrationPeeks.ts +0 -20
  328. package/src/lib/ui/COMPONENT_WRAPPER_PREFIX.ts +0 -5
  329. package/src/lib/ui/compile/REACTIVE_CALLEES.ts +0 -11
  330. package/src/lib/ui/compile/assertRuntimeHelpersBound.ts +0 -80
  331. package/src/lib/ui/compile/markupTokens.ts +0 -313
  332. package/src/lib/ui/compile/structuralBlockTokens.ts +0 -100
  333. package/src/lib/ui/dom/applyResolved.ts +0 -87
  334. package/src/lib/ui/dom/scopeLabel.ts +0 -21
  335. package/src/lib/ui/dom/text.ts +0 -20
  336. package/src/lib/ui/history.ts +0 -101
  337. package/src/lib/ui/installHotBridge.ts +0 -93
  338. package/src/lib/ui/installInspectorBridge.ts +0 -140
  339. package/src/lib/ui/outbox.ts +0 -35
  340. package/src/lib/ui/persist.ts +0 -115
  341. package/src/lib/ui/rpcOutbox/createOutboxQueue.ts +0 -281
  342. package/src/lib/ui/rpcOutbox/outboxRegistry.ts +0 -69
  343. package/src/lib/ui/runtime/PATCH_BUS.ts +0 -28
  344. package/src/lib/ui/runtime/captureModelDoc.ts +0 -28
  345. package/src/lib/ui/runtime/hotInstances.ts +0 -10
  346. package/src/lib/ui/runtime/hotReloadEnabled.ts +0 -8
  347. package/src/lib/ui/runtime/hotReplace.ts +0 -38
  348. package/src/lib/ui/runtime/liveScopes.ts +0 -15
  349. package/src/lib/ui/runtime/localStoragePersistence.ts +0 -47
  350. package/src/lib/ui/runtime/registerHotInstance.ts +0 -23
  351. package/src/lib/ui/runtime/seedModelDoc.ts +0 -26
  352. package/src/lib/ui/runtime/types/HotInstance.ts +0 -22
  353. package/src/lib/ui/runtime/types/PatchEvent.ts +0 -16
  354. package/src/lib/ui/seedResolved.ts +0 -28
  355. package/src/lib/ui/sync.ts +0 -48
  356. package/src/lib/ui/types/History.ts +0 -14
  357. package/src/lib/ui/types/PersistHandle.ts +0 -11
  358. package/src/lib/ui/types/PersistenceStore.ts +0 -12
  359. package/src/lib/ui/types/ResolvedFrame.ts +0 -15
  360. package/src/lib/ui/types/SyncTransport.ts +0 -13
@@ -149,72 +149,69 @@ async function saveConfig() {
149
149
  }
150
150
  </script>
151
151
 
152
- <template if={phase === 'splash'}>
152
+ {#if phase === 'splash'}
153
153
  <div class="flex min-h-screen items-center justify-center bg-gray-50 dark:bg-gray-950">
154
- <template if={logo}>
154
+ {#if logo}
155
155
  <img src={logo} alt="" class="h-16 w-16 rounded-xl object-contain opacity-90" />
156
- </template>
156
+ {/if}
157
157
  </div>
158
- <template else>
159
- <main
160
- class="flex min-h-screen items-center justify-center bg-gray-50 p-6 text-gray-900 dark:bg-gray-950 dark:text-gray-100">
161
- <div
162
- class="w-full max-w-sm rounded-2xl bg-white p-8 shadow-sm ring-1 ring-gray-200 dark:bg-gray-900 dark:ring-gray-800">
163
- <template if={logo}>
164
- <img
165
- src={logo}
166
- alt=""
167
- class="mx-auto mb-5 h-16 w-16 rounded-xl object-contain" />
168
- </template>
169
- <h1 class="mb-6 text-center text-xl font-semibold tracking-tight">{heading}</h1>
170
-
171
- <form class="flex flex-col gap-3" onsubmit={(event) => { event.preventDefault(); void connect() }}>
172
- <input
173
- type="url"
174
- bind:value={url}
175
- placeholder={placeholder}
176
- autocomplete="url"
177
- class="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm outline-none focus:border-gray-900 focus:ring-1 focus:ring-gray-900 dark:border-gray-700 dark:bg-gray-800 dark:focus:border-gray-100 dark:focus:ring-gray-100" />
178
- <button
179
- type="submit"
180
- class="w-full rounded-lg bg-gray-900 px-3 py-2 text-sm font-medium text-white hover:bg-gray-700 dark:bg-gray-100 dark:text-gray-900 dark:hover:bg-gray-300">
181
- Connect
182
- </button>
183
- </form>
184
-
185
- <div class="my-5 flex items-center gap-3 text-xs text-gray-400 dark:text-gray-500">
186
- <span class="h-px flex-1 bg-gray-200 dark:bg-gray-800"></span>
187
- or
188
- <span class="h-px flex-1 bg-gray-200 dark:bg-gray-800"></span>
189
- </div>
158
+ {:else}
159
+ <main
160
+ class="flex min-h-screen items-center justify-center bg-gray-50 p-6 text-gray-900 dark:bg-gray-950 dark:text-gray-100">
161
+ <div
162
+ class="w-full max-w-sm rounded-2xl bg-white p-8 shadow-sm ring-1 ring-gray-200 dark:bg-gray-900 dark:ring-gray-800">
163
+ {#if logo}
164
+ <img
165
+ src={logo}
166
+ alt=""
167
+ class="mx-auto mb-5 h-16 w-16 rounded-xl object-contain" />
168
+ {/if}
169
+ <h1 class="mb-6 text-center text-xl font-semibold tracking-tight">{heading}</h1>
190
170
 
171
+ <form class="flex flex-col gap-3" onsubmit={(event) => { event.preventDefault(); void connect() }}>
172
+ <input
173
+ type="url"
174
+ bind:value={url}
175
+ placeholder={placeholder}
176
+ autocomplete="url"
177
+ class="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm outline-none focus:border-gray-900 focus:ring-1 focus:ring-gray-900 dark:border-gray-700 dark:bg-gray-800 dark:focus:border-gray-100 dark:focus:ring-gray-100" />
191
178
  <button
192
- type="button"
193
- onclick={() => void start()}
194
- class="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm font-medium hover:bg-gray-50 dark:border-gray-700 dark:hover:bg-gray-800">
195
- Start server
179
+ type="submit"
180
+ class="w-full rounded-lg bg-gray-900 px-3 py-2 text-sm font-medium text-white hover:bg-gray-700 dark:bg-gray-100 dark:text-gray-900 dark:hover:bg-gray-300">
181
+ Connect
196
182
  </button>
183
+ </form>
197
184
 
198
- <template if={error}>
199
- <p class="mt-4 text-center text-sm text-red-600 dark:text-red-400">
200
- {error}
201
- </p>
202
- </template>
203
-
204
- <p class="mt-8 text-center text-xs text-gray-400 dark:text-gray-500">
205
- made with
206
- <a
207
- href="https://github.com/briancray/abide"
208
- class="underline hover:text-gray-600 dark:hover:text-gray-300"
209
- >abide</a
210
- >
211
- </p>
185
+ <div class="my-5 flex items-center gap-3 text-xs text-gray-400 dark:text-gray-500">
186
+ <span class="h-px flex-1 bg-gray-200 dark:bg-gray-800"></span>
187
+ or
188
+ <span class="h-px flex-1 bg-gray-200 dark:bg-gray-800"></span>
212
189
  </div>
213
- </main>
214
- </template>
215
- </template>
216
190
 
217
- <template if={showConfig}>
191
+ <button
192
+ type="button"
193
+ onclick={() => void start()}
194
+ class="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm font-medium hover:bg-gray-50 dark:border-gray-700 dark:hover:bg-gray-800">
195
+ Start server
196
+ </button>
197
+
198
+ {#if error}
199
+ <p class="mt-4 text-center text-sm text-red-600 dark:text-red-400">{error}</p>
200
+ {/if}
201
+
202
+ <p class="mt-8 text-center text-xs text-gray-400 dark:text-gray-500">
203
+ made with
204
+ <a
205
+ href="https://github.com/briancray/abide"
206
+ class="underline hover:text-gray-600 dark:hover:text-gray-300"
207
+ >abide</a
208
+ >
209
+ </p>
210
+ </div>
211
+ </main>
212
+ {/if}
213
+
214
+ {#if showConfig}
218
215
  <div
219
216
  class="fixed inset-0 z-10 flex items-center justify-center bg-black/40 p-6 text-gray-900 dark:text-gray-100">
220
217
  <div
@@ -222,29 +219,28 @@ async function saveConfig() {
222
219
  <h2 class="mb-5 text-lg font-semibold tracking-tight">Set up {heading}</h2>
223
220
 
224
221
  <form class="flex flex-col gap-4" onsubmit={(event) => { event.preventDefault(); void saveConfig() }}>
225
- <template each={configFields} as="field" key="field.key">
222
+ {#for field of configFields by field.key}
226
223
  <label class="flex flex-col gap-1 text-sm">
227
224
  <span class="font-medium">{field.label}</span>
228
- <template if={field.inputType === 'checkbox'}>
225
+ {#if field.inputType === 'checkbox'}
229
226
  <input
230
227
  type="checkbox"
231
228
  onchange={(event) => { configValues[field.key] = event.currentTarget.checked ? 'true' : 'false' }}
232
229
  class="mt-1 size-4 self-start rounded border-gray-300 dark:border-gray-700" />
233
- <template else>
234
- <input
235
- type={field.inputType}
236
- value={configValues[field.key]}
237
- oninput={(event) => { configValues[field.key] = event.currentTarget.value }}
238
- class="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm outline-none focus:border-gray-900 focus:ring-1 focus:ring-gray-900 dark:border-gray-700 dark:bg-gray-800 dark:focus:border-gray-100 dark:focus:ring-gray-100" />
239
- </template>
240
- </template>
241
- <template if={field.description}>
230
+ {:else}
231
+ <input
232
+ type={field.inputType}
233
+ value={configValues[field.key]}
234
+ oninput={(event) => { configValues[field.key] = event.currentTarget.value }}
235
+ class="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm outline-none focus:border-gray-900 focus:ring-1 focus:ring-gray-900 dark:border-gray-700 dark:bg-gray-800 dark:focus:border-gray-100 dark:focus:ring-gray-100" />
236
+ {/if}
237
+ {#if field.description}
242
238
  <span class="text-xs text-gray-400 dark:text-gray-500"
243
239
  >{field.description}</span
244
240
  >
245
- </template>
241
+ {/if}
246
242
  </label>
247
- </template>
243
+ {/for}
248
244
 
249
245
  <div class="mt-1 flex gap-3">
250
246
  <button
@@ -261,9 +257,9 @@ async function saveConfig() {
261
257
  </div>
262
258
  </form>
263
259
 
264
- <template if={error}>
260
+ {#if error}
265
261
  <p class="mt-4 text-center text-sm text-red-600 dark:text-red-400">{error}</p>
266
- </template>
262
+ {/if}
267
263
  </div>
268
264
  </div>
269
- </template>
265
+ {/if}
@@ -15,9 +15,15 @@ export function infoPlist({
15
15
  version: string
16
16
  icon?: string
17
17
  }): string {
18
+ /* Interpolated values ride inside plist `<string>` elements, so any `&`/`<`/`>` in a
19
+ project name would otherwise emit malformed XML that codesign/Gatekeeper reject. */
20
+ const escapeXml = (value: string): string =>
21
+ value.replaceAll('&', '&amp;').replaceAll('<', '&lt;').replaceAll('>', '&gt;')
22
+ const safeName = escapeXml(name)
23
+ const safeVersion = escapeXml(version)
18
24
  const iconEntry = icon
19
25
  ? ` <key>CFBundleIconFile</key>
20
- <string>${icon}</string>
26
+ <string>${escapeXml(icon)}</string>
21
27
  `
22
28
  : ''
23
29
  return `<?xml version="1.0" encoding="UTF-8"?>
@@ -25,17 +31,17 @@ export function infoPlist({
25
31
  <plist version="1.0">
26
32
  <dict>
27
33
  <key>CFBundleName</key>
28
- <string>${name}</string>
34
+ <string>${safeName}</string>
29
35
  <key>CFBundleDisplayName</key>
30
- <string>${name}</string>
36
+ <string>${safeName}</string>
31
37
  <key>CFBundleExecutable</key>
32
- <string>${name}</string>
38
+ <string>${safeName}</string>
33
39
  <key>CFBundleIdentifier</key>
34
- <string>com.abide.${name}</string>
40
+ <string>com.abide.${safeName}</string>
35
41
  <key>CFBundleVersion</key>
36
- <string>${version}</string>
42
+ <string>${safeVersion}</string>
37
43
  <key>CFBundleShortVersionString</key>
38
- <string>${version}</string>
44
+ <string>${safeVersion}</string>
39
45
  ${iconEntry} <key>CFBundlePackageType</key>
40
46
  <string>APPL</string>
41
47
  <key>NSHighResolutionCapable</key>
@@ -16,9 +16,19 @@ export function installDownloads(libPath: string, webviewHandle: Pointer | null)
16
16
  if (process.platform !== 'darwin') {
17
17
  return
18
18
  }
19
- const { symbols, close } = dlopen(libPath, {
20
- abide_install_downloads: { args: [FFIType.ptr], returns: FFIType.void },
21
- })
19
+ /* abide_install_downloads exists only in abide's own webview lib. A vanilla
20
+ ABIDE_WEBVIEW_LIB on darwin lacks it, so dlopen throws — degrade to a no-op
21
+ (downloads simply unhandled) instead of crashing the launcher, matching the
22
+ optional-symbol FFI binders. */
23
+ let symbols: { abide_install_downloads: (handle: Pointer | null) => void }
24
+ let close: () => void
25
+ try {
26
+ ;({ symbols, close } = dlopen(libPath, {
27
+ abide_install_downloads: { args: [FFIType.ptr], returns: FFIType.void },
28
+ }))
29
+ } catch {
30
+ return
31
+ }
22
32
  symbols.abide_install_downloads(webviewHandle)
23
33
  close()
24
34
  }
@@ -30,9 +30,19 @@ export function installMacMenu(
30
30
  if (process.platform !== 'darwin') {
31
31
  return
32
32
  }
33
- const { symbols, close } = dlopen(libPath, {
34
- abide_install_app_menu: { args: [FFIType.ptr, FFIType.ptr], returns: FFIType.void },
35
- })
33
+ /* The abide_install_app_menu symbol is compiled only into abide's own webview lib.
34
+ A user who points ABIDE_WEBVIEW_LIB at a vanilla webview.dylib is on darwin but
35
+ lacks the symbol, so dlopen throws — degrade to a no-op (native menu simply absent)
36
+ rather than crashing the launcher, matching bindConnectedFlag/bindRequestNavigate. */
37
+ let symbols: { abide_install_app_menu: (handle: Pointer | null, config: Uint8Array) => void }
38
+ let close: () => void
39
+ try {
40
+ ;({ symbols, close } = dlopen(libPath, {
41
+ abide_install_app_menu: { args: [FFIType.ptr, FFIType.ptr], returns: FFIType.void },
42
+ }))
43
+ } catch {
44
+ return
45
+ }
36
46
  const config = JSON.stringify({ appName, fileMenu, menu })
37
47
  symbols.abide_install_app_menu(webviewHandle, new TextEncoder().encode(`${config}\0`))
38
48
  close()
@@ -53,10 +53,19 @@ export async function spawnEmbeddedServer({
53
53
  env: { ...process.env, PORT: String(port), ABIDE_PARENT_PID: String(process.pid) },
54
54
  stdio: ['inherit', 'inherit', 'inherit'],
55
55
  })
56
- const outcome = await Promise.race([
57
- waitForServer(url, timeoutMs ? { timeoutMs } : undefined).then(() => undefined),
58
- child.exited,
59
- ])
56
+ let outcome: number | undefined
57
+ try {
58
+ outcome = await Promise.race([
59
+ waitForServer(url, timeoutMs ? { timeoutMs } : undefined).then(() => undefined),
60
+ child.exited,
61
+ ])
62
+ } catch (error) {
63
+ /* Readiness timed out (waitForServer threw) — reap the child we spawned so it doesn't
64
+ orphan and hold its fixed PORT, which would then fail the next spawn's bind. The caller
65
+ only owns the child once we successfully return it. */
66
+ child.kill()
67
+ throw error
68
+ }
60
69
  if (outcome !== undefined) {
61
70
  throw new Error(`[abide] embedded server exited (code ${outcome}) before binding`)
62
71
  }
@@ -13,7 +13,12 @@ export async function waitForServer(
13
13
  const deadline = Bun.nanoseconds() + timeoutMs * 1e6
14
14
  while (Bun.nanoseconds() < deadline) {
15
15
  try {
16
- await fetch(url)
16
+ /* Bound each probe by the time left to the deadline: a connection that is accepted but
17
+ stalls its HTTP response leaves a bare `fetch(url)` pending forever (it never rejects),
18
+ which would hang the loop past its own timeout. The abort caps a single hung probe at
19
+ the remaining budget so the deadline is always honored. */
20
+ const remainingMs = Math.max(0, (deadline - Bun.nanoseconds()) / 1e6)
21
+ await fetch(url, { signal: AbortSignal.timeout(remainingMs) })
17
22
  return
18
23
  } catch {
19
24
  await Bun.sleep(intervalMs)
@@ -74,8 +74,36 @@ export async function parseArgvForRpc(
74
74
  }
75
75
  const name = token.name
76
76
  const propType = properties[name]?.type
77
+ /* Coerce one string token to a schema scalar type (or throw). Shared by scalar
78
+ props and array ELEMENTS so `--tags 1 --tags 2` on `z.array(z.number())` yields
79
+ numbers, not strings the server then rejects. */
80
+ const coerceScalar = (raw: string, type: string | undefined): unknown => {
81
+ if (type === 'number' || type === 'integer') {
82
+ // Reject a blank value explicitly — `Number('')` / `Number(' ')` is 0,
83
+ // not NaN, so the NaN guard alone would silently coerce it to zero.
84
+ const n = raw.trim() === '' ? Number.NaN : Number(raw)
85
+ if (Number.isNaN(n)) {
86
+ throw new Error(`--${name} expects a number, got ${raw}`)
87
+ }
88
+ return n
89
+ }
90
+ if (type === 'boolean') {
91
+ const lowered = raw.trim().toLowerCase()
92
+ if (lowered === 'true' || lowered === '1') {
93
+ return true
94
+ }
95
+ if (lowered === 'false' || lowered === '0') {
96
+ return false
97
+ }
98
+ throw new Error(`--${name} expects true or false, got ${raw}`)
99
+ }
100
+ return raw
101
+ }
77
102
  if (propType === 'boolean') {
78
- args[name] = !token.negated
103
+ /* Bare `--flag` / `--no-flag` toggles; inline `--flag=false` honours the RHS
104
+ instead of always resolving to true. */
105
+ args[name] =
106
+ token.value !== undefined ? coerceScalar(token.value, 'boolean') : !token.negated
79
107
  continue
80
108
  }
81
109
  if (token.missingValue || token.value === undefined) {
@@ -83,18 +111,17 @@ export async function parseArgvForRpc(
83
111
  }
84
112
  const value = token.value
85
113
  if (propType === 'number' || propType === 'integer') {
86
- // Reject a blank value explicitly — `Number('')` / `Number(' ')` is 0,
87
- // not NaN, so the NaN guard alone would silently coerce it to zero.
88
- const n = value.trim() === '' ? Number.NaN : Number(value)
89
- if (Number.isNaN(n)) {
90
- throw new Error(`--${name} expects a number, got ${value}`)
91
- }
92
- args[name] = n
114
+ args[name] = coerceScalar(value, propType)
93
115
  continue
94
116
  }
95
117
  if (propType === 'array') {
118
+ /* Coerce each repeated element per the schema's `items.type` — a string-only
119
+ array arg silently fails server-side Zod validation for numeric/boolean items. */
120
+ const itemType = (properties[name] as { items?: { type?: string } } | undefined)?.items
121
+ ?.type
122
+ const element = coerceScalar(value, itemType)
96
123
  const existing = args[name]
97
- args[name] = Array.isArray(existing) ? [...existing, value] : [value]
124
+ args[name] = Array.isArray(existing) ? [...existing, element] : [element]
98
125
  continue
99
126
  }
100
127
  args[name] = value
@@ -64,9 +64,11 @@ export function* tokenizeArgvFlags(
64
64
  : undefined
65
65
  const negated = negatedName !== undefined && properties[negatedName]?.type === 'boolean'
66
66
  const name = negated ? (negatedName as string) : literalName
67
- // Boolean props and inline `--name=value` consume no following token.
67
+ // Boolean props and inline `--name=value` consume no following token. Pass any
68
+ // inline value through (`--flag=false`) so the parser can honour the RHS instead
69
+ // of the boolean always resolving to true.
68
70
  if (properties[name]?.type === 'boolean') {
69
- yield { name, negated }
71
+ yield { name, negated, value: inlineValue }
70
72
  continue
71
73
  }
72
74
  if (inlineValue !== undefined) {
@@ -1,5 +1,6 @@
1
1
  // node:fs existsSync — cheap sync presence check, mirrors createPublicAssetServer
2
2
  import { existsSync } from 'node:fs'
3
+ import { resolve, sep } from 'node:path'
3
4
  import { Glob } from 'bun'
4
5
  import { mimeForExtension } from '../server/runtime/mimeForExtension.ts'
5
6
  import type { Assets } from '../server/runtime/types/Assets.ts'
@@ -81,8 +82,10 @@ export function createMcpResourceServer({
81
82
  return undefined
82
83
  }
83
84
  const relativePath = uri.slice(URI_PREFIX.length)
84
- // reject `..` traversal in the requested uri before any disk read
85
- if (relativePath.split('/').includes('..')) {
85
+ // reject `..` traversal on EITHER separator a backslash-joined `..\` escapes
86
+ // resourcesDir on Windows (its OS layer treats `\` as a separator regardless of
87
+ // how the JS string was built), so a `/`-only split lets it through.
88
+ if (relativePath.split(/[\\/]/).includes('..')) {
86
89
  return undefined
87
90
  }
88
91
  if (mcpResources) {
@@ -92,6 +95,14 @@ export function createMcpResourceServer({
92
95
  }
93
96
  return contentsFor(relativePath, Bun.gunzipSync(compressed))
94
97
  }
98
+ // Belt-and-braces for the on-disk branch: resolve the target and confirm it
99
+ // stays inside resourcesDir, catching any traversal the segment check missed
100
+ // (absolute paths, platform separator quirks).
101
+ const root = resolve(resourcesDir)
102
+ const target = resolve(root, relativePath)
103
+ if (target !== root && !target.startsWith(root + sep)) {
104
+ return undefined
105
+ }
95
106
  const file = Bun.file(`${resourcesDir}/${relativePath}`)
96
107
  if (!(await file.exists())) {
97
108
  return undefined
@@ -1,4 +1,8 @@
1
1
  import { dispatchRpcInProcess } from '../server/rpc/dispatchRpcInProcess.ts'
2
+ import {
3
+ resolveInputJsonSchema,
4
+ resolveOutputJsonSchema,
5
+ } from '../server/rpc/resolveRpcJsonSchema.ts'
2
6
  import { rpcRegistry } from '../server/rpc/rpcRegistry.ts'
3
7
  import type { RpcRegistryEntry } from '../server/rpc/types/RpcRegistryEntry.ts'
4
8
  import { socketOperations } from '../server/sockets/socketOperations.ts'
@@ -37,7 +41,7 @@ RPCs enumerate first, so on a name collision the rpc tool wins.
37
41
  function mcpToolRefs(): McpToolRef[] {
38
42
  const refs: McpToolRef[] = []
39
43
  for (const entry of rpcRegistry.values()) {
40
- if (!entry.clients.mcp) {
44
+ if (!entry.remote.clients.mcp) {
41
45
  continue
42
46
  }
43
47
  refs.push({ kind: 'rpc', name: commandNameForUrl(entry.remote.url), entry })
@@ -63,7 +67,9 @@ from a write.
63
67
  */
64
68
  function describeTool(ref: McpToolRef): ToolDescriptor {
65
69
  if (ref.kind === 'rpc') {
66
- const inputSchema = jsonSchemaForSchema(ref.entry.inputSchema)
70
+ // The declared inputSchema VALIDATOR overrides the build-projected input-type schema
71
+ // (ADR-0030 input side); with neither the tool advertises the opaque fallback, as before.
72
+ const inputSchema = resolveInputJsonSchema(ref.entry) ?? jsonSchemaForSchema(undefined)
67
73
  const tool: ToolDescriptor = {
68
74
  name: ref.name,
69
75
  description:
@@ -72,8 +78,11 @@ function describeTool(ref: McpToolRef): ToolDescriptor {
72
78
  inputSchema,
73
79
  annotations: annotationsForMethod(ref.entry.remote.method),
74
80
  }
75
- if (ref.entry.outputSchema) {
76
- tool.outputSchema = jsonSchemaForSchema(ref.entry.outputSchema)
81
+ // The declared outputSchema VALIDATOR overrides the build-projected return-type schema
82
+ // (ADR-0030 D2); with neither the tool advertises no outputSchema, exactly as before.
83
+ const outputSchema = resolveOutputJsonSchema(ref.entry)
84
+ if (outputSchema) {
85
+ tool.outputSchema = outputSchema
77
86
  }
78
87
  return tool
79
88
  }
@@ -149,8 +158,8 @@ function callSocketTool(
149
158
  }
150
159
  }
151
160
  try {
152
- // broadcast() validates the payload against the socket schema and throws on failure.
153
- entry.socket.broadcast(args)
161
+ // publish() validates the payload against the socket schema and throws on failure.
162
+ entry.socket.publish(args)
154
163
  } catch (error) {
155
164
  return textResult(messageFromError(error), true)
156
165
  }
@@ -4,6 +4,12 @@ import { messageFromError } from '../shared/messageFromError.ts'
4
4
  import { responseErrorText } from '../shared/responseErrorText.ts'
5
5
  import { streamResponse } from '../shared/streamResponse.ts'
6
6
 
7
+ /* MCP `tools/call` is request/response, not a streaming transport, so a genuinely live
8
+ stream must be bounded: return what arrived within these limits (with a truncation note)
9
+ rather than buffering forever and never answering. */
10
+ const MAX_STREAM_FRAMES = 1000
11
+ const STREAM_TOOL_TIMEOUT_MS = 30_000
12
+
7
13
  // Frames a value as MCP text content — strings verbatim, everything else as JSON.
8
14
  // An undefined body (204 / empty) becomes '' — `JSON.stringify(undefined)` is the
9
15
  // value `undefined`, not a string, which would make an invalid text content block.
@@ -38,26 +44,48 @@ export async function toolResultFromResponse(response: Response): Promise<Record
38
44
 
39
45
  if (isStreamingResponse(response)) {
40
46
  const frames: unknown[] = []
47
+ // Set when WE stop the stream (cap/timeout), so the iterator's resulting end/throw
48
+ // reads as truncation, not a stream error.
49
+ let truncated: string | undefined
50
+ const timer = setTimeout(() => {
51
+ truncated = `stream truncated after ${STREAM_TOOL_TIMEOUT_MS}ms — MCP tools/call is request/response, not a live stream`
52
+ void response.body?.cancel()
53
+ }, STREAM_TOOL_TIMEOUT_MS)
41
54
  try {
42
55
  for await (const frame of streamResponse(response)) {
43
56
  frames.push(frame)
57
+ if (frames.length >= MAX_STREAM_FRAMES) {
58
+ truncated = `stream truncated at ${MAX_STREAM_FRAMES} frames`
59
+ void response.body?.cancel()
60
+ break
61
+ }
44
62
  }
45
63
  } catch (error) {
46
- return {
47
- content: [
48
- { type: 'text', text: frames.map(asText).join('\n') },
49
- {
50
- type: 'text',
51
- text: `stream error: ${messageFromError(error)}`,
52
- },
53
- ],
54
- structuredContent: { frames },
55
- isError: true,
64
+ /* A real mid-stream error (we didn't cancel) surfaces as isError; a throw caused
65
+ by our own cancel falls through to the truncated result below. */
66
+ if (truncated === undefined) {
67
+ clearTimeout(timer)
68
+ return {
69
+ content: [
70
+ { type: 'text', text: frames.map(asText).join('\n') },
71
+ { type: 'text', text: `stream error: ${messageFromError(error)}` },
72
+ ],
73
+ structuredContent: { frames },
74
+ isError: true,
75
+ }
56
76
  }
77
+ } finally {
78
+ clearTimeout(timer)
79
+ }
80
+ const content: { type: 'text'; text: string }[] = [
81
+ { type: 'text', text: frames.map(asText).join('\n') },
82
+ ]
83
+ if (truncated !== undefined) {
84
+ content.push({ type: 'text', text: truncated })
57
85
  }
58
86
  return {
59
- content: [{ type: 'text', text: frames.map(asText).join('\n') }],
60
- structuredContent: { frames },
87
+ content,
88
+ structuredContent: truncated === undefined ? { frames } : { frames, truncated },
61
89
  }
62
90
  }
63
91
 
@@ -93,15 +93,15 @@ expose the error on `rpc.isError(e, 'name')` (`.kind` and typed `.data`). No
93
93
  (a const can't read its own variable name); `schema` types `.data` and is never
94
94
  validated at runtime here.
95
95
  */
96
- function typed<Name extends string, Schema extends StandardSchemaV1>(
96
+ function typed<Name extends string, Status extends number, Schema extends StandardSchemaV1>(
97
97
  name: Name,
98
- status: number,
98
+ status: Status,
99
99
  schema: Schema,
100
- ): (data: StandardSchemaV1.InferInput<Schema>) => TypedError<Name, { status: number; data: Schema }>
101
- function typed<Name extends string>(
100
+ ): (data: StandardSchemaV1.InferInput<Schema>) => TypedError<Name, { status: Status; data: Schema }>
101
+ function typed<Name extends string, Status extends number>(
102
102
  name: Name,
103
- status: number,
104
- ): () => TypedError<Name, { status: number; data?: undefined }>
103
+ status: Status,
104
+ ): () => TypedError<Name, { status: Status; data?: undefined }>
105
105
  function typed(name: string, status: number, _schema?: StandardSchemaV1) {
106
106
  return (data?: unknown) => typedErrorResponse(name, status, data)
107
107
  }
@@ -1,7 +1,13 @@
1
1
  import { NO_STORE } from '../shared/CACHE_CONTROL_VALUES.ts'
2
+ import { encodeWireBody } from '../shared/encodeWireBody.ts'
2
3
  import type { TypedResponse } from './rpc/types/TypedResponse.ts'
3
4
  import { withResponseDefaults } from './runtime/withResponseDefaults.ts'
4
5
 
6
+ /* The Content-Type `Response.json` sets — replicated here because the wire-encode step serializes
7
+ with a replacer, so the body is built by hand rather than through `Response.json`. Seeds the
8
+ default header set so an explicit caller `content-type` still wins. */
9
+ const JSON_CONTENT_TYPE = 'application/json;charset=utf-8'
10
+
5
11
  /*
6
12
  JSON Response with rpc-friendly defaults — same shape as
7
13
  `Response.json(data, init)`, except `Cache-Control: no-store` is set
@@ -36,8 +42,16 @@ export function json<T>(data: T, init?: ResponseInit): TypedResponse<T> {
36
42
  withResponseDefaults(init, { 'Cache-Control': NO_STORE }, 204),
37
43
  ) as TypedResponse<T>
38
44
  }
39
- return Response.json(
40
- data,
41
- withResponseDefaults(init, { 'Cache-Control': NO_STORE }),
45
+ /* Wire-encode the body (ADR-0029 output path) through the shared honest-JSON encoder: a
46
+ value-directed replacer rewrites a `Set` → array, a `Map` → `[K,V]` entries, and a `bigint`
47
+ → digit string, so a structured return crosses as honest JSON rather than a lost `{}` or a
48
+ 500 (plain `Response.json` throws on a bigint). A `Date` already rides as an ISO string via
49
+ its native `toJSON`. */
50
+ return new Response(
51
+ encodeWireBody(data),
52
+ withResponseDefaults(init, {
53
+ 'Cache-Control': NO_STORE,
54
+ 'Content-Type': JSON_CONTENT_TYPE,
55
+ }),
42
56
  ) as TypedResponse<T>
43
57
  }