@openclaw/crabline 0.1.11 → 0.1.13

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 (214) hide show
  1. package/README.md +137 -23
  2. package/dist/src/bin/crabline.js +3 -1
  3. package/dist/src/bin/crabline.js.map +1 -1
  4. package/dist/src/cli/program.d.ts +5 -1
  5. package/dist/src/cli/program.js +281 -52
  6. package/dist/src/cli/program.js.map +1 -1
  7. package/dist/src/config/load.js +39 -3
  8. package/dist/src/config/load.js.map +1 -1
  9. package/dist/src/config/schema.d.ts +20 -0
  10. package/dist/src/config/schema.js +163 -35
  11. package/dist/src/config/schema.js.map +1 -1
  12. package/dist/src/core/errors.d.ts +1 -0
  13. package/dist/src/core/errors.js +6 -3
  14. package/dist/src/core/errors.js.map +1 -1
  15. package/dist/src/core/http-path.d.ts +1 -0
  16. package/dist/src/core/http-path.js +14 -0
  17. package/dist/src/core/http-path.js.map +1 -0
  18. package/dist/src/core/matcher.d.ts +3 -1
  19. package/dist/src/core/matcher.js +58 -2
  20. package/dist/src/core/matcher.js.map +1 -1
  21. package/dist/src/core/nonces.d.ts +2 -0
  22. package/dist/src/core/nonces.js +11 -1
  23. package/dist/src/core/nonces.js.map +1 -1
  24. package/dist/src/core/reporters.d.ts +5 -0
  25. package/dist/src/core/reporters.js +26 -2
  26. package/dist/src/core/reporters.js.map +1 -1
  27. package/dist/src/core/run.d.ts +3 -1
  28. package/dist/src/core/run.js +178 -66
  29. package/dist/src/core/run.js.map +1 -1
  30. package/dist/src/index.d.ts +12 -10
  31. package/dist/src/index.js +6 -5
  32. package/dist/src/index.js.map +1 -1
  33. package/dist/src/matrix-ids.d.ts +5 -0
  34. package/dist/src/matrix-ids.js +83 -0
  35. package/dist/src/matrix-ids.js.map +1 -0
  36. package/dist/src/openclaw/artifact-generation.d.ts +7 -6
  37. package/dist/src/openclaw/artifact-generation.js +385 -41
  38. package/dist/src/openclaw/artifact-generation.js.map +1 -1
  39. package/dist/src/openclaw/bridges/discord.d.ts +1 -0
  40. package/dist/src/openclaw/bridges/discord.js +149 -0
  41. package/dist/src/openclaw/bridges/discord.js.map +1 -0
  42. package/dist/src/openclaw/bridges/matrix.js +22 -5
  43. package/dist/src/openclaw/bridges/matrix.js.map +1 -1
  44. package/dist/src/openclaw/bridges/mattermost.js +14 -9
  45. package/dist/src/openclaw/bridges/mattermost.js.map +1 -1
  46. package/dist/src/openclaw/bridges/probe-response.d.ts +1 -0
  47. package/dist/src/openclaw/bridges/probe-response.js +5 -0
  48. package/dist/src/openclaw/bridges/probe-response.js.map +1 -0
  49. package/dist/src/openclaw/bridges/signal.js +16 -7
  50. package/dist/src/openclaw/bridges/signal.js.map +1 -1
  51. package/dist/src/openclaw/bridges/slack.js +117 -24
  52. package/dist/src/openclaw/bridges/slack.js.map +1 -1
  53. package/dist/src/openclaw/bridges/telegram.js +46 -20
  54. package/dist/src/openclaw/bridges/telegram.js.map +1 -1
  55. package/dist/src/openclaw/bridges/whatsapp.js +33 -17
  56. package/dist/src/openclaw/bridges/whatsapp.js.map +1 -1
  57. package/dist/src/openclaw/bridges/zalo.js +9 -4
  58. package/dist/src/openclaw/bridges/zalo.js.map +1 -1
  59. package/dist/src/openclaw/outbound-contract.js +3 -0
  60. package/dist/src/openclaw/outbound-contract.js.map +1 -1
  61. package/dist/src/openclaw/private-file.d.ts +38 -3
  62. package/dist/src/openclaw/private-file.js +2008 -87
  63. package/dist/src/openclaw/private-file.js.map +1 -1
  64. package/dist/src/openclaw/{smoke-lock.d.ts → provider-readiness-lock.d.ts} +14 -5
  65. package/dist/src/openclaw/provider-readiness-lock.js +1320 -0
  66. package/dist/src/openclaw/provider-readiness-lock.js.map +1 -0
  67. package/dist/src/openclaw/shared.d.ts +10 -17
  68. package/dist/src/openclaw/shared.js +53 -19
  69. package/dist/src/openclaw/shared.js.map +1 -1
  70. package/dist/src/openclaw.d.ts +3 -5
  71. package/dist/src/openclaw.js +245 -60
  72. package/dist/src/openclaw.js.map +1 -1
  73. package/dist/src/platform/process-owned-lock.d.ts +11 -0
  74. package/dist/src/platform/process-owned-lock.js +1698 -0
  75. package/dist/src/platform/process-owned-lock.js.map +1 -0
  76. package/dist/src/platform/recorder-directory.d.ts +1 -0
  77. package/dist/src/platform/recorder-directory.js +14 -0
  78. package/dist/src/platform/recorder-directory.js.map +1 -0
  79. package/dist/src/platform/windows-acl.d.ts +18 -0
  80. package/dist/src/platform/windows-acl.js +1367 -0
  81. package/dist/src/platform/windows-acl.js.map +1 -0
  82. package/dist/src/platform/windows-lock-root.d.ts +13 -0
  83. package/dist/src/platform/windows-lock-root.js +98 -0
  84. package/dist/src/platform/windows-lock-root.js.map +1 -0
  85. package/dist/src/providers/builtin/discord.d.ts +4 -0
  86. package/dist/src/providers/builtin/discord.js +52 -9
  87. package/dist/src/providers/builtin/discord.js.map +1 -1
  88. package/dist/src/providers/builtin/external-webhook-auth.d.ts +1 -0
  89. package/dist/src/providers/builtin/external-webhook-auth.js +38 -1
  90. package/dist/src/providers/builtin/external-webhook-auth.js.map +1 -1
  91. package/dist/src/providers/builtin/feishu.d.ts +1 -0
  92. package/dist/src/providers/builtin/feishu.js +96 -26
  93. package/dist/src/providers/builtin/feishu.js.map +1 -1
  94. package/dist/src/providers/builtin/googlechat.d.ts +1 -0
  95. package/dist/src/providers/builtin/googlechat.js +95 -26
  96. package/dist/src/providers/builtin/googlechat.js.map +1 -1
  97. package/dist/src/providers/builtin/imessage.js +60 -17
  98. package/dist/src/providers/builtin/imessage.js.map +1 -1
  99. package/dist/src/providers/builtin/loopback.js +88 -35
  100. package/dist/src/providers/builtin/loopback.js.map +1 -1
  101. package/dist/src/providers/builtin/matrix.d.ts +2 -2
  102. package/dist/src/providers/builtin/matrix.js +78 -21
  103. package/dist/src/providers/builtin/matrix.js.map +1 -1
  104. package/dist/src/providers/builtin/mattermost.d.ts +11 -7
  105. package/dist/src/providers/builtin/mattermost.js +138 -24
  106. package/dist/src/providers/builtin/mattermost.js.map +1 -1
  107. package/dist/src/providers/builtin/msteams.d.ts +1 -0
  108. package/dist/src/providers/builtin/msteams.js +118 -38
  109. package/dist/src/providers/builtin/msteams.js.map +1 -1
  110. package/dist/src/providers/builtin/native-local-mock.js +4 -4
  111. package/dist/src/providers/builtin/native-local-mock.js.map +1 -1
  112. package/dist/src/providers/builtin/script.js +1057 -146
  113. package/dist/src/providers/builtin/script.js.map +1 -1
  114. package/dist/src/providers/builtin/slack.d.ts +8 -1
  115. package/dist/src/providers/builtin/slack.js +327 -24
  116. package/dist/src/providers/builtin/slack.js.map +1 -1
  117. package/dist/src/providers/builtin/telegram.d.ts +1 -1
  118. package/dist/src/providers/builtin/telegram.js +55 -14
  119. package/dist/src/providers/builtin/telegram.js.map +1 -1
  120. package/dist/src/providers/builtin/whatsapp.d.ts +4 -3
  121. package/dist/src/providers/builtin/whatsapp.js +157 -29
  122. package/dist/src/providers/builtin/whatsapp.js.map +1 -1
  123. package/dist/src/providers/builtin/zalo.d.ts +5 -2
  124. package/dist/src/providers/builtin/zalo.js +31 -7
  125. package/dist/src/providers/builtin/zalo.js.map +1 -1
  126. package/dist/src/providers/catalog.d.ts +1 -1
  127. package/dist/src/providers/catalog.js +1 -1
  128. package/dist/src/providers/catalog.js.map +1 -1
  129. package/dist/src/providers/local-mock.d.ts +4 -0
  130. package/dist/src/providers/local-mock.js +162 -35
  131. package/dist/src/providers/local-mock.js.map +1 -1
  132. package/dist/src/providers/native-ids.d.ts +2 -0
  133. package/dist/src/providers/native-ids.js +3 -0
  134. package/dist/src/providers/native-ids.js.map +1 -1
  135. package/dist/src/providers/recorder.d.ts +14 -3
  136. package/dist/src/providers/recorder.js +521 -41
  137. package/dist/src/providers/recorder.js.map +1 -1
  138. package/dist/src/providers/registry.d.ts +1 -0
  139. package/dist/src/providers/registry.js +65 -20
  140. package/dist/src/providers/registry.js.map +1 -1
  141. package/dist/src/providers/signed-jwt.d.ts +6 -1
  142. package/dist/src/providers/signed-jwt.js +241 -50
  143. package/dist/src/providers/signed-jwt.js.map +1 -1
  144. package/dist/src/providers/slack-ids.d.ts +1 -0
  145. package/dist/src/providers/slack-ids.js +5 -0
  146. package/dist/src/providers/slack-ids.js.map +1 -1
  147. package/dist/src/providers/target-normalizers.d.ts +9 -1
  148. package/dist/src/providers/target-normalizers.js +177 -58
  149. package/dist/src/providers/target-normalizers.js.map +1 -1
  150. package/dist/src/providers/webhook-server.d.ts +3 -0
  151. package/dist/src/providers/webhook-server.js +268 -47
  152. package/dist/src/providers/webhook-server.js.map +1 -1
  153. package/dist/src/servers/discord.d.ts +40 -0
  154. package/dist/src/servers/discord.js +842 -0
  155. package/dist/src/servers/discord.js.map +1 -0
  156. package/dist/src/servers/http.d.ts +14 -2
  157. package/dist/src/servers/http.js +229 -58
  158. package/dist/src/servers/http.js.map +1 -1
  159. package/dist/src/servers/index.d.ts +10 -4
  160. package/dist/src/servers/index.js +4 -0
  161. package/dist/src/servers/index.js.map +1 -1
  162. package/dist/src/servers/matrix.d.ts +3 -0
  163. package/dist/src/servers/matrix.js +268 -110
  164. package/dist/src/servers/matrix.js.map +1 -1
  165. package/dist/src/servers/mattermost.d.ts +5 -0
  166. package/dist/src/servers/mattermost.js +389 -91
  167. package/dist/src/servers/mattermost.js.map +1 -1
  168. package/dist/src/servers/recorder.d.ts +11 -0
  169. package/dist/src/servers/recorder.js +1072 -69
  170. package/dist/src/servers/recorder.js.map +1 -1
  171. package/dist/src/servers/signal.d.ts +1 -0
  172. package/dist/src/servers/signal.js +279 -44
  173. package/dist/src/servers/signal.js.map +1 -1
  174. package/dist/src/servers/slack.d.ts +15 -0
  175. package/dist/src/servers/slack.js +270 -54
  176. package/dist/src/servers/slack.js.map +1 -1
  177. package/dist/src/servers/telegram-identity.d.ts +6 -0
  178. package/dist/src/servers/telegram-identity.js +27 -0
  179. package/dist/src/servers/telegram-identity.js.map +1 -0
  180. package/dist/src/servers/telegram.d.ts +17 -6
  181. package/dist/src/servers/telegram.js +1218 -107
  182. package/dist/src/servers/telegram.js.map +1 -1
  183. package/dist/src/servers/webhook-target.d.ts +12 -4
  184. package/dist/src/servers/webhook-target.js +226 -28
  185. package/dist/src/servers/webhook-target.js.map +1 -1
  186. package/dist/src/servers/whatsapp-baileys-websocket.d.ts +60 -1
  187. package/dist/src/servers/whatsapp-baileys-websocket.js +782 -112
  188. package/dist/src/servers/whatsapp-baileys-websocket.js.map +1 -1
  189. package/dist/src/servers/whatsapp-jid.d.ts +1 -0
  190. package/dist/src/servers/whatsapp-jid.js +9 -5
  191. package/dist/src/servers/whatsapp-jid.js.map +1 -1
  192. package/dist/src/servers/whatsapp-wire/binary-node.d.ts +1 -0
  193. package/dist/src/servers/whatsapp-wire/binary-node.js +37 -13
  194. package/dist/src/servers/whatsapp-wire/binary-node.js.map +1 -1
  195. package/dist/src/servers/whatsapp-wire/crypto.d.ts +3 -0
  196. package/dist/src/servers/whatsapp-wire/crypto.js +23 -8
  197. package/dist/src/servers/whatsapp-wire/crypto.js.map +1 -1
  198. package/dist/src/servers/whatsapp-wire/handshake.d.ts +9 -3
  199. package/dist/src/servers/whatsapp-wire/handshake.js +173 -12
  200. package/dist/src/servers/whatsapp-wire/handshake.js.map +1 -1
  201. package/dist/src/servers/whatsapp-wire/signal.js +3 -3
  202. package/dist/src/servers/whatsapp-wire/signal.js.map +1 -1
  203. package/dist/src/servers/whatsapp.d.ts +7 -1
  204. package/dist/src/servers/whatsapp.js +149 -25
  205. package/dist/src/servers/whatsapp.js.map +1 -1
  206. package/dist/src/servers/zalo.d.ts +1 -0
  207. package/dist/src/servers/zalo.js +193 -82
  208. package/dist/src/servers/zalo.js.map +1 -1
  209. package/docs/channel-setup.md +251 -64
  210. package/fixtures/examples/crabline.example.yaml +3 -2
  211. package/package.json +18 -10
  212. package/dist/src/openclaw/smoke-lock.js +0 -1004
  213. package/dist/src/openclaw/smoke-lock.js.map +0 -1
  214. package/fixtures/examples/openclaw-bridge.yaml +0 -125
@@ -1,9 +1,11 @@
1
1
  import path from "node:path";
2
2
  import { CrablineError, ensureErrorMessage } from "../core/errors.js";
3
- import { appendRecordedInbound, cloneRecordedInboundCursor, createRecordedInboundCursor, waitForRecordedInbound, watchRecordedInbound, } from "./recorder.js";
3
+ import { isJsonMediaType } from "../servers/http.js";
4
+ import { appendRecordedInbound, appendRecordedInboundBatch, cloneRecordedInboundCursor, createRecordedInboundCursor, waitForRecordedInbound, watchRecordedInbound, } from "./recorder.js";
4
5
  import { startWebhookServer } from "./webhook-server.js";
5
6
  export { createGenericLocalMockTargetCodec } from "./target-normalizers.js";
6
7
  const MAX_WAIT_CURSORS = 64;
8
+ const DEFAULT_WEBHOOK_CLEANUP_GRACE_MS = 250;
7
9
  function cursorHasAdvanced(candidate, current) {
8
10
  if (!current) {
9
11
  return true;
@@ -57,10 +59,33 @@ function isAddressInChannel(threadId, channelId) {
57
59
  function createMessageId(platform) {
58
60
  return `${platform}-mock-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
59
61
  }
62
+ function unsafeGeneratedRecorderProviderId(providerId) {
63
+ return (path.posix.parse(providerId).root !== "" ||
64
+ path.win32.parse(providerId).root !== "" ||
65
+ providerId.split(/[\\/]/u).includes(".."));
66
+ }
67
+ export function resolveGeneratedLocalMockRecorderPath(providerId, suffix = "") {
68
+ const recorderDirectory = path.resolve(".crabline", "recorders");
69
+ if (unsafeGeneratedRecorderProviderId(providerId)) {
70
+ throw new CrablineError("Provider ID cannot contain absolute or parent-directory paths.", {
71
+ kind: "config",
72
+ });
73
+ }
74
+ const recorderPath = path.resolve(recorderDirectory, `${providerId}${suffix}.jsonl`);
75
+ const relativePath = path.relative(recorderDirectory, recorderPath);
76
+ if (relativePath === ".." ||
77
+ relativePath.startsWith(`..${path.sep}`) ||
78
+ path.isAbsolute(relativePath)) {
79
+ throw new CrablineError("Generated provider recorder path escapes its directory.", {
80
+ kind: "config",
81
+ });
82
+ }
83
+ return recorderPath;
84
+ }
60
85
  function toRecorderPath(providerId, configuredPath) {
61
86
  return configuredPath
62
87
  ? path.resolve(configuredPath)
63
- : path.resolve(".crabline", "recorders", `${providerId}.jsonl`);
88
+ : resolveGeneratedLocalMockRecorderPath(providerId);
64
89
  }
65
90
  function authorFromPayload(payload) {
66
91
  const explicit = payload.message?.author ?? payload.author;
@@ -99,7 +124,7 @@ function normalizeWebhookPayload(payload) {
99
124
  if (envelope.authorIsBot !== undefined && typeof envelope.authorIsBot !== "boolean") {
100
125
  throw new CrablineError(`${label}.authorIsBot must be a boolean`, { kind: "inbound" });
101
126
  }
102
- for (const field of ["id", "text", "threadId"]) {
127
+ for (const field of ["id", "threadId"]) {
103
128
  const value = envelope[field];
104
129
  if (value !== undefined && (typeof value !== "string" || value.length === 0)) {
105
130
  throw new CrablineError(`${label}.${field} must be a non-empty string`, {
@@ -107,12 +132,45 @@ function normalizeWebhookPayload(payload) {
107
132
  });
108
133
  }
109
134
  }
135
+ if (envelope.text !== undefined && typeof envelope.text !== "string") {
136
+ throw new CrablineError(`${label}.text must be a string`, {
137
+ kind: "inbound",
138
+ });
139
+ }
110
140
  }
111
141
  return payload;
112
142
  }
113
143
  function mockReplyText(params) {
114
144
  return `[${params.platform} mock] ${params.text}`;
115
145
  }
146
+ function reportPostCommitWebhookSettlementFailure(providerId, error) {
147
+ try {
148
+ process.emitWarning(`Webhook acceptance committed for provider ${JSON.stringify(providerId)}, but settlement failed: ${ensureErrorMessage(error)}`, {
149
+ code: "CRABLINE_WEBHOOK_SETTLEMENT",
150
+ type: "ProviderWebhookWarning",
151
+ });
152
+ }
153
+ catch {
154
+ // Error reporting must not change the result of an accepted webhook.
155
+ }
156
+ }
157
+ async function settleWebhookHandlers(providerId, handlers, graceMs) {
158
+ if (handlers.length === 0) {
159
+ return;
160
+ }
161
+ let timer;
162
+ const deadline = new Promise((_, reject) => {
163
+ timer = setTimeout(() => {
164
+ reject(new CrablineError(`Provider "${providerId}" webhook handlers did not settle within ${graceMs}ms after cleanup.`, { kind: "timeout" }));
165
+ }, graceMs);
166
+ });
167
+ try {
168
+ await Promise.race([Promise.allSettled(handlers).then(() => undefined), deadline]);
169
+ }
170
+ finally {
171
+ clearTimeout(timer);
172
+ }
173
+ }
116
174
  export class LocalMockProviderAdapter {
117
175
  id;
118
176
  platform;
@@ -123,12 +181,15 @@ export class LocalMockProviderAdapter {
123
181
  #options;
124
182
  #publicUrl;
125
183
  #recorderPath;
184
+ #webhookCleanupGraceMs;
126
185
  #activeSends = new Set();
186
+ #activeWebhookHandlers = new Set();
127
187
  #cleanupController = new AbortController();
128
188
  #waitCursors = new Map();
129
189
  #cleanupBegun = false;
130
190
  #cleanupPromise = null;
131
191
  #server = null;
192
+ #serverClosing = null;
132
193
  #serverStarting = null;
133
194
  constructor(params) {
134
195
  this.id = params.id;
@@ -139,6 +200,14 @@ export class LocalMockProviderAdapter {
139
200
  this.#options = params.options;
140
201
  this.#publicUrl = params.options.publicUrl ?? params.options.webhook?.publicUrl;
141
202
  this.#recorderPath = toRecorderPath(params.id, params.options.recorderPath);
203
+ const webhookCleanupGraceMs = params.options.webhookCleanupGraceMs ?? DEFAULT_WEBHOOK_CLEANUP_GRACE_MS;
204
+ if (!Number.isSafeInteger(webhookCleanupGraceMs) || webhookCleanupGraceMs < 1) {
205
+ throw new CrablineError("Webhook cleanup grace must be a positive safe integer.", {
206
+ kind: "config",
207
+ });
208
+ }
209
+ this.#webhookCleanupGraceMs = webhookCleanupGraceMs;
210
+ this.#installCleanupFence();
142
211
  }
143
212
  normalizeTarget(target) {
144
213
  this.#assertActive();
@@ -181,28 +250,30 @@ export class LocalMockProviderAdapter {
181
250
  return sending;
182
251
  }
183
252
  async #send(context) {
184
- const { signal } = context;
253
+ const signal = this.#signalFor(context.signal);
185
254
  const threadId = this.#codec.resolveThreadId(context.fixture.target);
186
255
  const messageId = createMessageId(this.platform);
187
- signal?.throwIfAborted();
188
- await appendRecordedInbound(this.#recorderPath, {
189
- author: "user",
190
- id: messageId,
191
- provider: this.id,
192
- raw: {
193
- direction: "outbound",
194
- mode: context.mode,
195
- platform: this.platform,
256
+ signal.throwIfAborted();
257
+ const events = [
258
+ {
259
+ author: "user",
260
+ id: messageId,
261
+ provider: this.id,
262
+ raw: {
263
+ direction: "outbound",
264
+ mode: context.mode,
265
+ platform: this.platform,
266
+ },
267
+ recordedDirection: "outbound",
268
+ sentAt: new Date().toISOString(),
269
+ text: context.text,
270
+ threadId,
196
271
  },
197
- recordedDirection: "outbound",
198
- sentAt: new Date().toISOString(),
199
- text: context.text,
200
- threadId,
201
- });
272
+ ];
202
273
  if (context.mode !== "send" && context.fixture.target.behavior !== "sink") {
203
274
  await sleep(this.#config.loopback?.delayMs ?? 25, signal);
204
- signal?.throwIfAborted();
205
- await appendRecordedInbound(this.#recorderPath, {
275
+ signal.throwIfAborted();
276
+ events.push({
206
277
  author: "assistant",
207
278
  id: `${messageId}-reply`,
208
279
  provider: this.id,
@@ -217,6 +288,7 @@ export class LocalMockProviderAdapter {
217
288
  threadId,
218
289
  });
219
290
  }
291
+ await appendRecordedInboundBatch(this.#recorderPath, events);
220
292
  return {
221
293
  accepted: true,
222
294
  messageId,
@@ -266,6 +338,7 @@ export class LocalMockProviderAdapter {
266
338
  this.#waitCursors.get(cursorKey) === cursorState) {
267
339
  this.#waitCursors.delete(cursorKey);
268
340
  }
341
+ pruneInactiveWaitCursors(this.#waitCursors);
269
342
  }
270
343
  }
271
344
  async *watch(context) {
@@ -291,30 +364,60 @@ export class LocalMockProviderAdapter {
291
364
  this.#cleanupBegun = true;
292
365
  this.#cleanupController.abort(this.#cleanedUpError());
293
366
  this.#waitCursors.clear();
367
+ this.#serverClosing = this.#closeWebhookServer();
368
+ void this.#serverClosing.catch(() => undefined);
369
+ }
370
+ #installCleanupFence() {
371
+ const adapter = this;
372
+ if (adapter.beginCleanup) {
373
+ return;
374
+ }
375
+ Object.defineProperty(adapter, "beginCleanup", {
376
+ value: () => this.#beginCleanup(),
377
+ });
294
378
  }
295
379
  async cleanup() {
296
380
  this.#beginCleanup();
297
381
  this.#cleanupPromise ??= (async () => {
298
382
  const sends = [...this.#activeSends];
299
- const [serverResult] = await Promise.allSettled([this.#closeWebhookServer(), ...sends]);
383
+ const webhookHandlers = [...this.#activeWebhookHandlers];
384
+ const results = await Promise.allSettled([
385
+ this.#serverClosing,
386
+ ...sends,
387
+ settleWebhookHandlers(this.id, webhookHandlers, this.#webhookCleanupGraceMs),
388
+ ]);
389
+ const serverResult = results[0];
300
390
  if (serverResult?.status === "rejected") {
301
391
  throw serverResult.reason;
302
392
  }
393
+ const webhookResult = results.at(-1);
394
+ if (webhookResult?.status === "rejected") {
395
+ throw webhookResult.reason;
396
+ }
303
397
  })();
304
398
  await this.#cleanupPromise;
305
399
  }
306
- async #handleWebhook(request) {
400
+ #handleWebhook(request) {
307
401
  if (this.#cleanupBegun) {
308
- return new Response("provider is shutting down", { status: 503 });
402
+ return Promise.resolve(new Response("provider is shutting down", { status: 503 }));
309
403
  }
404
+ const admittedRequest = new Request(request, {
405
+ signal: this.#signalFor(request.signal),
406
+ });
407
+ const handling = this.#handleAdmittedWebhook(admittedRequest);
408
+ this.#activeWebhookHandlers.add(handling);
409
+ void handling.then(() => this.#activeWebhookHandlers.delete(handling), () => this.#activeWebhookHandlers.delete(handling));
410
+ return handling;
411
+ }
412
+ async #handleAdmittedWebhook(request) {
310
413
  const rawBody = await request.text();
311
414
  const authenticationFailure = await this.#options.authenticateWebhookRequest?.(request, rawBody);
312
415
  if (authenticationFailure) {
313
416
  return authenticationFailure;
314
417
  }
315
- const mediaType = request.headers.get("content-type")?.split(";", 1)[0]?.trim().toLowerCase();
418
+ const isJson = isJsonMediaType(request.headers.get("content-type") ?? "");
316
419
  let rawPayload;
317
- if (mediaType === "application/json") {
420
+ if (isJson) {
318
421
  try {
319
422
  rawPayload = JSON.parse(rawBody);
320
423
  }
@@ -342,12 +445,29 @@ export class LocalMockProviderAdapter {
342
445
  settle(response.ok);
343
446
  return response;
344
447
  };
448
+ let uncommittedResponse;
449
+ const settleCommittedAcceptance = () => {
450
+ if (settled) {
451
+ return;
452
+ }
453
+ settled = true;
454
+ try {
455
+ this.#options.settleWebhookRequest?.({
456
+ accepted: true,
457
+ payload: rawPayload,
458
+ rawBody,
459
+ });
460
+ }
461
+ catch (error) {
462
+ reportPostCommitWebhookSettlementFailure(this.id, error);
463
+ }
464
+ };
345
465
  try {
346
466
  const directResponse = await this.#options.handleWebhookPayload?.(rawPayload, request, rawBody);
347
467
  if (directResponse) {
348
468
  return respond(directResponse);
349
469
  }
350
- if (mediaType !== "application/json") {
470
+ if (!isJson) {
351
471
  return respond(new Response("expected application/json", { status: 415 }));
352
472
  }
353
473
  let payload;
@@ -365,12 +485,15 @@ export class LocalMockProviderAdapter {
365
485
  const id = payload.message?.id ?? payload.id ?? createMessageId(this.platform);
366
486
  const threadId = payload.message?.threadId ?? payload.threadId;
367
487
  const text = payload.message?.text ?? payload.text;
368
- if (!threadId || !text) {
488
+ if (!threadId || text === undefined) {
369
489
  return respond(new Response("payload requires message.threadId and message.text", { status: 400 }));
370
490
  }
371
- if (this.#cleanupBegun) {
372
- return respond(new Response("provider is shutting down", { status: 503 }));
373
- }
491
+ const successResponse = (await this.#options.createWebhookSuccessResponse?.(rawPayload, id)) ??
492
+ new Response(JSON.stringify({ ok: true, id }), {
493
+ headers: { "content-type": "application/json" },
494
+ status: 200,
495
+ });
496
+ uncommittedResponse = successResponse;
374
497
  await appendRecordedInbound(this.#recorderPath, {
375
498
  author: authorFromPayload(payload),
376
499
  id,
@@ -381,13 +504,12 @@ export class LocalMockProviderAdapter {
381
504
  text,
382
505
  threadId,
383
506
  });
384
- return respond((await this.#options.createWebhookSuccessResponse?.(rawPayload, id)) ??
385
- new Response(JSON.stringify({ ok: true, id }), {
386
- headers: { "content-type": "application/json" },
387
- status: 200,
388
- }));
507
+ settleCommittedAcceptance();
508
+ uncommittedResponse = undefined;
509
+ return successResponse;
389
510
  }
390
511
  catch (error) {
512
+ void uncommittedResponse?.body?.cancel(error).catch(() => undefined);
391
513
  settle(false);
392
514
  throw error;
393
515
  }
@@ -411,8 +533,13 @@ export class LocalMockProviderAdapter {
411
533
  return await startWebhookServer({
412
534
  handle: (request) => this.#handleWebhook(request),
413
535
  host,
536
+ methods: this.#options.webhookMethods ??
537
+ (this.#options.handleWebhookPayload ? ["GET", "POST"] : ["POST"]),
414
538
  path: webhookPath,
415
539
  port,
540
+ ...(this.#options.preflightWebhookRequest
541
+ ? { preflight: this.#options.preflightWebhookRequest }
542
+ : {}),
416
543
  });
417
544
  }
418
545
  catch (error) {
@@ -1 +1 @@
1
- {"version":3,"file":"local-mock.js","sourceRoot":"","sources":["../../../src/providers/local-mock.ts"],"names":[],"mappings":"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","sourcesContent":["import path from \"node:path\";\nimport { CrablineError, ensureErrorMessage } from \"../core/errors.js\";\nimport type { ProviderConfig, ProviderPlatform } from \"../config/schema.js\";\nimport {\n appendRecordedInbound,\n cloneRecordedInboundCursor,\n createRecordedInboundCursor,\n waitForRecordedInbound,\n watchRecordedInbound,\n type RecordedInboundCursor,\n} from \"./recorder.js\";\nimport type {\n InboundEnvelope,\n NormalizedTarget,\n ProbeResult,\n ProviderAdapter,\n ProviderContext,\n SendContext,\n SendResult,\n WaitContext,\n WatchContext,\n} from \"./types.js\";\nimport { startWebhookServer, type StartedWebhookServer } from \"./webhook-server.js\";\n\nexport { createGenericLocalMockTargetCodec } from \"./target-normalizers.js\";\n\nexport type LocalMockWebhookConfig = {\n host?: string;\n path?: string;\n port?: number;\n publicUrl?: string | undefined;\n};\n\nexport type LocalMockAdapterOptions = {\n authenticateWebhookRequest?: (\n request: Request,\n rawBody: string,\n ) => Promise<Response | undefined> | Response | undefined;\n createWebhookSuccessResponse?: (payload: unknown, id: string) => Promise<Response> | Response;\n defaultWebhook: Required<Pick<LocalMockWebhookConfig, \"host\" | \"path\" | \"port\">>;\n endpointLabel: string;\n matchesThread?: (\n candidateThreadId: string,\n expectedThreadId: string | undefined,\n target: NormalizedTarget,\n raw?: unknown,\n ) => boolean;\n handleWebhookPayload?: (\n payload: unknown,\n request: Request,\n rawBody: string,\n ) => Promise<Response | undefined> | Response | undefined;\n normalizeWebhookPayload?: (payload: unknown) => unknown;\n platform: ProviderPlatform;\n publicUrl?: string | undefined;\n recorderPath?: string | undefined;\n settleWebhookRequest?: (params: { accepted: boolean; payload: unknown; rawBody: string }) => void;\n webhook?: LocalMockWebhookConfig | undefined;\n};\n\nconst MAX_WAIT_CURSORS = 64;\n\ntype WaitCursorState = {\n active: number;\n cursor?: RecordedInboundCursor | undefined;\n};\n\nfunction cursorHasAdvanced(\n candidate: RecordedInboundCursor,\n current: RecordedInboundCursor | undefined,\n): boolean {\n if (!current) {\n return true;\n }\n if (candidate.readState.generation !== current.readState.generation) {\n return candidate.readState.generation > current.readState.generation;\n }\n if (candidate.readState.offset !== current.readState.offset) {\n return candidate.readState.offset > current.readState.offset;\n }\n return candidate.buffered.length < current.buffered.length;\n}\n\nfunction pruneInactiveWaitCursors(cursors: Map<string, WaitCursorState>): void {\n for (const [key, state] of cursors) {\n if (cursors.size <= MAX_WAIT_CURSORS) {\n return;\n }\n if (state.active === 0) {\n cursors.delete(key);\n }\n }\n}\n\nexport type LocalMockTargetCodec = {\n normalize(target: ProviderContext[\"fixture\"][\"target\"]): NormalizedTarget;\n resolveThreadId(target: ProviderContext[\"fixture\"][\"target\"]): string;\n};\n\ntype MockWebhookPayload = {\n author?: \"assistant\" | \"system\" | \"user\";\n authorIsBot?: boolean;\n id?: string;\n message?: {\n author?: \"assistant\" | \"system\" | \"user\";\n authorIsBot?: boolean;\n id?: string;\n raw?: unknown;\n text?: string;\n threadId?: string;\n };\n raw?: unknown;\n text?: string;\n threadId?: string;\n};\n\nfunction sleep(ms: number, signal?: AbortSignal): Promise<void> {\n return new Promise((resolve, reject) => {\n let timer: ReturnType<typeof setTimeout> | undefined;\n const cleanup = () => signal?.removeEventListener(\"abort\", abort);\n const abort = () => {\n if (timer) {\n clearTimeout(timer);\n }\n cleanup();\n reject(signal?.reason ?? new Error(\"Provider operation aborted.\"));\n };\n timer = setTimeout(() => {\n cleanup();\n resolve();\n }, ms);\n if (signal?.aborted) {\n abort();\n return;\n }\n signal?.addEventListener(\"abort\", abort, { once: true });\n });\n}\n\nfunction isAddressInChannel(threadId: string, channelId?: string): boolean {\n if (!channelId) {\n return true;\n }\n return threadId === channelId || threadId.startsWith(`${channelId}:`);\n}\n\nfunction createMessageId(platform: ProviderPlatform) {\n return `${platform}-mock-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;\n}\n\nfunction toRecorderPath(providerId: string, configuredPath?: string): string {\n return configuredPath\n ? path.resolve(configuredPath)\n : path.resolve(\".crabline\", \"recorders\", `${providerId}.jsonl`);\n}\n\nfunction authorFromPayload(payload: MockWebhookPayload): InboundEnvelope[\"author\"] {\n const explicit = payload.message?.author ?? payload.author;\n if (explicit) {\n return explicit;\n }\n return (payload.message?.authorIsBot ?? payload.authorIsBot ?? true) ? \"assistant\" : \"user\";\n}\n\nfunction normalizeWebhookPayload(payload: unknown): MockWebhookPayload {\n if (!payload || typeof payload !== \"object\" || Array.isArray(payload)) {\n throw new CrablineError(\"mock webhook payload must be an object\", { kind: \"inbound\" });\n }\n\n const record = payload as Record<string, unknown>;\n const messageValue = record.message;\n if (\n messageValue !== undefined &&\n (!messageValue || typeof messageValue !== \"object\" || Array.isArray(messageValue))\n ) {\n throw new CrablineError(\"mock webhook payload message must be an object\", {\n kind: \"inbound\",\n });\n }\n\n for (const [label, envelope] of [\n [\"payload\", record],\n [\"payload.message\", messageValue as Record<string, unknown> | undefined],\n ] as const) {\n if (!envelope) {\n continue;\n }\n if (\n envelope.author !== undefined &&\n envelope.author !== \"assistant\" &&\n envelope.author !== \"system\" &&\n envelope.author !== \"user\"\n ) {\n throw new CrablineError(`${label}.author must be assistant, system, or user`, {\n kind: \"inbound\",\n });\n }\n if (envelope.authorIsBot !== undefined && typeof envelope.authorIsBot !== \"boolean\") {\n throw new CrablineError(`${label}.authorIsBot must be a boolean`, { kind: \"inbound\" });\n }\n for (const field of [\"id\", \"text\", \"threadId\"] as const) {\n const value = envelope[field];\n if (value !== undefined && (typeof value !== \"string\" || value.length === 0)) {\n throw new CrablineError(`${label}.${field} must be a non-empty string`, {\n kind: \"inbound\",\n });\n }\n }\n }\n\n return payload as MockWebhookPayload;\n}\n\nfunction mockReplyText(params: { platform: ProviderPlatform; text: string }) {\n return `[${params.platform} mock] ${params.text}`;\n}\n\nexport class LocalMockProviderAdapter implements ProviderAdapter {\n readonly id;\n readonly platform;\n readonly status = \"ready\" as const;\n readonly supports: ProviderAdapter[\"supports\"];\n\n readonly #codec: LocalMockTargetCodec;\n readonly #config: ProviderConfig;\n readonly #options: LocalMockAdapterOptions;\n readonly #publicUrl: string | undefined;\n readonly #recorderPath: string;\n readonly #activeSends = new Set<Promise<SendResult>>();\n readonly #cleanupController = new AbortController();\n readonly #waitCursors = new Map<string, WaitCursorState>();\n #cleanupBegun = false;\n #cleanupPromise: Promise<void> | null = null;\n #server: StartedWebhookServer | null = null;\n #serverStarting: Promise<StartedWebhookServer> | null = null;\n\n constructor(params: {\n codec: LocalMockTargetCodec;\n config: ProviderConfig;\n id: string;\n options: LocalMockAdapterOptions;\n }) {\n this.id = params.id;\n this.platform = params.options.platform;\n this.supports = [...params.config.capabilities];\n this.#codec = params.codec;\n this.#config = params.config;\n this.#options = params.options;\n this.#publicUrl = params.options.publicUrl ?? params.options.webhook?.publicUrl;\n this.#recorderPath = toRecorderPath(params.id, params.options.recorderPath);\n }\n\n normalizeTarget(target: ProviderContext[\"fixture\"][\"target\"]): NormalizedTarget {\n this.#assertActive();\n return this.#codec.normalize(target);\n }\n\n async probe(context: ProviderContext): Promise<ProbeResult> {\n context.signal?.throwIfAborted();\n const server = await this.#ensureWebhookServer();\n context.signal?.throwIfAborted();\n const target = this.normalizeTarget(context.fixture.target);\n const details = [\n `${this.platform} local mock ready`,\n `recorder path ${this.#recorderPath}`,\n `${this.#options.endpointLabel} ${server.endpointUrl}`,\n ];\n if (this.#publicUrl) {\n details.push(`public webhook ${this.#publicUrl}`);\n }\n if (target.threadId) {\n details.push(`thread reachable ${target.threadId}`);\n } else if (target.channelId) {\n details.push(`channel reachable ${target.channelId}`);\n } else {\n details.push(`dm reachable ${this.#codec.resolveThreadId(context.fixture.target)}`);\n }\n return { details, healthy: true };\n }\n\n send(context: SendContext): Promise<SendResult> {\n try {\n this.#assertActive();\n } catch (error) {\n return Promise.reject(error);\n }\n const sending = this.#send(context);\n this.#activeSends.add(sending);\n void sending.then(\n () => this.#activeSends.delete(sending),\n () => this.#activeSends.delete(sending),\n );\n return sending;\n }\n\n async #send(context: SendContext): Promise<SendResult> {\n const { signal } = context;\n const threadId = this.#codec.resolveThreadId(context.fixture.target);\n const messageId = createMessageId(this.platform);\n signal?.throwIfAborted();\n await appendRecordedInbound(this.#recorderPath, {\n author: \"user\",\n id: messageId,\n provider: this.id,\n raw: {\n direction: \"outbound\",\n mode: context.mode,\n platform: this.platform,\n },\n recordedDirection: \"outbound\",\n sentAt: new Date().toISOString(),\n text: context.text,\n threadId,\n });\n\n if (context.mode !== \"send\" && context.fixture.target.behavior !== \"sink\") {\n await sleep(this.#config.loopback?.delayMs ?? 25, signal);\n signal?.throwIfAborted();\n await appendRecordedInbound(this.#recorderPath, {\n author: \"assistant\",\n id: `${messageId}-reply`,\n provider: this.id,\n raw: {\n direction: \"mock-reply\",\n mode: context.mode,\n platform: this.platform,\n },\n recordedDirection: \"inbound\",\n sentAt: new Date().toISOString(),\n text: mockReplyText({ platform: this.platform, text: context.text }),\n threadId,\n });\n }\n\n return {\n accepted: true,\n messageId,\n threadId,\n };\n }\n\n async waitForInbound(context: WaitContext): Promise<InboundEnvelope | null> {\n this.#assertActive();\n const signal = this.#signalFor(context.signal);\n await this.#ensureWebhookServer();\n const target = this.normalizeTarget(context.fixture.target);\n const expectedAuthor = context.fixture.inboundMatch.author;\n const channelId = context.threadId ?? target.threadId ?? target.channelId;\n const excludedIds = new Set(context.excludeIds ?? []);\n const cursorKey = JSON.stringify([context.nonce, context.since, channelId, expectedAuthor]);\n const cursorState = this.#waitCursors.get(cursorKey) ?? { active: 0 };\n const cursor = cursorState.cursor\n ? cloneRecordedInboundCursor(cursorState.cursor)\n : createRecordedInboundCursor();\n cursorState.active++;\n this.#waitCursors.delete(cursorKey);\n this.#waitCursors.set(cursorKey, cursorState);\n pruneInactiveWaitCursors(this.#waitCursors);\n\n try {\n const event = await waitForRecordedInbound({\n cursor,\n filePath: this.#recorderPath,\n matches: (candidate) =>\n candidate.provider === this.id &&\n !excludedIds.has(candidate.id) &&\n (expectedAuthor === \"any\" || candidate.author === expectedAuthor) &&\n (this.#options.matchesThread ?? isAddressInChannel)(\n candidate.threadId,\n channelId,\n target,\n candidate.raw,\n ),\n recordedDirection: \"inbound\",\n since: context.since,\n signal,\n timeoutMs: context.timeoutMs,\n });\n if (\n this.#waitCursors.get(cursorKey) === cursorState &&\n cursorHasAdvanced(cursor, cursorState.cursor)\n ) {\n cursorState.cursor = cursor;\n }\n return event;\n } finally {\n cursorState.active--;\n if (\n cursorState.active === 0 &&\n !cursorState.cursor &&\n this.#waitCursors.get(cursorKey) === cursorState\n ) {\n this.#waitCursors.delete(cursorKey);\n }\n }\n }\n\n async *watch(context: WatchContext): AsyncIterable<InboundEnvelope> {\n this.#assertActive();\n const signal = this.#signalFor(context.signal);\n await this.#ensureWebhookServer();\n const target = this.normalizeTarget(context.fixture.target);\n for await (const event of watchRecordedInbound({\n filePath: this.#recorderPath,\n matches: (entry) =>\n entry.provider === this.id &&\n (this.#options.matchesThread ?? isAddressInChannel)(\n entry.threadId,\n target.threadId ?? target.channelId,\n target,\n entry.raw,\n ),\n recordedDirection: \"inbound\",\n signal,\n since: context.since,\n })) {\n yield event;\n }\n }\n\n #beginCleanup(): void {\n if (this.#cleanupBegun) {\n return;\n }\n this.#cleanupBegun = true;\n this.#cleanupController.abort(this.#cleanedUpError());\n this.#waitCursors.clear();\n }\n\n async cleanup(): Promise<void> {\n this.#beginCleanup();\n this.#cleanupPromise ??= (async () => {\n const sends = [...this.#activeSends];\n const [serverResult] = await Promise.allSettled([this.#closeWebhookServer(), ...sends]);\n if (serverResult?.status === \"rejected\") {\n throw serverResult.reason;\n }\n })();\n await this.#cleanupPromise;\n }\n\n async #handleWebhook(request: Request): Promise<Response> {\n if (this.#cleanupBegun) {\n return new Response(\"provider is shutting down\", { status: 503 });\n }\n const rawBody = await request.text();\n const authenticationFailure = await this.#options.authenticateWebhookRequest?.(\n request,\n rawBody,\n );\n if (authenticationFailure) {\n return authenticationFailure;\n }\n const mediaType = request.headers.get(\"content-type\")?.split(\";\", 1)[0]?.trim().toLowerCase();\n let rawPayload: unknown;\n if (mediaType === \"application/json\") {\n try {\n rawPayload = JSON.parse(rawBody) as unknown;\n } catch {\n this.#options.settleWebhookRequest?.({\n accepted: false,\n payload: undefined,\n rawBody,\n });\n return new Response(\"invalid JSON\", { status: 400 });\n }\n } else {\n rawPayload = rawBody;\n }\n let settled = false;\n const settle = (accepted: boolean) => {\n if (settled) {\n return;\n }\n settled = true;\n this.#options.settleWebhookRequest?.({ accepted, payload: rawPayload, rawBody });\n };\n const respond = (response: Response) => {\n settle(response.ok);\n return response;\n };\n try {\n const directResponse = await this.#options.handleWebhookPayload?.(\n rawPayload,\n request,\n rawBody,\n );\n if (directResponse) {\n return respond(directResponse);\n }\n if (mediaType !== \"application/json\") {\n return respond(new Response(\"expected application/json\", { status: 415 }));\n }\n let payload: MockWebhookPayload;\n try {\n payload = normalizeWebhookPayload(\n this.#options.normalizeWebhookPayload\n ? this.#options.normalizeWebhookPayload(rawPayload)\n : rawPayload,\n );\n } catch (error) {\n if (error instanceof CrablineError && error.kind === \"inbound\") {\n return respond(new Response(ensureErrorMessage(error), { status: 400 }));\n }\n throw error;\n }\n\n const id = payload.message?.id ?? payload.id ?? createMessageId(this.platform);\n const threadId = payload.message?.threadId ?? payload.threadId;\n const text = payload.message?.text ?? payload.text;\n if (!threadId || !text) {\n return respond(\n new Response(\"payload requires message.threadId and message.text\", { status: 400 }),\n );\n }\n if (this.#cleanupBegun) {\n return respond(new Response(\"provider is shutting down\", { status: 503 }));\n }\n\n await appendRecordedInbound(this.#recorderPath, {\n author: authorFromPayload(payload),\n id,\n provider: this.id,\n raw: payload.message?.raw ?? payload.raw ?? payload,\n recordedDirection: \"inbound\",\n sentAt: new Date().toISOString(),\n text,\n threadId,\n });\n return respond(\n (await this.#options.createWebhookSuccessResponse?.(rawPayload, id)) ??\n new Response(JSON.stringify({ ok: true, id }), {\n headers: { \"content-type\": \"application/json\" },\n status: 200,\n }),\n );\n } catch (error) {\n settle(false);\n throw error;\n }\n }\n\n async #ensureWebhookServer(): Promise<StartedWebhookServer> {\n if (this.#cleanupBegun) {\n throw this.#cleanedUpError();\n }\n if (this.#server) {\n return this.#server;\n }\n if (this.#serverStarting) {\n return await this.#serverStarting;\n }\n\n const webhook = this.#options.webhook;\n const host = webhook?.host ?? this.#options.defaultWebhook.host;\n const port = webhook?.port ?? this.#options.defaultWebhook.port;\n const webhookPath = webhook?.path ?? this.#options.defaultWebhook.path;\n const starting = (async () => {\n try {\n return await startWebhookServer({\n handle: (request) => this.#handleWebhook(request),\n host,\n path: webhookPath,\n port,\n });\n } catch (error) {\n throw new CrablineError(\n `${this.platform} local mock webhook server failed: ${ensureErrorMessage(error)}`,\n { cause: error, kind: \"connectivity\" },\n );\n }\n })();\n this.#serverStarting = starting;\n\n try {\n const server = await starting;\n if (this.#cleanupBegun) {\n throw this.#cleanedUpError();\n }\n this.#server = server;\n return server;\n } finally {\n if (this.#serverStarting === starting) {\n this.#serverStarting = null;\n }\n }\n }\n\n async #closeWebhookServer(): Promise<void> {\n let server = this.#server;\n if (!server && this.#serverStarting) {\n try {\n server = await this.#serverStarting;\n } catch {\n return;\n }\n }\n if (!server) {\n return;\n }\n\n if (this.#server === server) {\n this.#server = null;\n }\n await server.close();\n }\n\n #cleanedUpError(): CrablineError {\n return new CrablineError(`Provider \"${this.id}\" has been cleaned up.`, { kind: \"config\" });\n }\n\n #assertActive(): void {\n if (this.#cleanupBegun) {\n throw this.#cleanedUpError();\n }\n }\n\n #signalFor(signal?: AbortSignal): AbortSignal {\n return signal\n ? AbortSignal.any([signal, this.#cleanupController.signal])\n : this.#cleanupController.signal;\n }\n}\n"]}
1
+ {"version":3,"file":"local-mock.js","sourceRoot":"","sources":["../../../src/providers/local-mock.ts"],"names":[],"mappings":"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","sourcesContent":["import path from \"node:path\";\nimport { CrablineError, ensureErrorMessage } from \"../core/errors.js\";\nimport type { ProviderConfig, ProviderPlatform } from \"../config/schema.js\";\nimport { isJsonMediaType } from \"../servers/http.js\";\nimport {\n appendRecordedInbound,\n appendRecordedInboundBatch,\n cloneRecordedInboundCursor,\n createRecordedInboundCursor,\n waitForRecordedInbound,\n watchRecordedInbound,\n type RecordedInboundCursor,\n} from \"./recorder.js\";\nimport type {\n InboundEnvelope,\n NormalizedTarget,\n ProbeResult,\n ProviderAdapter,\n ProviderContext,\n SendContext,\n SendResult,\n WaitContext,\n WatchContext,\n} from \"./types.js\";\nimport { startWebhookServer, type StartedWebhookServer } from \"./webhook-server.js\";\n\nexport { createGenericLocalMockTargetCodec } from \"./target-normalizers.js\";\n\nexport type LocalMockWebhookConfig = {\n host?: string;\n path?: string;\n port?: number;\n publicUrl?: string | undefined;\n};\n\nexport type LocalMockAdapterOptions = {\n authenticateWebhookRequest?: (\n request: Request,\n rawBody: string,\n ) => Promise<Response | undefined> | Response | undefined;\n createWebhookSuccessResponse?: (payload: unknown, id: string) => Promise<Response> | Response;\n defaultWebhook: Required<Pick<LocalMockWebhookConfig, \"host\" | \"path\" | \"port\">>;\n endpointLabel: string;\n matchesThread?: (\n candidateThreadId: string,\n expectedThreadId: string | undefined,\n target: NormalizedTarget,\n raw?: unknown,\n ) => boolean;\n handleWebhookPayload?: (\n payload: unknown,\n request: Request,\n rawBody: string,\n ) => Promise<Response | undefined> | Response | undefined;\n normalizeWebhookPayload?: (payload: unknown) => unknown;\n platform: ProviderPlatform;\n preflightWebhookRequest?: (\n request: Request,\n ) => Promise<Response | undefined> | Response | undefined;\n publicUrl?: string | undefined;\n recorderPath?: string | undefined;\n settleWebhookRequest?: (params: { accepted: boolean; payload: unknown; rawBody: string }) => void;\n webhook?: LocalMockWebhookConfig | undefined;\n webhookCleanupGraceMs?: number | undefined;\n webhookMethods?: readonly string[] | undefined;\n};\n\nconst MAX_WAIT_CURSORS = 64;\nconst DEFAULT_WEBHOOK_CLEANUP_GRACE_MS = 250;\n\ntype WaitCursorState = {\n active: number;\n cursor?: RecordedInboundCursor | undefined;\n};\n\nfunction cursorHasAdvanced(\n candidate: RecordedInboundCursor,\n current: RecordedInboundCursor | undefined,\n): boolean {\n if (!current) {\n return true;\n }\n if (candidate.readState.generation !== current.readState.generation) {\n return candidate.readState.generation > current.readState.generation;\n }\n if (candidate.readState.offset !== current.readState.offset) {\n return candidate.readState.offset > current.readState.offset;\n }\n return candidate.buffered.length < current.buffered.length;\n}\n\nfunction pruneInactiveWaitCursors(cursors: Map<string, WaitCursorState>): void {\n for (const [key, state] of cursors) {\n if (cursors.size <= MAX_WAIT_CURSORS) {\n return;\n }\n if (state.active === 0) {\n cursors.delete(key);\n }\n }\n}\n\nexport type LocalMockTargetCodec = {\n normalize(target: ProviderContext[\"fixture\"][\"target\"]): NormalizedTarget;\n resolveThreadId(target: ProviderContext[\"fixture\"][\"target\"]): string;\n};\n\ntype MockWebhookPayload = {\n author?: \"assistant\" | \"system\" | \"user\";\n authorIsBot?: boolean;\n id?: string;\n message?: {\n author?: \"assistant\" | \"system\" | \"user\";\n authorIsBot?: boolean;\n id?: string;\n raw?: unknown;\n text?: string;\n threadId?: string;\n };\n raw?: unknown;\n text?: string;\n threadId?: string;\n};\n\nfunction sleep(ms: number, signal?: AbortSignal): Promise<void> {\n return new Promise((resolve, reject) => {\n let timer: ReturnType<typeof setTimeout> | undefined;\n const cleanup = () => signal?.removeEventListener(\"abort\", abort);\n const abort = () => {\n if (timer) {\n clearTimeout(timer);\n }\n cleanup();\n reject(signal?.reason ?? new Error(\"Provider operation aborted.\"));\n };\n timer = setTimeout(() => {\n cleanup();\n resolve();\n }, ms);\n if (signal?.aborted) {\n abort();\n return;\n }\n signal?.addEventListener(\"abort\", abort, { once: true });\n });\n}\n\nfunction isAddressInChannel(threadId: string, channelId?: string): boolean {\n if (!channelId) {\n return true;\n }\n return threadId === channelId || threadId.startsWith(`${channelId}:`);\n}\n\nfunction createMessageId(platform: ProviderPlatform) {\n return `${platform}-mock-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;\n}\n\nfunction unsafeGeneratedRecorderProviderId(providerId: string): boolean {\n return (\n path.posix.parse(providerId).root !== \"\" ||\n path.win32.parse(providerId).root !== \"\" ||\n providerId.split(/[\\\\/]/u).includes(\"..\")\n );\n}\n\nexport function resolveGeneratedLocalMockRecorderPath(providerId: string, suffix = \"\"): string {\n const recorderDirectory = path.resolve(\".crabline\", \"recorders\");\n if (unsafeGeneratedRecorderProviderId(providerId)) {\n throw new CrablineError(\"Provider ID cannot contain absolute or parent-directory paths.\", {\n kind: \"config\",\n });\n }\n\n const recorderPath = path.resolve(recorderDirectory, `${providerId}${suffix}.jsonl`);\n const relativePath = path.relative(recorderDirectory, recorderPath);\n if (\n relativePath === \"..\" ||\n relativePath.startsWith(`..${path.sep}`) ||\n path.isAbsolute(relativePath)\n ) {\n throw new CrablineError(\"Generated provider recorder path escapes its directory.\", {\n kind: \"config\",\n });\n }\n return recorderPath;\n}\n\nfunction toRecorderPath(providerId: string, configuredPath?: string): string {\n return configuredPath\n ? path.resolve(configuredPath)\n : resolveGeneratedLocalMockRecorderPath(providerId);\n}\n\nfunction authorFromPayload(payload: MockWebhookPayload): InboundEnvelope[\"author\"] {\n const explicit = payload.message?.author ?? payload.author;\n if (explicit) {\n return explicit;\n }\n return (payload.message?.authorIsBot ?? payload.authorIsBot ?? true) ? \"assistant\" : \"user\";\n}\n\nfunction normalizeWebhookPayload(payload: unknown): MockWebhookPayload {\n if (!payload || typeof payload !== \"object\" || Array.isArray(payload)) {\n throw new CrablineError(\"mock webhook payload must be an object\", { kind: \"inbound\" });\n }\n\n const record = payload as Record<string, unknown>;\n const messageValue = record.message;\n if (\n messageValue !== undefined &&\n (!messageValue || typeof messageValue !== \"object\" || Array.isArray(messageValue))\n ) {\n throw new CrablineError(\"mock webhook payload message must be an object\", {\n kind: \"inbound\",\n });\n }\n\n for (const [label, envelope] of [\n [\"payload\", record],\n [\"payload.message\", messageValue as Record<string, unknown> | undefined],\n ] as const) {\n if (!envelope) {\n continue;\n }\n if (\n envelope.author !== undefined &&\n envelope.author !== \"assistant\" &&\n envelope.author !== \"system\" &&\n envelope.author !== \"user\"\n ) {\n throw new CrablineError(`${label}.author must be assistant, system, or user`, {\n kind: \"inbound\",\n });\n }\n if (envelope.authorIsBot !== undefined && typeof envelope.authorIsBot !== \"boolean\") {\n throw new CrablineError(`${label}.authorIsBot must be a boolean`, { kind: \"inbound\" });\n }\n for (const field of [\"id\", \"threadId\"] as const) {\n const value = envelope[field];\n if (value !== undefined && (typeof value !== \"string\" || value.length === 0)) {\n throw new CrablineError(`${label}.${field} must be a non-empty string`, {\n kind: \"inbound\",\n });\n }\n }\n if (envelope.text !== undefined && typeof envelope.text !== \"string\") {\n throw new CrablineError(`${label}.text must be a string`, {\n kind: \"inbound\",\n });\n }\n }\n\n return payload as MockWebhookPayload;\n}\n\nfunction mockReplyText(params: { platform: ProviderPlatform; text: string }) {\n return `[${params.platform} mock] ${params.text}`;\n}\n\nfunction reportPostCommitWebhookSettlementFailure(providerId: string, error: unknown): void {\n try {\n process.emitWarning(\n `Webhook acceptance committed for provider ${JSON.stringify(providerId)}, but settlement failed: ${ensureErrorMessage(error)}`,\n {\n code: \"CRABLINE_WEBHOOK_SETTLEMENT\",\n type: \"ProviderWebhookWarning\",\n },\n );\n } catch {\n // Error reporting must not change the result of an accepted webhook.\n }\n}\n\nasync function settleWebhookHandlers(\n providerId: string,\n handlers: readonly Promise<Response>[],\n graceMs: number,\n): Promise<void> {\n if (handlers.length === 0) {\n return;\n }\n let timer: NodeJS.Timeout | undefined;\n const deadline = new Promise<never>((_, reject) => {\n timer = setTimeout(() => {\n reject(\n new CrablineError(\n `Provider \"${providerId}\" webhook handlers did not settle within ${graceMs}ms after cleanup.`,\n { kind: \"timeout\" },\n ),\n );\n }, graceMs);\n });\n try {\n await Promise.race([Promise.allSettled(handlers).then(() => undefined), deadline]);\n } finally {\n clearTimeout(timer);\n }\n}\n\nexport class LocalMockProviderAdapter implements ProviderAdapter {\n readonly id;\n readonly platform;\n readonly status = \"ready\" as const;\n readonly supports: ProviderAdapter[\"supports\"];\n\n readonly #codec: LocalMockTargetCodec;\n readonly #config: ProviderConfig;\n readonly #options: LocalMockAdapterOptions;\n readonly #publicUrl: string | undefined;\n readonly #recorderPath: string;\n readonly #webhookCleanupGraceMs: number;\n readonly #activeSends = new Set<Promise<SendResult>>();\n readonly #activeWebhookHandlers = new Set<Promise<Response>>();\n readonly #cleanupController = new AbortController();\n readonly #waitCursors = new Map<string, WaitCursorState>();\n #cleanupBegun = false;\n #cleanupPromise: Promise<void> | null = null;\n #server: StartedWebhookServer | null = null;\n #serverClosing: Promise<void> | null = null;\n #serverStarting: Promise<StartedWebhookServer> | null = null;\n\n constructor(params: {\n codec: LocalMockTargetCodec;\n config: ProviderConfig;\n id: string;\n options: LocalMockAdapterOptions;\n }) {\n this.id = params.id;\n this.platform = params.options.platform;\n this.supports = [...params.config.capabilities];\n this.#codec = params.codec;\n this.#config = params.config;\n this.#options = params.options;\n this.#publicUrl = params.options.publicUrl ?? params.options.webhook?.publicUrl;\n this.#recorderPath = toRecorderPath(params.id, params.options.recorderPath);\n const webhookCleanupGraceMs =\n params.options.webhookCleanupGraceMs ?? DEFAULT_WEBHOOK_CLEANUP_GRACE_MS;\n if (!Number.isSafeInteger(webhookCleanupGraceMs) || webhookCleanupGraceMs < 1) {\n throw new CrablineError(\"Webhook cleanup grace must be a positive safe integer.\", {\n kind: \"config\",\n });\n }\n this.#webhookCleanupGraceMs = webhookCleanupGraceMs;\n this.#installCleanupFence();\n }\n\n normalizeTarget(target: ProviderContext[\"fixture\"][\"target\"]): NormalizedTarget {\n this.#assertActive();\n return this.#codec.normalize(target);\n }\n\n async probe(context: ProviderContext): Promise<ProbeResult> {\n context.signal?.throwIfAborted();\n const server = await this.#ensureWebhookServer();\n context.signal?.throwIfAborted();\n const target = this.normalizeTarget(context.fixture.target);\n const details = [\n `${this.platform} local mock ready`,\n `recorder path ${this.#recorderPath}`,\n `${this.#options.endpointLabel} ${server.endpointUrl}`,\n ];\n if (this.#publicUrl) {\n details.push(`public webhook ${this.#publicUrl}`);\n }\n if (target.threadId) {\n details.push(`thread reachable ${target.threadId}`);\n } else if (target.channelId) {\n details.push(`channel reachable ${target.channelId}`);\n } else {\n details.push(`dm reachable ${this.#codec.resolveThreadId(context.fixture.target)}`);\n }\n return { details, healthy: true };\n }\n\n send(context: SendContext): Promise<SendResult> {\n try {\n this.#assertActive();\n } catch (error) {\n return Promise.reject(error);\n }\n const sending = this.#send(context);\n this.#activeSends.add(sending);\n void sending.then(\n () => this.#activeSends.delete(sending),\n () => this.#activeSends.delete(sending),\n );\n return sending;\n }\n\n async #send(context: SendContext): Promise<SendResult> {\n const signal = this.#signalFor(context.signal);\n const threadId = this.#codec.resolveThreadId(context.fixture.target);\n const messageId = createMessageId(this.platform);\n signal.throwIfAborted();\n const events: Parameters<typeof appendRecordedInboundBatch>[1] = [\n {\n author: \"user\",\n id: messageId,\n provider: this.id,\n raw: {\n direction: \"outbound\",\n mode: context.mode,\n platform: this.platform,\n },\n recordedDirection: \"outbound\",\n sentAt: new Date().toISOString(),\n text: context.text,\n threadId,\n },\n ];\n\n if (context.mode !== \"send\" && context.fixture.target.behavior !== \"sink\") {\n await sleep(this.#config.loopback?.delayMs ?? 25, signal);\n signal.throwIfAborted();\n events.push({\n author: \"assistant\",\n id: `${messageId}-reply`,\n provider: this.id,\n raw: {\n direction: \"mock-reply\",\n mode: context.mode,\n platform: this.platform,\n },\n recordedDirection: \"inbound\",\n sentAt: new Date().toISOString(),\n text: mockReplyText({ platform: this.platform, text: context.text }),\n threadId,\n });\n }\n await appendRecordedInboundBatch(this.#recorderPath, events);\n\n return {\n accepted: true,\n messageId,\n threadId,\n };\n }\n\n async waitForInbound(context: WaitContext): Promise<InboundEnvelope | null> {\n this.#assertActive();\n const signal = this.#signalFor(context.signal);\n await this.#ensureWebhookServer();\n const target = this.normalizeTarget(context.fixture.target);\n const expectedAuthor = context.fixture.inboundMatch.author;\n const channelId = context.threadId ?? target.threadId ?? target.channelId;\n const excludedIds = new Set(context.excludeIds ?? []);\n const cursorKey = JSON.stringify([context.nonce, context.since, channelId, expectedAuthor]);\n const cursorState = this.#waitCursors.get(cursorKey) ?? { active: 0 };\n const cursor = cursorState.cursor\n ? cloneRecordedInboundCursor(cursorState.cursor)\n : createRecordedInboundCursor();\n cursorState.active++;\n this.#waitCursors.delete(cursorKey);\n this.#waitCursors.set(cursorKey, cursorState);\n pruneInactiveWaitCursors(this.#waitCursors);\n\n try {\n const event = await waitForRecordedInbound({\n cursor,\n filePath: this.#recorderPath,\n matches: (candidate) =>\n candidate.provider === this.id &&\n !excludedIds.has(candidate.id) &&\n (expectedAuthor === \"any\" || candidate.author === expectedAuthor) &&\n (this.#options.matchesThread ?? isAddressInChannel)(\n candidate.threadId,\n channelId,\n target,\n candidate.raw,\n ),\n recordedDirection: \"inbound\",\n since: context.since,\n signal,\n timeoutMs: context.timeoutMs,\n });\n if (\n this.#waitCursors.get(cursorKey) === cursorState &&\n cursorHasAdvanced(cursor, cursorState.cursor)\n ) {\n cursorState.cursor = cursor;\n }\n return event;\n } finally {\n cursorState.active--;\n if (\n cursorState.active === 0 &&\n !cursorState.cursor &&\n this.#waitCursors.get(cursorKey) === cursorState\n ) {\n this.#waitCursors.delete(cursorKey);\n }\n pruneInactiveWaitCursors(this.#waitCursors);\n }\n }\n\n async *watch(context: WatchContext): AsyncIterable<InboundEnvelope> {\n this.#assertActive();\n const signal = this.#signalFor(context.signal);\n await this.#ensureWebhookServer();\n const target = this.normalizeTarget(context.fixture.target);\n for await (const event of watchRecordedInbound({\n filePath: this.#recorderPath,\n matches: (entry) =>\n entry.provider === this.id &&\n (this.#options.matchesThread ?? isAddressInChannel)(\n entry.threadId,\n target.threadId ?? target.channelId,\n target,\n entry.raw,\n ),\n recordedDirection: \"inbound\",\n signal,\n since: context.since,\n })) {\n yield event;\n }\n }\n\n #beginCleanup(): void {\n if (this.#cleanupBegun) {\n return;\n }\n this.#cleanupBegun = true;\n this.#cleanupController.abort(this.#cleanedUpError());\n this.#waitCursors.clear();\n this.#serverClosing = this.#closeWebhookServer();\n void this.#serverClosing.catch(() => undefined);\n }\n\n #installCleanupFence(): void {\n const adapter = this as ProviderAdapter;\n if (adapter.beginCleanup) {\n return;\n }\n Object.defineProperty(adapter, \"beginCleanup\", {\n value: () => this.#beginCleanup(),\n });\n }\n\n async cleanup(): Promise<void> {\n this.#beginCleanup();\n this.#cleanupPromise ??= (async () => {\n const sends = [...this.#activeSends];\n const webhookHandlers = [...this.#activeWebhookHandlers];\n const results = await Promise.allSettled([\n this.#serverClosing,\n ...sends,\n settleWebhookHandlers(this.id, webhookHandlers, this.#webhookCleanupGraceMs),\n ]);\n const serverResult = results[0];\n if (serverResult?.status === \"rejected\") {\n throw serverResult.reason;\n }\n const webhookResult = results.at(-1);\n if (webhookResult?.status === \"rejected\") {\n throw webhookResult.reason;\n }\n })();\n await this.#cleanupPromise;\n }\n\n #handleWebhook(request: Request): Promise<Response> {\n if (this.#cleanupBegun) {\n return Promise.resolve(new Response(\"provider is shutting down\", { status: 503 }));\n }\n const admittedRequest = new Request(request, {\n signal: this.#signalFor(request.signal),\n });\n const handling = this.#handleAdmittedWebhook(admittedRequest);\n this.#activeWebhookHandlers.add(handling);\n void handling.then(\n () => this.#activeWebhookHandlers.delete(handling),\n () => this.#activeWebhookHandlers.delete(handling),\n );\n return handling;\n }\n\n async #handleAdmittedWebhook(request: Request): Promise<Response> {\n const rawBody = await request.text();\n const authenticationFailure = await this.#options.authenticateWebhookRequest?.(\n request,\n rawBody,\n );\n if (authenticationFailure) {\n return authenticationFailure;\n }\n const isJson = isJsonMediaType(request.headers.get(\"content-type\") ?? \"\");\n let rawPayload: unknown;\n if (isJson) {\n try {\n rawPayload = JSON.parse(rawBody) as unknown;\n } catch {\n this.#options.settleWebhookRequest?.({\n accepted: false,\n payload: undefined,\n rawBody,\n });\n return new Response(\"invalid JSON\", { status: 400 });\n }\n } else {\n rawPayload = rawBody;\n }\n let settled = false;\n const settle = (accepted: boolean) => {\n if (settled) {\n return;\n }\n settled = true;\n this.#options.settleWebhookRequest?.({ accepted, payload: rawPayload, rawBody });\n };\n const respond = (response: Response) => {\n settle(response.ok);\n return response;\n };\n let uncommittedResponse: Response | undefined;\n const settleCommittedAcceptance = () => {\n if (settled) {\n return;\n }\n settled = true;\n try {\n this.#options.settleWebhookRequest?.({\n accepted: true,\n payload: rawPayload,\n rawBody,\n });\n } catch (error) {\n reportPostCommitWebhookSettlementFailure(this.id, error);\n }\n };\n try {\n const directResponse = await this.#options.handleWebhookPayload?.(\n rawPayload,\n request,\n rawBody,\n );\n if (directResponse) {\n return respond(directResponse);\n }\n if (!isJson) {\n return respond(new Response(\"expected application/json\", { status: 415 }));\n }\n let payload: MockWebhookPayload;\n try {\n payload = normalizeWebhookPayload(\n this.#options.normalizeWebhookPayload\n ? this.#options.normalizeWebhookPayload(rawPayload)\n : rawPayload,\n );\n } catch (error) {\n if (error instanceof CrablineError && error.kind === \"inbound\") {\n return respond(new Response(ensureErrorMessage(error), { status: 400 }));\n }\n throw error;\n }\n\n const id = payload.message?.id ?? payload.id ?? createMessageId(this.platform);\n const threadId = payload.message?.threadId ?? payload.threadId;\n const text = payload.message?.text ?? payload.text;\n if (!threadId || text === undefined) {\n return respond(\n new Response(\"payload requires message.threadId and message.text\", { status: 400 }),\n );\n }\n const successResponse =\n (await this.#options.createWebhookSuccessResponse?.(rawPayload, id)) ??\n new Response(JSON.stringify({ ok: true, id }), {\n headers: { \"content-type\": \"application/json\" },\n status: 200,\n });\n uncommittedResponse = successResponse;\n await appendRecordedInbound(this.#recorderPath, {\n author: authorFromPayload(payload),\n id,\n provider: this.id,\n raw: payload.message?.raw ?? payload.raw ?? payload,\n recordedDirection: \"inbound\",\n sentAt: new Date().toISOString(),\n text,\n threadId,\n });\n settleCommittedAcceptance();\n uncommittedResponse = undefined;\n return successResponse;\n } catch (error) {\n void uncommittedResponse?.body?.cancel(error).catch(() => undefined);\n settle(false);\n throw error;\n }\n }\n\n async #ensureWebhookServer(): Promise<StartedWebhookServer> {\n if (this.#cleanupBegun) {\n throw this.#cleanedUpError();\n }\n if (this.#server) {\n return this.#server;\n }\n if (this.#serverStarting) {\n return await this.#serverStarting;\n }\n\n const webhook = this.#options.webhook;\n const host = webhook?.host ?? this.#options.defaultWebhook.host;\n const port = webhook?.port ?? this.#options.defaultWebhook.port;\n const webhookPath = webhook?.path ?? this.#options.defaultWebhook.path;\n const starting = (async () => {\n try {\n return await startWebhookServer({\n handle: (request) => this.#handleWebhook(request),\n host,\n methods:\n this.#options.webhookMethods ??\n (this.#options.handleWebhookPayload ? [\"GET\", \"POST\"] : [\"POST\"]),\n path: webhookPath,\n port,\n ...(this.#options.preflightWebhookRequest\n ? { preflight: this.#options.preflightWebhookRequest }\n : {}),\n });\n } catch (error) {\n throw new CrablineError(\n `${this.platform} local mock webhook server failed: ${ensureErrorMessage(error)}`,\n { cause: error, kind: \"connectivity\" },\n );\n }\n })();\n this.#serverStarting = starting;\n\n try {\n const server = await starting;\n if (this.#cleanupBegun) {\n throw this.#cleanedUpError();\n }\n this.#server = server;\n return server;\n } finally {\n if (this.#serverStarting === starting) {\n this.#serverStarting = null;\n }\n }\n }\n\n async #closeWebhookServer(): Promise<void> {\n let server = this.#server;\n if (!server && this.#serverStarting) {\n try {\n server = await this.#serverStarting;\n } catch {\n return;\n }\n }\n if (!server) {\n return;\n }\n\n if (this.#server === server) {\n this.#server = null;\n }\n await server.close();\n }\n\n #cleanedUpError(): CrablineError {\n return new CrablineError(`Provider \"${this.id}\" has been cleaned up.`, { kind: \"config\" });\n }\n\n #assertActive(): void {\n if (this.#cleanupBegun) {\n throw this.#cleanedUpError();\n }\n }\n\n #signalFor(signal?: AbortSignal): AbortSignal {\n return signal\n ? AbortSignal.any([signal, this.#cleanupController.signal])\n : this.#cleanupController.signal;\n }\n}\n"]}
@@ -2,5 +2,7 @@ export type NativeIdRule = {
2
2
  example: string;
3
3
  name: string;
4
4
  pattern: RegExp;
5
+ validate?: ((value: string) => boolean) | undefined;
5
6
  };
7
+ export declare function matchesNativeId(value: string, rule: NativeIdRule): boolean;
6
8
  export declare function numericNativeId(value: number | bigint): string | undefined;
@@ -1,3 +1,6 @@
1
+ export function matchesNativeId(value, rule) {
2
+ return rule.pattern.test(value) && (rule.validate?.(value) ?? true);
3
+ }
1
4
  export function numericNativeId(value) {
2
5
  if (typeof value === "number" && !Number.isSafeInteger(value)) {
3
6
  return undefined;
@@ -1 +1 @@
1
- {"version":3,"file":"native-ids.js","sourceRoot":"","sources":["../../../src/providers/native-ids.ts"],"names":[],"mappings":"AAMA,MAAM,UAAU,eAAe,CAAC,KAAsB;IACpD,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,KAAK,CAAC,EAAE,CAAC;QAC9D,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,OAAO,KAAK,CAAC,QAAQ,EAAE,CAAC;AAC1B,CAAC","sourcesContent":["export type NativeIdRule = {\n example: string;\n name: string;\n pattern: RegExp;\n};\n\nexport function numericNativeId(value: number | bigint): string | undefined {\n if (typeof value === \"number\" && !Number.isSafeInteger(value)) {\n return undefined;\n }\n return value.toString();\n}\n"]}
1
+ {"version":3,"file":"native-ids.js","sourceRoot":"","sources":["../../../src/providers/native-ids.ts"],"names":[],"mappings":"AAOA,MAAM,UAAU,eAAe,CAAC,KAAa,EAAE,IAAkB;IAC/D,OAAO,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,CAAC;AACtE,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,KAAsB;IACpD,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,KAAK,CAAC,EAAE,CAAC;QAC9D,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,OAAO,KAAK,CAAC,QAAQ,EAAE,CAAC;AAC1B,CAAC","sourcesContent":["export type NativeIdRule = {\n example: string;\n name: string;\n pattern: RegExp;\n validate?: ((value: string) => boolean) | undefined;\n};\n\nexport function matchesNativeId(value: string, rule: NativeIdRule): boolean {\n return rule.pattern.test(value) && (rule.validate?.(value) ?? true);\n}\n\nexport function numericNativeId(value: number | bigint): string | undefined {\n if (typeof value === \"number\" && !Number.isSafeInteger(value)) {\n return undefined;\n }\n return value.toString();\n}\n"]}
@@ -1,3 +1,4 @@
1
+ import { type WindowsDirectorySecuritySnapshot } from "../platform/windows-acl.js";
1
2
  import type { InboundEnvelope } from "./types.js";
2
3
  export type RecordableInboundEnvelope = InboundEnvelope & {
3
4
  recordedDirection?: "inbound" | "outbound";
@@ -5,13 +6,18 @@ export type RecordableInboundEnvelope = InboundEnvelope & {
5
6
  export type RecordedInboundEnvelope = RecordableInboundEnvelope & {
6
7
  recordedAt: string;
7
8
  };
9
+ export declare class ProviderRecorderCommittedError extends AggregateError {
10
+ readonly committed = true;
11
+ readonly indeterminate = true;
12
+ constructor(filePath: string, cause: unknown, relatedErrors?: unknown[]);
13
+ }
8
14
  type IncrementalReadState = {
9
15
  caughtUp: boolean;
10
16
  continuity: Buffer;
11
17
  generation: number;
12
18
  identity: {
13
- dev: number;
14
- ino: number;
19
+ dev: bigint;
20
+ ino: bigint;
15
21
  } | undefined;
16
22
  offset: number;
17
23
  pending: Buffer;
@@ -23,8 +29,13 @@ export type RecordedInboundCursor = {
23
29
  };
24
30
  export declare function createRecordedInboundCursor(): RecordedInboundCursor;
25
31
  export declare function cloneRecordedInboundCursor(cursor: RecordedInboundCursor): RecordedInboundCursor;
32
+ export declare function secureProviderRecorderLockRoot(root: string, currentUserId: number | undefined, options?: {
33
+ platform?: NodeJS.Platform;
34
+ createWindowsDirectory?: (directoryPath: string) => Promise<void>;
35
+ readWindowsDirectorySecuritySnapshot?: (directoryPath: string) => Promise<WindowsDirectorySecuritySnapshot>;
36
+ }): Promise<string>;
26
37
  export declare function appendRecordedInbound(filePath: string, event: RecordableInboundEnvelope): Promise<RecordedInboundEnvelope>;
27
- export declare function appendRecordedInboundBatch(filePath: string, events: InboundEnvelope[]): Promise<RecordedInboundEnvelope[]>;
38
+ export declare function appendRecordedInboundBatch(filePath: string, events: RecordableInboundEnvelope[]): Promise<RecordedInboundEnvelope[]>;
28
39
  export declare function readRecordedInbound(filePath: string): Promise<RecordedInboundEnvelope[]>;
29
40
  export declare function waitForRecordedInbound(params: {
30
41
  cursor?: RecordedInboundCursor | undefined;