effect 4.1.0 → 4.1.1

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 (354) hide show
  1. package/README.md +30 -0
  2. package/THIRD_PARTY_NOTICES.md +62 -0
  3. package/dist/BigDecimal.d.ts +139 -7
  4. package/dist/BigDecimal.d.ts.map +1 -1
  5. package/dist/BigDecimal.js +83 -14
  6. package/dist/BigDecimal.js.map +1 -1
  7. package/dist/Equal.d.ts +2 -2
  8. package/dist/Equal.d.ts.map +1 -1
  9. package/dist/Equal.js +3 -3
  10. package/dist/Equal.js.map +1 -1
  11. package/dist/Hash.d.ts +3 -2
  12. package/dist/Hash.d.ts.map +1 -1
  13. package/dist/Hash.js +5 -4
  14. package/dist/Hash.js.map +1 -1
  15. package/dist/Iterable.d.ts.map +1 -1
  16. package/dist/Iterable.js +11 -8
  17. package/dist/Iterable.js.map +1 -1
  18. package/dist/Path.d.ts.map +1 -1
  19. package/dist/Path.js +5 -1
  20. package/dist/Path.js.map +1 -1
  21. package/dist/Pool.d.ts.map +1 -1
  22. package/dist/Pool.js +2 -1
  23. package/dist/Pool.js.map +1 -1
  24. package/dist/Schema.d.ts +2 -1
  25. package/dist/Schema.d.ts.map +1 -1
  26. package/dist/Schema.js +2 -1
  27. package/dist/Schema.js.map +1 -1
  28. package/dist/SchemaRepresentation.d.ts +4 -2
  29. package/dist/SchemaRepresentation.d.ts.map +1 -1
  30. package/dist/SchemaRepresentation.js +4 -2
  31. package/dist/SchemaRepresentation.js.map +1 -1
  32. package/dist/SchemaTransformation.d.ts +2 -2
  33. package/dist/SchemaTransformation.js +2 -2
  34. package/dist/ai/McpClient.d.ts +171 -19
  35. package/dist/ai/McpClient.d.ts.map +1 -1
  36. package/dist/ai/McpClient.js +277 -66
  37. package/dist/ai/McpClient.js.map +1 -1
  38. package/dist/ai/McpSchema.d.ts +108 -0
  39. package/dist/ai/McpSchema.d.ts.map +1 -1
  40. package/dist/ai/McpSchema.js +99 -0
  41. package/dist/ai/McpSchema.js.map +1 -1
  42. package/dist/ai/McpServer.d.ts +120 -6
  43. package/dist/ai/McpServer.d.ts.map +1 -1
  44. package/dist/ai/McpServer.js +215 -34
  45. package/dist/ai/McpServer.js.map +1 -1
  46. package/dist/ai/Prompt.d.ts.map +1 -1
  47. package/dist/ai/Prompt.js +20 -11
  48. package/dist/ai/Prompt.js.map +1 -1
  49. package/dist/ai/Tool.d.ts.map +1 -1
  50. package/dist/ai/Tool.js +5 -7
  51. package/dist/ai/Tool.js.map +1 -1
  52. package/dist/ai/internal/mcpCore.js +72 -3
  53. package/dist/ai/internal/mcpCore.js.map +1 -1
  54. package/dist/ai/internal/mcpProtocol/v2024_11_05.js +3 -1
  55. package/dist/ai/internal/mcpProtocol/v2024_11_05.js.map +1 -1
  56. package/dist/ai/internal/mcpProtocol/v2025_03_26.js +3 -1
  57. package/dist/ai/internal/mcpProtocol/v2025_03_26.js.map +1 -1
  58. package/dist/ai/internal/mcpProtocol/v2025_06_18.js +3 -1
  59. package/dist/ai/internal/mcpProtocol/v2025_06_18.js.map +1 -1
  60. package/dist/ai/internal/mcpProtocol/v2025_11_25.js +3 -1
  61. package/dist/ai/internal/mcpProtocol/v2025_11_25.js.map +1 -1
  62. package/dist/ai/internal/mcpProtocol/v2026_07_28.js +42 -7
  63. package/dist/ai/internal/mcpProtocol/v2026_07_28.js.map +1 -1
  64. package/dist/ai/internal/mcpProtocol.js +12 -0
  65. package/dist/ai/internal/mcpProtocol.js.map +1 -1
  66. package/dist/ai/internal/mcpRuntime.js +3 -3
  67. package/dist/ai/internal/mcpRuntime.js.map +1 -1
  68. package/dist/ai/internal/mcpSkills.d.ts +2 -0
  69. package/dist/ai/internal/mcpSkills.d.ts.map +1 -0
  70. package/dist/ai/internal/mcpSkills.js +428 -0
  71. package/dist/ai/internal/mcpSkills.js.map +1 -0
  72. package/dist/cluster/ClusterWorkflowEngine.d.ts.map +1 -1
  73. package/dist/cluster/ClusterWorkflowEngine.js +15 -5
  74. package/dist/cluster/ClusterWorkflowEngine.js.map +1 -1
  75. package/dist/cluster/Reply.d.ts +1 -1
  76. package/dist/cluster/Reply.d.ts.map +1 -1
  77. package/dist/cluster/Reply.js.map +1 -1
  78. package/dist/cluster/Sharding.d.ts.map +1 -1
  79. package/dist/cluster/Sharding.js +4 -1
  80. package/dist/cluster/Sharding.js.map +1 -1
  81. package/dist/cluster/internal/entityManager.js +15 -7
  82. package/dist/cluster/internal/entityManager.js.map +1 -1
  83. package/dist/crypto/Aead.d.ts +2 -1
  84. package/dist/crypto/Aead.d.ts.map +1 -1
  85. package/dist/crypto/Aead.js +2 -2
  86. package/dist/crypto/Aead.js.map +1 -1
  87. package/dist/crypto/Cipher.d.ts +2 -1
  88. package/dist/crypto/Cipher.d.ts.map +1 -1
  89. package/dist/crypto/Cipher.js +2 -2
  90. package/dist/crypto/Cipher.js.map +1 -1
  91. package/dist/crypto/KeyAgreement.d.ts +3 -2
  92. package/dist/crypto/KeyAgreement.d.ts.map +1 -1
  93. package/dist/crypto/KeyAgreement.js +2 -2
  94. package/dist/crypto/KeyAgreement.js.map +1 -1
  95. package/dist/crypto/KeyFormat.d.ts +33 -1
  96. package/dist/crypto/KeyFormat.d.ts.map +1 -1
  97. package/dist/crypto/KeyFormat.js.map +1 -1
  98. package/dist/crypto/KeyWrap.d.ts +4 -3
  99. package/dist/crypto/KeyWrap.d.ts.map +1 -1
  100. package/dist/crypto/KeyWrap.js +2 -2
  101. package/dist/crypto/KeyWrap.js.map +1 -1
  102. package/dist/crypto/Mac.d.ts +2 -1
  103. package/dist/crypto/Mac.d.ts.map +1 -1
  104. package/dist/crypto/Mac.js +2 -2
  105. package/dist/crypto/Mac.js.map +1 -1
  106. package/dist/crypto/Signature.d.ts +3 -2
  107. package/dist/crypto/Signature.d.ts.map +1 -1
  108. package/dist/crypto/Signature.js +2 -2
  109. package/dist/crypto/Signature.js.map +1 -1
  110. package/dist/crypto/WebCrypto.d.ts +19 -0
  111. package/dist/crypto/WebCrypto.d.ts.map +1 -1
  112. package/dist/crypto/WebCrypto.js.map +1 -1
  113. package/dist/crypto/internal/asymmetric.js.map +1 -1
  114. package/dist/crypto/internal/key.js +3 -2
  115. package/dist/crypto/internal/key.js.map +1 -1
  116. package/dist/encoding/Base64Url.d.ts.map +1 -1
  117. package/dist/encoding/Base64Url.js +26 -3
  118. package/dist/encoding/Base64Url.js.map +1 -1
  119. package/dist/encoding/Ndjson.d.ts +5 -4
  120. package/dist/encoding/Ndjson.d.ts.map +1 -1
  121. package/dist/encoding/Ndjson.js +6 -5
  122. package/dist/encoding/Ndjson.js.map +1 -1
  123. package/dist/encoding/Sse.d.ts +3 -1
  124. package/dist/encoding/Sse.d.ts.map +1 -1
  125. package/dist/encoding/Sse.js +5 -3
  126. package/dist/encoding/Sse.js.map +1 -1
  127. package/dist/eventlog/EventLog.d.ts +7 -0
  128. package/dist/eventlog/EventLog.d.ts.map +1 -1
  129. package/dist/eventlog/EventLog.js +7 -0
  130. package/dist/eventlog/EventLog.js.map +1 -1
  131. package/dist/eventlog/EventLogServer.d.ts +7 -1
  132. package/dist/eventlog/EventLogServer.d.ts.map +1 -1
  133. package/dist/eventlog/EventLogServer.js +27 -12
  134. package/dist/eventlog/EventLogServer.js.map +1 -1
  135. package/dist/eventlog/EventLogServerEncrypted.d.ts +10 -0
  136. package/dist/eventlog/EventLogServerEncrypted.d.ts.map +1 -1
  137. package/dist/eventlog/EventLogServerEncrypted.js.map +1 -1
  138. package/dist/eventlog/EventLogServerUnencrypted.d.ts +17 -0
  139. package/dist/eventlog/EventLogServerUnencrypted.d.ts.map +1 -1
  140. package/dist/eventlog/EventLogServerUnencrypted.js +14 -2
  141. package/dist/eventlog/EventLogServerUnencrypted.js.map +1 -1
  142. package/dist/eventlog/EventLogSessionAuth.d.ts +21 -0
  143. package/dist/eventlog/EventLogSessionAuth.d.ts.map +1 -1
  144. package/dist/eventlog/EventLogSessionAuth.js +22 -0
  145. package/dist/eventlog/EventLogSessionAuth.js.map +1 -1
  146. package/dist/graphql/GraphQLClient.d.ts +3 -1
  147. package/dist/graphql/GraphQLClient.d.ts.map +1 -1
  148. package/dist/graphql/GraphQLClient.js.map +1 -1
  149. package/dist/graphql/GraphQLClientError.d.ts +14 -9
  150. package/dist/graphql/GraphQLClientError.d.ts.map +1 -1
  151. package/dist/graphql/GraphQLClientError.js +14 -21
  152. package/dist/graphql/GraphQLClientError.js.map +1 -1
  153. package/dist/graphql/GraphQLProtocol.d.ts +12 -3
  154. package/dist/graphql/GraphQLProtocol.d.ts.map +1 -1
  155. package/dist/graphql/GraphQLProtocol.js +4 -1
  156. package/dist/graphql/GraphQLProtocol.js.map +1 -1
  157. package/dist/http/HttpClient.d.ts +189 -7
  158. package/dist/http/HttpClient.d.ts.map +1 -1
  159. package/dist/http/HttpClient.js +75 -2
  160. package/dist/http/HttpClient.js.map +1 -1
  161. package/dist/http/HttpServerRequest.d.ts.map +1 -1
  162. package/dist/http/HttpServerRequest.js +38 -22
  163. package/dist/http/HttpServerRequest.js.map +1 -1
  164. package/dist/http/internal/mimeTypes.d.ts +4 -1
  165. package/dist/http/internal/mimeTypes.d.ts.map +1 -1
  166. package/dist/http/internal/mimeTypes.js +4 -1
  167. package/dist/http/internal/mimeTypes.js.map +1 -1
  168. package/dist/http-api/HttpApiSchema.js +1 -1
  169. package/dist/http-api/HttpApiSchema.js.map +1 -1
  170. package/dist/internal/dateTime.js +49 -24
  171. package/dist/internal/dateTime.js.map +1 -1
  172. package/dist/internal/effect.js +10 -0
  173. package/dist/internal/effect.js.map +1 -1
  174. package/dist/internal/glob.js +234 -56
  175. package/dist/internal/glob.js.map +1 -1
  176. package/dist/internal/graphql.js +21 -7
  177. package/dist/internal/graphql.js.map +1 -1
  178. package/dist/internal/graphqlHeaders.d.ts +7 -0
  179. package/dist/internal/graphqlHeaders.d.ts.map +1 -1
  180. package/dist/internal/graphqlHeaders.js +11 -0
  181. package/dist/internal/graphqlHeaders.js.map +1 -1
  182. package/dist/internal/request.js +29 -42
  183. package/dist/internal/request.js.map +1 -1
  184. package/dist/internal/schema/fromJsonSchemaDocument.js +7 -8
  185. package/dist/internal/schema/fromJsonSchemaDocument.js.map +1 -1
  186. package/dist/internal/version.js +1 -1
  187. package/dist/jose/JoseError.d.ts +285 -0
  188. package/dist/jose/JoseError.d.ts.map +1 -0
  189. package/dist/jose/JoseError.js +269 -0
  190. package/dist/jose/JoseError.js.map +1 -0
  191. package/dist/jose/Jwe.d.ts +128 -0
  192. package/dist/jose/Jwe.d.ts.map +1 -0
  193. package/dist/jose/Jwe.js +134 -0
  194. package/dist/jose/Jwe.js.map +1 -0
  195. package/dist/jose/Jwk.d.ts +597 -0
  196. package/dist/jose/Jwk.d.ts.map +1 -0
  197. package/dist/jose/Jwk.js +503 -0
  198. package/dist/jose/Jwk.js.map +1 -0
  199. package/dist/jose/Jwks.d.ts +236 -0
  200. package/dist/jose/Jwks.d.ts.map +1 -0
  201. package/dist/jose/Jwks.js +306 -0
  202. package/dist/jose/Jwks.js.map +1 -0
  203. package/dist/jose/Jws.d.ts +177 -0
  204. package/dist/jose/Jws.d.ts.map +1 -0
  205. package/dist/jose/Jws.js +183 -0
  206. package/dist/jose/Jws.js.map +1 -0
  207. package/dist/jose/Jwt.d.ts +270 -0
  208. package/dist/jose/Jwt.d.ts.map +1 -0
  209. package/dist/jose/Jwt.js +293 -0
  210. package/dist/jose/Jwt.js.map +1 -0
  211. package/dist/jose/index.d.ts +51 -0
  212. package/dist/jose/index.d.ts.map +1 -0
  213. package/dist/jose/index.js +52 -0
  214. package/dist/jose/index.js.map +1 -0
  215. package/dist/jose/internal/compact.d.ts +2 -0
  216. package/dist/jose/internal/compact.d.ts.map +1 -0
  217. package/dist/jose/internal/compact.js +179 -0
  218. package/dist/jose/internal/compact.js.map +1 -0
  219. package/dist/net/Host.d.ts.map +1 -1
  220. package/dist/net/Host.js +5 -10
  221. package/dist/net/Host.js.map +1 -1
  222. package/dist/observability/OtlpSerialization.d.ts +1 -1
  223. package/dist/observability/OtlpSerialization.d.ts.map +1 -1
  224. package/dist/observability/OtlpSerialization.js +10 -4
  225. package/dist/observability/OtlpSerialization.js.map +1 -1
  226. package/dist/observability/internal/otlpProtobuf.js +3 -3
  227. package/dist/observability/internal/otlpProtobuf.js.map +1 -1
  228. package/dist/observability/internal/protobuf.js +6 -0
  229. package/dist/observability/internal/protobuf.js.map +1 -1
  230. package/dist/persistence/CircuitBreaker.d.ts +98 -38
  231. package/dist/persistence/CircuitBreaker.d.ts.map +1 -1
  232. package/dist/persistence/CircuitBreaker.js +67 -29
  233. package/dist/persistence/CircuitBreaker.js.map +1 -1
  234. package/dist/persistence/RateLimiter.d.ts +8 -0
  235. package/dist/persistence/RateLimiter.d.ts.map +1 -1
  236. package/dist/persistence/RateLimiter.js +63 -12
  237. package/dist/persistence/RateLimiter.js.map +1 -1
  238. package/dist/persistence/index.d.ts +1 -1
  239. package/dist/persistence/index.js +1 -1
  240. package/dist/reactivity/AtomRegistry.js +17 -7
  241. package/dist/reactivity/AtomRegistry.js.map +1 -1
  242. package/dist/rpc/Rpc.d.ts +1 -1
  243. package/dist/rpc/Rpc.d.ts.map +1 -1
  244. package/dist/rpc/Rpc.js.map +1 -1
  245. package/dist/rpc/RpcClient.d.ts +14 -2
  246. package/dist/rpc/RpcClient.d.ts.map +1 -1
  247. package/dist/rpc/RpcClient.js +38 -21
  248. package/dist/rpc/RpcClient.js.map +1 -1
  249. package/dist/rpc/RpcMessage.d.ts +3 -2
  250. package/dist/rpc/RpcMessage.d.ts.map +1 -1
  251. package/dist/rpc/RpcMessage.js.map +1 -1
  252. package/dist/sql/Migrator.d.ts +9 -1
  253. package/dist/sql/Migrator.d.ts.map +1 -1
  254. package/dist/sql/Migrator.js +18 -6
  255. package/dist/sql/Migrator.js.map +1 -1
  256. package/dist/sql/SqlClient.d.ts +38 -3
  257. package/dist/sql/SqlClient.d.ts.map +1 -1
  258. package/dist/sql/SqlClient.js +4 -3
  259. package/dist/sql/SqlClient.js.map +1 -1
  260. package/dist/sql/Statement.d.ts +41 -3
  261. package/dist/sql/Statement.d.ts.map +1 -1
  262. package/dist/sql/Statement.js +24 -20
  263. package/dist/sql/Statement.js.map +1 -1
  264. package/dist/workflow/Workflow.d.ts +4 -0
  265. package/dist/workflow/Workflow.d.ts.map +1 -1
  266. package/dist/workflow/Workflow.js.map +1 -1
  267. package/dist/workflow/WorkflowEngine.d.ts.map +1 -1
  268. package/dist/workflow/WorkflowEngine.js +5 -0
  269. package/dist/workflow/WorkflowEngine.js.map +1 -1
  270. package/package.json +5 -1
  271. package/src/BigDecimal.ts +170 -16
  272. package/src/Equal.ts +3 -3
  273. package/src/Hash.ts +5 -4
  274. package/src/Iterable.ts +8 -5
  275. package/src/Path.ts +6 -1
  276. package/src/Pool.ts +2 -1
  277. package/src/Schema.ts +3 -2
  278. package/src/SchemaRepresentation.ts +4 -2
  279. package/src/SchemaTransformation.ts +2 -2
  280. package/src/ai/McpClient.ts +488 -91
  281. package/src/ai/McpSchema.ts +119 -0
  282. package/src/ai/McpServer.ts +261 -38
  283. package/src/ai/Prompt.ts +16 -11
  284. package/src/ai/Tool.ts +5 -10
  285. package/src/ai/internal/mcpCore.ts +165 -12
  286. package/src/ai/internal/mcpProtocol/v2024_11_05.ts +3 -1
  287. package/src/ai/internal/mcpProtocol/v2025_03_26.ts +3 -1
  288. package/src/ai/internal/mcpProtocol/v2025_06_18.ts +3 -1
  289. package/src/ai/internal/mcpProtocol/v2025_11_25.ts +3 -1
  290. package/src/ai/internal/mcpProtocol/v2026_07_28.ts +41 -7
  291. package/src/ai/internal/mcpProtocol.ts +15 -0
  292. package/src/ai/internal/mcpRuntime.ts +6 -3
  293. package/src/ai/internal/mcpSkills.ts +480 -0
  294. package/src/cluster/ClusterWorkflowEngine.ts +22 -8
  295. package/src/cluster/Reply.ts +1 -1
  296. package/src/cluster/Sharding.ts +4 -1
  297. package/src/cluster/internal/entityManager.ts +24 -10
  298. package/src/crypto/Aead.ts +3 -3
  299. package/src/crypto/Cipher.ts +3 -3
  300. package/src/crypto/KeyAgreement.ts +4 -4
  301. package/src/crypto/KeyFormat.ts +30 -1
  302. package/src/crypto/KeyWrap.ts +6 -6
  303. package/src/crypto/Mac.ts +3 -3
  304. package/src/crypto/Signature.ts +4 -4
  305. package/src/crypto/WebCrypto.ts +20 -0
  306. package/src/crypto/internal/asymmetric.ts +2 -1
  307. package/src/crypto/internal/key.ts +12 -5
  308. package/src/encoding/Base64Url.ts +27 -3
  309. package/src/encoding/Ndjson.ts +6 -5
  310. package/src/encoding/Sse.ts +5 -3
  311. package/src/eventlog/EventLog.ts +7 -0
  312. package/src/eventlog/EventLogServer.ts +31 -12
  313. package/src/eventlog/EventLogServerEncrypted.ts +5 -0
  314. package/src/eventlog/EventLogServerUnencrypted.ts +19 -2
  315. package/src/eventlog/EventLogSessionAuth.ts +26 -0
  316. package/src/graphql/GraphQLClient.ts +3 -1
  317. package/src/graphql/GraphQLClientError.ts +15 -22
  318. package/src/graphql/GraphQLProtocol.ts +12 -3
  319. package/src/http/HttpClient.ts +210 -11
  320. package/src/http/HttpServerRequest.ts +61 -23
  321. package/src/http/internal/mimeTypes.ts +4 -1
  322. package/src/http-api/HttpApiSchema.ts +1 -1
  323. package/src/internal/dateTime.ts +54 -24
  324. package/src/internal/effect.ts +6 -0
  325. package/src/internal/glob.ts +279 -63
  326. package/src/internal/graphql.ts +27 -9
  327. package/src/internal/graphqlHeaders.ts +13 -0
  328. package/src/internal/request.ts +51 -58
  329. package/src/internal/schema/fromJsonSchemaDocument.ts +11 -10
  330. package/src/internal/version.ts +1 -1
  331. package/src/jose/JoseError.ts +329 -0
  332. package/src/jose/Jwe.ts +196 -0
  333. package/src/jose/Jwk.ts +777 -0
  334. package/src/jose/Jwks.ts +418 -0
  335. package/src/jose/Jws.ts +312 -0
  336. package/src/jose/Jwt.ts +493 -0
  337. package/src/jose/index.ts +58 -0
  338. package/src/jose/internal/compact.ts +195 -0
  339. package/src/net/Host.ts +5 -10
  340. package/src/observability/OtlpSerialization.ts +13 -5
  341. package/src/observability/internal/otlpProtobuf.ts +6 -6
  342. package/src/observability/internal/protobuf.ts +10 -0
  343. package/src/persistence/CircuitBreaker.ts +113 -48
  344. package/src/persistence/RateLimiter.ts +84 -15
  345. package/src/persistence/index.ts +1 -1
  346. package/src/reactivity/AtomRegistry.ts +19 -8
  347. package/src/rpc/Rpc.ts +3 -1
  348. package/src/rpc/RpcClient.ts +44 -24
  349. package/src/rpc/RpcMessage.ts +3 -2
  350. package/src/sql/Migrator.ts +35 -18
  351. package/src/sql/SqlClient.ts +43 -8
  352. package/src/sql/Statement.ts +52 -22
  353. package/src/workflow/Workflow.ts +4 -0
  354. package/src/workflow/WorkflowEngine.ts +5 -0
@@ -0,0 +1,480 @@
1
+ /**
2
+ * The Skills extension (SEP-2640): its wire vocabulary, the catalog behind a
3
+ * static skill, and the handlers shared by the sessionful adapters.
4
+ *
5
+ * @internal
6
+ */
7
+ import * as Cause from "../../Cause.ts"
8
+ import * as Digest from "../../crypto/Digest.ts"
9
+ import * as Effect from "../../Effect.ts"
10
+ import * as Yaml from "../../encoding/Yaml.ts"
11
+ import * as Mime from "../../http/Mime.ts"
12
+ import * as Option from "../../Option.ts"
13
+ import * as Predicate from "../../Predicate.ts"
14
+ import * as Rpc from "../../rpc/Rpc.ts"
15
+ import * as RpcGroup from "../../rpc/RpcGroup.ts"
16
+ import * as Schema from "../../Schema.ts"
17
+ import * as PublicMcpSchema from "../McpSchema.ts"
18
+ import type * as McpCore from "./mcpCore.ts"
19
+ import * as McpProtocol from "./mcpProtocol.ts"
20
+ import * as Legacy from "./mcpSchema/v2024_11_05.ts"
21
+ import * as Stateless from "./mcpSchema/v2026_07_28.ts"
22
+
23
+ /**
24
+ * @internal
25
+ */
26
+ export const EXTENSION_ID = "io.modelcontextprotocol/skills"
27
+
28
+ const DIRECTORY_MIME_TYPE = "inode/directory"
29
+
30
+ // The extension's per-skill limits, which every conforming host accepts.
31
+ const MAX_FILES_PER_SKILL = 512
32
+ const MAX_BYTES_PER_SKILL = 16 * 1024 * 1024
33
+
34
+ /**
35
+ * Adds the extension declaration while any skill is registered. Declaring it
36
+ * commits the server to `skills/list`, `skills/get` and, through
37
+ * `directoryRead`, `resources/directory/read`.
38
+ *
39
+ * @internal
40
+ */
41
+ export const declareExtension = <A>(
42
+ extensions: Readonly<Record<string, A>> | undefined,
43
+ servesSkills: boolean
44
+ ): Readonly<Record<string, A | { readonly directoryRead: true }>> | undefined =>
45
+ servesSkills ? { ...extensions, [EXTENSION_ID]: { directoryRead: true } } : extensions
46
+
47
+ // =============================================================================
48
+ // Wire schemas
49
+ // =============================================================================
50
+
51
+ const Skill = Schema.Struct({
52
+ uri: Schema.String,
53
+ frontmatter: Legacy.UnknownObject,
54
+ resources: Schema.Union([
55
+ Schema.Array(Schema.Struct(PublicMcpSchema.SkillResource.fields)),
56
+ Schema.Literal("dynamic")
57
+ ])
58
+ })
59
+
60
+ // Listed entries are checked one at a time, so that a client can leave out an
61
+ // invalid entry instead of rejecting the whole page.
62
+ const ListedSkills = Schema.Array(Schema.Unknown)
63
+
64
+ /**
65
+ * @internal
66
+ */
67
+ export class LegacyListSkills extends Rpc.make("skills/list", {
68
+ success: Schema.Struct({ ...Legacy.PaginatedResult.fields, skills: ListedSkills }),
69
+ error: Legacy.McpError,
70
+ payload: Schema.UndefinedOr(Legacy.PaginatedRequest)
71
+ }) {}
72
+
73
+ /**
74
+ * @internal
75
+ */
76
+ export class LegacyGetSkill extends Rpc.make("skills/get", {
77
+ success: Schema.Struct({ ...Legacy.ResultMeta.fields, skill: Skill }),
78
+ error: Legacy.McpError,
79
+ payload: { ...Legacy.RequestMeta.fields, uri: Schema.String }
80
+ }) {}
81
+
82
+ /**
83
+ * @internal
84
+ */
85
+ export class LegacyReadResourceDirectory extends Rpc.make("resources/directory/read", {
86
+ success: Schema.Struct({ ...Legacy.PaginatedResult.fields, resources: Schema.Array(Legacy.Resource) }),
87
+ error: Legacy.McpError,
88
+ payload: { ...Legacy.PaginatedRequest.fields, uri: Schema.String }
89
+ }) {}
90
+
91
+ /**
92
+ * The extension's methods on revisions before 2026-07-28, which have no
93
+ * result caching attributes.
94
+ *
95
+ * @internal
96
+ */
97
+ export class LegacyRpcs extends RpcGroup.make(LegacyListSkills, LegacyGetSkill, LegacyReadResourceDirectory) {}
98
+
99
+ /**
100
+ * @internal
101
+ */
102
+ export const ListSkillsResult = Schema.StructWithRest(
103
+ Schema.Struct({ ...Stateless.PaginatedResult, ...Stateless.CacheableResult, skills: ListedSkills }),
104
+ [Stateless.UnknownObject]
105
+ )
106
+
107
+ /**
108
+ * @internal
109
+ */
110
+ export class ListSkills extends Rpc.make("skills/list", {
111
+ success: ListSkillsResult,
112
+ error: Stateless.McpError,
113
+ payload: Stateless.PaginatedRequestParams
114
+ }) {}
115
+
116
+ /**
117
+ * @internal
118
+ */
119
+ export const GetSkillResult = Schema.StructWithRest(
120
+ Schema.Struct({ ...Stateless.CacheableResult, skill: Skill }),
121
+ [Stateless.UnknownObject]
122
+ )
123
+
124
+ /**
125
+ * @internal
126
+ */
127
+ export class GetSkill extends Rpc.make("skills/get", {
128
+ success: GetSkillResult,
129
+ error: Stateless.McpError,
130
+ payload: { ...Stateless.RequestParams.fields, uri: Schema.String }
131
+ }) {}
132
+
133
+ /**
134
+ * @internal
135
+ */
136
+ export const ReadResourceDirectoryResult = Schema.StructWithRest(
137
+ Schema.Struct({
138
+ ...Stateless.PaginatedResult,
139
+ ...Stateless.CacheableResult,
140
+ resources: Schema.Array(Stateless.Resource)
141
+ }),
142
+ [Stateless.UnknownObject]
143
+ )
144
+
145
+ /**
146
+ * @internal
147
+ */
148
+ export class ReadResourceDirectory extends Rpc.make("resources/directory/read", {
149
+ success: ReadResourceDirectoryResult,
150
+ error: Stateless.McpError,
151
+ payload: { ...Stateless.PaginatedRequestParams, uri: Schema.String }
152
+ }) {}
153
+
154
+ /**
155
+ * The extension's methods on revision 2026-07-28.
156
+ *
157
+ * @internal
158
+ */
159
+ export class StatelessRpcs extends RpcGroup.make(ListSkills, GetSkill, ReadResourceDirectory) {}
160
+
161
+ // =============================================================================
162
+ // Handlers
163
+ // =============================================================================
164
+
165
+ const methodNotFound = (method: string) =>
166
+ new McpProtocol.ProtocolError({
167
+ code: PublicMcpSchema.METHOD_NOT_FOUND_ERROR_CODE,
168
+ message: `Method not found: ${method}`
169
+ })
170
+
171
+ // Every skill fits on one page, so the server never issues a cursor.
172
+ const unknownCursor = new McpProtocol.ProtocolError({
173
+ code: PublicMcpSchema.INVALID_PARAMS_ERROR_CODE,
174
+ message: "Unknown cursor"
175
+ })
176
+
177
+ /**
178
+ * Fails with `Method not found` while no skill is registered, as a server
179
+ * that does not declare the extension.
180
+ *
181
+ * @internal
182
+ */
183
+ export const requireSkills = (core: McpCore.McpCore, method: string) =>
184
+ Effect.flatMap(
185
+ core.registrationPresence,
186
+ (presence) => presence.skills ? Effect.void : Effect.fail(methodNotFound(method))
187
+ )
188
+
189
+ /**
190
+ * @internal
191
+ */
192
+ export const rejectCursor = (cursor: string | undefined) =>
193
+ cursor === undefined ? Effect.void : Effect.fail(unknownCursor)
194
+
195
+ /**
196
+ * Handlers for {@link LegacyRpcs}, for the sessionful adapters.
197
+ *
198
+ * @internal
199
+ */
200
+ export const legacyHandlers = (core: McpCore.McpCore) => ({
201
+ "skills/list": Effect.fnUntraced(function*(request: typeof LegacyListSkills.payloadSchema.Type) {
202
+ yield* requireSkills(core, "skills/list")
203
+ yield* rejectCursor(request?.cursor)
204
+ const skills = yield* McpProtocol.listForClient(core.skills.list)
205
+ return { skills }
206
+ }),
207
+ "skills/get": Effect.fnUntraced(function*(request: typeof LegacyGetSkill.payloadSchema.Type) {
208
+ yield* requireSkills(core, "skills/get")
209
+ const client = yield* PublicMcpSchema.McpServerClient
210
+ const skill = yield* core.skills.get(request.uri, McpProtocol.invocationFromClient(client)).pipe(
211
+ Effect.mapError(McpProtocol.ProtocolError.fromFeature)
212
+ )
213
+ return { skill }
214
+ }),
215
+ "resources/directory/read": Effect.fnUntraced(function*(
216
+ request: typeof LegacyReadResourceDirectory.payloadSchema.Type
217
+ ) {
218
+ yield* requireSkills(core, "resources/directory/read")
219
+ yield* rejectCursor(request.cursor)
220
+ const client = yield* PublicMcpSchema.McpServerClient
221
+ const resources = yield* core.skills.readDirectory(request.uri, McpProtocol.invocationFromClient(client)).pipe(
222
+ Effect.mapError(McpProtocol.ProtocolError.fromFeature)
223
+ )
224
+ return { resources }
225
+ })
226
+ })
227
+
228
+ // =============================================================================
229
+ // Static skills
230
+ // =============================================================================
231
+
232
+ /**
233
+ * A file of a static skill as handed in, `SKILL.md` included, with its path
234
+ * relative to the skill root.
235
+ *
236
+ * @internal
237
+ */
238
+ export interface SourceFile {
239
+ readonly path: string
240
+ readonly content: Uint8Array
241
+ readonly mimeType?: string | undefined
242
+ }
243
+
244
+ /**
245
+ * A file of a static skill as served through `resources/read`.
246
+ *
247
+ * @internal
248
+ */
249
+ export interface ServedFile {
250
+ // Listed by `resources/list` and as a child on `resources/directory/read`.
251
+ readonly resource: McpCore.DirectoryChild
252
+ readonly content: Uint8Array
253
+ // Set when the file is textual and valid UTF-8; otherwise it is served as a blob.
254
+ readonly text: string | undefined
255
+ }
256
+
257
+ /**
258
+ * @internal
259
+ */
260
+ export interface StaticSkill {
261
+ readonly entry: McpCore.Skill
262
+ readonly files: ReadonlyArray<ServedFile>
263
+ // Directory URI to its direct children.
264
+ readonly directories: ReadonlyMap<string, ReadonlyArray<McpCore.DirectoryChild>>
265
+ }
266
+
267
+ const TEXTUAL_APPLICATION_TYPES: ReadonlySet<string> = new Set([
268
+ "application/json",
269
+ "application/yaml",
270
+ "image/svg+xml"
271
+ ])
272
+
273
+ /**
274
+ * @internal
275
+ */
276
+ export const mimeTypeFor = (path: string): string => {
277
+ // The standard table maps `.ts` to MPEG transport streams, but a skill's
278
+ // `.ts` files are TypeScript scripts.
279
+ if (/\.ts$/i.test(path)) return "text/x-typescript"
280
+ return Option.getOrElse(Mime.getType(path), () => "application/octet-stream")
281
+ }
282
+
283
+ // `ignoreBOM` keeps a leading BOM in the decoded text, so re-encoding the text
284
+ // reproduces exactly the bytes the manifest digest covers.
285
+ const utf8 = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true })
286
+
287
+ const decodeText = (mimeType: string, content: Uint8Array): string | undefined => {
288
+ if (!mimeType.startsWith("text/") && !TEXTUAL_APPLICATION_TYPES.has(mimeType)) {
289
+ return undefined
290
+ }
291
+ try {
292
+ return utf8.decode(content)
293
+ } catch {
294
+ return undefined
295
+ }
296
+ }
297
+
298
+ const isSafeRelativePath = (path: string): boolean =>
299
+ path !== "" &&
300
+ !path.includes("\\") &&
301
+ path.split("/").every((segment) => segment !== "" && segment !== "." && segment !== "..")
302
+
303
+ // The Agent Skills `name` rule: lowercase letters, digits and single hyphens,
304
+ // neither leading nor trailing, at most 64 characters.
305
+ const isSkillName = (name: string): boolean => name.length <= 64 && /^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(name)
306
+
307
+ const FRONTMATTER = /^\uFEFF?---\r?\n([\s\S]*?)\r?\n---[ \t]*(?:\r?\n|$)/
308
+
309
+ /**
310
+ * The YAML frontmatter of a `SKILL.md`, or `undefined` when the file does not
311
+ * begin with a frontmatter block holding a YAML mapping.
312
+ *
313
+ * @internal
314
+ */
315
+ export const parseFrontmatter = (markdown: string): Record<string, unknown> | undefined => {
316
+ const block = FRONTMATTER.exec(markdown)?.[1]
317
+ if (block === undefined) return undefined
318
+ try {
319
+ const parsed = Yaml.parse(block)
320
+ // The frontmatter is listed as JSON, so keep only what JSON carries:
321
+ // `.inf` and `.nan` become `null`.
322
+ return Predicate.isObject(parsed) ? JSON.parse(JSON.stringify(parsed)) : undefined
323
+ } catch {
324
+ return undefined
325
+ }
326
+ }
327
+
328
+ // The directory holding a skill's `SKILL.md`, given the `SKILL.md` URI.
329
+ const skillRoot = (uri: string): string => uri.slice(0, -"/SKILL.md".length)
330
+
331
+ /**
332
+ * Whether `uri` addresses a file inside the skill whose `SKILL.md` is at
333
+ * `skillUri`.
334
+ *
335
+ * @internal
336
+ */
337
+ export const isSkillFile = (skillUri: string, uri: string): boolean => {
338
+ const root = skillRoot(skillUri)
339
+ return uri.startsWith(`${root}/`) && isSafeRelativePath(uri.slice(root.length + 1))
340
+ }
341
+
342
+ /**
343
+ * Why a skill entry received from a server breaks the extension's rules, or
344
+ * `undefined` when it is valid. Hosts must not load an invalid entry. The
345
+ * digest and size formats are checked by {@link PublicMcpSchema.SkillResource}.
346
+ *
347
+ * @internal
348
+ */
349
+ export const entryProblem = (entry: McpCore.Skill): string | undefined => {
350
+ const schemeEnd = entry.uri.indexOf("://")
351
+ if (schemeEnd <= 0 || !entry.uri.endsWith("/SKILL.md")) return "the URI does not address a SKILL.md"
352
+ const root = skillRoot(entry.uri)
353
+ const skillPath = root.slice(schemeEnd + 3)
354
+ if (!isSafeRelativePath(skillPath)) return "the skill path has an empty, '.' or '..' segment"
355
+ const name = skillPath.split("/").at(-1)!
356
+ if (entry.frontmatter.name !== name || !isSkillName(name)) {
357
+ return `the frontmatter name must equal "${name}" and follow the Agent Skills naming rules`
358
+ }
359
+ const description = entry.frontmatter.description
360
+ if (typeof description !== "string" || description === "" || description.length > 1024) {
361
+ return "the frontmatter description must be a string of 1 to 1024 characters"
362
+ }
363
+ if (entry.resources === "dynamic") return undefined
364
+ const uris = new Set<string>()
365
+ for (const resource of entry.resources) {
366
+ if (!isSkillFile(entry.uri, resource.uri)) return `${resource.uri} is not a file of the skill`
367
+ if (uris.has(resource.uri)) return `${resource.uri} is listed twice`
368
+ uris.add(resource.uri)
369
+ }
370
+ return uris.has(entry.uri) ? undefined : "the manifest does not list SKILL.md"
371
+ }
372
+
373
+ /**
374
+ * Whether `child` is a direct child of the directory at `directory`.
375
+ *
376
+ * @internal
377
+ */
378
+ export const isDirectChild = (directory: string, child: string): boolean => {
379
+ if (!child.startsWith(`${directory}/`)) return false
380
+ const name = child.slice(directory.length + 1)
381
+ return name !== "" && name !== "." && name !== ".." && !name.includes("/")
382
+ }
383
+
384
+ const decodeFrontmatter = Schema.decodeUnknownEffect(PublicMcpSchema.SkillFrontmatter)
385
+
386
+ /**
387
+ * Validates a static skill against the extension's server requirements and
388
+ * precomputes what the protocol serves: the manifest, with digests over the
389
+ * exact bytes served, each file's resource metadata, and the directory tree
390
+ * from the namespace root down.
391
+ *
392
+ * @internal
393
+ */
394
+ export const makeStaticSkill = (options: {
395
+ readonly uri: string
396
+ readonly files: ReadonlyArray<SourceFile>
397
+ }): Effect.Effect<StaticSkill, Cause.IllegalArgumentError, Digest.Digest> =>
398
+ Effect.gen(function*() {
399
+ const { uri } = options
400
+ const fail = (reason: string) => Effect.fail(new Cause.IllegalArgumentError(`Skill ${uri}: ${reason}`))
401
+
402
+ const schemeEnd = uri.indexOf("://")
403
+ if (schemeEnd <= 0 || !uri.endsWith("/SKILL.md")) {
404
+ return yield* fail("the URI must have the form <scheme>://<skill-path>/SKILL.md")
405
+ }
406
+ const scheme = uri.slice(0, schemeEnd + 3)
407
+ const skillPath = uri.slice(scheme.length, -"/SKILL.md".length)
408
+ if (!isSafeRelativePath(skillPath)) {
409
+ return yield* fail("the skill path must have no empty, '.' or '..' segment")
410
+ }
411
+ const name = skillPath.split("/").at(-1)!
412
+ if (options.files.length > MAX_FILES_PER_SKILL) {
413
+ return yield* fail(`it has ${options.files.length} files; the limit is ${MAX_FILES_PER_SKILL}`)
414
+ }
415
+ const totalBytes = options.files.reduce((sum, file) => sum + file.content.byteLength, 0)
416
+ if (totalBytes > MAX_BYTES_PER_SKILL) {
417
+ return yield* fail(`it has ${totalBytes} bytes; the limit is ${MAX_BYTES_PER_SKILL}`)
418
+ }
419
+ const paths = new Set<string>()
420
+ for (const file of options.files) {
421
+ if (!isSafeRelativePath(file.path)) return yield* fail(`the file path "${file.path}" escapes the skill`)
422
+ if (paths.has(file.path)) return yield* fail(`the file "${file.path}" is given twice`)
423
+ paths.add(file.path)
424
+ }
425
+ const skillMd = options.files.find((file) => file.path === "SKILL.md")
426
+ if (skillMd === undefined) return yield* fail("SKILL.md is missing")
427
+
428
+ // The listed frontmatter is parsed from the served SKILL.md, so the two
429
+ // cannot differ.
430
+ const frontmatter = parseFrontmatter(decodeText("text/markdown", skillMd.content) ?? "")
431
+ if (frontmatter === undefined) return yield* fail("SKILL.md must begin with YAML frontmatter holding a mapping")
432
+ if (frontmatter.name !== name) {
433
+ return yield* fail(`the frontmatter name ${JSON.stringify(frontmatter.name)} must equal "${name}"`)
434
+ }
435
+ // A skill served over MCP must conform to the Agent Skills specification.
436
+ const { description } = yield* decodeFrontmatter(frontmatter).pipe(
437
+ Effect.catch((error) => fail(`the frontmatter is not valid Agent Skills frontmatter: ${error.message}`))
438
+ )
439
+
440
+ const digest = yield* Digest.Digest
441
+ const sorted = [...options.files].sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0)
442
+ const resources: Array<McpCore.SkillResource> = []
443
+ const files: Array<ServedFile> = []
444
+ const children = new Map<string, Map<string, McpCore.DirectoryChild>>()
445
+ for (const file of sorted) {
446
+ const fileUri = `${scheme}${skillPath}/${file.path}`
447
+ const fileDigest = `sha256:${yield* digest.digestHex(Digest.sha256, file.content)}`
448
+ const mimeType = file.mimeType ?? mimeTypeFor(file.path)
449
+ const size = file.content.byteLength
450
+ const resource: McpCore.DirectoryChild = file.path === "SKILL.md"
451
+ ? { uri: fileUri, name, description, mimeType, size }
452
+ : { uri: fileUri, name: file.path.split("/").at(-1)!, mimeType, size }
453
+ files.push({ resource, content: file.content, text: decodeText(mimeType, file.content) })
454
+ resources.push({ uri: fileUri, digest: fileDigest, size })
455
+
456
+ // Every directory from the namespace root down is readable, including
457
+ // the prefix directories above the skill root.
458
+ const segments = `${skillPath}/${file.path}`.split("/")
459
+ for (let depth = 1; depth < segments.length; depth++) {
460
+ const directory = `${scheme}${segments.slice(0, depth).join("/")}`
461
+ const child: McpCore.DirectoryChild = depth === segments.length - 1 ? resource : {
462
+ uri: `${scheme}${segments.slice(0, depth + 1).join("/")}`,
463
+ name: segments[depth]!,
464
+ mimeType: DIRECTORY_MIME_TYPE
465
+ }
466
+ let siblings = children.get(directory)
467
+ if (siblings === undefined) {
468
+ siblings = new Map()
469
+ children.set(directory, siblings)
470
+ }
471
+ siblings.set(child.uri, child)
472
+ }
473
+ }
474
+
475
+ const directories = new Map<string, ReadonlyArray<McpCore.DirectoryChild>>()
476
+ for (const [directory, siblings] of children) {
477
+ directories.set(directory, Array.from(siblings.values()))
478
+ }
479
+ return { entry: { uri, frontmatter, resources }, files, directories }
480
+ })
@@ -535,14 +535,15 @@ export const make = Effect.gen(function*() {
535
535
  if (instance.abandoned) {
536
536
  instance.suspended = false
537
537
  }
538
+ // Abandoned runs never wake the parent; another owner replays them.
539
+ const resumeParent = parent && !parent.detached && !instance.abandoned
540
+ ? ensureSuccess(sendResumeParent(parent, executionId))
541
+ : Effect.void
538
542
  if (
539
543
  !instance.suspended &&
540
544
  !instance.abandoned &&
541
545
  !(suspendOnFailure && exit._tag === "Failure")
542
546
  ) {
543
- const resumeParent = parent && !parent.detached
544
- ? ensureSuccess(sendResumeParent(parent, executionId))
545
- : Effect.void
546
547
  if (exit._tag === "Success" || !Cause.hasInterruptsOnly(exit.cause)) {
547
548
  return resumeParent
548
549
  }
@@ -568,9 +569,13 @@ export const make = Effect.gen(function*() {
568
569
  }
569
570
  instance.suspended = false
570
571
  instance.interrupted = true
571
- return Effect.andThen(
572
- Effect.ignore(clearClock({ workflow, executionId })),
573
- Effect.withFiber<void>((fiber) => Effect.interruptible(Fiber.interrupt(fiber)))
572
+ // The interrupt completes the run, so wake the parent
573
+ // to observe it.
574
+ return Effect.ignore(clearClock({ workflow, executionId })).pipe(
575
+ Effect.andThen(resumeParent),
576
+ Effect.andThen(
577
+ Effect.withFiber<void>((fiber) => Effect.interruptible(Fiber.interrupt(fiber)))
578
+ )
574
579
  )
575
580
  }),
576
581
  Effect.orDie
@@ -760,7 +765,7 @@ export const make = Effect.gen(function*() {
760
765
  // A pending interrupt replays only to rebuild compensations, so it must
761
766
  // not run new activities. Finalizers run once `interrupted` is set.
762
767
  if (!instance.interrupted && (yield* interruptPending(instance))) {
763
- const reply = yield* Effect.orDie(requestReply({
768
+ const reply = yield* retryPersistence(requestReply({
764
769
  workflow: instance.workflow,
765
770
  entityType: `Workflow/${instance.workflow._tag}`,
766
771
  executionId: instance.executionId,
@@ -777,7 +782,7 @@ export const make = Effect.gen(function*() {
777
782
  if (!activities.has(activityId)) {
778
783
  activities.set(activityId, { activity, context: services })
779
784
  }
780
- const result = yield* Effect.orDie(
785
+ const result = yield* retryPersistence(
781
786
  client.activity({
782
787
  name: activity.name,
783
788
  attempt,
@@ -917,6 +922,15 @@ const retryPolicy = Schedule.min([
917
922
  Schedule.spaced("1 minute")
918
923
  ])
919
924
 
925
+ // A transient storage failure must not become the workflow result. Requests
926
+ // are saved under a primary key, so a retry after a lost acknowledgement is
927
+ // deduplicated instead of running the activity again.
928
+ const retryPersistence = <A, E extends { readonly _tag: string }, R>(effect: Effect.Effect<A, E, R>) =>
929
+ effect.pipe(
930
+ Effect.retry({ schedule: retryPolicy, while: (e) => e._tag === "PersistenceError" }),
931
+ Effect.orDie
932
+ )
933
+
920
934
  // A cluster abandonment means this owner can no longer complete the effect, so
921
935
  // retrying it would only wait on a retired run. Re-raise it as an annotated
922
936
  // interrupt, so the activation can unwind and its shard can be released.
@@ -220,7 +220,7 @@ export class Chunk<R extends Rpc.Any> extends Data.TaggedClass("Chunk")<{
220
220
  * @stability unstable
221
221
  * @since 4.0.0
222
222
  */
223
- static readonly Any = Schema.declare((u): u is Chunk<never> => isReply(u) && u._tag === "Chunk")
223
+ static readonly Any = Schema.declare((u): u is Chunk<Rpc.Any> => isReply(u) && u._tag === "Chunk")
224
224
 
225
225
  /**
226
226
  * Transformation between encoded chunk records and `Chunk` instances.
@@ -1697,7 +1697,10 @@ const make = Effect.gen(function*() {
1697
1697
  }
1698
1698
  clientRequests.set(id, entry)
1699
1699
  respond = makeClientRespond(entry, client.write)
1700
- return sendOutgoing(message, false)
1700
+ // A failed send leaves no reply or interrupt to release the entry.
1701
+ // Only drop the local bookkeeping, as the request may have been
1702
+ // persisted.
1703
+ return Effect.tapError(sendOutgoing(message, false), () => Effect.sync(() => clientRequests.delete(id)))
1701
1704
  }
1702
1705
  case "Ack": {
1703
1706
  const requestId = Snowflake.Snowflake(options.message.requestId)
@@ -369,8 +369,11 @@ export const make = Effect.fnUntraced(function*<
369
369
  )
370
370
  }
371
371
  }
372
+ // A failed save can abort the request transaction, so a retry
373
+ // could store a success outside it. Save once and let the
374
+ // failed transaction replay the request.
372
375
  const write = retryRespond(
373
- 4,
376
+ persisted && request.transaction && Exit.isSuccess(response.exit) ? 0 : 4,
374
377
  Effect.suspend(() =>
375
378
  request.message.respond(
376
379
  new Reply.WithExit({
@@ -407,8 +410,25 @@ export const make = Effect.fnUntraced(function*<
407
410
  }
408
411
  removeActiveRequest(Snowflake.Snowflake(response.requestId))
409
412
  })
413
+ // Let callers read the saved reply instead of waiting on a
414
+ // handler that will never be resumed.
415
+ const completeFromStorage = Effect.andThen(
416
+ complete,
417
+ options.storage.unregisterReplyHandler(request.message.envelope.requestId)
418
+ )
410
419
  const respond = Effect.andThen(save, complete).pipe(
411
420
  Effect.catchTag("EntityNotAssignedToRunner", () => abandonFenced),
421
+ // A save can commit and still fail, e.g. when the COMMIT
422
+ // acknowledgement is lost. Volatile requests skip the lookup:
423
+ // their replies are never stored, and a storage read could
424
+ // stall a custom responder's failure.
425
+ Effect.catchTag("PersistenceError", (error) =>
426
+ persisted
427
+ ? Effect.flatMap(
428
+ hasStoredExit(request.message.envelope.requestId),
429
+ (committed) => committed ? completeFromStorage : Effect.fail(error)
430
+ )
431
+ : Effect.fail(error)),
412
432
  Effect.orDie
413
433
  )
414
434
  const transaction = request.transaction
@@ -427,16 +447,10 @@ export const make = Effect.fnUntraced(function*<
427
447
  // A rejected save failed the transaction, rolling back the
428
448
  // handler's writes; the request is left for the owner.
429
449
  if (fenced) return abandonFenced
450
+ // The commit can succeed despite the error.
430
451
  return Effect.flatMap(
431
452
  hasStoredExit(request.message.envelope.requestId),
432
- (committed) => {
433
- if (!committed) return restartFrom(outcome.cause)
434
- // Commit succeeded despite the error; let callers read the saved reply.
435
- return Effect.andThen(
436
- complete,
437
- options.storage.unregisterReplyHandler(request.message.envelope.requestId)
438
- )
439
- }
453
+ (committed) => committed ? completeFromStorage : restartFrom(outcome.cause)
440
454
  )
441
455
  }
442
456
  return Effect.orDie(save)
@@ -977,7 +991,7 @@ const makeMessageDecode = <Rpcs extends Rpc.Any>(entityRpcs: Map<string, Rpcs>)
977
991
  )
978
992
  )
979
993
  }
980
- return decodeRequest(message, rpc) as Effect.Effect<
994
+ return decodeRequest(message, rpc) as any as Effect.Effect<
981
995
  {
982
996
  readonly _tag: "IncomingRequest"
983
997
  readonly envelope: Envelope.Request.Any
@@ -37,7 +37,7 @@ import * as Chacha from "./internal/chacha.ts"
37
37
  import * as KeyModel from "./internal/key.ts"
38
38
  import * as internal from "./internal/webCrypto.ts"
39
39
  import * as SecureRandom from "./SecureRandom.ts"
40
- import { WebCrypto } from "./WebCrypto.ts"
40
+ import * as WebCrypto from "./WebCrypto.ts"
41
41
 
42
42
  const TypeId = "~effect/crypto/Aead/Key" as const
43
43
 
@@ -374,7 +374,7 @@ export const layerWebCryptoWith = (options: {
374
374
  Aead,
375
375
  Effect.gen(function*() {
376
376
  const random = yield* SecureRandom.SecureRandom
377
- const crypto = yield* WebCrypto
377
+ const crypto = yield* WebCrypto.WebCrypto
378
378
  const cryptoKeyFor = KeyModel.makeCache((subtle, bytes) =>
379
379
  subtle.importKey("raw", bytes as BufferSource, "AES-GCM", false, ["encrypt", "decrypt"])
380
380
  )
@@ -488,7 +488,7 @@ export const key = <A extends Algorithm>(
488
488
  */
489
489
  export const fromCryptoKey = <A extends AesGcm>(
490
490
  algorithm: A,
491
- cryptoKey: CryptoKey
491
+ cryptoKey: WebCrypto.CryptoKey
492
492
  ): Effect.Effect<Key<A>, CryptoError.CryptoError> =>
493
493
  algorithm.implementation !== undefined
494
494
  ? internal.invalidKey("Aead", "fromCryptoKey", `${algorithm.name} keys cannot be CryptoKeys`)