@moltzap/client 2026.511.1 → 2026.519.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 (218) hide show
  1. package/dist/auth.d.ts +12 -6
  2. package/dist/auth.d.ts.map +1 -1
  3. package/dist/auth.js +67 -29
  4. package/dist/auth.js.map +1 -1
  5. package/dist/auth.test.js +113 -76
  6. package/dist/auth.test.js.map +1 -1
  7. package/dist/channel-base/format-cross-conv.d.ts +43 -0
  8. package/dist/channel-base/format-cross-conv.d.ts.map +1 -0
  9. package/dist/channel-base/format-cross-conv.js +69 -0
  10. package/dist/channel-base/format-cross-conv.js.map +1 -0
  11. package/dist/channel-base/format-group-block.d.ts +43 -0
  12. package/dist/channel-base/format-group-block.d.ts.map +1 -0
  13. package/dist/channel-base/format-group-block.js +59 -0
  14. package/dist/channel-base/format-group-block.js.map +1 -0
  15. package/dist/channel-base/index.d.ts +21 -0
  16. package/dist/channel-base/index.d.ts.map +1 -0
  17. package/dist/channel-base/index.js +24 -0
  18. package/dist/channel-base/index.js.map +1 -0
  19. package/dist/channel-base/lease-guard.d.ts +30 -0
  20. package/dist/channel-base/lease-guard.d.ts.map +1 -0
  21. package/dist/channel-base/lease-guard.js +40 -0
  22. package/dist/channel-base/lease-guard.js.map +1 -0
  23. package/dist/channel-base/lease-store.d.ts +30 -0
  24. package/dist/channel-base/lease-store.d.ts.map +1 -0
  25. package/dist/channel-base/lease-store.js +58 -0
  26. package/dist/channel-base/lease-store.js.map +1 -0
  27. package/dist/channel-base/lease.d.ts +94 -0
  28. package/dist/channel-base/lease.d.ts.map +1 -0
  29. package/dist/channel-base/lease.js +110 -0
  30. package/dist/channel-base/lease.js.map +1 -0
  31. package/dist/channel-core-context.test.d.ts +2 -0
  32. package/dist/channel-core-context.test.d.ts.map +1 -0
  33. package/dist/channel-core-context.test.js +334 -0
  34. package/dist/channel-core-context.test.js.map +1 -0
  35. package/dist/channel-core-dispatch.test.d.ts +2 -0
  36. package/dist/channel-core-dispatch.test.d.ts.map +1 -0
  37. package/dist/channel-core-dispatch.test.js +550 -0
  38. package/dist/channel-core-dispatch.test.js.map +1 -0
  39. package/dist/channel-core-enrichment.d.ts +8 -0
  40. package/dist/channel-core-enrichment.d.ts.map +1 -0
  41. package/dist/channel-core-enrichment.js +134 -0
  42. package/dist/channel-core-enrichment.js.map +1 -0
  43. package/dist/channel-core-errors.d.ts +20 -0
  44. package/dist/channel-core-errors.d.ts.map +1 -0
  45. package/dist/channel-core-errors.js +12 -0
  46. package/dist/channel-core-errors.js.map +1 -0
  47. package/dist/channel-core-test-support.d.ts +48 -0
  48. package/dist/channel-core-test-support.d.ts.map +1 -0
  49. package/dist/channel-core-test-support.js +111 -0
  50. package/dist/channel-core-test-support.js.map +1 -0
  51. package/dist/channel-core.d.ts +30 -7
  52. package/dist/channel-core.d.ts.map +1 -1
  53. package/dist/channel-core.js +214 -237
  54. package/dist/channel-core.js.map +1 -1
  55. package/dist/channel-core.test.js +216 -1082
  56. package/dist/channel-core.test.js.map +1 -1
  57. package/dist/cli/__tests__/cli-multi-agent.int.test.js +129 -23
  58. package/dist/cli/__tests__/cli-multi-agent.int.test.js.map +1 -1
  59. package/dist/cli/commands/conversations-v2.test.js +64 -54
  60. package/dist/cli/commands/conversations-v2.test.js.map +1 -1
  61. package/dist/cli/commands/conversations.d.ts +5 -5
  62. package/dist/cli/commands/conversations.d.ts.map +1 -1
  63. package/dist/cli/commands/conversations.js +43 -36
  64. package/dist/cli/commands/conversations.js.map +1 -1
  65. package/dist/cli/commands/messages.d.ts +4 -4
  66. package/dist/cli/commands/messages.d.ts.map +1 -1
  67. package/dist/cli/commands/messages.js +4 -4
  68. package/dist/cli/commands/messages.js.map +1 -1
  69. package/dist/cli/commands/messages.test.js +61 -49
  70. package/dist/cli/commands/messages.test.js.map +1 -1
  71. package/dist/cli/commands/ping.d.ts +0 -9
  72. package/dist/cli/commands/ping.d.ts.map +1 -1
  73. package/dist/cli/commands/ping.js +1 -1
  74. package/dist/cli/commands/ping.js.map +1 -1
  75. package/dist/cli/commands/presence.d.ts +1 -1
  76. package/dist/cli/commands/presence.js +1 -1
  77. package/dist/cli/commands/register.d.ts +2 -2
  78. package/dist/cli/commands/register.d.ts.map +1 -1
  79. package/dist/cli/commands/register.js +98 -77
  80. package/dist/cli/commands/register.js.map +1 -1
  81. package/dist/cli/commands/register.test.js +63 -39
  82. package/dist/cli/commands/register.test.js.map +1 -1
  83. package/dist/cli/commands/send.d.ts +6 -6
  84. package/dist/cli/commands/send.js +6 -6
  85. package/dist/cli/commands/send.test.js +32 -28
  86. package/dist/cli/commands/send.test.js.map +1 -1
  87. package/dist/cli/commands/status.d.ts.map +1 -1
  88. package/dist/cli/commands/status.js +3 -4
  89. package/dist/cli/commands/status.js.map +1 -1
  90. package/dist/cli/config.d.ts +0 -2
  91. package/dist/cli/config.d.ts.map +1 -1
  92. package/dist/cli/config.js +86 -76
  93. package/dist/cli/config.js.map +1 -1
  94. package/dist/cli/config.test.js +80 -42
  95. package/dist/cli/config.test.js.map +1 -1
  96. package/dist/cli/index.d.ts +1 -16
  97. package/dist/cli/index.d.ts.map +1 -1
  98. package/dist/cli/index.js +50 -113
  99. package/dist/cli/index.js.map +1 -1
  100. package/dist/cli/process-argv.d.ts +2 -0
  101. package/dist/cli/process-argv.d.ts.map +1 -0
  102. package/dist/cli/process-argv.js +2 -0
  103. package/dist/cli/process-argv.js.map +1 -0
  104. package/dist/cli/profile.d.ts +3 -10
  105. package/dist/cli/profile.d.ts.map +1 -1
  106. package/dist/cli/profile.js +105 -104
  107. package/dist/cli/profile.js.map +1 -1
  108. package/dist/cli/profile.test.js +338 -232
  109. package/dist/cli/profile.test.js.map +1 -1
  110. package/dist/cli/runtime.d.ts +5 -17
  111. package/dist/cli/runtime.d.ts.map +1 -1
  112. package/dist/cli/runtime.js +6 -59
  113. package/dist/cli/runtime.js.map +1 -1
  114. package/dist/cli/socket-client.d.ts +10 -15
  115. package/dist/cli/socket-client.d.ts.map +1 -1
  116. package/dist/cli/socket-client.js +74 -89
  117. package/dist/cli/socket-client.js.map +1 -1
  118. package/dist/cli/transport.d.ts +9 -5
  119. package/dist/cli/transport.d.ts.map +1 -1
  120. package/dist/cli/transport.js +45 -42
  121. package/dist/cli/transport.js.map +1 -1
  122. package/dist/cli/transport.test.js +227 -133
  123. package/dist/cli/transport.test.js.map +1 -1
  124. package/dist/index.d.ts +4 -1
  125. package/dist/index.d.ts.map +1 -1
  126. package/dist/index.js +3 -0
  127. package/dist/index.js.map +1 -1
  128. package/dist/local-daemon-rpc.d.ts +13 -0
  129. package/dist/local-daemon-rpc.d.ts.map +1 -0
  130. package/dist/local-daemon-rpc.js +17 -0
  131. package/dist/local-daemon-rpc.js.map +1 -0
  132. package/dist/local-paths.d.ts +7 -0
  133. package/dist/local-paths.d.ts.map +1 -0
  134. package/dist/local-paths.js +20 -0
  135. package/dist/local-paths.js.map +1 -0
  136. package/dist/runtime/close-info.d.ts +3 -12
  137. package/dist/runtime/close-info.d.ts.map +1 -1
  138. package/dist/runtime/close-info.js +28 -39
  139. package/dist/runtime/close-info.js.map +1 -1
  140. package/dist/runtime/close-info.test.js +77 -70
  141. package/dist/runtime/close-info.test.js.map +1 -1
  142. package/dist/runtime/errors.test.js +42 -28
  143. package/dist/runtime/errors.test.js.map +1 -1
  144. package/dist/runtime/frame.d.ts +4 -31
  145. package/dist/runtime/frame.d.ts.map +1 -1
  146. package/dist/runtime/frame.js +7 -116
  147. package/dist/runtime/frame.js.map +1 -1
  148. package/dist/runtime/frame.test.js +41 -54
  149. package/dist/runtime/frame.test.js.map +1 -1
  150. package/dist/runtime/index.d.ts +7 -0
  151. package/dist/runtime/index.d.ts.map +1 -1
  152. package/dist/runtime/index.js +7 -11
  153. package/dist/runtime/index.js.map +1 -1
  154. package/dist/runtime/local-history.d.ts +67 -0
  155. package/dist/runtime/local-history.d.ts.map +1 -0
  156. package/dist/runtime/local-history.js +77 -0
  157. package/dist/runtime/local-history.js.map +1 -0
  158. package/dist/runtime/local-service-commands.d.ts +19 -0
  159. package/dist/runtime/local-service-commands.d.ts.map +1 -1
  160. package/dist/runtime/local-service-commands.js +23 -0
  161. package/dist/runtime/local-service-commands.js.map +1 -1
  162. package/dist/runtime/local-socket-server.d.ts +20 -0
  163. package/dist/runtime/local-socket-server.d.ts.map +1 -0
  164. package/dist/runtime/local-socket-server.js +94 -0
  165. package/dist/runtime/local-socket-server.js.map +1 -0
  166. package/dist/runtime/refs.d.ts +1 -1
  167. package/dist/runtime/refs.js +1 -1
  168. package/dist/runtime/runtime.test.js +29 -21
  169. package/dist/runtime/runtime.test.js.map +1 -1
  170. package/dist/runtime/service-helpers.d.ts +32 -0
  171. package/dist/runtime/service-helpers.d.ts.map +1 -0
  172. package/dist/runtime/service-helpers.js +85 -0
  173. package/dist/runtime/service-helpers.js.map +1 -0
  174. package/dist/runtime/subscribers.d.ts +13 -17
  175. package/dist/runtime/subscribers.d.ts.map +1 -1
  176. package/dist/runtime/subscribers.js +37 -32
  177. package/dist/runtime/subscribers.js.map +1 -1
  178. package/dist/runtime/subscribers.test.js +139 -131
  179. package/dist/runtime/subscribers.test.js.map +1 -1
  180. package/dist/service-socket-path.test.d.ts +2 -0
  181. package/dist/service-socket-path.test.d.ts.map +1 -0
  182. package/dist/service-socket-path.test.js +87 -0
  183. package/dist/service-socket-path.test.js.map +1 -0
  184. package/dist/service.d.ts +23 -20
  185. package/dist/service.d.ts.map +1 -1
  186. package/dist/service.js +192 -340
  187. package/dist/service.js.map +1 -1
  188. package/dist/service.test.js +652 -653
  189. package/dist/service.test.js.map +1 -1
  190. package/dist/test/index.d.ts +30 -14
  191. package/dist/test/index.d.ts.map +1 -1
  192. package/dist/test/index.js +25 -21
  193. package/dist/test/index.js.map +1 -1
  194. package/dist/test-utils/channel-service-fixture.d.ts +1 -0
  195. package/dist/test-utils/channel-service-fixture.d.ts.map +1 -1
  196. package/dist/test-utils/channel-service-fixture.js +139 -98
  197. package/dist/test-utils/channel-service-fixture.js.map +1 -1
  198. package/dist/test-utils/conformance-adapter.d.ts.map +1 -1
  199. package/dist/test-utils/conformance-adapter.js +145 -124
  200. package/dist/test-utils/conformance-adapter.js.map +1 -1
  201. package/dist/test-utils/fake-service.d.ts.map +1 -1
  202. package/dist/test-utils/fake-service.js +3 -3
  203. package/dist/test-utils/fake-service.js.map +1 -1
  204. package/dist/test-utils/index.d.ts +25 -0
  205. package/dist/test-utils/index.d.ts.map +1 -1
  206. package/dist/test-utils/index.js +25 -8
  207. package/dist/test-utils/index.js.map +1 -1
  208. package/dist/ws-client-test-support.d.ts +180 -0
  209. package/dist/ws-client-test-support.d.ts.map +1 -0
  210. package/dist/ws-client-test-support.js +399 -0
  211. package/dist/ws-client-test-support.js.map +1 -0
  212. package/dist/ws-client.d.ts +59 -32
  213. package/dist/ws-client.d.ts.map +1 -1
  214. package/dist/ws-client.js +272 -321
  215. package/dist/ws-client.js.map +1 -1
  216. package/dist/ws-client.test.js +388 -800
  217. package/dist/ws-client.test.js.map +1 -1
  218. package/package.json +18 -10
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Channel-base group-block helpers.
3
+ *
4
+ * Replaces nanoclaw's inline `formatGroupBlock` at
5
+ * `packages/nanoclaw-channel/src/channels/moltzap.ts → formatGroupBlock`. Also
6
+ * extracts the shared type-narrowing predicate (`getGroupFields`) consumed by
7
+ * openclaw (deriving `groupSubject` / `groupMembers` for the OpenClaw context)
8
+ * and nanoclaw (gating the inline group-block render).
9
+ */
10
+ import { sanitizeForSystemReminder } from "../service.js";
11
+ const UNNAMED_GROUP_FALLBACK = "(unnamed)";
12
+ const NO_PARTICIPANTS_FALLBACK = "(none listed)";
13
+ /**
14
+ * Returns `{ name, participants }` when `meta?.type === "group"`; else `null`.
15
+ * Pure narrowing helper — callers use the non-null return to drive
16
+ * `formatGroupBlock` or per-channel field extraction (openclaw's
17
+ * `groupSubject` + `groupMembers`).
18
+ */
19
+ export function getGroupFields(meta) {
20
+ if (meta?.type !== "group")
21
+ return null;
22
+ return { name: meta.name, participants: meta.participants };
23
+ }
24
+ function formatXmlSystemReminder(fields) {
25
+ const safeName = sanitizeForSystemReminder(fields.name ?? UNNAMED_GROUP_FALLBACK);
26
+ const safeParticipants = fields.participants.map(sanitizeForSystemReminder);
27
+ return [
28
+ "<system-reminder>",
29
+ "This is a group conversation.",
30
+ `Group name: ${safeName}`,
31
+ `Participants (${fields.participants.length}): ${safeParticipants.join(", ") || NO_PARTICIPANTS_FALLBACK}`,
32
+ "</system-reminder>",
33
+ ].join("\n");
34
+ }
35
+ /**
36
+ * Returns the formatted group block.
37
+ *
38
+ * Markup variants:
39
+ * - `"xml-system-reminder"`: byte-identical to the pre-refactor nanoclaw
40
+ * output (a `&lt;system-reminder&gt;` block with group name + participants).
41
+ * - `"json-header"`: empty string. Openclaw does not render a group block;
42
+ * it consumes `getGroupFields` directly to derive its OpenClaw-side
43
+ * `groupSubject` / `groupMembers` fields. The empty-string return locks
44
+ * the "openclaw renders no group block" behavior as an explicit,
45
+ * fixtured output (see arch sub-issue #605 §3.5).
46
+ *
47
+ * Or pass a custom `formatter` callback.
48
+ */
49
+ export function formatGroupBlock(fields, opts) {
50
+ if ("formatter" in opts)
51
+ return opts.formatter(fields);
52
+ switch (opts.markup) {
53
+ case "xml-system-reminder":
54
+ return formatXmlSystemReminder(fields);
55
+ case "json-header":
56
+ return "";
57
+ }
58
+ }
59
+ //# sourceMappingURL=format-group-block.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"format-group-block.js","sourceRoot":"","sources":["../../src/channel-base/format-group-block.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,yBAAyB,EAAE,MAAM,eAAe,CAAC;AAW1D,MAAM,sBAAsB,GAAG,WAAW,CAAC;AAC3C,MAAM,wBAAwB,GAAG,eAAe,CAAC;AAEjD;;;;;GAKG;AACH,MAAM,UAAU,cAAc,CAC5B,IAA0C;IAE1C,IAAI,IAAI,EAAE,IAAI,KAAK,OAAO;QAAE,OAAO,IAAI,CAAC;IACxC,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,YAAY,EAAE,IAAI,CAAC,YAAY,EAAE,CAAC;AAC9D,CAAC;AAED,SAAS,uBAAuB,CAAC,MAAmB;IAClD,MAAM,QAAQ,GAAG,yBAAyB,CACxC,MAAM,CAAC,IAAI,IAAI,sBAAsB,CACtC,CAAC;IACF,MAAM,gBAAgB,GAAG,MAAM,CAAC,YAAY,CAAC,GAAG,CAAC,yBAAyB,CAAC,CAAC;IAC5E,OAAO;QACL,mBAAmB;QACnB,+BAA+B;QAC/B,eAAe,QAAQ,EAAE;QACzB,iBAAiB,MAAM,CAAC,YAAY,CAAC,MAAM,MAAM,gBAAgB,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,wBAAwB,EAAE;QAC1G,oBAAoB;KACrB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACf,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,gBAAgB,CAC9B,MAAmB,EACnB,IAE0C;IAE1C,IAAI,WAAW,IAAI,IAAI;QAAE,OAAO,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;IACvD,QAAQ,IAAI,CAAC,MAAM,EAAE,CAAC;QACpB,KAAK,qBAAqB;YACxB,OAAO,uBAAuB,CAAC,MAAM,CAAC,CAAC;QACzC,KAAK,aAAa;YAChB,OAAO,EAAE,CAAC;IACd,CAAC;AACH,CAAC"}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * @file `@moltzap/client/channel-base` — shared scaffolding for channel adapters.
3
+ *
4
+ * Used by `@moltzap/openclaw-channel`, `@moltzap/claude-code-channel`, and
5
+ * `@moltzap/nanoclaw-channel` to canonicalize the lease-lifecycle primitives,
6
+ * the `LeaseAlreadyConsumed` tagged error, and the markup-parameterized
7
+ * cross-conv + group-block formatters. Detail doc:
8
+ * `packages/client/docs/architecture/08-channel-base.md`. Spec: #597.
9
+ * Architect plan: #605.
10
+ *
11
+ * Note: this subpath is opt-in. Direct `@moltzap/client` consumers
12
+ * (server-core, runtimes, test-utils) see no API-surface change.
13
+ */
14
+ export { LeaseAlreadyConsumed, projectLeaseInvalid, catchLeaseInvalid, type LeaseInvalidProjectionError, } from "./lease.js";
15
+ export { LeaseStore } from "./lease-store.js";
16
+ export { LeaseGuard } from "./lease-guard.js";
17
+ export { formatCrossConv, type CrossConvFormatter, type CrossConvMarkup, } from "./format-cross-conv.js";
18
+ export { formatGroupBlock, getGroupFields, type GroupFields, type GroupFormatter, } from "./format-group-block.js";
19
+ export { sanitizeForSystemReminder, type CrossConvMessage, } from "../service.js";
20
+ export { type EnrichedConversationMeta } from "../channel-core.js";
21
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/channel-base/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EACL,oBAAoB,EACpB,mBAAmB,EACnB,iBAAiB,EACjB,KAAK,2BAA2B,GACjC,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAE9C,OAAO,EACL,eAAe,EACf,KAAK,kBAAkB,EACvB,KAAK,eAAe,GACrB,MAAM,wBAAwB,CAAC;AAEhC,OAAO,EACL,gBAAgB,EAChB,cAAc,EACd,KAAK,WAAW,EAChB,KAAK,cAAc,GACpB,MAAM,yBAAyB,CAAC;AAKjC,OAAO,EACL,yBAAyB,EACzB,KAAK,gBAAgB,GACtB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,KAAK,wBAAwB,EAAE,MAAM,oBAAoB,CAAC"}
@@ -0,0 +1,24 @@
1
+ /**
2
+ * @file `@moltzap/client/channel-base` — shared scaffolding for channel adapters.
3
+ *
4
+ * Used by `@moltzap/openclaw-channel`, `@moltzap/claude-code-channel`, and
5
+ * `@moltzap/nanoclaw-channel` to canonicalize the lease-lifecycle primitives,
6
+ * the `LeaseAlreadyConsumed` tagged error, and the markup-parameterized
7
+ * cross-conv + group-block formatters. Detail doc:
8
+ * `packages/client/docs/architecture/08-channel-base.md`. Spec: #597.
9
+ * Architect plan: #605.
10
+ *
11
+ * Note: this subpath is opt-in. Direct `@moltzap/client` consumers
12
+ * (server-core, runtimes, test-utils) see no API-surface change.
13
+ */
14
+ export { LeaseAlreadyConsumed, projectLeaseInvalid, catchLeaseInvalid, } from "./lease.js";
15
+ export { LeaseStore } from "./lease-store.js";
16
+ export { LeaseGuard } from "./lease-guard.js";
17
+ export { formatCrossConv, } from "./format-cross-conv.js";
18
+ export { formatGroupBlock, getGroupFields, } from "./format-group-block.js";
19
+ // Re-exports for ergonomics on the subpath (these are part of the public
20
+ // `@moltzap/client` barrel already; channel-base callers shouldn't need to
21
+ // import from two paths to use the formatters).
22
+ export { sanitizeForSystemReminder, } from "../service.js";
23
+ export {} from "../channel-core.js";
24
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/channel-base/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EACL,oBAAoB,EACpB,mBAAmB,EACnB,iBAAiB,GAElB,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAE9C,OAAO,EACL,eAAe,GAGhB,MAAM,wBAAwB,CAAC;AAEhC,OAAO,EACL,gBAAgB,EAChB,cAAc,GAGf,MAAM,yBAAyB,CAAC;AAEjC,yEAAyE;AACzE,2EAA2E;AAC3E,gDAAgD;AAChD,OAAO,EACL,yBAAyB,GAE1B,MAAM,eAAe,CAAC;AACvB,OAAO,EAAiC,MAAM,oBAAoB,CAAC"}
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Channel-base `LeaseGuard` — per-dispatch single-shot client-side dup-reply
3
+ * detection.
4
+ *
5
+ * Replaces openclaw's `consumedLeaseAt: number | null` closure in
6
+ * `packages/openclaw-channel/src/openclaw-entry.ts → createLeaseConsumingDeliver`.
7
+ * One `LeaseGuard` instance per inbound message (created inside the deliver
8
+ * wrapper); `consume()` returns true exactly once, false on every subsequent
9
+ * call.
10
+ *
11
+ * Backed by a private `number | null` field that records
12
+ * `Clock.currentTimeMillis` on the first consume.
13
+ */
14
+ import { Effect, Option } from "effect";
15
+ export declare class LeaseGuard {
16
+ #private;
17
+ /**
18
+ * Returns `true` on first call (transitions internal state from
19
+ * "not-consumed" to "consumed-at-now"); returns `false` on every later
20
+ * call. Reads `Clock.currentTimeMillis` inside the Effect on first call.
21
+ */
22
+ consume(): Effect.Effect<boolean, never, never>;
23
+ /**
24
+ * `Option.none` before the first `consume`; `Option.some(epochMs)` after,
25
+ * where `epochMs` is the value Clock returned at the first-consume moment.
26
+ * Idempotent on second-and-later reads.
27
+ */
28
+ get consumedAt(): Effect.Effect<Option.Option<number>, never, never>;
29
+ }
30
+ //# sourceMappingURL=lease-guard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lease-guard.d.ts","sourceRoot":"","sources":["../../src/channel-base/lease-guard.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAS,MAAM,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAE/C,qBAAa,UAAU;;IAGrB;;;;OAIG;IACH,OAAO,IAAI,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,KAAK,EAAE,KAAK,CAAC;IAS/C;;;;OAIG;IACH,IAAI,UAAU,IAAI,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,CAInE;CACF"}
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Channel-base `LeaseGuard` — per-dispatch single-shot client-side dup-reply
3
+ * detection.
4
+ *
5
+ * Replaces openclaw's `consumedLeaseAt: number | null` closure in
6
+ * `packages/openclaw-channel/src/openclaw-entry.ts → createLeaseConsumingDeliver`.
7
+ * One `LeaseGuard` instance per inbound message (created inside the deliver
8
+ * wrapper); `consume()` returns true exactly once, false on every subsequent
9
+ * call.
10
+ *
11
+ * Backed by a private `number | null` field that records
12
+ * `Clock.currentTimeMillis` on the first consume.
13
+ */
14
+ import { Clock, Effect, Option } from "effect";
15
+ export class LeaseGuard {
16
+ #consumedAt = null;
17
+ /**
18
+ * Returns `true` on first call (transitions internal state from
19
+ * "not-consumed" to "consumed-at-now"); returns `false` on every later
20
+ * call. Reads `Clock.currentTimeMillis` inside the Effect on first call.
21
+ */
22
+ consume() {
23
+ return Effect.gen(this, function* () {
24
+ if (this.#consumedAt !== null)
25
+ return false;
26
+ const ts = yield* Clock.currentTimeMillis;
27
+ this.#consumedAt = ts;
28
+ return true;
29
+ });
30
+ }
31
+ /**
32
+ * `Option.none` before the first `consume`; `Option.some(epochMs)` after,
33
+ * where `epochMs` is the value Clock returned at the first-consume moment.
34
+ * Idempotent on second-and-later reads.
35
+ */
36
+ get consumedAt() {
37
+ return Effect.sync(() => this.#consumedAt === null ? Option.none() : Option.some(this.#consumedAt));
38
+ }
39
+ }
40
+ //# sourceMappingURL=lease-guard.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lease-guard.js","sourceRoot":"","sources":["../../src/channel-base/lease-guard.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAE/C,MAAM,OAAO,UAAU;IACrB,WAAW,GAAkB,IAAI,CAAC;IAElC;;;;OAIG;IACH,OAAO;QACL,OAAO,MAAM,CAAC,GAAG,CAAC,IAAI,EAAE,QAAQ,CAAC;YAC/B,IAAI,IAAI,CAAC,WAAW,KAAK,IAAI;gBAAE,OAAO,KAAK,CAAC;YAC5C,MAAM,EAAE,GAAG,KAAK,CAAC,CAAC,KAAK,CAAC,iBAAiB,CAAC;YAC1C,IAAI,CAAC,WAAW,GAAG,EAAE,CAAC;YACtB,OAAO,IAAI,CAAC;QACd,CAAC,CAAC,CAAC;IACL,CAAC;IAED;;;;OAIG;IACH,IAAI,UAAU;QACZ,OAAO,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,CACtB,IAAI,CAAC,WAAW,KAAK,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,CAC1E,CAAC;IACJ,CAAC;CACF"}
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Channel-base `LeaseStore` — generic per-key payload tracker.
3
+ *
4
+ * Nanoclaw uses a string-keyed, string-valued store (keyed by JID, payload
5
+ * is the dispatchLeaseId string). The deliberate "keep stale entry to trigger
6
+ * server CONSUMED rejection on retry" semantic is preserved via `peek`
7
+ * (read-only) — see arch sub-issue #605 §3.3 and §6.3.
8
+ *
9
+ * The class wraps a private `Map`; Effect wrappers are for caller-side
10
+ * composition. Single-threaded by construction — one instance lives per
11
+ * channel runtime, accessed from the host event loop.
12
+ */
13
+ import { Effect, Option } from "effect";
14
+ export declare class LeaseStore<HostKey, T> {
15
+ #private;
16
+ /** Overwrite-on-newer-inbound. Last-write-wins per spec table. */
17
+ remember(key: HostKey, payload: T): Effect.Effect<void, never, never>;
18
+ /**
19
+ * Read-only lookup. Does NOT delete. Nanoclaw uses this for the
20
+ * stale-entry-on-retry semantic at `MoltZapChannel.sendMessage`.
21
+ */
22
+ peek(key: HostKey): Effect.Effect<Option.Option<T>, never, never>;
23
+ /** Read-and-delete. `Option.none` if absent (no throw). */
24
+ consume(key: HostKey): Effect.Effect<Option.Option<T>, never, never>;
25
+ /** Clears one key (when provided) or the whole store. */
26
+ clear(key?: HostKey): Effect.Effect<void, never, never>;
27
+ /** Entry count. */
28
+ get size(): Effect.Effect<number, never, never>;
29
+ }
30
+ //# sourceMappingURL=lease-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lease-store.d.ts","sourceRoot":"","sources":["../../src/channel-base/lease-store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAExC,qBAAa,UAAU,CAAC,OAAO,EAAE,CAAC;;IAGhC,kEAAkE;IAClE,QAAQ,CAAC,GAAG,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,KAAK,EAAE,KAAK,CAAC;IAMrE;;;OAGG;IACH,IAAI,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC;IAOjE,2DAA2D;IAC3D,OAAO,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC;IASpE,yDAAyD;IACzD,KAAK,CAAC,GAAG,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,KAAK,EAAE,KAAK,CAAC;IAUvD,mBAAmB;IACnB,IAAI,IAAI,IAAI,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,KAAK,EAAE,KAAK,CAAC,CAE9C;CACF"}
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Channel-base `LeaseStore` — generic per-key payload tracker.
3
+ *
4
+ * Nanoclaw uses a string-keyed, string-valued store (keyed by JID, payload
5
+ * is the dispatchLeaseId string). The deliberate "keep stale entry to trigger
6
+ * server CONSUMED rejection on retry" semantic is preserved via `peek`
7
+ * (read-only) — see arch sub-issue #605 §3.3 and §6.3.
8
+ *
9
+ * The class wraps a private `Map`; Effect wrappers are for caller-side
10
+ * composition. Single-threaded by construction — one instance lives per
11
+ * channel runtime, accessed from the host event loop.
12
+ */
13
+ import { Effect, Option } from "effect";
14
+ export class LeaseStore {
15
+ #entries = new Map();
16
+ /** Overwrite-on-newer-inbound. Last-write-wins per spec table. */
17
+ remember(key, payload) {
18
+ return Effect.sync(() => {
19
+ this.#entries.set(key, payload);
20
+ });
21
+ }
22
+ /**
23
+ * Read-only lookup. Does NOT delete. Nanoclaw uses this for the
24
+ * stale-entry-on-retry semantic at `MoltZapChannel.sendMessage`.
25
+ */
26
+ peek(key) {
27
+ return Effect.sync(() => {
28
+ const existing = this.#entries.get(key);
29
+ return existing === undefined ? Option.none() : Option.some(existing);
30
+ });
31
+ }
32
+ /** Read-and-delete. `Option.none` if absent (no throw). */
33
+ consume(key) {
34
+ return Effect.sync(() => {
35
+ const existing = this.#entries.get(key);
36
+ if (existing === undefined)
37
+ return Option.none();
38
+ this.#entries.delete(key);
39
+ return Option.some(existing);
40
+ });
41
+ }
42
+ /** Clears one key (when provided) or the whole store. */
43
+ clear(key) {
44
+ return Effect.sync(() => {
45
+ if (key === undefined) {
46
+ this.#entries.clear();
47
+ }
48
+ else {
49
+ this.#entries.delete(key);
50
+ }
51
+ });
52
+ }
53
+ /** Entry count. */
54
+ get size() {
55
+ return Effect.sync(() => this.#entries.size);
56
+ }
57
+ }
58
+ //# sourceMappingURL=lease-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lease-store.js","sourceRoot":"","sources":["../../src/channel-base/lease-store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAExC,MAAM,OAAO,UAAU;IACZ,QAAQ,GAAG,IAAI,GAAG,EAAc,CAAC;IAE1C,kEAAkE;IAClE,QAAQ,CAAC,GAAY,EAAE,OAAU;QAC/B,OAAO,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE;YACtB,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;QAClC,CAAC,CAAC,CAAC;IACL,CAAC;IAED;;;OAGG;IACH,IAAI,CAAC,GAAY;QACf,OAAO,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE;YACtB,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YACxC,OAAO,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACxE,CAAC,CAAC,CAAC;IACL,CAAC;IAED,2DAA2D;IAC3D,OAAO,CAAC,GAAY;QAClB,OAAO,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE;YACtB,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YACxC,IAAI,QAAQ,KAAK,SAAS;gBAAE,OAAO,MAAM,CAAC,IAAI,EAAE,CAAC;YACjD,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YAC1B,OAAO,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QAC/B,CAAC,CAAC,CAAC;IACL,CAAC;IAED,yDAAyD;IACzD,KAAK,CAAC,GAAa;QACjB,OAAO,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE;YACtB,IAAI,GAAG,KAAK,SAAS,EAAE,CAAC;gBACtB,IAAI,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAC;YACxB,CAAC;iBAAM,CAAC;gBACN,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YAC5B,CAAC;QACH,CAAC,CAAC,CAAC;IACL,CAAC;IAED,mBAAmB;IACnB,IAAI,IAAI;QACN,OAAO,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;IAC/C,CAAC;CACF"}
@@ -0,0 +1,94 @@
1
+ /**
2
+ * Channel-base lease primitives.
3
+ *
4
+ * Public surface for spec C (#597):
5
+ * - `LeaseAlreadyConsumed`: canonical tagged error class. One definition site
6
+ * across all three channels (claude-code, openclaw, nanoclaw).
7
+ * - `projectLeaseInvalid`: predicate that narrows a `RpcServerError` to
8
+ * `LeaseAlreadyConsumed` when the server's wire-error payload matches the
9
+ * single-use-lease shape.
10
+ * - `catchLeaseInvalid`: Effect-pipe convenience that runs the projection
11
+ * inside `Effect.catchTag("RpcServerError", ...)` with
12
+ * `Clock.currentTimeMillis` threaded into the projector as `consumedAt`.
13
+ */
14
+ import { Effect } from "effect";
15
+ import { RpcServerError } from "@moltzap/protocol";
16
+ declare const LeaseAlreadyConsumed_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
17
+ readonly _tag: "LeaseAlreadyConsumed";
18
+ } & Readonly<A>;
19
+ /**
20
+ * The dispatch lease was already consumed (server returned the typed
21
+ * single-use-lease failure on a second `messages/send` for the same lease).
22
+ *
23
+ * Construction is **only** via `projectLeaseInvalid` (or `catchLeaseInvalid`).
24
+ * The original `RpcServerError` is preserved on `cause` so hosts can inspect
25
+ * the wire payload without re-fetching.
26
+ *
27
+ * See arch sub-issue #605 §3.1 for the shape rationale (cause field is the
28
+ * verbatim wire error; consumedAt is `Clock.currentTimeMillis` at projection
29
+ * time; message is derived from cause.message).
30
+ */
31
+ export declare class LeaseAlreadyConsumed extends LeaseAlreadyConsumed_base<{
32
+ readonly leaseId: string;
33
+ readonly consumedAt: number;
34
+ readonly cause: RpcServerError;
35
+ readonly message: string;
36
+ }> {
37
+ }
38
+ /**
39
+ * Named alias for the error channel produced by `catchLeaseInvalid` over an
40
+ * effect with residual error `E`. Equivalent to
41
+ * `LeaseAlreadyConsumed | RpcServerError | E`, but referencable from external
42
+ * consumers (per arch sub-issue #605 §5.1 named-error-union commitment).
43
+ */
44
+ export type LeaseInvalidProjectionError<E> = LeaseAlreadyConsumed | RpcServerError | E;
45
+ /**
46
+ * Project an `RpcServerError` to `LeaseAlreadyConsumed` if it matches the
47
+ * lease-invalid wire shape; otherwise return the original error unchanged.
48
+ *
49
+ * Predicate (architect-corrected per arch sub-issue #605 §3.2):
50
+ * `err.data.reason === "LeaseInvalid"` OR
51
+ * `err.data._tag === "LeaseAlreadyConsumed"` (forward-compat for a future
52
+ * server that emits the canonical tag in data).
53
+ *
54
+ * The wire code (-32001 / generic Forbidden) is intentionally NOT part of
55
+ * the predicate because the code is too generic to discriminate on alone.
56
+ *
57
+ * `ctx.leaseId` (optional) is the lease the caller just sent. Caller-supplied
58
+ * because the server's `ForbiddenError.data` shape does NOT carry leaseId.
59
+ * Falls back to `"(unknown)"` when omitted (matches the pre-refactor
60
+ * claude-code behavior).
61
+ *
62
+ * `ctx.consumedAt` (required) is the epoch ms to stamp on the resulting
63
+ * `LeaseAlreadyConsumed.consumedAt`. Required because `LeaseAlreadyConsumed`
64
+ * requires it and this function is synchronous (no Clock access). Callers
65
+ * either pass `Date.now()` directly or use `catchLeaseInvalid` which reads
66
+ * `Clock.currentTimeMillis` inside the Effect.
67
+ */
68
+ export declare function projectLeaseInvalid(err: RpcServerError, ctx: {
69
+ readonly leaseId?: string;
70
+ readonly consumedAt: number;
71
+ }): LeaseAlreadyConsumed | RpcServerError;
72
+ /**
73
+ * Effect-pipe convenience: catches `RpcServerError` and runs
74
+ * `projectLeaseInvalid` on each instance. Reads `Clock.currentTimeMillis`
75
+ * inside the catch and passes the result to `projectLeaseInvalid` as
76
+ * `consumedAt`. Matching errors are surfaced as the typed
77
+ * `LeaseAlreadyConsumed` on the failure channel; non-matching errors are
78
+ * re-raised unchanged so downstream `mapError`s see the original
79
+ * `RpcServerError`.
80
+ *
81
+ * Use at every channel's outbound `core.sendReply(...)` boundary:
82
+ *
83
+ * ```ts
84
+ * core.sendReply(conv, text, { dispatchLeaseId: leaseId }).pipe(
85
+ * catchLeaseInvalid({ leaseId }),
86
+ * // ... per-channel surfacing
87
+ * )
88
+ * ```
89
+ */
90
+ export declare function catchLeaseInvalid<A, E2, R>(ctx?: {
91
+ readonly leaseId?: string;
92
+ }): (eff: Effect.Effect<A, RpcServerError | E2, R>) => Effect.Effect<A, LeaseInvalidProjectionError<E2>, R>;
93
+ export {};
94
+ //# sourceMappingURL=lease.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lease.d.ts","sourceRoot":"","sources":["../../src/channel-base/lease.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAe,MAAM,EAAE,MAAM,QAAQ,CAAC;AAC7C,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;;;;AAEnD;;;;;;;;;;;GAWG;AACH,qBAAa,oBAAqB,SAAQ,0BAExC;IACA,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,KAAK,EAAE,cAAc,CAAC;IAC/B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B,CAAC;CAAG;AAEL;;;;;GAKG;AACH,MAAM,MAAM,2BAA2B,CAAC,CAAC,IACrC,oBAAoB,GACpB,cAAc,GACd,CAAC,CAAC;AAcN;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,mBAAmB,CACjC,GAAG,EAAE,cAAc,EACnB,GAAG,EAAE;IAAE,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;CAAE,GAC9D,oBAAoB,GAAG,cAAc,CAQvC;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,GAAG,CAAC,EAAE;IAChD,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;CAC3B,GAAG,CACF,GAAG,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,cAAc,GAAG,EAAE,EAAE,CAAC,CAAC,KAC1C,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,2BAA2B,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAyBxD"}
@@ -0,0 +1,110 @@
1
+ /**
2
+ * Channel-base lease primitives.
3
+ *
4
+ * Public surface for spec C (#597):
5
+ * - `LeaseAlreadyConsumed`: canonical tagged error class. One definition site
6
+ * across all three channels (claude-code, openclaw, nanoclaw).
7
+ * - `projectLeaseInvalid`: predicate that narrows a `RpcServerError` to
8
+ * `LeaseAlreadyConsumed` when the server's wire-error payload matches the
9
+ * single-use-lease shape.
10
+ * - `catchLeaseInvalid`: Effect-pipe convenience that runs the projection
11
+ * inside `Effect.catchTag("RpcServerError", ...)` with
12
+ * `Clock.currentTimeMillis` threaded into the projector as `consumedAt`.
13
+ */
14
+ import { Clock, Data, Effect } from "effect";
15
+ import { RpcServerError } from "@moltzap/protocol";
16
+ /**
17
+ * The dispatch lease was already consumed (server returned the typed
18
+ * single-use-lease failure on a second `messages/send` for the same lease).
19
+ *
20
+ * Construction is **only** via `projectLeaseInvalid` (or `catchLeaseInvalid`).
21
+ * The original `RpcServerError` is preserved on `cause` so hosts can inspect
22
+ * the wire payload without re-fetching.
23
+ *
24
+ * See arch sub-issue #605 §3.1 for the shape rationale (cause field is the
25
+ * verbatim wire error; consumedAt is `Clock.currentTimeMillis` at projection
26
+ * time; message is derived from cause.message).
27
+ */
28
+ export class LeaseAlreadyConsumed extends Data.TaggedError("LeaseAlreadyConsumed") {
29
+ }
30
+ const LEASE_ID_FALLBACK = "(unknown)";
31
+ function isLeaseInvalidData(data) {
32
+ if (typeof data !== "object" || data === null)
33
+ return false;
34
+ const reason = data.reason;
35
+ const tag = data._tag;
36
+ // Today's wire shape: `ForbiddenError.data.reason === "LeaseInvalid"`
37
+ // (arch sub-issue #605 §3.2). The `_tag` arm is forward-compat for a
38
+ // future server that emits the canonical tag in `data` directly.
39
+ return reason === "LeaseInvalid" || tag === "LeaseAlreadyConsumed";
40
+ }
41
+ /**
42
+ * Project an `RpcServerError` to `LeaseAlreadyConsumed` if it matches the
43
+ * lease-invalid wire shape; otherwise return the original error unchanged.
44
+ *
45
+ * Predicate (architect-corrected per arch sub-issue #605 §3.2):
46
+ * `err.data.reason === "LeaseInvalid"` OR
47
+ * `err.data._tag === "LeaseAlreadyConsumed"` (forward-compat for a future
48
+ * server that emits the canonical tag in data).
49
+ *
50
+ * The wire code (-32001 / generic Forbidden) is intentionally NOT part of
51
+ * the predicate because the code is too generic to discriminate on alone.
52
+ *
53
+ * `ctx.leaseId` (optional) is the lease the caller just sent. Caller-supplied
54
+ * because the server's `ForbiddenError.data` shape does NOT carry leaseId.
55
+ * Falls back to `"(unknown)"` when omitted (matches the pre-refactor
56
+ * claude-code behavior).
57
+ *
58
+ * `ctx.consumedAt` (required) is the epoch ms to stamp on the resulting
59
+ * `LeaseAlreadyConsumed.consumedAt`. Required because `LeaseAlreadyConsumed`
60
+ * requires it and this function is synchronous (no Clock access). Callers
61
+ * either pass `Date.now()` directly or use `catchLeaseInvalid` which reads
62
+ * `Clock.currentTimeMillis` inside the Effect.
63
+ */
64
+ export function projectLeaseInvalid(err, ctx) {
65
+ if (!isLeaseInvalidData(err.data))
66
+ return err;
67
+ return new LeaseAlreadyConsumed({
68
+ leaseId: ctx.leaseId ?? LEASE_ID_FALLBACK,
69
+ consumedAt: ctx.consumedAt,
70
+ cause: err,
71
+ message: err.message,
72
+ });
73
+ }
74
+ /**
75
+ * Effect-pipe convenience: catches `RpcServerError` and runs
76
+ * `projectLeaseInvalid` on each instance. Reads `Clock.currentTimeMillis`
77
+ * inside the catch and passes the result to `projectLeaseInvalid` as
78
+ * `consumedAt`. Matching errors are surfaced as the typed
79
+ * `LeaseAlreadyConsumed` on the failure channel; non-matching errors are
80
+ * re-raised unchanged so downstream `mapError`s see the original
81
+ * `RpcServerError`.
82
+ *
83
+ * Use at every channel's outbound `core.sendReply(...)` boundary:
84
+ *
85
+ * ```ts
86
+ * core.sendReply(conv, text, { dispatchLeaseId: leaseId }).pipe(
87
+ * catchLeaseInvalid({ leaseId }),
88
+ * // ... per-channel surfacing
89
+ * )
90
+ * ```
91
+ */
92
+ export function catchLeaseInvalid(ctx) {
93
+ // `Effect.catchAll` over the union (rather than `catchTag("RpcServerError",
94
+ // ...)`) keeps the typechecker happy when `E2` is unconstrained — without
95
+ // narrowing on the runtime instance, TS conservatively infers that `E2`
96
+ // could itself carry `_tag: "RpcServerError"`. The `instanceof` branch
97
+ // partitions cleanly.
98
+ return (eff) => Effect.catchAll(eff, (err) => {
99
+ if (err instanceof RpcServerError) {
100
+ return Effect.flatMap(Clock.currentTimeMillis, (consumedAt) => Effect.fail(projectLeaseInvalid(err, {
101
+ ...(ctx?.leaseId !== undefined ? { leaseId: ctx.leaseId } : {}),
102
+ consumedAt,
103
+ })));
104
+ }
105
+ // Non-RpcServerError residual (E2). Re-raise unchanged so downstream
106
+ // `mapError`s see it without coupling to channel-base.
107
+ return Effect.fail(err);
108
+ });
109
+ }
110
+ //# sourceMappingURL=lease.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lease.js","sourceRoot":"","sources":["../../src/channel-base/lease.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAC7C,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAEnD;;;;;;;;;;;GAWG;AACH,MAAM,OAAO,oBAAqB,SAAQ,IAAI,CAAC,WAAW,CACxD,sBAAsB,CAMtB;CAAG;AAaL,MAAM,iBAAiB,GAAG,WAAW,CAAC;AAEtC,SAAS,kBAAkB,CAAC,IAAa;IACvC,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,KAAK,IAAI;QAAE,OAAO,KAAK,CAAC;IAC5D,MAAM,MAAM,GAAI,IAAsC,CAAC,MAAM,CAAC;IAC9D,MAAM,GAAG,GAAI,IAAoC,CAAC,IAAI,CAAC;IACvD,sEAAsE;IACtE,qEAAqE;IACrE,iEAAiE;IACjE,OAAO,MAAM,KAAK,cAAc,IAAI,GAAG,KAAK,sBAAsB,CAAC;AACrE,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,MAAM,UAAU,mBAAmB,CACjC,GAAmB,EACnB,GAA+D;IAE/D,IAAI,CAAC,kBAAkB,CAAC,GAAG,CAAC,IAAI,CAAC;QAAE,OAAO,GAAG,CAAC;IAC9C,OAAO,IAAI,oBAAoB,CAAC;QAC9B,OAAO,EAAE,GAAG,CAAC,OAAO,IAAI,iBAAiB;QACzC,UAAU,EAAE,GAAG,CAAC,UAAU;QAC1B,KAAK,EAAE,GAAG;QACV,OAAO,EAAE,GAAG,CAAC,OAAO;KACrB,CAAC,CAAC;AACL,CAAC;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,UAAU,iBAAiB,CAAW,GAE3C;IAGC,4EAA4E;IAC5E,0EAA0E;IAC1E,wEAAwE;IACxE,uEAAuE;IACvE,sBAAsB;IACtB,OAAO,CAAC,GAAG,EAAE,EAAE,CACb,MAAM,CAAC,QAAQ,CACb,GAAG,EACH,CAAC,GAAG,EAAwD,EAAE;QAC5D,IAAI,GAAG,YAAY,cAAc,EAAE,CAAC;YAClC,OAAO,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,iBAAiB,EAAE,CAAC,UAAU,EAAE,EAAE,CAC5D,MAAM,CAAC,IAAI,CACT,mBAAmB,CAAC,GAAG,EAAE;gBACvB,GAAG,CAAC,GAAG,EAAE,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBAC/D,UAAU;aACX,CAAC,CACH,CACF,CAAC;QACJ,CAAC;QACD,qEAAqE;QACrE,uDAAuD;QACvD,OAAO,MAAM,CAAC,IAAI,CAAC,GAAS,CAAC,CAAC;IAChC,CAAC,CACF,CAAC;AACN,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=channel-core-context.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"channel-core-context.test.d.ts","sourceRoot":"","sources":["../src/channel-core-context.test.ts"],"names":[],"mappings":""}