@abide/abide 0.46.0 → 0.48.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 (194) hide show
  1. package/AGENTS.md +370 -320
  2. package/CHANGELOG.md +94 -0
  3. package/README.md +203 -168
  4. package/package.json +12 -8
  5. package/src/abideLsp.ts +11 -12
  6. package/src/abideResolverPlugin.ts +9 -4
  7. package/src/lib/bundle/disconnected.abide +3 -0
  8. package/src/lib/cli/printCommandHelp.ts +43 -0
  9. package/src/lib/cli/printSessionHelp.ts +2 -1
  10. package/src/lib/cli/{printHelp.ts → printTopLevelHelp.ts} +3 -40
  11. package/src/lib/cli/runCli.ts +2 -1
  12. package/src/lib/mcp/buildPrompts.ts +25 -0
  13. package/src/lib/mcp/dispatchMcpRequest.ts +8 -6
  14. package/src/lib/mcp/mcpResourceServerSlot.ts +5 -10
  15. package/src/lib/mcp/mcpSurface.ts +13 -252
  16. package/src/lib/mcp/mcpTools.ts +187 -0
  17. package/src/lib/mcp/renderPrompt.ts +25 -0
  18. package/src/lib/mcp/types/McpSurface.ts +15 -0
  19. package/src/lib/mcp/types/PromptDescriptor.ts +5 -0
  20. package/src/lib/mcp/types/PromptMessage.ts +2 -0
  21. package/src/lib/mcp/types/ToolDescriptor.ts +7 -0
  22. package/src/lib/mcp/types/ToolResult.ts +2 -0
  23. package/src/lib/server/agent.ts +1 -1
  24. package/src/lib/server/jsonl.ts +2 -2
  25. package/src/lib/server/rpc/defineRpc.ts +5 -0
  26. package/src/lib/server/runtime/buildCacheSnapshot.ts +6 -6
  27. package/src/lib/server/runtime/containsTraversal.ts +21 -17
  28. package/src/lib/server/runtime/createAppAssetServer.ts +11 -16
  29. package/src/lib/server/runtime/createServer.ts +101 -71
  30. package/src/lib/server/runtime/createUiPageRenderer.ts +23 -4
  31. package/src/lib/server/runtime/devClientFingerprint.ts +3 -3
  32. package/src/lib/server/runtime/devHotModuleResponse.ts +2 -1
  33. package/src/lib/server/runtime/finalizeResponse.ts +32 -0
  34. package/src/lib/server/runtime/snapshotEntryFromCache.ts +8 -17
  35. package/src/lib/server/runtime/types/InspectorCacheEntry.ts +1 -1
  36. package/src/lib/server/runtime/types/InspectorCacheSnapshot.ts +4 -4
  37. package/src/lib/server/runtime/types/InspectorContext.ts +1 -1
  38. package/src/lib/server/socket.ts +1 -1
  39. package/src/lib/server/sockets/createSocketDispatcher.ts +28 -5
  40. package/src/lib/server/sockets/defineSocket.ts +26 -7
  41. package/src/lib/server/sockets/types/SocketRegistryEntry.ts +1 -1
  42. package/src/lib/server/sockets/types/SocketRoutes.ts +1 -1
  43. package/src/lib/server/sse.ts +2 -2
  44. package/src/lib/shared/DEFER.ts +8 -0
  45. package/src/lib/shared/activeCacheStore.ts +2 -2
  46. package/src/lib/shared/activePage.ts +2 -2
  47. package/src/lib/shared/attachRpcSelectorMethods.ts +42 -0
  48. package/src/lib/shared/attachSocketSelectorMethods.ts +34 -0
  49. package/src/lib/shared/basePath.ts +2 -2
  50. package/src/lib/shared/baseSlot.ts +6 -5
  51. package/src/lib/shared/bodyValueForKind.ts +1 -2
  52. package/src/lib/shared/buildSocketOverChannel.ts +40 -4
  53. package/src/lib/shared/cache.ts +522 -117
  54. package/src/lib/shared/cacheEntryFromSnapshot.ts +3 -22
  55. package/src/lib/shared/cacheStoreSlot.ts +9 -5
  56. package/src/lib/shared/cacheStores.ts +3 -3
  57. package/src/lib/{server/runtime → shared}/createReachable.ts +2 -2
  58. package/src/lib/shared/createRemoteFunction.ts +58 -9
  59. package/src/lib/shared/createResolverSlot.ts +24 -31
  60. package/src/lib/shared/createSocketSubRegistry.ts +2 -2
  61. package/src/lib/shared/decodeRefJson.ts +4 -2
  62. package/src/lib/shared/decodeResponse.ts +8 -6
  63. package/src/lib/shared/done.ts +14 -0
  64. package/src/lib/shared/encodeRefJson.ts +4 -2
  65. package/src/lib/shared/hydratingSlot.ts +12 -0
  66. package/src/lib/shared/isLayoutFile.ts +12 -0
  67. package/src/lib/shared/keyForRemoteCall.ts +2 -1
  68. package/src/lib/shared/keyPrefixForRemote.ts +12 -0
  69. package/src/lib/shared/matchRoute.ts +175 -0
  70. package/src/lib/shared/normalizePathname.ts +11 -0
  71. package/src/lib/shared/pageSlot.ts +14 -5
  72. package/src/lib/shared/pageUrlForFile.ts +3 -3
  73. package/src/lib/shared/parseRouteSegments.ts +16 -7
  74. package/src/lib/shared/patch.ts +41 -0
  75. package/src/lib/shared/peek.ts +35 -0
  76. package/src/lib/shared/prepareRemoteExport.ts +40 -0
  77. package/src/lib/shared/prepareRpcModule.ts +98 -16
  78. package/src/lib/shared/prepareSocketModule.ts +11 -15
  79. package/src/lib/shared/queryStringFromArgs.ts +11 -0
  80. package/src/lib/shared/reachable.ts +102 -0
  81. package/src/lib/shared/refresh.ts +22 -0
  82. package/src/lib/shared/requestScopeSlot.ts +10 -7
  83. package/src/lib/shared/routeParamsShape.ts +9 -9
  84. package/src/lib/shared/rpcErrorRegistry.ts +69 -0
  85. package/src/lib/shared/selectorPrefix.ts +3 -10
  86. package/src/lib/shared/setOwnProperty.ts +19 -0
  87. package/src/lib/shared/sharedCacheStore.ts +14 -0
  88. package/src/lib/shared/sharedCacheStoreSlot.ts +10 -0
  89. package/src/lib/shared/snippet.ts +1 -1
  90. package/src/lib/shared/subscribableProbes.ts +111 -0
  91. package/src/lib/shared/tailProbeSlot.ts +8 -1
  92. package/src/lib/shared/types/CacheEntry.ts +9 -15
  93. package/src/lib/shared/types/CacheOptions.ts +23 -11
  94. package/src/lib/shared/types/CacheSnapshotEntry.ts +0 -5
  95. package/src/lib/shared/types/CacheStats.ts +1 -1
  96. package/src/lib/shared/types/RemoteCallable.ts +25 -7
  97. package/src/lib/shared/types/RemoteFunction.ts +48 -13
  98. package/src/lib/shared/types/ResolverSlot.ts +7 -4
  99. package/src/lib/shared/types/RpcError.ts +26 -0
  100. package/src/lib/shared/types/SmartReadOptions.ts +36 -0
  101. package/src/lib/shared/types/Socket.ts +54 -0
  102. package/src/lib/shared/types/SsrBootState.ts +27 -0
  103. package/src/lib/shared/types/SsrPayload.ts +21 -0
  104. package/src/lib/shared/types/Subscribable.ts +13 -13
  105. package/src/lib/shared/types/TailHooks.ts +2 -1
  106. package/src/lib/shared/url.ts +29 -14
  107. package/src/lib/shared/wakeHydrationPeeks.ts +20 -0
  108. package/src/lib/shared/writeTestSocketsDts.ts +1 -1
  109. package/src/lib/test/createScriptedSurface.ts +1 -1
  110. package/src/lib/test/createTestApp.ts +8 -8
  111. package/src/lib/test/createTestSocketChannel.ts +3 -3
  112. package/src/lib/ui/compile/REACTIVE_CALLEES.ts +5 -6
  113. package/src/lib/ui/compile/UI_RUNTIME_IMPORTS.ts +10 -7
  114. package/src/lib/ui/compile/abideUiPlugin.ts +2 -2
  115. package/src/lib/ui/compile/analyzeComponent.ts +28 -6
  116. package/src/lib/ui/compile/compileModule.ts +137 -11
  117. package/src/lib/ui/compile/compileShadow.ts +101 -84
  118. package/src/lib/ui/compile/desugarSignals.ts +120 -107
  119. package/src/lib/ui/compile/generateBuild.ts +48 -16
  120. package/src/lib/ui/compile/generateSSR.ts +117 -14
  121. package/src/lib/ui/compile/identifierReferencePattern.ts +13 -0
  122. package/src/lib/ui/compile/lowerContext.ts +10 -4
  123. package/src/lib/ui/compile/lowerScript.ts +72 -5
  124. package/src/lib/ui/compile/parseTemplate.ts +1 -14
  125. package/src/lib/ui/compile/prepareNestedScript.ts +31 -17
  126. package/src/lib/ui/compile/resolveReactiveExport.ts +95 -0
  127. package/src/lib/ui/compile/signalCallee.ts +24 -0
  128. package/src/lib/ui/compile/stripEffects.ts +14 -11
  129. package/src/lib/ui/compile/types/AnalyzedComponent.ts +5 -0
  130. package/src/lib/ui/compile/types/TemplateNode.ts +0 -5
  131. package/src/lib/ui/dom/appendStatic.ts +9 -1
  132. package/src/lib/ui/dom/appendText.ts +15 -3
  133. package/src/lib/ui/dom/assertClaimedText.ts +20 -0
  134. package/src/lib/ui/dom/awaitBlock.ts +19 -83
  135. package/src/lib/ui/dom/bindSelectValue.ts +48 -0
  136. package/src/lib/ui/dom/each.ts +12 -1
  137. package/src/lib/ui/dom/hydrate.ts +10 -0
  138. package/src/lib/ui/dom/mountChild.ts +2 -5
  139. package/src/lib/ui/dom/mountRange.ts +0 -75
  140. package/src/lib/ui/effect.ts +4 -3
  141. package/src/lib/ui/installHotBridge.ts +4 -2
  142. package/src/lib/ui/remoteProxy.ts +18 -2
  143. package/src/lib/ui/renderToStream.ts +9 -13
  144. package/src/lib/ui/resumeSeedScript.ts +2 -2
  145. package/src/lib/ui/router.ts +21 -2
  146. package/src/lib/ui/runtime/RESUME.ts +0 -6
  147. package/src/lib/ui/runtime/ambientScopeBacking.ts +1 -1
  148. package/src/lib/ui/runtime/types/SsrRender.ts +3 -4
  149. package/src/lib/ui/scope.ts +6 -3
  150. package/src/lib/ui/seedBootState.ts +53 -0
  151. package/src/lib/ui/socketChannel.ts +2 -2
  152. package/src/lib/ui/socketProxy.ts +9 -3
  153. package/src/lib/ui/startClient.ts +17 -31
  154. package/src/lib/ui/state.ts +44 -13
  155. package/src/lib/ui/sync.ts +11 -5
  156. package/src/lib/ui/tryEncodeResume.ts +2 -5
  157. package/src/lib/ui/types/Scope.ts +14 -10
  158. package/src/lib/ui/watch.ts +140 -0
  159. package/src/serverEntry.ts +13 -11
  160. package/template/CLAUDE.md +1 -1
  161. package/template/package.json +1 -1
  162. package/template/src/server/rpc/getHello.ts +3 -3
  163. package/template/src/ui/pages/page.abide +2 -2
  164. package/template/test/app.test.ts +1 -1
  165. package/src/lib/server/reachable.ts +0 -45
  166. package/src/lib/server/sockets/types/Socket.ts +0 -23
  167. package/src/lib/shared/CACHE_WRAPPED.ts +0 -9
  168. package/src/lib/shared/DEFER_MIN_ARRAY_LENGTH.ts +0 -14
  169. package/src/lib/shared/baseResolver.ts +0 -10
  170. package/src/lib/shared/cacheKeyOf.ts +0 -7
  171. package/src/lib/shared/cacheKeyStore.ts +0 -8
  172. package/src/lib/shared/cacheStoreResolver.ts +0 -12
  173. package/src/lib/shared/globalCacheStore.ts +0 -15
  174. package/src/lib/shared/globalCacheStoreResolver.ts +0 -12
  175. package/src/lib/shared/globalCacheStoreSlot.ts +0 -9
  176. package/src/lib/shared/pageResolver.ts +0 -16
  177. package/src/lib/shared/recordCacheKey.ts +0 -6
  178. package/src/lib/shared/requestScopeResolver.ts +0 -12
  179. package/src/lib/shared/setBaseResolver.ts +0 -4
  180. package/src/lib/shared/setCacheStoreResolver.ts +0 -4
  181. package/src/lib/shared/setGlobalCacheStoreResolver.ts +0 -4
  182. package/src/lib/shared/setPageResolver.ts +0 -4
  183. package/src/lib/shared/setRequestScopeResolver.ts +0 -4
  184. package/src/lib/shared/toBunRoutePattern.ts +0 -28
  185. package/src/lib/shared/types/TailOptions.ts +0 -10
  186. package/src/lib/ui/deferResume.ts +0 -36
  187. package/src/lib/ui/dom/firstElementBetween.ts +0 -14
  188. package/src/lib/ui/matchRoute.ts +0 -106
  189. package/src/lib/ui/runtime/scheduleWake.ts +0 -28
  190. package/src/lib/ui/runtime/whenIdle.ts +0 -21
  191. package/src/lib/ui/runtime/whenVisible.ts +0 -105
  192. package/src/lib/ui/tail.ts +0 -324
  193. /package/src/lib/{server/sockets → shared}/types/SocketClientFrame.ts +0 -0
  194. /package/src/lib/{server/sockets → shared}/types/SocketServerFrame.ts +0 -0
@@ -0,0 +1,187 @@
1
+ import { dispatchRpcInProcess } from '../server/rpc/dispatchRpcInProcess.ts'
2
+ import { rpcRegistry } from '../server/rpc/rpcRegistry.ts'
3
+ import type { RpcRegistryEntry } from '../server/rpc/types/RpcRegistryEntry.ts'
4
+ import { socketOperations } from '../server/sockets/socketOperations.ts'
5
+ import { socketRegistry } from '../server/sockets/socketRegistry.ts'
6
+ import type { SocketOperation } from '../server/sockets/types/SocketOperation.ts'
7
+ import type { SocketRegistryEntry } from '../server/sockets/types/SocketRegistryEntry.ts'
8
+ import { commandNameForUrl } from '../shared/commandNameForUrl.ts'
9
+ import { forwardHeaders } from '../shared/forwardHeaders.ts'
10
+ import { jsonSchemaForSchema } from '../shared/jsonSchemaForSchema.ts'
11
+ import { messageFromError } from '../shared/messageFromError.ts'
12
+ import { annotationsForMethod } from './annotationsForMethod.ts'
13
+ import { toolResultFromResponse } from './toolResultFromResponse.ts'
14
+ import type { ToolDescriptor } from './types/ToolDescriptor.ts'
15
+ import type { ToolResult } from './types/ToolResult.ts'
16
+
17
+ /*
18
+ One MCP-exposed tool, resolved from the live registries: an rpc, or one of a
19
+ socket's operations. The ref carries the tool's identity (name) plus the
20
+ registry entry it projects from; the descriptor and the dispatch below are two
21
+ renderings of the same ref, so tools/list and tools/call can't disagree on
22
+ which tools exist or what they're called.
23
+ */
24
+ type McpToolRef =
25
+ | { kind: 'rpc'; name: string; entry: RpcRegistryEntry }
26
+ | { kind: 'socket'; name: string; entry: SocketRegistryEntry; operation: SocketOperation }
27
+
28
+ /*
29
+ Enumerates the MCP tool namespace — the single decision of which tools exist.
30
+ RPCs with clients.mcp=true become one tool named after the export's URL
31
+ (folder segments joined with `-`; reads auto-expose while mutating rpcs
32
+ require an explicit clients.mcp — see resolveClientFlags). Sockets with
33
+ clients.mcp=true contribute a `<base>-tail` read tool and, when clientPublish
34
+ is set, a `<base>-publish` tool (existence + naming live in socketOperations).
35
+ RPCs enumerate first, so on a name collision the rpc tool wins.
36
+ */
37
+ function mcpToolRefs(): McpToolRef[] {
38
+ const refs: McpToolRef[] = []
39
+ for (const entry of rpcRegistry.values()) {
40
+ if (!entry.clients.mcp) {
41
+ continue
42
+ }
43
+ refs.push({ kind: 'rpc', name: commandNameForUrl(entry.remote.url), entry })
44
+ }
45
+ for (const entry of socketRegistry.values()) {
46
+ if (!entry.clients.mcp) {
47
+ continue
48
+ }
49
+ for (const operation of socketOperations(entry)) {
50
+ refs.push({ kind: 'socket', name: operation.name, entry, operation })
51
+ }
52
+ }
53
+ return refs
54
+ }
55
+
56
+ /*
57
+ The advertised face of one tool. An rpc tool's description favours the
58
+ schema's top-level description (the vendor's JSON Schema conversion carries
59
+ `.describe(...)` through), falling back to `method url` so the tool is still
60
+ labelled when the schema has none; the HTTP method feeds the annotations
61
+ (readOnlyHint / destructiveHint / idempotentHint) so a model can tell a read
62
+ from a write.
63
+ */
64
+ function describeTool(ref: McpToolRef): ToolDescriptor {
65
+ if (ref.kind === 'rpc') {
66
+ const inputSchema = jsonSchemaForSchema(ref.entry.inputSchema)
67
+ const tool: ToolDescriptor = {
68
+ name: ref.name,
69
+ description:
70
+ (inputSchema.description as string | undefined) ??
71
+ `${ref.entry.remote.method} ${ref.entry.remote.url}`,
72
+ inputSchema,
73
+ annotations: annotationsForMethod(ref.entry.remote.method),
74
+ }
75
+ if (ref.entry.outputSchema) {
76
+ tool.outputSchema = jsonSchemaForSchema(ref.entry.outputSchema)
77
+ }
78
+ return tool
79
+ }
80
+ const payloadSchema = jsonSchemaForSchema(ref.entry.schema)
81
+ if (ref.operation.kind === 'tail') {
82
+ return {
83
+ name: ref.name,
84
+ description: `Read recent messages from the "${ref.operation.socketName}" socket`,
85
+ inputSchema: {
86
+ type: 'object',
87
+ properties: {
88
+ count: { type: 'number', description: 'max recent messages to return' },
89
+ },
90
+ },
91
+ outputSchema: {
92
+ type: 'object',
93
+ properties: { frames: { type: 'array', items: payloadSchema } },
94
+ },
95
+ annotations: { readOnlyHint: true, destructiveHint: false },
96
+ }
97
+ }
98
+ return {
99
+ name: ref.name,
100
+ description: `Publish a message to the "${ref.operation.socketName}" socket`,
101
+ inputSchema: payloadSchema,
102
+ annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false },
103
+ }
104
+ }
105
+
106
+ function textResult(text: string, isError = false): ToolResult {
107
+ return { content: [{ type: 'text', text }], ...(isError ? { isError: true } : {}) }
108
+ }
109
+
110
+ /*
111
+ Dispatches an rpc tool by synthesizing a Request (with forwarded auth headers
112
+ from `inbound`) and piping it through rpc.fetch inside the request scope — the
113
+ same seam the HTTP router crosses, so validation, the handler, and the
114
+ request-scoped helpers (per-call cache(), cookies(), request()) behave
115
+ identically. A handler throw is caught by the scope and framed as an isError
116
+ tool result (via the 500 response) rather than escaping. The response
117
+ (buffered or streaming) is framed by toolResultFromResponse.
118
+ */
119
+ async function callRpcTool(
120
+ entry: RpcRegistryEntry,
121
+ args: Record<string, unknown> | undefined,
122
+ inbound: Request,
123
+ ): Promise<ToolResult> {
124
+ const response = await dispatchRpcInProcess({
125
+ remote: entry.remote,
126
+ args,
127
+ baseUrl: `${new URL(inbound.url).origin}/`,
128
+ headers: forwardHeaders(inbound.headers),
129
+ })
130
+ return toolResultFromResponse(response)
131
+ }
132
+
133
+ /*
134
+ Dispatches a socket tool. tail returns the retained tail (request/response
135
+ can't hold a live subscription); publish validates against the socket schema
136
+ and fans out.
137
+ */
138
+ function callSocketTool(
139
+ entry: SocketRegistryEntry,
140
+ operation: SocketOperation,
141
+ args: Record<string, unknown> | undefined,
142
+ ): Promise<ToolResult> | ToolResult {
143
+ if (operation.kind === 'tail') {
144
+ const count = typeof args?.count === 'number' ? args.count : undefined
145
+ const frames = entry.snapshotTail(count)
146
+ return {
147
+ content: [{ type: 'text', text: frames.map((f) => JSON.stringify(f)).join('\n') }],
148
+ structuredContent: { frames },
149
+ }
150
+ }
151
+ try {
152
+ // broadcast() validates the payload against the socket schema and throws on failure.
153
+ entry.socket.broadcast(args)
154
+ } catch (error) {
155
+ return textResult(messageFromError(error), true)
156
+ }
157
+ return textResult('ok')
158
+ }
159
+
160
+ /*
161
+ The MCP tool surface: what tools/list advertises and what tools/call runs.
162
+ Both faces render from mcpToolRefs — the tool namespace is decided once, so
163
+ everything advertised is callable and everything callable is advertised.
164
+ list() is the only face that builds descriptors (schema projection is
165
+ per-call work dispatch never pays). Read at call time (not cached) so rpcs
166
+ constructed after boot show up, matching the other registry projections.
167
+ */
168
+ export const mcpTools = {
169
+ list(): ToolDescriptor[] {
170
+ return mcpToolRefs().map(describeTool)
171
+ },
172
+ async call(
173
+ toolName: string,
174
+ args: Record<string, unknown> | undefined,
175
+ inbound: Request,
176
+ ): Promise<ToolResult> {
177
+ for (const ref of mcpToolRefs()) {
178
+ if (ref.name !== toolName) {
179
+ continue
180
+ }
181
+ return ref.kind === 'rpc'
182
+ ? callRpcTool(ref.entry, args, inbound)
183
+ : callSocketTool(ref.entry, ref.operation, args)
184
+ }
185
+ throw new Error(`unknown tool: ${toolName}`)
186
+ },
187
+ }
@@ -0,0 +1,25 @@
1
+ import { promptRegistry } from '../server/prompts/promptRegistry.ts'
2
+ import type { PromptMessage } from './types/PromptMessage.ts'
3
+
4
+ /*
5
+ Renders a prompt: looks it up, interpolates the caller's args, and returns its
6
+ optional description plus the message(s) that seed a conversation. A markdown
7
+ prompt is a single user turn whose text is the interpolated template. Throws on
8
+ an unknown prompt name. The one place prompt rendering lives — dispatchMcpRequest
9
+ wraps this in the prompts/get wire shape, the agent loop reads the messages plain.
10
+ */
11
+ export function renderPrompt(
12
+ name: string,
13
+ args?: Record<string, unknown>,
14
+ ): { description?: string; messages: PromptMessage[] } {
15
+ const entry = promptRegistry.get(name)
16
+ if (!entry) {
17
+ throw new Error(`unknown prompt: ${name}`)
18
+ }
19
+ return {
20
+ ...(entry.prompt.description ? { description: entry.prompt.description } : {}),
21
+ messages: [
22
+ { role: 'user', text: entry.prompt.render((args ?? {}) as Record<string, string>) },
23
+ ],
24
+ }
25
+ }
@@ -0,0 +1,15 @@
1
+ import type { McpResourceContents } from './McpResourceContents.ts'
2
+ import type { McpResourceDescriptor } from './McpResourceDescriptor.ts'
3
+ import type { PromptDescriptor } from './PromptDescriptor.ts'
4
+ import type { PromptMessage } from './PromptMessage.ts'
5
+ import type { ToolDescriptor } from './ToolDescriptor.ts'
6
+ import type { ToolResult } from './ToolResult.ts'
7
+
8
+ export type McpSurface = {
9
+ tools: ToolDescriptor[]
10
+ call(name: string, args: Record<string, unknown> | undefined): Promise<ToolResult>
11
+ prompts: PromptDescriptor[]
12
+ getPrompt(name: string, args?: Record<string, unknown>): PromptMessage[]
13
+ listResources(): Promise<McpResourceDescriptor[]>
14
+ readResource(uri: string): Promise<McpResourceContents | undefined>
15
+ }
@@ -0,0 +1,5 @@
1
+ export type PromptDescriptor = {
2
+ name: string
3
+ description?: string
4
+ arguments: Array<{ name: string; description?: string; required: boolean }>
5
+ }
@@ -0,0 +1,2 @@
1
+ // A prompt rendered to plain messages — the user turn(s) that seed a conversation.
2
+ export type PromptMessage = { role: 'user'; text: string }
@@ -0,0 +1,7 @@
1
+ export type ToolDescriptor = {
2
+ name: string
3
+ description: string
4
+ inputSchema: Record<string, unknown>
5
+ outputSchema?: Record<string, unknown>
6
+ annotations?: Record<string, boolean>
7
+ }
@@ -0,0 +1,2 @@
1
+ // The MCP tools/call result shape (a text content block plus optional structuredContent).
2
+ export type ToolResult = Record<string, unknown>
@@ -1,5 +1,5 @@
1
- import type { McpSurface } from '../mcp/mcpSurface.ts'
2
1
  import { mcpSurface } from '../mcp/mcpSurface.ts'
2
+ import type { McpSurface } from '../mcp/types/McpSurface.ts'
3
3
  import { request } from './request.ts'
4
4
 
5
5
  /*
@@ -31,7 +31,7 @@ import { withResponseDefaults } from './runtime/withResponseDefaults.ts'
31
31
  export function jsonl<Frame>(
32
32
  iterable: AsyncIterable<Frame>,
33
33
  init?: ResponseInit,
34
- ): TypedResponse<Frame> {
34
+ ): TypedResponse<AsyncIterable<Frame>> {
35
35
  const body = streamFromIterator(iterable, {
36
36
  encodeFrame: (value) => `${JSON.stringify(value)}\n`,
37
37
  encodeError: (message) => jsonlErrorFrame.encode(message),
@@ -43,5 +43,5 @@ export function jsonl<Frame>(
43
43
  'Cache-Control': NO_STORE,
44
44
  'X-Content-Type-Options': 'nosniff',
45
45
  }),
46
- ) as TypedResponse<Frame>
46
+ ) as TypedResponse<AsyncIterable<Frame>>
47
47
  }
@@ -64,6 +64,10 @@ export function defineRpc<Args, Return>(
64
64
  parked write drains on `rpc.outbox.retry()` (no auto-drain). Mutating methods
65
65
  only. */
66
66
  outbox?: boolean
67
+ /* Handler returns jsonl()/sse(): the bare call returns the Subscribable directly (for
68
+ isomorphic `for await (… of fn(args))` in server code). Bundler-stamped; the router's
69
+ wire path (.fetch) is unaffected. */
70
+ streaming?: boolean
67
71
  },
68
72
  ): RemoteFunction<Args, Return> {
69
73
  /* `outbox: true` is a mutation contract — a read RPC has nothing to durably deliver,
@@ -183,6 +187,7 @@ export function defineRpc<Args, Return>(
183
187
  method,
184
188
  url,
185
189
  clients,
190
+ streaming: opts?.streaming ?? false,
186
191
  crossOrigin: opts?.crossOrigin,
187
192
  buildRequest,
188
193
  invoke,
@@ -1,4 +1,4 @@
1
- import { globalCacheStore } from '../../shared/globalCacheStore.ts'
1
+ import { sharedCacheStore } from '../../shared/sharedCacheStore.ts'
2
2
  import type { CacheEntry } from '../../shared/types/CacheEntry.ts'
3
3
  import type { InspectorCacheEntry } from './types/InspectorCacheEntry.ts'
4
4
  import type { InspectorCacheSnapshot } from './types/InspectorCacheSnapshot.ts'
@@ -48,14 +48,14 @@ function projectEntry(entry: CacheEntry, now: number): InspectorCacheEntry {
48
48
  }
49
49
 
50
50
  /*
51
- Snapshots the process-level cache store (the persistent one `cache(fn, { global:
52
- true })` writes to) for the inspector. Read at call time, so it reflects the
53
- store as it stands; request-scoped stores are deliberately excluded — they're
54
- ephemeral and already visible as per-request cache tallies.
51
+ Snapshots the process-level cache store (the persistent one `cache(fn, { shared:
52
+ true, ttl: Infinity })` writes to) for the inspector. Read at call time, so it
53
+ reflects the store as it stands; request-scoped stores are deliberately
54
+ excluded — they're ephemeral and already visible as per-request cache tallies.
55
55
  */
56
56
  export function buildCacheSnapshot(): InspectorCacheSnapshot {
57
57
  const now = Date.now()
58
- const entries = Array.from(globalCacheStore().entries.values(), (entry) =>
58
+ const entries = Array.from(sharedCacheStore().entries.values(), (entry) =>
59
59
  projectEntry(entry, now),
60
60
  )
61
61
  return { entries }
@@ -1,37 +1,41 @@
1
1
  /*
2
- Inspects the raw request URL (not the parsed pathname) for path-traversal
3
- patterns. The WHATWG URL parser decodes `%2E%2E` to `..` and then collapses
4
- dot-segments out of the pathname during normalization, so by the time
5
- `url.pathname` is observable any encoded traversal has been masked. The
6
- remaining literal `..` check guards against any future URL-parser quirk
2
+ Inspects the raw request URL's PATH portion (not the parsed pathname) for
3
+ path-traversal patterns. The WHATWG URL parser decodes `%2E%2E` to `..` and
4
+ then collapses dot-segments out of the pathname during normalization, so by
5
+ the time `url.pathname` is observable any encoded traversal has been masked.
6
+ The remaining literal `..` check guards against any future URL-parser quirk
7
7
  that lets a normalised path through.
8
8
 
9
+ Only the path is scanned — an encoded slash or backslash in a QUERY value
10
+ (`/logo.png?from=%2Fblog`) is legitimate and must not 404 a real asset.
11
+
9
12
  Hot path early-out: if none of the suspect substrings appear in the raw
10
- URL we never lowercase the whole string nor walk segments.
13
+ path we never lowercase it nor walk segments.
11
14
  */
12
15
  export function containsTraversal(rawUrl: string): boolean {
13
- if (rawUrl.includes('\\')) {
16
+ const rawPath = rawPathPortion(rawUrl)
17
+ if (rawPath.includes('\\')) {
14
18
  return true
15
19
  }
16
- if (rawUrl.includes('..') && segmentContainsDotDot(rawUrl)) {
20
+ if (rawPath.includes('..') && rawPath.split('/').some((segment) => segment === '..')) {
17
21
  return true
18
22
  }
19
- if (rawUrl.indexOf('%') === -1) {
23
+ if (rawPath.indexOf('%') === -1) {
20
24
  return false
21
25
  }
22
- const lower = rawUrl.toLowerCase()
26
+ const lower = rawPath.toLowerCase()
23
27
  return lower.includes('%2e%2e') || lower.includes('%2f') || lower.includes('%5c')
24
28
  }
25
29
 
26
- function segmentContainsDotDot(rawUrl: string): boolean {
30
+ /* The raw path of an absolute request URL: first `/` after the authority up
31
+ to the query. Requests never carry a fragment, so `?` is the only cutoff. */
32
+ function rawPathPortion(rawUrl: string): string {
27
33
  const queryStart = rawUrl.indexOf('?')
28
34
  const pathEnd = queryStart === -1 ? rawUrl.length : queryStart
29
- const pathStart = rawUrl.indexOf('/', rawUrl.indexOf('://') + 3)
35
+ const schemeEnd = rawUrl.indexOf('://')
36
+ const pathStart = rawUrl.indexOf('/', schemeEnd === -1 ? 0 : schemeEnd + 3)
30
37
  if (pathStart === -1 || pathStart >= pathEnd) {
31
- return false
38
+ return ''
32
39
  }
33
- return rawUrl
34
- .slice(pathStart, pathEnd)
35
- .split('/')
36
- .some((segment) => segment === '..')
40
+ return rawUrl.slice(pathStart, pathEnd)
37
41
  }
@@ -48,12 +48,16 @@ export async function createAppAssetServer({
48
48
  [...diskPaths].filter((path) => path.endsWith('.gz')).map((path) => path.slice(0, -3)),
49
49
  )
50
50
 
51
+ // Fresh 404 per call — a Response body is single-use, so it can't be hoisted to a const.
52
+ const notFound = (): Response =>
53
+ new Response('Not Found', {
54
+ status: 404,
55
+ headers: { 'Content-Type': TEXT_PLAIN, 'Cache-Control': NO_STORE },
56
+ })
57
+
51
58
  return async function serveAppAsset(req, url) {
52
59
  if (containsTraversal(req.url)) {
53
- return new Response('Not Found', {
54
- status: 404,
55
- headers: { 'Content-Type': TEXT_PLAIN, 'Cache-Control': NO_STORE },
56
- })
60
+ return notFound()
57
61
  }
58
62
  /* Embed map and gzip Set hold decoded filesystem names; url.pathname stays
59
63
  percent-encoded — decode so a chunk/asset with a non-ASCII name matches and
@@ -62,10 +66,7 @@ export async function createAppAssetServer({
62
66
  try {
63
67
  assetPath = decodeURIComponent(url.pathname)
64
68
  } catch {
65
- return new Response('Not Found', {
66
- status: 404,
67
- headers: { 'Content-Type': TEXT_PLAIN, 'Cache-Control': NO_STORE },
68
- })
69
+ return notFound()
69
70
  }
70
71
  if (assets) {
71
72
  const compressed = assets[assetPath]
@@ -73,10 +74,7 @@ export async function createAppAssetServer({
73
74
  (request-controlled) pathnames, so building bundles for junk
74
75
  `/_app/*` probes would grow it without bound. */
75
76
  if (!compressed) {
76
- return new Response('Not Found', {
77
- status: 404,
78
- headers: { 'Content-Type': TEXT_PLAIN, 'Cache-Control': NO_STORE },
79
- })
77
+ return notFound()
80
78
  }
81
79
  return respondWithEmbeddedAsset(
82
80
  compressed,
@@ -87,10 +85,7 @@ export async function createAppAssetServer({
87
85
  /* Miss-check before header work so probes for non-existent chunks can't grow the
88
86
  header cache (the embed branch above guards the same way). */
89
87
  if (!diskPaths.has(assetPath)) {
90
- return new Response('Not Found', {
91
- status: 404,
92
- headers: { 'Content-Type': TEXT_PLAIN, 'Cache-Control': NO_STORE },
93
- })
88
+ return notFound()
94
89
  }
95
90
  const { base: baseHeaders, gzip: gzipHeaders } = headersForAsset(assetPath)
96
91
  const diskPath = distDir + assetPath