@fdcn/sourcefed 0.2.2 → 0.2.3

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 (29) hide show
  1. package/README.md +2 -2
  2. package/dist/chunk-UOTCX4ME.js +3963 -0
  3. package/dist/cli.js +474 -361
  4. package/dist/data/core/SKILL.md +10 -0
  5. package/dist/data/core/references/commands.md +1 -0
  6. package/dist.dts/packages/cli/src/commands/daemon.d.ts +1 -0
  7. package/dist.dts/packages/cli/src/commands/mcp.d.ts +1 -0
  8. package/dist.dts/packages/cli/src/commands/monitor/ack.d.ts +2 -0
  9. package/dist.dts/packages/cli/src/commands/monitor/create.d.ts +2 -0
  10. package/dist.dts/packages/cli/src/commands/monitor/events.d.ts +2 -0
  11. package/dist.dts/packages/cli/src/commands/monitor/follow.d.ts +2 -0
  12. package/dist.dts/packages/cli/src/commands/monitor/index.d.ts +10 -0
  13. package/dist.dts/packages/cli/src/commands/monitor/list.d.ts +2 -0
  14. package/dist.dts/packages/cli/src/commands/monitor/logs.d.ts +2 -0
  15. package/dist.dts/packages/cli/src/commands/monitor/remove.d.ts +2 -0
  16. package/dist.dts/packages/cli/src/commands/monitor/sources.d.ts +2 -0
  17. package/dist.dts/packages/cli/src/commands/monitor/start.d.ts +2 -0
  18. package/dist.dts/packages/cli/src/commands/monitor/status.d.ts +2 -0
  19. package/dist.dts/packages/cli/src/commands/monitor/stop.d.ts +2 -0
  20. package/dist.dts/packages/cli/src/commands/monitor.d.ts +1 -0
  21. package/dist.dts/packages/cli/src/commands/setup.d.ts +1 -0
  22. package/dist.dts/packages/cli/src/follow.test.d.ts +1 -0
  23. package/dist.dts/packages/cli/src/utils/daemon.d.ts +2 -0
  24. package/dist.dts/packages/cli/src/utils/flags.d.ts +1 -0
  25. package/dist.dts/packages/cli/src/utils/format.d.ts +8 -0
  26. package/dist.dts/packages/cli/src/utils/net.d.ts +1 -0
  27. package/dist.dts/packages/cli/src/utils/setup.d.ts +2 -0
  28. package/dist.dts/packages/cli/src/utils/webhooks.d.ts +2 -0
  29. package/package.json +1 -1
@@ -0,0 +1,3963 @@
1
+ // packages/daemon/dist/index.js
2
+ import { link, mkdir, open, readFile, rename, rm, writeFile } from "node:fs/promises";
3
+ import os from "node:os";
4
+ import path from "node:path";
5
+ import { link as link2, mkdir as mkdir2, open as open2, readFile as readFile2, rename as rename2, rm as rm2, writeFile as writeFile2 } from "node:fs/promises";
6
+ import os2 from "node:os";
7
+ import path2 from "node:path";
8
+ import { inflateRawSync } from "node:zlib";
9
+ import { createHmac, timingSafeEqual } from "node:crypto";
10
+ import { link as link3, mkdir as mkdir3, open as open3, readFile as readFile3, rename as rename3, rm as rm3, writeFile as writeFile3 } from "node:fs/promises";
11
+ import os3 from "node:os";
12
+ import path3 from "node:path";
13
+ import { link as link4, mkdir as mkdir4, open as open4, readFile as readFile4, rename as rename4, rm as rm4, writeFile as writeFile4 } from "node:fs/promises";
14
+ import os4 from "node:os";
15
+ import path4 from "node:path";
16
+ import { createHmac as createHmac2 } from "node:crypto";
17
+ import { timingSafeEqual as timingSafeEqual2 } from "node:crypto";
18
+ import os5 from "node:os";
19
+ import path5 from "node:path";
20
+ import { createServer } from "node:http";
21
+ import { Readable } from "node:stream";
22
+ import { spawn } from "node:child_process";
23
+ import path6 from "node:path";
24
+ var EMOJI_REGEX = new RegExp("^(?:[\\u{1F1E6}-\\u{1F1FF}]{2}|\\u{1F3F4}[\\u{E0061}-\\u{E007A}]{2}[\\u{E0030}-\\u{E0039}\\u{E0061}-\\u{E007A}]{1,3}\\u{E007F}|(?:\\p{Emoji}\\uFE0F\\u20E3?|\\p{Emoji_Modifier_Base}\\p{Emoji_Modifier}?|(?![\\p{Emoji_Modifier_Base}\\u{1F1E6}-\\u{1F1FF}])\\p{Emoji_Presentation})(?:\\u200D(?:\\p{Emoji}\\uFE0F\\u20E3?|\\p{Emoji_Modifier_Base}\\p{Emoji_Modifier}?|(?![\\p{Emoji_Modifier_Base}\\u{1F1E6}-\\u{1F1FF}])\\p{Emoji_Presentation}))*)+$", "u");
25
+ var MAX_WEBHOOK_BODY_BYTES = 1024 * 1024;
26
+ var store$42;
27
+ var DEFAULT_CONFIG2 = {
28
+ lang: void 0,
29
+ message: void 0,
30
+ abortEarly: void 0,
31
+ abortPipeEarly: void 0
32
+ };
33
+ // @__NO_SIDE_EFFECTS__
34
+ function getGlobalConfig2(config$1) {
35
+ if (!config$1 && !store$42) return DEFAULT_CONFIG2;
36
+ return {
37
+ lang: config$1?.lang ?? store$42?.lang,
38
+ message: config$1?.message,
39
+ abortEarly: config$1?.abortEarly ?? store$42?.abortEarly,
40
+ abortPipeEarly: config$1?.abortPipeEarly ?? store$42?.abortPipeEarly
41
+ };
42
+ }
43
+ var store$32;
44
+ // @__NO_SIDE_EFFECTS__
45
+ function getGlobalMessage2(lang) {
46
+ return store$32?.get(lang);
47
+ }
48
+ var store$22;
49
+ // @__NO_SIDE_EFFECTS__
50
+ function getSchemaMessage2(lang) {
51
+ return store$22?.get(lang);
52
+ }
53
+ var store$12;
54
+ // @__NO_SIDE_EFFECTS__
55
+ function getSpecificMessage2(reference, lang) {
56
+ return store$12?.get(reference)?.get(lang);
57
+ }
58
+ // @__NO_SIDE_EFFECTS__
59
+ function _stringify2(input) {
60
+ const type = typeof input;
61
+ if (type === "string") return `"${input}"`;
62
+ if (type === "number" || type === "bigint" || type === "boolean") return `${input}`;
63
+ if (type === "object" || type === "function") return (input && Object.getPrototypeOf(input)?.constructor?.name) ?? "null";
64
+ return type;
65
+ }
66
+ function _addIssue2(context, label, dataset, config$1, other) {
67
+ const input = other && "input" in other ? other.input : dataset.value;
68
+ const expected = other?.expected ?? context.expects ?? null;
69
+ const received = other?.received ?? /* @__PURE__ */ _stringify2(input);
70
+ const issue = {
71
+ kind: context.kind,
72
+ type: context.type,
73
+ input,
74
+ expected,
75
+ received,
76
+ message: `Invalid ${label}: ${expected ? `Expected ${expected} but r` : "R"}eceived ${received}`,
77
+ requirement: context.requirement,
78
+ path: other?.path,
79
+ issues: other?.issues,
80
+ lang: config$1.lang,
81
+ abortEarly: config$1.abortEarly,
82
+ abortPipeEarly: config$1.abortPipeEarly
83
+ };
84
+ const isSchema = context.kind === "schema";
85
+ const message$1 = other?.message ?? context.message ?? /* @__PURE__ */ getSpecificMessage2(context.reference, issue.lang) ?? (isSchema ? /* @__PURE__ */ getSchemaMessage2(issue.lang) : null) ?? config$1.message ?? /* @__PURE__ */ getGlobalMessage2(issue.lang);
86
+ if (message$1 !== void 0) issue.message = typeof message$1 === "function" ? message$1(issue) : message$1;
87
+ if (isSchema) dataset.typed = false;
88
+ if (dataset.issues) dataset.issues.push(issue);
89
+ else dataset.issues = [issue];
90
+ }
91
+ var _standardCache2 = /* @__PURE__ */ new WeakMap();
92
+ // @__NO_SIDE_EFFECTS__
93
+ function _getStandardProps2(context) {
94
+ let cached = _standardCache2.get(context);
95
+ if (!cached) {
96
+ cached = {
97
+ version: 1,
98
+ vendor: "valibot",
99
+ validate(value$1) {
100
+ return context["~run"]({ value: value$1 }, /* @__PURE__ */ getGlobalConfig2());
101
+ }
102
+ };
103
+ _standardCache2.set(context, cached);
104
+ }
105
+ return cached;
106
+ }
107
+ var EMOJI_REGEX2 = new RegExp("^(?:[\\u{1F1E6}-\\u{1F1FF}]{2}|\\u{1F3F4}[\\u{E0061}-\\u{E007A}]{2}[\\u{E0030}-\\u{E0039}\\u{E0061}-\\u{E007A}]{1,3}\\u{E007F}|(?:\\p{Emoji}\\uFE0F\\u20E3?|\\p{Emoji_Modifier_Base}\\p{Emoji_Modifier}?|(?![\\p{Emoji_Modifier_Base}\\u{1F1E6}-\\u{1F1FF}])\\p{Emoji_Presentation})(?:\\u200D(?:\\p{Emoji}\\uFE0F\\u20E3?|\\p{Emoji_Modifier_Base}\\p{Emoji_Modifier}?|(?![\\p{Emoji_Modifier_Base}\\u{1F1E6}-\\u{1F1FF}])\\p{Emoji_Presentation}))*)+$", "u");
108
+ // @__NO_SIDE_EFFECTS__
109
+ function getFallback2(schema, dataset, config$1) {
110
+ return typeof schema.fallback === "function" ? schema.fallback(dataset, config$1) : schema.fallback;
111
+ }
112
+ // @__NO_SIDE_EFFECTS__
113
+ function getDefault2(schema, dataset, config$1) {
114
+ return typeof schema.default === "function" ? schema.default(dataset, config$1) : schema.default;
115
+ }
116
+ // @__NO_SIDE_EFFECTS__
117
+ function array2(item, message$1) {
118
+ return {
119
+ kind: "schema",
120
+ type: "array",
121
+ reference: array2,
122
+ expects: "Array",
123
+ async: false,
124
+ item,
125
+ message: message$1,
126
+ get "~standard"() {
127
+ return /* @__PURE__ */ _getStandardProps2(this);
128
+ },
129
+ "~run"(dataset, config$1) {
130
+ const input = dataset.value;
131
+ if (Array.isArray(input)) {
132
+ dataset.typed = true;
133
+ dataset.value = [];
134
+ for (let key = 0; key < input.length; key++) {
135
+ const value$1 = input[key];
136
+ const itemDataset = this.item["~run"]({ value: value$1 }, config$1);
137
+ if (itemDataset.issues) {
138
+ const pathItem = {
139
+ type: "array",
140
+ origin: "value",
141
+ input,
142
+ key,
143
+ value: value$1
144
+ };
145
+ for (const issue of itemDataset.issues) {
146
+ if (issue.path) issue.path.unshift(pathItem);
147
+ else issue.path = [pathItem];
148
+ dataset.issues?.push(issue);
149
+ }
150
+ if (!dataset.issues) dataset.issues = itemDataset.issues;
151
+ if (config$1.abortEarly) {
152
+ dataset.typed = false;
153
+ break;
154
+ }
155
+ }
156
+ if (!itemDataset.typed) dataset.typed = false;
157
+ dataset.value.push(itemDataset.value);
158
+ }
159
+ } else _addIssue2(this, "type", dataset, config$1);
160
+ return dataset;
161
+ }
162
+ };
163
+ }
164
+ // @__NO_SIDE_EFFECTS__
165
+ function boolean2(message$1) {
166
+ return {
167
+ kind: "schema",
168
+ type: "boolean",
169
+ reference: boolean2,
170
+ expects: "boolean",
171
+ async: false,
172
+ message: message$1,
173
+ get "~standard"() {
174
+ return /* @__PURE__ */ _getStandardProps2(this);
175
+ },
176
+ "~run"(dataset, config$1) {
177
+ if (typeof dataset.value === "boolean") dataset.typed = true;
178
+ else _addIssue2(this, "type", dataset, config$1);
179
+ return dataset;
180
+ }
181
+ };
182
+ }
183
+ // @__NO_SIDE_EFFECTS__
184
+ function object2(entries$1, message$1) {
185
+ return {
186
+ kind: "schema",
187
+ type: "object",
188
+ reference: object2,
189
+ expects: "Object",
190
+ async: false,
191
+ entries: entries$1,
192
+ message: message$1,
193
+ get "~standard"() {
194
+ return /* @__PURE__ */ _getStandardProps2(this);
195
+ },
196
+ "~run"(dataset, config$1) {
197
+ const input = dataset.value;
198
+ if (input && typeof input === "object") {
199
+ dataset.typed = true;
200
+ dataset.value = {};
201
+ for (const key in this.entries) {
202
+ const valueSchema = this.entries[key];
203
+ if (key in input || (valueSchema.type === "exact_optional" || valueSchema.type === "optional" || valueSchema.type === "nullish") && valueSchema.default !== void 0) {
204
+ const value$1 = key in input ? input[key] : /* @__PURE__ */ getDefault2(valueSchema);
205
+ const valueDataset = valueSchema["~run"]({ value: value$1 }, config$1);
206
+ if (valueDataset.issues) {
207
+ const pathItem = {
208
+ type: "object",
209
+ origin: "value",
210
+ input,
211
+ key,
212
+ value: value$1
213
+ };
214
+ for (const issue of valueDataset.issues) {
215
+ if (issue.path) issue.path.unshift(pathItem);
216
+ else issue.path = [pathItem];
217
+ dataset.issues?.push(issue);
218
+ }
219
+ if (!dataset.issues) dataset.issues = valueDataset.issues;
220
+ if (config$1.abortEarly) {
221
+ dataset.typed = false;
222
+ break;
223
+ }
224
+ }
225
+ if (!valueDataset.typed) dataset.typed = false;
226
+ dataset.value[key] = valueDataset.value;
227
+ } else if (valueSchema.fallback !== void 0) dataset.value[key] = /* @__PURE__ */ getFallback2(valueSchema);
228
+ else if (valueSchema.type !== "exact_optional" && valueSchema.type !== "optional" && valueSchema.type !== "nullish") {
229
+ _addIssue2(this, "key", dataset, config$1, {
230
+ input: void 0,
231
+ expected: `"${key}"`,
232
+ path: [{
233
+ type: "object",
234
+ origin: "key",
235
+ input,
236
+ key,
237
+ value: input[key]
238
+ }]
239
+ });
240
+ if (config$1.abortEarly) break;
241
+ }
242
+ }
243
+ } else _addIssue2(this, "type", dataset, config$1);
244
+ return dataset;
245
+ }
246
+ };
247
+ }
248
+ // @__NO_SIDE_EFFECTS__
249
+ function optional(wrapped, default_) {
250
+ return {
251
+ kind: "schema",
252
+ type: "optional",
253
+ reference: optional,
254
+ expects: `(${wrapped.expects} | undefined)`,
255
+ async: false,
256
+ wrapped,
257
+ default: default_,
258
+ get "~standard"() {
259
+ return /* @__PURE__ */ _getStandardProps2(this);
260
+ },
261
+ "~run"(dataset, config$1) {
262
+ if (dataset.value === void 0) {
263
+ if (this.default !== void 0) dataset.value = /* @__PURE__ */ getDefault2(this, dataset, config$1);
264
+ if (dataset.value === void 0) {
265
+ dataset.typed = true;
266
+ return dataset;
267
+ }
268
+ }
269
+ return this.wrapped["~run"](dataset, config$1);
270
+ }
271
+ };
272
+ }
273
+ // @__NO_SIDE_EFFECTS__
274
+ function string2(message$1) {
275
+ return {
276
+ kind: "schema",
277
+ type: "string",
278
+ reference: string2,
279
+ expects: "string",
280
+ async: false,
281
+ message: message$1,
282
+ get "~standard"() {
283
+ return /* @__PURE__ */ _getStandardProps2(this);
284
+ },
285
+ "~run"(dataset, config$1) {
286
+ if (typeof dataset.value === "string") dataset.typed = true;
287
+ else _addIssue2(this, "type", dataset, config$1);
288
+ return dataset;
289
+ }
290
+ };
291
+ }
292
+ // @__NO_SIDE_EFFECTS__
293
+ function unknown2() {
294
+ return {
295
+ kind: "schema",
296
+ type: "unknown",
297
+ reference: unknown2,
298
+ expects: "unknown",
299
+ async: false,
300
+ get "~standard"() {
301
+ return /* @__PURE__ */ _getStandardProps2(this);
302
+ },
303
+ "~run"(dataset) {
304
+ dataset.typed = true;
305
+ return dataset;
306
+ }
307
+ };
308
+ }
309
+ // @__NO_SIDE_EFFECTS__
310
+ function safeParse2(schema, input, config$1) {
311
+ const dataset = schema["~run"]({ value: input }, /* @__PURE__ */ getGlobalConfig2(config$1));
312
+ return {
313
+ typed: dataset.typed,
314
+ success: !dataset.issues,
315
+ output: dataset.value,
316
+ issues: dataset.issues
317
+ };
318
+ }
319
+ var Monitor = class {
320
+ type;
321
+ schema;
322
+ poll;
323
+ webhook;
324
+ constructor(options) {
325
+ this.type = options.type;
326
+ this.schema = options.schema;
327
+ }
328
+ validateSource(source) {
329
+ const result = /* @__PURE__ */ safeParse2(this.schema, source);
330
+ if (result.success) return result.output;
331
+ throw new Error(`invalid ${this.type} monitor source`);
332
+ }
333
+ initialDelivery(source) {
334
+ this.validateSource(source);
335
+ if (this.webhook && this.webhook.preferred && this.webhook.configured()) return "webhook";
336
+ if (this.poll) return "poll";
337
+ if (this.webhook) return "webhook";
338
+ throw new Error(`source ${this.type} has no configured transport`);
339
+ }
340
+ resolveDelivery(monitor) {
341
+ let webhookHealthy = false;
342
+ if (this.webhook) webhookHealthy = this.webhook.isHealthy(monitor);
343
+ if (monitor.delivery === "webhook") {
344
+ if (this.webhook && webhookHealthy) return "webhook";
345
+ if (this.poll) return "poll";
346
+ if (this.webhook) return "webhook";
347
+ return void 0;
348
+ }
349
+ if (this.webhook && webhookHealthy) return "webhook";
350
+ if (this.poll) return "poll";
351
+ if (this.webhook) return "webhook";
352
+ return void 0;
353
+ }
354
+ async tick(context, record2) {
355
+ const fresh = await context.service.get(record2.id);
356
+ if (!fresh || !fresh.enabled) return 0;
357
+ if (this.webhook) await this.webhook.retryPending(context, this, fresh);
358
+ const current = await context.service.get(record2.id);
359
+ if (!current || !current.enabled) return 0;
360
+ const nextDelivery = this.resolveDelivery(current);
361
+ if (!nextDelivery) return 0;
362
+ if (nextDelivery !== current.delivery) {
363
+ await context.service.setDelivery(current.id, nextDelivery);
364
+ current.delivery = nextDelivery;
365
+ }
366
+ if (current.delivery !== "poll" || !this.poll) return 0;
367
+ const lastAttemptAt = Number(current.cursors.__lastAttemptAt ?? current.cursors.__lastPolledAt ?? 0);
368
+ const pollIntervalSec = current.pollIntervalSec || 60;
369
+ if (lastAttemptAt && Date.now() - lastAttemptAt < pollIntervalSec * 1e3) return 0;
370
+ return this.poll.run(context, this, current);
371
+ }
372
+ async deliver(context, record2, event, deliveryId) {
373
+ if (!this.webhook) return 0;
374
+ return this.webhook.deliver(context, this, record2, event, deliveryId);
375
+ }
376
+ unresponsive(record2, now = Date.now()) {
377
+ if (!record2.enabled) return false;
378
+ if (record2.delivery === "poll") {
379
+ if (!this.poll) return true;
380
+ return this.poll.isUnresponsive(record2, now);
381
+ }
382
+ if (!this.webhook) return true;
383
+ return this.webhook.isUnresponsive(record2, now);
384
+ }
385
+ };
386
+ var RoutedEventCursorSchema = /* @__PURE__ */ array2(/* @__PURE__ */ string2());
387
+ function eventDeliveryId(event) {
388
+ if (event.id) return event.id;
389
+ return `${event.kind}:${event.at}:${event.summary}`;
390
+ }
391
+ var activePolls = /* @__PURE__ */ new Set();
392
+ var PollMonitor = class {
393
+ constructor(options) {
394
+ this.options = options;
395
+ }
396
+ async run(context, source, record2) {
397
+ if (activePolls.has(record2.id)) return 0;
398
+ activePolls.add(record2.id);
399
+ try {
400
+ const fresh = await context.service.get(record2.id);
401
+ if (!fresh) return 0;
402
+ const sourceRecord = source.validateSource(fresh.source);
403
+ const cursorKey = source.key(sourceRecord);
404
+ const result = await this.options.run(sourceRecord, fresh.cursors[cursorKey]);
405
+ const current = await context.service.get(record2.id);
406
+ if (!current || !current.enabled) return 0;
407
+ const parsedRoutedIds = /* @__PURE__ */ safeParse2(RoutedEventCursorSchema, fresh.cursors.__routedEventIds);
408
+ let routedIds = [];
409
+ if (parsedRoutedIds.success) routedIds = parsedRoutedIds.output;
410
+ let routeFailed = false;
411
+ for (const event of result.events) {
412
+ const eventId = eventDeliveryId(event);
413
+ if (routedIds.includes(eventId)) continue;
414
+ const routed = await context.sink.deliver({ monitor: fresh, event });
415
+ if (!routed.ok) {
416
+ routeFailed = true;
417
+ console.error(`[sourcefed] route failed for ${fresh.id}: ${routed.error}`);
418
+ continue;
419
+ }
420
+ if (!routedIds.includes(eventId)) routedIds.push(eventId);
421
+ }
422
+ if (routeFailed) {
423
+ await context.service.updateCursor(fresh, "__routedEventIds", routedIds);
424
+ return 0;
425
+ }
426
+ await context.service.updateCursorValue(fresh, cursorKey, (current2) => {
427
+ if (this.options.mergeCursor) return this.options.mergeCursor(current2, result.cursor);
428
+ return result.cursor;
429
+ });
430
+ await context.service.updateCursor(fresh, "__routedEventIds", []);
431
+ if (result.terminal) await context.service.remove(fresh.id);
432
+ await context.service.updateCursor(fresh, "__lastPolledAt", Date.now());
433
+ return result.events.length;
434
+ } finally {
435
+ activePolls.delete(record2.id);
436
+ }
437
+ }
438
+ isUnresponsive(record2, now) {
439
+ const intervalMs = Math.max((record2.pollIntervalSec || 60) * 3e3, 12e4);
440
+ const lastPolledAt = Number(record2.cursors.__lastPolledAt ?? 0);
441
+ if (lastPolledAt > 0) return now - lastPolledAt > intervalMs;
442
+ return now - Date.parse(record2.createdAt) > intervalMs;
443
+ }
444
+ };
445
+ function isRecord(value) {
446
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
447
+ }
448
+ function mergeCursors(current, result) {
449
+ if (Array.isArray(current) && Array.isArray(result)) {
450
+ return [.../* @__PURE__ */ new Set([...current, ...result])];
451
+ }
452
+ if (isRecord(current) && isRecord(result)) {
453
+ const merged = { ...current };
454
+ for (const [key, value] of Object.entries(result)) {
455
+ if (value === void 0) continue;
456
+ merged[key] = key in current ? mergeCursors(current[key], value) : value;
457
+ }
458
+ return merged;
459
+ }
460
+ return result === void 0 ? current : result;
461
+ }
462
+ var WEBHOOK_STALE_MS = 9e4;
463
+ var WEBHOOK_STARTUP_GRACE_MS = 12e4;
464
+ var PendingWebhookEventSchema = /* @__PURE__ */ object2({
465
+ deliveryId: /* @__PURE__ */ string2(),
466
+ event: /* @__PURE__ */ object2({
467
+ source: /* @__PURE__ */ unknown2(),
468
+ kind: /* @__PURE__ */ string2(),
469
+ id: /* @__PURE__ */ optional(/* @__PURE__ */ string2()),
470
+ at: /* @__PURE__ */ string2(),
471
+ summary: /* @__PURE__ */ string2(),
472
+ body: /* @__PURE__ */ optional(/* @__PURE__ */ string2()),
473
+ actionable: /* @__PURE__ */ boolean2(),
474
+ terminal: /* @__PURE__ */ optional(/* @__PURE__ */ boolean2())
475
+ })
476
+ });
477
+ var PendingWebhookEventsSchema = /* @__PURE__ */ array2(PendingWebhookEventSchema);
478
+ var pendingDeliveries = /* @__PURE__ */ new Set();
479
+ var PENDING_WEBHOOK_EVENTS = "__pendingWebhookEvents";
480
+ var WebhookMonitor = class {
481
+ path;
482
+ preferred;
483
+ configured;
484
+ verify;
485
+ acknowledgeBeforeDelivery;
486
+ challenge;
487
+ deliveryId;
488
+ eventName;
489
+ parse;
490
+ updateCursor;
491
+ constructor(options) {
492
+ this.path = options.path;
493
+ this.preferred = options.preferred ?? true;
494
+ this.configured = options.configured;
495
+ this.verify = options.verify;
496
+ this.acknowledgeBeforeDelivery = options.acknowledgeBeforeDelivery ?? false;
497
+ this.challenge = options.challenge;
498
+ this.deliveryId = options.deliveryId;
499
+ this.eventName = options.eventName;
500
+ this.parse = options.parse;
501
+ this.updateCursor = options.updateCursor;
502
+ }
503
+ isHealthy(record2) {
504
+ if (!this.preferred || !this.configured()) return false;
505
+ const heartbeatAt = Number(record2.cursors.__webhookHeartbeatAt ?? 0);
506
+ if (heartbeatAt > 0) return Date.now() - heartbeatAt <= WEBHOOK_STALE_MS;
507
+ return Date.now() - Date.parse(record2.createdAt) <= WEBHOOK_STARTUP_GRACE_MS;
508
+ }
509
+ isUnresponsive(record2, now) {
510
+ const heartbeatAt = Number(record2.cursors.__webhookHeartbeatAt ?? 0);
511
+ if (heartbeatAt > 0) return now - heartbeatAt > WEBHOOK_STALE_MS;
512
+ return now - Date.parse(record2.createdAt) > WEBHOOK_STARTUP_GRACE_MS;
513
+ }
514
+ async retryPending(context, source, record2) {
515
+ const fresh = await context.service.get(record2.id);
516
+ if (!fresh || !fresh.enabled) return 0;
517
+ const parsed = /* @__PURE__ */ safeParse2(PendingWebhookEventsSchema, fresh.cursors[PENDING_WEBHOOK_EVENTS]);
518
+ if (!parsed.success) return 0;
519
+ let delivered = 0;
520
+ for (const pending of parsed.output) {
521
+ const current = await context.service.get(record2.id);
522
+ if (!current || !current.enabled) break;
523
+ const cursorKey = `webhook:${pending.deliveryId}`;
524
+ if (current.cursors[cursorKey]) {
525
+ await this.removePending(context, current, pending.deliveryId);
526
+ continue;
527
+ }
528
+ delivered += await this.deliver(context, source, current, pending.event, pending.deliveryId);
529
+ }
530
+ return delivered;
531
+ }
532
+ async addPending(context, monitor, deliveryId, event) {
533
+ await context.service.updateCursorValue(monitor, PENDING_WEBHOOK_EVENTS, (cursor) => {
534
+ const parsed = /* @__PURE__ */ safeParse2(PendingWebhookEventsSchema, cursor);
535
+ let pending = [];
536
+ if (parsed.success) pending = parsed.output;
537
+ if (pending.some((entry) => entry.deliveryId === deliveryId)) return pending;
538
+ pending.push({ deliveryId, event });
539
+ return pending;
540
+ });
541
+ }
542
+ async removePending(context, monitor, deliveryId) {
543
+ await context.service.updateCursorValue(monitor, PENDING_WEBHOOK_EVENTS, (cursor) => {
544
+ const parsed = /* @__PURE__ */ safeParse2(PendingWebhookEventsSchema, cursor);
545
+ if (!parsed.success) return [];
546
+ return parsed.output.filter((entry) => entry.deliveryId !== deliveryId);
547
+ });
548
+ }
549
+ async deliver(context, source, record2, event, deliveryId) {
550
+ const fresh = await context.service.get(record2.id);
551
+ if (!fresh || !fresh.enabled) return 0;
552
+ const freshSource = source.validateSource(fresh.source);
553
+ const eventSource = source.validateSource(event.source);
554
+ if (source.key(freshSource) !== source.key(eventSource)) return 0;
555
+ if (event.kind === "ci" && !event.actionable) return 0;
556
+ const cursorKey = `webhook:${deliveryId}`;
557
+ const reservationKey = `${fresh.id}:${deliveryId}`;
558
+ if (pendingDeliveries.has(reservationKey)) return 0;
559
+ if (fresh.cursors[cursorKey]) {
560
+ await this.removePending(context, fresh, deliveryId);
561
+ return 0;
562
+ }
563
+ pendingDeliveries.add(reservationKey);
564
+ try {
565
+ await this.addPending(context, fresh, deliveryId, event);
566
+ if (this.updateCursor) {
567
+ const sourceCursorKey = source.key(freshSource);
568
+ await context.service.updateCursorValue(fresh, sourceCursorKey, (cursor) => this.updateCursor(event, cursor));
569
+ }
570
+ const result = await context.sink.deliver({ monitor: fresh, event });
571
+ if (!result.ok) {
572
+ console.error(`[sourcefed] webhook route failed for ${fresh.id}: ${result.error}`);
573
+ return 0;
574
+ }
575
+ await context.service.setDelivery(fresh.id, "webhook");
576
+ await context.service.updateCursor(fresh, cursorKey, true);
577
+ await this.removePending(context, fresh, deliveryId);
578
+ if (event.terminal) await context.service.remove(fresh.id);
579
+ return 1;
580
+ } finally {
581
+ pendingDeliveries.delete(reservationKey);
582
+ }
583
+ }
584
+ };
585
+ var MAX_WEBHOOK_BODY_BYTES2 = 1024 * 1024;
586
+ function toMonitorEvent(event, source) {
587
+ return {
588
+ source,
589
+ kind: event.kind,
590
+ id: event.id,
591
+ at: event.at,
592
+ summary: event.summary,
593
+ body: event.body,
594
+ actionable: event.actionable,
595
+ terminal: event.terminal
596
+ };
597
+ }
598
+ var store$422;
599
+ var DEFAULT_CONFIG22 = {
600
+ lang: void 0,
601
+ message: void 0,
602
+ abortEarly: void 0,
603
+ abortPipeEarly: void 0
604
+ };
605
+ // @__NO_SIDE_EFFECTS__
606
+ function getGlobalConfig22(config$1) {
607
+ if (!config$1 && !store$422) return DEFAULT_CONFIG22;
608
+ return {
609
+ lang: config$1?.lang ?? store$422?.lang,
610
+ message: config$1?.message,
611
+ abortEarly: config$1?.abortEarly ?? store$422?.abortEarly,
612
+ abortPipeEarly: config$1?.abortPipeEarly ?? store$422?.abortPipeEarly
613
+ };
614
+ }
615
+ var store$322;
616
+ // @__NO_SIDE_EFFECTS__
617
+ function getGlobalMessage22(lang) {
618
+ return store$322?.get(lang);
619
+ }
620
+ var store$222;
621
+ // @__NO_SIDE_EFFECTS__
622
+ function getSchemaMessage22(lang) {
623
+ return store$222?.get(lang);
624
+ }
625
+ var store$122;
626
+ // @__NO_SIDE_EFFECTS__
627
+ function getSpecificMessage22(reference, lang) {
628
+ return store$122?.get(reference)?.get(lang);
629
+ }
630
+ // @__NO_SIDE_EFFECTS__
631
+ function _stringify22(input) {
632
+ const type = typeof input;
633
+ if (type === "string") return `"${input}"`;
634
+ if (type === "number" || type === "bigint" || type === "boolean") return `${input}`;
635
+ if (type === "object" || type === "function") return (input && Object.getPrototypeOf(input)?.constructor?.name) ?? "null";
636
+ return type;
637
+ }
638
+ function _addIssue22(context, label, dataset, config$1, other) {
639
+ const input = other && "input" in other ? other.input : dataset.value;
640
+ const expected = other?.expected ?? context.expects ?? null;
641
+ const received = other?.received ?? /* @__PURE__ */ _stringify22(input);
642
+ const issue = {
643
+ kind: context.kind,
644
+ type: context.type,
645
+ input,
646
+ expected,
647
+ received,
648
+ message: `Invalid ${label}: ${expected ? `Expected ${expected} but r` : "R"}eceived ${received}`,
649
+ requirement: context.requirement,
650
+ path: other?.path,
651
+ issues: other?.issues,
652
+ lang: config$1.lang,
653
+ abortEarly: config$1.abortEarly,
654
+ abortPipeEarly: config$1.abortPipeEarly
655
+ };
656
+ const isSchema = context.kind === "schema";
657
+ const message$1 = other?.message ?? context.message ?? /* @__PURE__ */ getSpecificMessage22(context.reference, issue.lang) ?? (isSchema ? /* @__PURE__ */ getSchemaMessage22(issue.lang) : null) ?? config$1.message ?? /* @__PURE__ */ getGlobalMessage22(issue.lang);
658
+ if (message$1 !== void 0) issue.message = typeof message$1 === "function" ? message$1(issue) : message$1;
659
+ if (isSchema) dataset.typed = false;
660
+ if (dataset.issues) dataset.issues.push(issue);
661
+ else dataset.issues = [issue];
662
+ }
663
+ var _standardCache22 = /* @__PURE__ */ new WeakMap();
664
+ // @__NO_SIDE_EFFECTS__
665
+ function _getStandardProps22(context) {
666
+ let cached = _standardCache22.get(context);
667
+ if (!cached) {
668
+ cached = {
669
+ version: 1,
670
+ vendor: "valibot",
671
+ validate(value$1) {
672
+ return context["~run"]({ value: value$1 }, /* @__PURE__ */ getGlobalConfig22());
673
+ }
674
+ };
675
+ _standardCache22.set(context, cached);
676
+ }
677
+ return cached;
678
+ }
679
+ var EMOJI_REGEX22 = new RegExp("^(?:[\\u{1F1E6}-\\u{1F1FF}]{2}|\\u{1F3F4}[\\u{E0061}-\\u{E007A}]{2}[\\u{E0030}-\\u{E0039}\\u{E0061}-\\u{E007A}]{1,3}\\u{E007F}|(?:\\p{Emoji}\\uFE0F\\u20E3?|\\p{Emoji_Modifier_Base}\\p{Emoji_Modifier}?|(?![\\p{Emoji_Modifier_Base}\\u{1F1E6}-\\u{1F1FF}])\\p{Emoji_Presentation})(?:\\u200D(?:\\p{Emoji}\\uFE0F\\u20E3?|\\p{Emoji_Modifier_Base}\\p{Emoji_Modifier}?|(?![\\p{Emoji_Modifier_Base}\\u{1F1E6}-\\u{1F1FF}])\\p{Emoji_Presentation}))*)+$", "u");
680
+ // @__NO_SIDE_EFFECTS__
681
+ function getFallback22(schema, dataset, config$1) {
682
+ return typeof schema.fallback === "function" ? schema.fallback(dataset, config$1) : schema.fallback;
683
+ }
684
+ // @__NO_SIDE_EFFECTS__
685
+ function getDefault22(schema, dataset, config$1) {
686
+ return typeof schema.default === "function" ? schema.default(dataset, config$1) : schema.default;
687
+ }
688
+ // @__NO_SIDE_EFFECTS__
689
+ function array22(item, message$1) {
690
+ return {
691
+ kind: "schema",
692
+ type: "array",
693
+ reference: array22,
694
+ expects: "Array",
695
+ async: false,
696
+ item,
697
+ message: message$1,
698
+ get "~standard"() {
699
+ return /* @__PURE__ */ _getStandardProps22(this);
700
+ },
701
+ "~run"(dataset, config$1) {
702
+ const input = dataset.value;
703
+ if (Array.isArray(input)) {
704
+ dataset.typed = true;
705
+ dataset.value = [];
706
+ for (let key = 0; key < input.length; key++) {
707
+ const value$1 = input[key];
708
+ const itemDataset = this.item["~run"]({ value: value$1 }, config$1);
709
+ if (itemDataset.issues) {
710
+ const pathItem = {
711
+ type: "array",
712
+ origin: "value",
713
+ input,
714
+ key,
715
+ value: value$1
716
+ };
717
+ for (const issue of itemDataset.issues) {
718
+ if (issue.path) issue.path.unshift(pathItem);
719
+ else issue.path = [pathItem];
720
+ dataset.issues?.push(issue);
721
+ }
722
+ if (!dataset.issues) dataset.issues = itemDataset.issues;
723
+ if (config$1.abortEarly) {
724
+ dataset.typed = false;
725
+ break;
726
+ }
727
+ }
728
+ if (!itemDataset.typed) dataset.typed = false;
729
+ dataset.value.push(itemDataset.value);
730
+ }
731
+ } else _addIssue22(this, "type", dataset, config$1);
732
+ return dataset;
733
+ }
734
+ };
735
+ }
736
+ // @__NO_SIDE_EFFECTS__
737
+ function boolean22(message$1) {
738
+ return {
739
+ kind: "schema",
740
+ type: "boolean",
741
+ reference: boolean22,
742
+ expects: "boolean",
743
+ async: false,
744
+ message: message$1,
745
+ get "~standard"() {
746
+ return /* @__PURE__ */ _getStandardProps22(this);
747
+ },
748
+ "~run"(dataset, config$1) {
749
+ if (typeof dataset.value === "boolean") dataset.typed = true;
750
+ else _addIssue22(this, "type", dataset, config$1);
751
+ return dataset;
752
+ }
753
+ };
754
+ }
755
+ // @__NO_SIDE_EFFECTS__
756
+ function literal(literal_, message$1) {
757
+ return {
758
+ kind: "schema",
759
+ type: "literal",
760
+ reference: literal,
761
+ expects: /* @__PURE__ */ _stringify22(literal_),
762
+ async: false,
763
+ literal: literal_,
764
+ message: message$1,
765
+ get "~standard"() {
766
+ return /* @__PURE__ */ _getStandardProps22(this);
767
+ },
768
+ "~run"(dataset, config$1) {
769
+ if (dataset.value === this.literal) dataset.typed = true;
770
+ else _addIssue22(this, "type", dataset, config$1);
771
+ return dataset;
772
+ }
773
+ };
774
+ }
775
+ // @__NO_SIDE_EFFECTS__
776
+ function number2(message$1) {
777
+ return {
778
+ kind: "schema",
779
+ type: "number",
780
+ reference: number2,
781
+ expects: "number",
782
+ async: false,
783
+ message: message$1,
784
+ get "~standard"() {
785
+ return /* @__PURE__ */ _getStandardProps22(this);
786
+ },
787
+ "~run"(dataset, config$1) {
788
+ if (typeof dataset.value === "number" && !isNaN(dataset.value)) dataset.typed = true;
789
+ else _addIssue22(this, "type", dataset, config$1);
790
+ return dataset;
791
+ }
792
+ };
793
+ }
794
+ // @__NO_SIDE_EFFECTS__
795
+ function object22(entries$1, message$1) {
796
+ return {
797
+ kind: "schema",
798
+ type: "object",
799
+ reference: object22,
800
+ expects: "Object",
801
+ async: false,
802
+ entries: entries$1,
803
+ message: message$1,
804
+ get "~standard"() {
805
+ return /* @__PURE__ */ _getStandardProps22(this);
806
+ },
807
+ "~run"(dataset, config$1) {
808
+ const input = dataset.value;
809
+ if (input && typeof input === "object") {
810
+ dataset.typed = true;
811
+ dataset.value = {};
812
+ for (const key in this.entries) {
813
+ const valueSchema = this.entries[key];
814
+ if (key in input || (valueSchema.type === "exact_optional" || valueSchema.type === "optional" || valueSchema.type === "nullish") && valueSchema.default !== void 0) {
815
+ const value$1 = key in input ? input[key] : /* @__PURE__ */ getDefault22(valueSchema);
816
+ const valueDataset = valueSchema["~run"]({ value: value$1 }, config$1);
817
+ if (valueDataset.issues) {
818
+ const pathItem = {
819
+ type: "object",
820
+ origin: "value",
821
+ input,
822
+ key,
823
+ value: value$1
824
+ };
825
+ for (const issue of valueDataset.issues) {
826
+ if (issue.path) issue.path.unshift(pathItem);
827
+ else issue.path = [pathItem];
828
+ dataset.issues?.push(issue);
829
+ }
830
+ if (!dataset.issues) dataset.issues = valueDataset.issues;
831
+ if (config$1.abortEarly) {
832
+ dataset.typed = false;
833
+ break;
834
+ }
835
+ }
836
+ if (!valueDataset.typed) dataset.typed = false;
837
+ dataset.value[key] = valueDataset.value;
838
+ } else if (valueSchema.fallback !== void 0) dataset.value[key] = /* @__PURE__ */ getFallback22(valueSchema);
839
+ else if (valueSchema.type !== "exact_optional" && valueSchema.type !== "optional" && valueSchema.type !== "nullish") {
840
+ _addIssue22(this, "key", dataset, config$1, {
841
+ input: void 0,
842
+ expected: `"${key}"`,
843
+ path: [{
844
+ type: "object",
845
+ origin: "key",
846
+ input,
847
+ key,
848
+ value: input[key]
849
+ }]
850
+ });
851
+ if (config$1.abortEarly) break;
852
+ }
853
+ }
854
+ } else _addIssue22(this, "type", dataset, config$1);
855
+ return dataset;
856
+ }
857
+ };
858
+ }
859
+ // @__NO_SIDE_EFFECTS__
860
+ function optional2(wrapped, default_) {
861
+ return {
862
+ kind: "schema",
863
+ type: "optional",
864
+ reference: optional2,
865
+ expects: `(${wrapped.expects} | undefined)`,
866
+ async: false,
867
+ wrapped,
868
+ default: default_,
869
+ get "~standard"() {
870
+ return /* @__PURE__ */ _getStandardProps22(this);
871
+ },
872
+ "~run"(dataset, config$1) {
873
+ if (dataset.value === void 0) {
874
+ if (this.default !== void 0) dataset.value = /* @__PURE__ */ getDefault22(this, dataset, config$1);
875
+ if (dataset.value === void 0) {
876
+ dataset.typed = true;
877
+ return dataset;
878
+ }
879
+ }
880
+ return this.wrapped["~run"](dataset, config$1);
881
+ }
882
+ };
883
+ }
884
+ // @__NO_SIDE_EFFECTS__
885
+ function string22(message$1) {
886
+ return {
887
+ kind: "schema",
888
+ type: "string",
889
+ reference: string22,
890
+ expects: "string",
891
+ async: false,
892
+ message: message$1,
893
+ get "~standard"() {
894
+ return /* @__PURE__ */ _getStandardProps22(this);
895
+ },
896
+ "~run"(dataset, config$1) {
897
+ if (typeof dataset.value === "string") dataset.typed = true;
898
+ else _addIssue22(this, "type", dataset, config$1);
899
+ return dataset;
900
+ }
901
+ };
902
+ }
903
+ // @__NO_SIDE_EFFECTS__
904
+ function safeParse22(schema, input, config$1) {
905
+ const dataset = schema["~run"]({ value: input }, /* @__PURE__ */ getGlobalConfig22(config$1));
906
+ return {
907
+ typed: dataset.typed,
908
+ success: !dataset.issues,
909
+ output: dataset.value,
910
+ issues: dataset.issues
911
+ };
912
+ }
913
+ var GithubSourceSchema = /* @__PURE__ */ object22({
914
+ type: /* @__PURE__ */ literal("github"),
915
+ repo: /* @__PURE__ */ string22(),
916
+ prNumber: /* @__PURE__ */ number2()
917
+ });
918
+ var GithubCursorSchema = /* @__PURE__ */ object22({
919
+ reviewIds: /* @__PURE__ */ array22(/* @__PURE__ */ string22()),
920
+ commentIds: /* @__PURE__ */ array22(/* @__PURE__ */ string22()),
921
+ historyPrimed: /* @__PURE__ */ optional2(/* @__PURE__ */ boolean22(), false),
922
+ lineCommentsPrimed: /* @__PURE__ */ optional2(/* @__PURE__ */ boolean22(), false),
923
+ conversationCommentsPrimed: /* @__PURE__ */ optional2(/* @__PURE__ */ boolean22(), false),
924
+ ciState: /* @__PURE__ */ string22(),
925
+ ciFailureState: /* @__PURE__ */ optional2(/* @__PURE__ */ string22(), ""),
926
+ mergeable: /* @__PURE__ */ string22(),
927
+ prState: /* @__PURE__ */ string22()
928
+ });
929
+ function failureRunIds(failed) {
930
+ const runIds = /* @__PURE__ */ new Set();
931
+ for (const check of failed) {
932
+ const details = check.detailsUrl ?? check.targetUrl ?? "";
933
+ const runId = String(details).match(/\/actions\/runs\/(\d+)/)?.[1];
934
+ if (runId) runIds.add(runId);
935
+ }
936
+ return runIds;
937
+ }
938
+ var ANSI_ESCAPE = /\u001B(?:\[[0-?]*[ -/]*[@-~]|\][^\u0007]*(?:\u0007|\u001B\\))/g;
939
+ var LOG_PREFIX = /^.*?\b\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?Z(?:\s+(?:stdout|stderr))?\s*(?:\|\s*)?/;
940
+ function cleanCiLog(raw) {
941
+ return raw.replace(/\r/g, "").replace(ANSI_ESCAPE, "").split("\n").map((line) => line.replace(LOG_PREFIX, "").trimEnd()).filter((line) => !/\bis in dev mode\. Not recommended for production!/i.test(line)).join("\n").replace(/\n{3,}/g, "\n\n").trim();
942
+ }
943
+ var API = "https://api.github.com";
944
+ var GRAPHQL = "https://api.github.com/graphql";
945
+ function githubToken() {
946
+ return process.env.GH_TOKEN ?? process.env.GITHUB_TOKEN;
947
+ }
948
+ async function githubJson(path22) {
949
+ const token = githubToken();
950
+ if (!token) return void 0;
951
+ try {
952
+ const response = await fetch(`${API}${path22}`, {
953
+ headers: { authorization: `Bearer ${token}`, "user-agent": "sourcefed", accept: "application/vnd.github+json" }
954
+ });
955
+ if (!response.ok) return void 0;
956
+ return await response.json();
957
+ } catch {
958
+ return void 0;
959
+ }
960
+ }
961
+ async function githubGraphQL(query, variables) {
962
+ const token = githubToken();
963
+ if (!token) return void 0;
964
+ try {
965
+ const response = await fetch(GRAPHQL, {
966
+ method: "POST",
967
+ headers: { authorization: `Bearer ${token}`, "user-agent": "sourcefed", "content-type": "application/json" },
968
+ body: JSON.stringify({ query, variables })
969
+ });
970
+ const body = await response.json();
971
+ if (!response.ok || body.errors) return void 0;
972
+ return body.data;
973
+ } catch {
974
+ return void 0;
975
+ }
976
+ }
977
+ var MAX_COMMENT_PAGES = 10;
978
+ async function fetchGithubComments(path22) {
979
+ const comments = [];
980
+ for (let page = 1; page <= MAX_COMMENT_PAGES; page++) {
981
+ const batch = await githubJson(`/${path22}?per_page=100&page=${page}`);
982
+ if (!Array.isArray(batch)) return void 0;
983
+ comments.push(...batch);
984
+ if (batch.length < 100) return comments;
985
+ }
986
+ return comments;
987
+ }
988
+ var API2 = "https://api.github.com";
989
+ var MAX_LOG_BYTES = 1e7;
990
+ var MAX_ENTRY_BYTES = 5e6;
991
+ var MAX_TOTAL_BYTES = 5e7;
992
+ async function fetchRunLog(repo, runId) {
993
+ const token = githubToken();
994
+ if (!token) return void 0;
995
+ try {
996
+ const response = await fetch(`${API2}/repos/${repo}/actions/runs/${runId}/logs`, {
997
+ headers: { authorization: `Bearer ${token}`, "user-agent": "sourcefed", accept: "application/vnd.github+json" }
998
+ });
999
+ if (!response.ok || !response.body) return void 0;
1000
+ const length = Number(response.headers.get("content-length") ?? 0);
1001
+ if (length > MAX_LOG_BYTES) return void 0;
1002
+ const reader = response.body.getReader();
1003
+ const chunks = [];
1004
+ let total = 0;
1005
+ while (true) {
1006
+ const { done, value } = await reader.read();
1007
+ if (done) break;
1008
+ total += value.byteLength;
1009
+ if (total > MAX_LOG_BYTES) return void 0;
1010
+ chunks.push(value);
1011
+ }
1012
+ const buffer = concatChunks(chunks);
1013
+ const entries = extractZipEntries(buffer);
1014
+ return entries.map((entry) => new TextDecoder().decode(entry.content)).join("\n");
1015
+ } catch {
1016
+ return void 0;
1017
+ }
1018
+ }
1019
+ function concatChunks(chunks) {
1020
+ const total = chunks.reduce((sum, chunk) => sum + chunk.length, 0);
1021
+ const result = new Uint8Array(total);
1022
+ let offset = 0;
1023
+ for (const chunk of chunks) {
1024
+ result.set(chunk, offset);
1025
+ offset += chunk.length;
1026
+ }
1027
+ return result;
1028
+ }
1029
+ function extractZipEntries(buffer) {
1030
+ const view = new DataView(buffer.buffer, buffer.byteOffset, buffer.byteLength);
1031
+ let end = -1;
1032
+ const scanStart = Math.max(0, buffer.length - 65557);
1033
+ for (let offset = buffer.length - 22; offset >= scanStart; offset--) {
1034
+ if (view.getUint32(offset, true) === 101010256) {
1035
+ end = offset;
1036
+ break;
1037
+ }
1038
+ }
1039
+ if (end < 0) return [];
1040
+ const entryCount = view.getUint16(end + 10, true);
1041
+ let cursor = view.getUint32(end + 16, true);
1042
+ const entries = [];
1043
+ let totalBytes = 0;
1044
+ for (let index = 0; index < entryCount; index++) {
1045
+ if (view.getUint32(cursor, true) !== 33639248) break;
1046
+ const method = view.getUint16(cursor + 10, true);
1047
+ const compressedSize = view.getUint32(cursor + 20, true);
1048
+ const nameLength = view.getUint16(cursor + 28, true);
1049
+ const extraLength = view.getUint16(cursor + 30, true);
1050
+ const commentLength = view.getUint16(cursor + 32, true);
1051
+ const localOffset = view.getUint32(cursor + 42, true);
1052
+ const name = new TextDecoder().decode(buffer.slice(cursor + 46, cursor + 46 + nameLength));
1053
+ const localNameLength = view.getUint16(localOffset + 26, true);
1054
+ const localExtraLength = view.getUint16(localOffset + 28, true);
1055
+ const dataStart = localOffset + 30 + localNameLength + localExtraLength;
1056
+ const compressed = buffer.slice(dataStart, dataStart + compressedSize);
1057
+ let content;
1058
+ if (method === 8) {
1059
+ const declaredSize = view.getUint32(localOffset + 22, true);
1060
+ if (declaredSize > MAX_ENTRY_BYTES) {
1061
+ cursor += 46 + nameLength + extraLength + commentLength;
1062
+ continue;
1063
+ }
1064
+ try {
1065
+ content = inflateRawSync(compressed);
1066
+ } catch {
1067
+ content = compressed;
1068
+ }
1069
+ } else {
1070
+ content = compressed;
1071
+ }
1072
+ totalBytes += content.length;
1073
+ if (totalBytes > MAX_TOTAL_BYTES) break;
1074
+ entries.push({ name, content });
1075
+ cursor += 46 + nameLength + extraLength + commentLength;
1076
+ }
1077
+ return entries;
1078
+ }
1079
+ var PR_DATA_QUERY = `
1080
+ query PullRequestData($owner: String!, $name: String!, $number: Int!, $reviewsAfter: String, $contextsAfter: String) {
1081
+ repository(owner: $owner, name: $name) {
1082
+ pullRequest(number: $number) {
1083
+ state
1084
+ mergeable
1085
+ mergeStateStatus
1086
+ reviews(first: 100, after: $reviewsAfter) {
1087
+ nodes {
1088
+ databaseId
1089
+ author { login }
1090
+ state
1091
+ submittedAt
1092
+ body
1093
+ }
1094
+ pageInfo {
1095
+ hasNextPage
1096
+ endCursor
1097
+ }
1098
+ }
1099
+ statusCheckRollup {
1100
+ contexts(first: 100, after: $contextsAfter) {
1101
+ nodes {
1102
+ ... on StatusContext {
1103
+ context
1104
+ state
1105
+ targetUrl
1106
+ }
1107
+ ... on CheckRun {
1108
+ name
1109
+ conclusion
1110
+ detailsUrl
1111
+ }
1112
+ }
1113
+ pageInfo {
1114
+ hasNextPage
1115
+ endCursor
1116
+ }
1117
+ }
1118
+ }
1119
+ }
1120
+ }
1121
+ }`;
1122
+ var MAX_REVIEW_PAGES = 300;
1123
+ async function fetchPrData(repo, prNumber) {
1124
+ const [owner, name] = repo.split("/");
1125
+ if (!owner || !name) return void 0;
1126
+ const data = await githubGraphQL(PR_DATA_QUERY, { owner, name, number: prNumber, reviewsAfter: void 0, contextsAfter: void 0 });
1127
+ const pullRequest = data?.repository?.pullRequest;
1128
+ if (!pullRequest) return void 0;
1129
+ const reviews = [];
1130
+ let page = pullRequest.reviews;
1131
+ while (page?.nodes && page.nodes.length > 0) {
1132
+ reviews.push(...page.nodes.map((node) => ({
1133
+ id: node.databaseId,
1134
+ author: node.author,
1135
+ state: node.state,
1136
+ submittedAt: node.submittedAt,
1137
+ body: node.body
1138
+ })));
1139
+ if (!page.pageInfo?.hasNextPage || reviews.length >= MAX_REVIEW_PAGES) break;
1140
+ const next = await githubGraphQL(PR_DATA_QUERY, { owner, name, number: prNumber, reviewsAfter: page.pageInfo.endCursor, contextsAfter: void 0 });
1141
+ page = next?.repository?.pullRequest?.reviews;
1142
+ }
1143
+ const statusCheckRollup = [];
1144
+ let contextsPage = pullRequest.statusCheckRollup?.contexts;
1145
+ while (contextsPage?.nodes && contextsPage.nodes.length > 0) {
1146
+ statusCheckRollup.push(...contextsPage.nodes);
1147
+ if (!contextsPage.pageInfo?.hasNextPage) break;
1148
+ const next = await githubGraphQL(PR_DATA_QUERY, { owner, name, number: prNumber, reviewsAfter: void 0, contextsAfter: contextsPage.pageInfo.endCursor });
1149
+ contextsPage = next?.repository?.pullRequest?.statusCheckRollup?.contexts;
1150
+ }
1151
+ return {
1152
+ reviews,
1153
+ statusCheckRollup,
1154
+ mergeable: pullRequest.mergeable,
1155
+ mergeStateStatus: pullRequest.mergeStateStatus,
1156
+ state: pullRequest.state
1157
+ };
1158
+ }
1159
+ async function failureRunLog(repo, runId) {
1160
+ const log = await fetchRunLog(repo, runId);
1161
+ if (!log || !log.trim()) return void 0;
1162
+ const tail = cleanCiLog(log).split("\n").slice(-60).join("\n");
1163
+ return `
1164
+ ### Failing log (run ${runId})
1165
+ \`\`\`
1166
+ ${tail}
1167
+ \`\`\``;
1168
+ }
1169
+ function formatFailedCheck(check) {
1170
+ let name = check.name;
1171
+ if (!name) name = check.context;
1172
+ if (!name) name = "check";
1173
+ let conclusion = check.conclusion;
1174
+ if (!conclusion) conclusion = check.state;
1175
+ if (!conclusion) conclusion = "FAILURE";
1176
+ return `- ${name}: ${conclusion}`;
1177
+ }
1178
+ async function ciFailureDetail(repo, failed) {
1179
+ const lines = failed.map(formatFailedCheck);
1180
+ const runIds = failureRunIds(failed);
1181
+ for (const runId of [...runIds].slice(0, 3)) {
1182
+ const log = await failureRunLog(repo, runId);
1183
+ if (log) lines.push(log);
1184
+ }
1185
+ return lines.join("\n");
1186
+ }
1187
+ async function confirmStillFailing(repo, prNumber, failed) {
1188
+ const data = await fetchPrData(repo, prNumber);
1189
+ if (!data) return failed;
1190
+ const rollup = data.statusCheckRollup ?? [];
1191
+ const current = /* @__PURE__ */ new Map();
1192
+ for (const check of rollup) {
1193
+ let name = check.name;
1194
+ if (!name) name = check.context;
1195
+ if (!name) name = "check";
1196
+ let state = check.conclusion;
1197
+ if (!state) state = check.state;
1198
+ if (!state) state = "PENDING";
1199
+ current.set(name, String(state).toUpperCase());
1200
+ }
1201
+ return failed.filter((check) => {
1202
+ let name = check.name;
1203
+ if (!name) name = check.context;
1204
+ if (!name) name = "check";
1205
+ const state = current.get(name);
1206
+ if (state === void 0) return false;
1207
+ return /FAIL|ERROR|CANCEL/i.test(state);
1208
+ });
1209
+ }
1210
+ async function appendGithubCiEvent(repo, prNumber, cursor, events, rollup) {
1211
+ const ciState = rollup.map((check) => check.conclusion ?? check.state ?? "PENDING").sort().join(",");
1212
+ if (!ciState || ciState === cursor.ciState) return;
1213
+ const previousCiState = cursor.ciState;
1214
+ cursor.ciState = ciState;
1215
+ if (previousCiState === void 0) return;
1216
+ let failed = rollup.filter((check) => /FAIL|ERROR|CANCEL/i.test(check.conclusion ?? ""));
1217
+ failed = await confirmStillFailing(repo, prNumber, failed);
1218
+ const failureState = failed.map((check) => `${check.name ?? check.context ?? "check"}:${check.conclusion ?? check.state ?? "FAILURE"}`).sort().join(",");
1219
+ if (failed.length > 0 && failureState !== cursor.ciFailureState) {
1220
+ const names = failed.map((check) => check.name ?? check.context ?? "check").join(", ");
1221
+ events.push({
1222
+ kind: "ci",
1223
+ id: `ci:${failureState}`,
1224
+ repo,
1225
+ prNumber,
1226
+ at: (/* @__PURE__ */ new Date()).toISOString(),
1227
+ summary: `CI failed on #${prNumber}: ${names}`,
1228
+ body: await ciFailureDetail(repo, failed),
1229
+ actionable: true
1230
+ });
1231
+ }
1232
+ cursor.ciFailureState = failureState;
1233
+ }
1234
+ function appendGithubConflictEvent(repo, prNumber, cursor, events, mergeable) {
1235
+ if (mergeable === cursor.mergeable) return;
1236
+ const previousMergeable = cursor.mergeable;
1237
+ cursor.mergeable = mergeable;
1238
+ if (previousMergeable === void 0) return;
1239
+ if (mergeable !== "CONFLICTING" && mergeable !== "DIRTY") return;
1240
+ events.push({
1241
+ kind: "conflict",
1242
+ id: `conflict:${mergeable}`,
1243
+ repo,
1244
+ prNumber,
1245
+ at: (/* @__PURE__ */ new Date()).toISOString(),
1246
+ summary: `PR #${prNumber} now has merge conflicts with its base (${mergeable})`,
1247
+ actionable: true
1248
+ });
1249
+ }
1250
+ function appendGithubReviews(repo, prNumber, cursor, events, reviews, historyPrimed) {
1251
+ const ref = `#${prNumber}`;
1252
+ for (const review of reviews) {
1253
+ const id = "rev:" + String(review.id ?? `${review.author?.login}-${review.submittedAt}`);
1254
+ if (cursor.reviewIds.includes(id)) continue;
1255
+ cursor.reviewIds.push(id);
1256
+ if (review.state === "PENDING" || !historyPrimed) continue;
1257
+ if (!review.body?.trim()) continue;
1258
+ let actionable = false;
1259
+ if (review.state === "CHANGES_REQUESTED") actionable = true;
1260
+ if (review.state === "COMMENTED" && review.body?.trim()) actionable = true;
1261
+ events.push({
1262
+ kind: "review",
1263
+ id,
1264
+ repo,
1265
+ prNumber,
1266
+ at: review.submittedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
1267
+ summary: `Review by ${review.author?.login ?? "someone"} [${review.state}] on ${ref}`,
1268
+ body: review.body,
1269
+ actionable
1270
+ });
1271
+ }
1272
+ }
1273
+ function appendGithubStateEvent(repo, prNumber, cursor, events, prState) {
1274
+ const isTerminal = prState === "MERGED" || prState === "CLOSED";
1275
+ if (prState === cursor.prState) return isTerminal;
1276
+ const previousPrState = cursor.prState;
1277
+ cursor.prState = prState;
1278
+ if (previousPrState !== void 0 && isTerminal) {
1279
+ let summary = `PR #${prNumber} was CLOSED without merging`;
1280
+ if (prState === "MERGED") summary = `PR #${prNumber} was MERGED`;
1281
+ events.push({
1282
+ kind: "merged",
1283
+ id: `state:${prState}`,
1284
+ repo,
1285
+ prNumber,
1286
+ at: (/* @__PURE__ */ new Date()).toISOString(),
1287
+ summary,
1288
+ actionable: prState === "MERGED",
1289
+ terminal: isTerminal
1290
+ });
1291
+ }
1292
+ return isTerminal;
1293
+ }
1294
+ function appendComments(repo, prNumber, cursor, events, comments, idPrefix, historyPrimed, summary, body) {
1295
+ for (const comment of comments) {
1296
+ const id = idPrefix + String(comment.id);
1297
+ if (cursor.commentIds.includes(id)) continue;
1298
+ cursor.commentIds.push(id);
1299
+ if (!historyPrimed) continue;
1300
+ const text = body(comment);
1301
+ events.push({
1302
+ kind: "comment",
1303
+ id,
1304
+ repo,
1305
+ prNumber,
1306
+ at: comment.created_at ?? (/* @__PURE__ */ new Date()).toISOString(),
1307
+ summary: summary(comment),
1308
+ body: text,
1309
+ actionable: Boolean(text.trim())
1310
+ });
1311
+ }
1312
+ }
1313
+ function emptyGithubCursor() {
1314
+ return {
1315
+ reviewIds: [],
1316
+ commentIds: [],
1317
+ historyPrimed: false,
1318
+ lineCommentsPrimed: false,
1319
+ conversationCommentsPrimed: false,
1320
+ ciState: "",
1321
+ ciFailureState: "",
1322
+ mergeable: "",
1323
+ prState: ""
1324
+ };
1325
+ }
1326
+ var warnedMissingToken = false;
1327
+ async function pollGithub(repo, prNumber, cursorRaw) {
1328
+ const parsedCursor = /* @__PURE__ */ safeParse22(GithubCursorSchema, cursorRaw);
1329
+ let cursor = emptyGithubCursor();
1330
+ if (parsedCursor.success) cursor = parsedCursor.output;
1331
+ const events = [];
1332
+ const ref = `#${prNumber}`;
1333
+ if (!githubToken()) {
1334
+ if (!warnedMissingToken) {
1335
+ warnedMissingToken = true;
1336
+ console.warn("[sourcefed] GH_TOKEN or GITHUB_TOKEN is not set; GitHub monitors will not poll");
1337
+ }
1338
+ return { events, cursor, terminal: false };
1339
+ }
1340
+ const data = await fetchPrData(repo, prNumber);
1341
+ if (!data) return { events, cursor, terminal: false };
1342
+ let historyPrimed = cursor.historyPrimed;
1343
+ if (!historyPrimed) historyPrimed = Boolean(cursor.reviewIds.length || cursor.commentIds.length || cursor.ciState || cursor.mergeable || cursor.prState);
1344
+ const lineCommentsPrimed = cursor.lineCommentsPrimed;
1345
+ const conversationCommentsPrimed = cursor.conversationCommentsPrimed;
1346
+ appendGithubReviews(repo, prNumber, cursor, events, data.reviews ?? [], historyPrimed);
1347
+ if (!cursor.historyPrimed) cursor.historyPrimed = true;
1348
+ const lineComments = await fetchGithubComments(`repos/${repo}/pulls/${prNumber}/comments`);
1349
+ if (lineComments) {
1350
+ appendComments(
1351
+ repo,
1352
+ prNumber,
1353
+ cursor,
1354
+ events,
1355
+ lineComments,
1356
+ "c:",
1357
+ lineCommentsPrimed,
1358
+ (comment) => `Comment by ${comment.user?.login ?? "someone"} on ${comment.path ?? ref}`,
1359
+ (comment) => {
1360
+ let location = comment.path ?? "";
1361
+ if (comment.line) location += `:${comment.line}`;
1362
+ return `${location} \u2014 ${comment.body ?? ""}`;
1363
+ }
1364
+ );
1365
+ cursor.lineCommentsPrimed = true;
1366
+ }
1367
+ const conversationComments = await fetchGithubComments(`repos/${repo}/issues/${prNumber}/comments`);
1368
+ if (conversationComments) {
1369
+ appendComments(
1370
+ repo,
1371
+ prNumber,
1372
+ cursor,
1373
+ events,
1374
+ conversationComments,
1375
+ "issue:",
1376
+ conversationCommentsPrimed,
1377
+ (comment) => `Comment by ${comment.user?.login ?? "someone"} on ${ref}`,
1378
+ (comment) => comment.body ?? ""
1379
+ );
1380
+ cursor.conversationCommentsPrimed = true;
1381
+ }
1382
+ await appendGithubCiEvent(repo, prNumber, cursor, events, data.statusCheckRollup ?? []);
1383
+ const mergeable = data.mergeable ?? data.mergeStateStatus ?? "UNKNOWN";
1384
+ appendGithubConflictEvent(repo, prNumber, cursor, events, mergeable);
1385
+ const prState = data.state ?? "OPEN";
1386
+ const terminal = appendGithubStateEvent(repo, prNumber, cursor, events, prState);
1387
+ return { events, cursor, terminal };
1388
+ }
1389
+ var githubPollMonitor = new PollMonitor({
1390
+ run: async (source, cursor) => {
1391
+ const result = await pollGithub(source.repo, source.prNumber, cursor);
1392
+ return {
1393
+ cursor: result.cursor,
1394
+ events: result.events.map((event) => toMonitorEvent(event, source)),
1395
+ terminal: result.terminal
1396
+ };
1397
+ },
1398
+ mergeCursor: mergeCursors
1399
+ });
1400
+ function updateGithubCursor(event, cursorRaw) {
1401
+ const parsed = /* @__PURE__ */ safeParse22(GithubCursorSchema, cursorRaw);
1402
+ let cursor = emptyGithubCursor();
1403
+ if (parsed.success) cursor = parsed.output;
1404
+ if (event.id?.startsWith("review:")) {
1405
+ const id = `rev:${event.id.slice("review:".length)}`;
1406
+ if (!cursor.reviewIds.includes(id)) cursor.reviewIds.push(id);
1407
+ }
1408
+ if (event.id?.startsWith("c:") || event.id?.startsWith("issue:")) {
1409
+ if (!cursor.commentIds.includes(event.id)) cursor.commentIds.push(event.id);
1410
+ }
1411
+ if (event.kind === "merged") cursor.prState = "MERGED";
1412
+ if (event.kind === "closed") cursor.prState = "CLOSED";
1413
+ if (event.kind === "conflict") cursor.mergeable = "CONFLICTING";
1414
+ return cursor;
1415
+ }
1416
+ function fallbackGithubEvent({ payload, actor, repo, prNumber, eventName }) {
1417
+ return {
1418
+ kind: eventName,
1419
+ summary: `GitHub ${eventName} ${payload.action ?? "changed"} by ${actor} on ${repo}#${prNumber}`,
1420
+ actionable: true
1421
+ };
1422
+ }
1423
+ function parseCheckRun({ payload, repo, prNumber }) {
1424
+ const conclusion = payload.check_run?.conclusion ?? payload.check_run?.status ?? "changed";
1425
+ return {
1426
+ kind: "ci",
1427
+ summary: `GitHub CI ${conclusion} on ${repo}#${prNumber}`,
1428
+ body: payload.check_run?.output?.text ?? payload.check_run?.name,
1429
+ actionable: /FAIL|ERROR|CANCEL/i.test(String(conclusion))
1430
+ };
1431
+ }
1432
+ function parseCheckSuite({ payload, repo, prNumber }) {
1433
+ const conclusion = payload.check_suite?.conclusion ?? payload.check_suite?.status ?? "changed";
1434
+ return {
1435
+ kind: "ci",
1436
+ summary: `GitHub CI ${conclusion} on ${repo}#${prNumber}`,
1437
+ body: payload.check_suite?.latest_check_runs?.map((check) => check.name).join(", "),
1438
+ actionable: /FAIL|ERROR|CANCEL/i.test(String(conclusion))
1439
+ };
1440
+ }
1441
+ function parseGithubComment({ payload, actor, repo, prNumber, eventName }) {
1442
+ return {
1443
+ kind: "comment",
1444
+ id: payload.comment?.id === void 0 ? void 0 : `${eventName === "issue_comment" ? "issue:" : "c:"}${payload.comment.id}`,
1445
+ summary: `GitHub comment by ${actor} on ${repo}#${prNumber}`,
1446
+ body: payload.comment?.body,
1447
+ actionable: true
1448
+ };
1449
+ }
1450
+ function parsePullRequest({ payload, repo, prNumber }) {
1451
+ const merged = payload.action === "closed" && payload.pull_request?.merged === true;
1452
+ let kind = payload.action ?? "changed";
1453
+ if (payload.action === "synchronize") kind = "commit";
1454
+ if (merged) kind = "merged";
1455
+ let summary = `GitHub PR ${payload.action ?? "changed"}`;
1456
+ if (merged) summary += " and merged";
1457
+ summary += ` on ${repo}#${prNumber}`;
1458
+ let body = payload.pull_request?.body;
1459
+ if (merged) body = void 0;
1460
+ let id;
1461
+ if (merged) id = "pr:MERGED";
1462
+ if (payload.action === "closed" && !merged) id = "pr:CLOSED";
1463
+ return { kind, id, summary, body, actionable: true };
1464
+ }
1465
+ function parsePullRequestReview({ payload, actor, repo, prNumber }) {
1466
+ let actionable = false;
1467
+ if (["changes_requested", "commented"].includes(String(payload.review?.state ?? "").toLowerCase())) actionable = true;
1468
+ return {
1469
+ kind: "review",
1470
+ id: payload.review?.id === void 0 ? void 0 : `review:${payload.review.id}`,
1471
+ summary: `GitHub review ${payload.action ?? "changed"} by ${actor} on ${repo}#${prNumber}`,
1472
+ body: payload.review?.body,
1473
+ actionable
1474
+ };
1475
+ }
1476
+ var githubEventHandlers = {
1477
+ pull_request_review: parsePullRequestReview,
1478
+ pull_request_review_comment: parseGithubComment,
1479
+ issue_comment: parseGithubComment,
1480
+ check_run: parseCheckRun,
1481
+ check_suite: parseCheckSuite,
1482
+ pull_request: parsePullRequest
1483
+ };
1484
+ function githubPullRequestNumber(payload, eventName) {
1485
+ let value = payload.pull_request?.number;
1486
+ if (value === void 0) value = payload.issue?.number;
1487
+ if (value === void 0 && eventName === "check_run") value = payload.check_run?.pull_requests?.[0]?.number;
1488
+ if (value === void 0 && eventName === "check_suite") value = payload.check_suite?.pull_requests?.[0]?.number;
1489
+ if (value === void 0) return void 0;
1490
+ const number22 = Number(value);
1491
+ if (!Number.isInteger(number22)) return void 0;
1492
+ return number22;
1493
+ }
1494
+ function parseGithubWebhook(payload, eventName, _deliveryId) {
1495
+ const repo = payload.repository?.full_name;
1496
+ const prNumber = githubPullRequestNumber(payload, eventName);
1497
+ if (!repo || prNumber === void 0) return void 0;
1498
+ if (eventName === "pull_request" && payload.action === "edited") return void 0;
1499
+ const handler = githubEventHandlers[eventName] ?? fallbackGithubEvent;
1500
+ const actor = payload.sender?.login ?? payload.review?.user?.login ?? "someone";
1501
+ const shape = handler({ payload, actor, repo, prNumber, eventName });
1502
+ if (eventName === "pull_request_review" && !shape.body?.trim()) return void 0;
1503
+ let body = shape.body;
1504
+ if (shape.kind === "merged") body = "";
1505
+ const event = {
1506
+ source: { type: "github", repo, prNumber },
1507
+ kind: shape.kind,
1508
+ id: shape.id,
1509
+ at: payload.repository?.updated_at ?? (/* @__PURE__ */ new Date()).toISOString(),
1510
+ summary: shape.summary,
1511
+ body: body || void 0,
1512
+ actionable: shape.actionable,
1513
+ terminal: shape.kind === "merged" || shape.kind === "closed"
1514
+ };
1515
+ return event;
1516
+ }
1517
+ function verifyHmac(body, signature, secret) {
1518
+ if (!secret || !signature) return false;
1519
+ const expected = `sha256=${createHmac("sha256", secret).update(body).digest("hex")}`;
1520
+ const actual = Buffer.from(signature);
1521
+ const wanted = Buffer.from(expected);
1522
+ return actual.length === wanted.length && timingSafeEqual(actual, wanted);
1523
+ }
1524
+ function verifyGithubWebhook(body, request) {
1525
+ return verifyHmac(body, request.headers.get("x-hub-signature-256"), process.env.SOURCEFED_GITHUB_WEBHOOK_SECRET);
1526
+ }
1527
+ var githubWebhookMonitor = new WebhookMonitor({
1528
+ path: "/webhooks/github",
1529
+ preferred: true,
1530
+ configured: () => Boolean(process.env.SOURCEFED_GITHUB_WEBHOOK_SECRET),
1531
+ verify: verifyGithubWebhook,
1532
+ deliveryId: (request) => request.headers.get("x-github-delivery") ?? void 0,
1533
+ eventName: (request) => request.headers.get("x-github-event") ?? "github",
1534
+ parse: parseGithubWebhook,
1535
+ updateCursor: updateGithubCursor
1536
+ });
1537
+ var GithubMonitor = class extends Monitor {
1538
+ poll = githubPollMonitor;
1539
+ webhook = githubWebhookMonitor;
1540
+ constructor() {
1541
+ super({ type: "github", schema: GithubSourceSchema });
1542
+ }
1543
+ key = (source) => `github:${source.repo}#${source.prNumber}`;
1544
+ icon = "\u{F02A4}";
1545
+ label = (source) => `#${source.prNumber}`;
1546
+ detail = (source) => ` ${source.repo}#${source.prNumber}`;
1547
+ describe = (source) => `GitHub ${source.repo}#${source.prNumber}`;
1548
+ build(input) {
1549
+ if (input.repo && input.prNumber) return { type: "github", repo: input.repo, prNumber: input.prNumber };
1550
+ return { error: "repo and prNumber are required for a github monitor" };
1551
+ }
1552
+ };
1553
+ var store$43;
1554
+ var DEFAULT_CONFIG3 = {
1555
+ lang: void 0,
1556
+ message: void 0,
1557
+ abortEarly: void 0,
1558
+ abortPipeEarly: void 0
1559
+ };
1560
+ // @__NO_SIDE_EFFECTS__
1561
+ function getGlobalConfig3(config$1) {
1562
+ if (!config$1 && !store$43) return DEFAULT_CONFIG3;
1563
+ return {
1564
+ lang: config$1?.lang ?? store$43?.lang,
1565
+ message: config$1?.message,
1566
+ abortEarly: config$1?.abortEarly ?? store$43?.abortEarly,
1567
+ abortPipeEarly: config$1?.abortPipeEarly ?? store$43?.abortPipeEarly
1568
+ };
1569
+ }
1570
+ var store$33;
1571
+ // @__NO_SIDE_EFFECTS__
1572
+ function getGlobalMessage3(lang) {
1573
+ return store$33?.get(lang);
1574
+ }
1575
+ var store$23;
1576
+ // @__NO_SIDE_EFFECTS__
1577
+ function getSchemaMessage3(lang) {
1578
+ return store$23?.get(lang);
1579
+ }
1580
+ var store$13;
1581
+ // @__NO_SIDE_EFFECTS__
1582
+ function getSpecificMessage3(reference, lang) {
1583
+ return store$13?.get(reference)?.get(lang);
1584
+ }
1585
+ // @__NO_SIDE_EFFECTS__
1586
+ function _stringify3(input) {
1587
+ const type = typeof input;
1588
+ if (type === "string") return `"${input}"`;
1589
+ if (type === "number" || type === "bigint" || type === "boolean") return `${input}`;
1590
+ if (type === "object" || type === "function") return (input && Object.getPrototypeOf(input)?.constructor?.name) ?? "null";
1591
+ return type;
1592
+ }
1593
+ function _addIssue3(context, label, dataset, config$1, other) {
1594
+ const input = other && "input" in other ? other.input : dataset.value;
1595
+ const expected = other?.expected ?? context.expects ?? null;
1596
+ const received = other?.received ?? /* @__PURE__ */ _stringify3(input);
1597
+ const issue = {
1598
+ kind: context.kind,
1599
+ type: context.type,
1600
+ input,
1601
+ expected,
1602
+ received,
1603
+ message: `Invalid ${label}: ${expected ? `Expected ${expected} but r` : "R"}eceived ${received}`,
1604
+ requirement: context.requirement,
1605
+ path: other?.path,
1606
+ issues: other?.issues,
1607
+ lang: config$1.lang,
1608
+ abortEarly: config$1.abortEarly,
1609
+ abortPipeEarly: config$1.abortPipeEarly
1610
+ };
1611
+ const isSchema = context.kind === "schema";
1612
+ const message$1 = other?.message ?? context.message ?? /* @__PURE__ */ getSpecificMessage3(context.reference, issue.lang) ?? (isSchema ? /* @__PURE__ */ getSchemaMessage3(issue.lang) : null) ?? config$1.message ?? /* @__PURE__ */ getGlobalMessage3(issue.lang);
1613
+ if (message$1 !== void 0) issue.message = typeof message$1 === "function" ? message$1(issue) : message$1;
1614
+ if (isSchema) dataset.typed = false;
1615
+ if (dataset.issues) dataset.issues.push(issue);
1616
+ else dataset.issues = [issue];
1617
+ }
1618
+ var _standardCache3 = /* @__PURE__ */ new WeakMap();
1619
+ // @__NO_SIDE_EFFECTS__
1620
+ function _getStandardProps3(context) {
1621
+ let cached = _standardCache3.get(context);
1622
+ if (!cached) {
1623
+ cached = {
1624
+ version: 1,
1625
+ vendor: "valibot",
1626
+ validate(value$1) {
1627
+ return context["~run"]({ value: value$1 }, /* @__PURE__ */ getGlobalConfig3());
1628
+ }
1629
+ };
1630
+ _standardCache3.set(context, cached);
1631
+ }
1632
+ return cached;
1633
+ }
1634
+ var EMOJI_REGEX3 = new RegExp("^(?:[\\u{1F1E6}-\\u{1F1FF}]{2}|\\u{1F3F4}[\\u{E0061}-\\u{E007A}]{2}[\\u{E0030}-\\u{E0039}\\u{E0061}-\\u{E007A}]{1,3}\\u{E007F}|(?:\\p{Emoji}\\uFE0F\\u20E3?|\\p{Emoji_Modifier_Base}\\p{Emoji_Modifier}?|(?![\\p{Emoji_Modifier_Base}\\u{1F1E6}-\\u{1F1FF}])\\p{Emoji_Presentation})(?:\\u200D(?:\\p{Emoji}\\uFE0F\\u20E3?|\\p{Emoji_Modifier_Base}\\p{Emoji_Modifier}?|(?![\\p{Emoji_Modifier_Base}\\u{1F1E6}-\\u{1F1FF}])\\p{Emoji_Presentation}))*)+$", "u");
1635
+ // @__NO_SIDE_EFFECTS__
1636
+ function array3(item, message$1) {
1637
+ return {
1638
+ kind: "schema",
1639
+ type: "array",
1640
+ reference: array3,
1641
+ expects: "Array",
1642
+ async: false,
1643
+ item,
1644
+ message: message$1,
1645
+ get "~standard"() {
1646
+ return /* @__PURE__ */ _getStandardProps3(this);
1647
+ },
1648
+ "~run"(dataset, config$1) {
1649
+ const input = dataset.value;
1650
+ if (Array.isArray(input)) {
1651
+ dataset.typed = true;
1652
+ dataset.value = [];
1653
+ for (let key = 0; key < input.length; key++) {
1654
+ const value$1 = input[key];
1655
+ const itemDataset = this.item["~run"]({ value: value$1 }, config$1);
1656
+ if (itemDataset.issues) {
1657
+ const pathItem = {
1658
+ type: "array",
1659
+ origin: "value",
1660
+ input,
1661
+ key,
1662
+ value: value$1
1663
+ };
1664
+ for (const issue of itemDataset.issues) {
1665
+ if (issue.path) issue.path.unshift(pathItem);
1666
+ else issue.path = [pathItem];
1667
+ dataset.issues?.push(issue);
1668
+ }
1669
+ if (!dataset.issues) dataset.issues = itemDataset.issues;
1670
+ if (config$1.abortEarly) {
1671
+ dataset.typed = false;
1672
+ break;
1673
+ }
1674
+ }
1675
+ if (!itemDataset.typed) dataset.typed = false;
1676
+ dataset.value.push(itemDataset.value);
1677
+ }
1678
+ } else _addIssue3(this, "type", dataset, config$1);
1679
+ return dataset;
1680
+ }
1681
+ };
1682
+ }
1683
+ // @__NO_SIDE_EFFECTS__
1684
+ function string3(message$1) {
1685
+ return {
1686
+ kind: "schema",
1687
+ type: "string",
1688
+ reference: string3,
1689
+ expects: "string",
1690
+ async: false,
1691
+ message: message$1,
1692
+ get "~standard"() {
1693
+ return /* @__PURE__ */ _getStandardProps3(this);
1694
+ },
1695
+ "~run"(dataset, config$1) {
1696
+ if (typeof dataset.value === "string") dataset.typed = true;
1697
+ else _addIssue3(this, "type", dataset, config$1);
1698
+ return dataset;
1699
+ }
1700
+ };
1701
+ }
1702
+ // @__NO_SIDE_EFFECTS__
1703
+ function safeParse3(schema, input, config$1) {
1704
+ const dataset = schema["~run"]({ value: input }, /* @__PURE__ */ getGlobalConfig3(config$1));
1705
+ return {
1706
+ typed: dataset.typed,
1707
+ success: !dataset.issues,
1708
+ output: dataset.value,
1709
+ issues: dataset.issues
1710
+ };
1711
+ }
1712
+ var Monitor2 = class {
1713
+ type;
1714
+ schema;
1715
+ poll;
1716
+ webhook;
1717
+ constructor(options) {
1718
+ this.type = options.type;
1719
+ this.schema = options.schema;
1720
+ }
1721
+ validateSource(source) {
1722
+ const result = /* @__PURE__ */ safeParse3(this.schema, source);
1723
+ if (result.success) return result.output;
1724
+ throw new Error(`invalid ${this.type} monitor source`);
1725
+ }
1726
+ initialDelivery(source) {
1727
+ this.validateSource(source);
1728
+ if (this.webhook && this.webhook.preferred && this.webhook.configured()) return "webhook";
1729
+ if (this.poll) return "poll";
1730
+ if (this.webhook) return "webhook";
1731
+ throw new Error(`source ${this.type} has no configured transport`);
1732
+ }
1733
+ resolveDelivery(monitor) {
1734
+ let webhookHealthy = false;
1735
+ if (this.webhook) webhookHealthy = this.webhook.isHealthy(monitor);
1736
+ if (monitor.delivery === "webhook") {
1737
+ if (this.webhook && webhookHealthy) return "webhook";
1738
+ if (this.poll) return "poll";
1739
+ if (this.webhook) return "webhook";
1740
+ return void 0;
1741
+ }
1742
+ if (this.webhook && webhookHealthy) return "webhook";
1743
+ if (this.poll) return "poll";
1744
+ if (this.webhook) return "webhook";
1745
+ return void 0;
1746
+ }
1747
+ async tick(context, record2) {
1748
+ const fresh = await context.service.get(record2.id);
1749
+ if (!fresh || !fresh.enabled) return 0;
1750
+ if (this.webhook) await this.webhook.retryPending(context, this, fresh);
1751
+ const current = await context.service.get(record2.id);
1752
+ if (!current || !current.enabled) return 0;
1753
+ const nextDelivery = this.resolveDelivery(current);
1754
+ if (!nextDelivery) return 0;
1755
+ if (nextDelivery !== current.delivery) {
1756
+ await context.service.setDelivery(current.id, nextDelivery);
1757
+ current.delivery = nextDelivery;
1758
+ }
1759
+ if (current.delivery !== "poll" || !this.poll) return 0;
1760
+ const lastAttemptAt = Number(current.cursors.__lastAttemptAt ?? current.cursors.__lastPolledAt ?? 0);
1761
+ const pollIntervalSec = current.pollIntervalSec || 60;
1762
+ if (lastAttemptAt && Date.now() - lastAttemptAt < pollIntervalSec * 1e3) return 0;
1763
+ return this.poll.run(context, this, current);
1764
+ }
1765
+ async deliver(context, record2, event, deliveryId) {
1766
+ if (!this.webhook) return 0;
1767
+ return this.webhook.deliver(context, this, record2, event, deliveryId);
1768
+ }
1769
+ unresponsive(record2, now = Date.now()) {
1770
+ if (!record2.enabled) return false;
1771
+ if (record2.delivery === "poll") {
1772
+ if (!this.poll) return true;
1773
+ return this.poll.isUnresponsive(record2, now);
1774
+ }
1775
+ if (!this.webhook) return true;
1776
+ return this.webhook.isUnresponsive(record2, now);
1777
+ }
1778
+ };
1779
+ var RoutedEventCursorSchema2 = /* @__PURE__ */ array3(/* @__PURE__ */ string3());
1780
+ function eventDeliveryId2(event) {
1781
+ if (event.id) return event.id;
1782
+ return `${event.kind}:${event.at}:${event.summary}`;
1783
+ }
1784
+ var activePolls2 = /* @__PURE__ */ new Set();
1785
+ var PollMonitor2 = class {
1786
+ constructor(options) {
1787
+ this.options = options;
1788
+ }
1789
+ async run(context, source, record2) {
1790
+ if (activePolls2.has(record2.id)) return 0;
1791
+ activePolls2.add(record2.id);
1792
+ try {
1793
+ const fresh = await context.service.get(record2.id);
1794
+ if (!fresh) return 0;
1795
+ const sourceRecord = source.validateSource(fresh.source);
1796
+ const cursorKey = source.key(sourceRecord);
1797
+ const result = await this.options.run(sourceRecord, fresh.cursors[cursorKey]);
1798
+ const current = await context.service.get(record2.id);
1799
+ if (!current || !current.enabled) return 0;
1800
+ const parsedRoutedIds = /* @__PURE__ */ safeParse3(RoutedEventCursorSchema2, fresh.cursors.__routedEventIds);
1801
+ let routedIds = [];
1802
+ if (parsedRoutedIds.success) routedIds = parsedRoutedIds.output;
1803
+ let routeFailed = false;
1804
+ for (const event of result.events) {
1805
+ const eventId = eventDeliveryId2(event);
1806
+ if (routedIds.includes(eventId)) continue;
1807
+ const routed = await context.sink.deliver({ monitor: fresh, event });
1808
+ if (!routed.ok) {
1809
+ routeFailed = true;
1810
+ console.error(`[sourcefed] route failed for ${fresh.id}: ${routed.error}`);
1811
+ continue;
1812
+ }
1813
+ if (!routedIds.includes(eventId)) routedIds.push(eventId);
1814
+ }
1815
+ if (routeFailed) {
1816
+ await context.service.updateCursor(fresh, "__routedEventIds", routedIds);
1817
+ return 0;
1818
+ }
1819
+ await context.service.updateCursorValue(fresh, cursorKey, (current2) => {
1820
+ if (this.options.mergeCursor) return this.options.mergeCursor(current2, result.cursor);
1821
+ return result.cursor;
1822
+ });
1823
+ await context.service.updateCursor(fresh, "__routedEventIds", []);
1824
+ if (result.terminal) await context.service.remove(fresh.id);
1825
+ await context.service.updateCursor(fresh, "__lastPolledAt", Date.now());
1826
+ return result.events.length;
1827
+ } finally {
1828
+ activePolls2.delete(record2.id);
1829
+ }
1830
+ }
1831
+ isUnresponsive(record2, now) {
1832
+ const intervalMs = Math.max((record2.pollIntervalSec || 60) * 3e3, 12e4);
1833
+ const lastPolledAt = Number(record2.cursors.__lastPolledAt ?? 0);
1834
+ if (lastPolledAt > 0) return now - lastPolledAt > intervalMs;
1835
+ return now - Date.parse(record2.createdAt) > intervalMs;
1836
+ }
1837
+ };
1838
+ var MAX_WEBHOOK_BODY_BYTES3 = 1024 * 1024;
1839
+ function toMonitorEvent2(event, source) {
1840
+ return {
1841
+ source,
1842
+ kind: event.kind,
1843
+ id: event.id,
1844
+ at: event.at,
1845
+ summary: event.summary,
1846
+ body: event.body,
1847
+ actionable: event.actionable,
1848
+ terminal: event.terminal
1849
+ };
1850
+ }
1851
+ var store$423;
1852
+ var DEFAULT_CONFIG23 = {
1853
+ lang: void 0,
1854
+ message: void 0,
1855
+ abortEarly: void 0,
1856
+ abortPipeEarly: void 0
1857
+ };
1858
+ // @__NO_SIDE_EFFECTS__
1859
+ function getGlobalConfig23(config$1) {
1860
+ if (!config$1 && !store$423) return DEFAULT_CONFIG23;
1861
+ return {
1862
+ lang: config$1?.lang ?? store$423?.lang,
1863
+ message: config$1?.message,
1864
+ abortEarly: config$1?.abortEarly ?? store$423?.abortEarly,
1865
+ abortPipeEarly: config$1?.abortPipeEarly ?? store$423?.abortPipeEarly
1866
+ };
1867
+ }
1868
+ var store$323;
1869
+ // @__NO_SIDE_EFFECTS__
1870
+ function getGlobalMessage23(lang) {
1871
+ return store$323?.get(lang);
1872
+ }
1873
+ var store$223;
1874
+ // @__NO_SIDE_EFFECTS__
1875
+ function getSchemaMessage23(lang) {
1876
+ return store$223?.get(lang);
1877
+ }
1878
+ var store$123;
1879
+ // @__NO_SIDE_EFFECTS__
1880
+ function getSpecificMessage23(reference, lang) {
1881
+ return store$123?.get(reference)?.get(lang);
1882
+ }
1883
+ // @__NO_SIDE_EFFECTS__
1884
+ function _stringify23(input) {
1885
+ const type = typeof input;
1886
+ if (type === "string") return `"${input}"`;
1887
+ if (type === "number" || type === "bigint" || type === "boolean") return `${input}`;
1888
+ if (type === "object" || type === "function") return (input && Object.getPrototypeOf(input)?.constructor?.name) ?? "null";
1889
+ return type;
1890
+ }
1891
+ function _addIssue23(context, label, dataset, config$1, other) {
1892
+ const input = other && "input" in other ? other.input : dataset.value;
1893
+ const expected = other?.expected ?? context.expects ?? null;
1894
+ const received = other?.received ?? /* @__PURE__ */ _stringify23(input);
1895
+ const issue = {
1896
+ kind: context.kind,
1897
+ type: context.type,
1898
+ input,
1899
+ expected,
1900
+ received,
1901
+ message: `Invalid ${label}: ${expected ? `Expected ${expected} but r` : "R"}eceived ${received}`,
1902
+ requirement: context.requirement,
1903
+ path: other?.path,
1904
+ issues: other?.issues,
1905
+ lang: config$1.lang,
1906
+ abortEarly: config$1.abortEarly,
1907
+ abortPipeEarly: config$1.abortPipeEarly
1908
+ };
1909
+ const isSchema = context.kind === "schema";
1910
+ const message$1 = other?.message ?? context.message ?? /* @__PURE__ */ getSpecificMessage23(context.reference, issue.lang) ?? (isSchema ? /* @__PURE__ */ getSchemaMessage23(issue.lang) : null) ?? config$1.message ?? /* @__PURE__ */ getGlobalMessage23(issue.lang);
1911
+ if (message$1 !== void 0) issue.message = typeof message$1 === "function" ? message$1(issue) : message$1;
1912
+ if (isSchema) dataset.typed = false;
1913
+ if (dataset.issues) dataset.issues.push(issue);
1914
+ else dataset.issues = [issue];
1915
+ }
1916
+ var _standardCache23 = /* @__PURE__ */ new WeakMap();
1917
+ // @__NO_SIDE_EFFECTS__
1918
+ function _getStandardProps23(context) {
1919
+ let cached = _standardCache23.get(context);
1920
+ if (!cached) {
1921
+ cached = {
1922
+ version: 1,
1923
+ vendor: "valibot",
1924
+ validate(value$1) {
1925
+ return context["~run"]({ value: value$1 }, /* @__PURE__ */ getGlobalConfig23());
1926
+ }
1927
+ };
1928
+ _standardCache23.set(context, cached);
1929
+ }
1930
+ return cached;
1931
+ }
1932
+ var EMOJI_REGEX23 = new RegExp("^(?:[\\u{1F1E6}-\\u{1F1FF}]{2}|\\u{1F3F4}[\\u{E0061}-\\u{E007A}]{2}[\\u{E0030}-\\u{E0039}\\u{E0061}-\\u{E007A}]{1,3}\\u{E007F}|(?:\\p{Emoji}\\uFE0F\\u20E3?|\\p{Emoji_Modifier_Base}\\p{Emoji_Modifier}?|(?![\\p{Emoji_Modifier_Base}\\u{1F1E6}-\\u{1F1FF}])\\p{Emoji_Presentation})(?:\\u200D(?:\\p{Emoji}\\uFE0F\\u20E3?|\\p{Emoji_Modifier_Base}\\p{Emoji_Modifier}?|(?![\\p{Emoji_Modifier_Base}\\u{1F1E6}-\\u{1F1FF}])\\p{Emoji_Presentation}))*)+$", "u");
1933
+ // @__NO_SIDE_EFFECTS__
1934
+ function getFallback3(schema, dataset, config$1) {
1935
+ return typeof schema.fallback === "function" ? schema.fallback(dataset, config$1) : schema.fallback;
1936
+ }
1937
+ // @__NO_SIDE_EFFECTS__
1938
+ function getDefault3(schema, dataset, config$1) {
1939
+ return typeof schema.default === "function" ? schema.default(dataset, config$1) : schema.default;
1940
+ }
1941
+ // @__NO_SIDE_EFFECTS__
1942
+ function array23(item, message$1) {
1943
+ return {
1944
+ kind: "schema",
1945
+ type: "array",
1946
+ reference: array23,
1947
+ expects: "Array",
1948
+ async: false,
1949
+ item,
1950
+ message: message$1,
1951
+ get "~standard"() {
1952
+ return /* @__PURE__ */ _getStandardProps23(this);
1953
+ },
1954
+ "~run"(dataset, config$1) {
1955
+ const input = dataset.value;
1956
+ if (Array.isArray(input)) {
1957
+ dataset.typed = true;
1958
+ dataset.value = [];
1959
+ for (let key = 0; key < input.length; key++) {
1960
+ const value$1 = input[key];
1961
+ const itemDataset = this.item["~run"]({ value: value$1 }, config$1);
1962
+ if (itemDataset.issues) {
1963
+ const pathItem = {
1964
+ type: "array",
1965
+ origin: "value",
1966
+ input,
1967
+ key,
1968
+ value: value$1
1969
+ };
1970
+ for (const issue of itemDataset.issues) {
1971
+ if (issue.path) issue.path.unshift(pathItem);
1972
+ else issue.path = [pathItem];
1973
+ dataset.issues?.push(issue);
1974
+ }
1975
+ if (!dataset.issues) dataset.issues = itemDataset.issues;
1976
+ if (config$1.abortEarly) {
1977
+ dataset.typed = false;
1978
+ break;
1979
+ }
1980
+ }
1981
+ if (!itemDataset.typed) dataset.typed = false;
1982
+ dataset.value.push(itemDataset.value);
1983
+ }
1984
+ } else _addIssue23(this, "type", dataset, config$1);
1985
+ return dataset;
1986
+ }
1987
+ };
1988
+ }
1989
+ // @__NO_SIDE_EFFECTS__
1990
+ function literal2(literal_, message$1) {
1991
+ return {
1992
+ kind: "schema",
1993
+ type: "literal",
1994
+ reference: literal2,
1995
+ expects: /* @__PURE__ */ _stringify23(literal_),
1996
+ async: false,
1997
+ literal: literal_,
1998
+ message: message$1,
1999
+ get "~standard"() {
2000
+ return /* @__PURE__ */ _getStandardProps23(this);
2001
+ },
2002
+ "~run"(dataset, config$1) {
2003
+ if (dataset.value === this.literal) dataset.typed = true;
2004
+ else _addIssue23(this, "type", dataset, config$1);
2005
+ return dataset;
2006
+ }
2007
+ };
2008
+ }
2009
+ // @__NO_SIDE_EFFECTS__
2010
+ function number3(message$1) {
2011
+ return {
2012
+ kind: "schema",
2013
+ type: "number",
2014
+ reference: number3,
2015
+ expects: "number",
2016
+ async: false,
2017
+ message: message$1,
2018
+ get "~standard"() {
2019
+ return /* @__PURE__ */ _getStandardProps23(this);
2020
+ },
2021
+ "~run"(dataset, config$1) {
2022
+ if (typeof dataset.value === "number" && !isNaN(dataset.value)) dataset.typed = true;
2023
+ else _addIssue23(this, "type", dataset, config$1);
2024
+ return dataset;
2025
+ }
2026
+ };
2027
+ }
2028
+ // @__NO_SIDE_EFFECTS__
2029
+ function object3(entries$1, message$1) {
2030
+ return {
2031
+ kind: "schema",
2032
+ type: "object",
2033
+ reference: object3,
2034
+ expects: "Object",
2035
+ async: false,
2036
+ entries: entries$1,
2037
+ message: message$1,
2038
+ get "~standard"() {
2039
+ return /* @__PURE__ */ _getStandardProps23(this);
2040
+ },
2041
+ "~run"(dataset, config$1) {
2042
+ const input = dataset.value;
2043
+ if (input && typeof input === "object") {
2044
+ dataset.typed = true;
2045
+ dataset.value = {};
2046
+ for (const key in this.entries) {
2047
+ const valueSchema = this.entries[key];
2048
+ if (key in input || (valueSchema.type === "exact_optional" || valueSchema.type === "optional" || valueSchema.type === "nullish") && valueSchema.default !== void 0) {
2049
+ const value$1 = key in input ? input[key] : /* @__PURE__ */ getDefault3(valueSchema);
2050
+ const valueDataset = valueSchema["~run"]({ value: value$1 }, config$1);
2051
+ if (valueDataset.issues) {
2052
+ const pathItem = {
2053
+ type: "object",
2054
+ origin: "value",
2055
+ input,
2056
+ key,
2057
+ value: value$1
2058
+ };
2059
+ for (const issue of valueDataset.issues) {
2060
+ if (issue.path) issue.path.unshift(pathItem);
2061
+ else issue.path = [pathItem];
2062
+ dataset.issues?.push(issue);
2063
+ }
2064
+ if (!dataset.issues) dataset.issues = valueDataset.issues;
2065
+ if (config$1.abortEarly) {
2066
+ dataset.typed = false;
2067
+ break;
2068
+ }
2069
+ }
2070
+ if (!valueDataset.typed) dataset.typed = false;
2071
+ dataset.value[key] = valueDataset.value;
2072
+ } else if (valueSchema.fallback !== void 0) dataset.value[key] = /* @__PURE__ */ getFallback3(valueSchema);
2073
+ else if (valueSchema.type !== "exact_optional" && valueSchema.type !== "optional" && valueSchema.type !== "nullish") {
2074
+ _addIssue23(this, "key", dataset, config$1, {
2075
+ input: void 0,
2076
+ expected: `"${key}"`,
2077
+ path: [{
2078
+ type: "object",
2079
+ origin: "key",
2080
+ input,
2081
+ key,
2082
+ value: input[key]
2083
+ }]
2084
+ });
2085
+ if (config$1.abortEarly) break;
2086
+ }
2087
+ }
2088
+ } else _addIssue23(this, "type", dataset, config$1);
2089
+ return dataset;
2090
+ }
2091
+ };
2092
+ }
2093
+ // @__NO_SIDE_EFFECTS__
2094
+ function optional3(wrapped, default_) {
2095
+ return {
2096
+ kind: "schema",
2097
+ type: "optional",
2098
+ reference: optional3,
2099
+ expects: `(${wrapped.expects} | undefined)`,
2100
+ async: false,
2101
+ wrapped,
2102
+ default: default_,
2103
+ get "~standard"() {
2104
+ return /* @__PURE__ */ _getStandardProps23(this);
2105
+ },
2106
+ "~run"(dataset, config$1) {
2107
+ if (dataset.value === void 0) {
2108
+ if (this.default !== void 0) dataset.value = /* @__PURE__ */ getDefault3(this, dataset, config$1);
2109
+ if (dataset.value === void 0) {
2110
+ dataset.typed = true;
2111
+ return dataset;
2112
+ }
2113
+ }
2114
+ return this.wrapped["~run"](dataset, config$1);
2115
+ }
2116
+ };
2117
+ }
2118
+ // @__NO_SIDE_EFFECTS__
2119
+ function string23(message$1) {
2120
+ return {
2121
+ kind: "schema",
2122
+ type: "string",
2123
+ reference: string23,
2124
+ expects: "string",
2125
+ async: false,
2126
+ message: message$1,
2127
+ get "~standard"() {
2128
+ return /* @__PURE__ */ _getStandardProps23(this);
2129
+ },
2130
+ "~run"(dataset, config$1) {
2131
+ if (typeof dataset.value === "string") dataset.typed = true;
2132
+ else _addIssue23(this, "type", dataset, config$1);
2133
+ return dataset;
2134
+ }
2135
+ };
2136
+ }
2137
+ // @__NO_SIDE_EFFECTS__
2138
+ function safeParse23(schema, input, config$1) {
2139
+ const dataset = schema["~run"]({ value: input }, /* @__PURE__ */ getGlobalConfig23(config$1));
2140
+ return {
2141
+ typed: dataset.typed,
2142
+ success: !dataset.issues,
2143
+ output: dataset.value,
2144
+ issues: dataset.issues
2145
+ };
2146
+ }
2147
+ var JiraSourceSchema = /* @__PURE__ */ object3({
2148
+ type: /* @__PURE__ */ literal2("jira"),
2149
+ issueKey: /* @__PURE__ */ string23()
2150
+ });
2151
+ var JiraCursorSchema = /* @__PURE__ */ object3({
2152
+ commentIds: /* @__PURE__ */ array23(/* @__PURE__ */ number3()),
2153
+ descriptionVersion: /* @__PURE__ */ optional3(/* @__PURE__ */ string23()),
2154
+ changelogCount: /* @__PURE__ */ number3()
2155
+ });
2156
+ function jiraChangeValue(item, side) {
2157
+ let value = item.toString ?? item.to;
2158
+ if (side === "from") value = item.fromString ?? item.from;
2159
+ if (value === void 0 || value === null || value === "") return "none";
2160
+ return String(value);
2161
+ }
2162
+ function formatJiraChangelogItem(item) {
2163
+ return `${item.field}: ${jiraChangeValue(item, "from")} \u2192 ${jiraChangeValue(item, "to")}`;
2164
+ }
2165
+ function isTerminalStatus(status) {
2166
+ if (typeof status !== "string") return false;
2167
+ const terminalStatus = process.env.SOURCEFED_JIRA_TERMINAL_STATUS;
2168
+ if (!terminalStatus) return false;
2169
+ return status.trim().toLowerCase() === terminalStatus.trim().toLowerCase();
2170
+ }
2171
+ var TRACKED_CHANGE_FIELDS = /* @__PURE__ */ new Set(["summary", "status", "assignee", "priority", "labels"]);
2172
+ function meaningfulChangelogItems(items) {
2173
+ return items.filter((item) => {
2174
+ if (!TRACKED_CHANGE_FIELDS.has(String(item.field))) return false;
2175
+ return jiraChangeValue(item, "from") !== jiraChangeValue(item, "to");
2176
+ });
2177
+ }
2178
+ function jiraChangelogEvent(issueKey, history) {
2179
+ const changes = meaningfulChangelogItems(history.items ?? []);
2180
+ const items = changes.map(formatJiraChangelogItem).join("; ");
2181
+ if (!items) return void 0;
2182
+ const terminal = changes.some((item) => item.field === "status" && isTerminalStatus(jiraChangeValue(item, "to")));
2183
+ return {
2184
+ kind: "changelog",
2185
+ id: `history:${history.id ?? history.created ?? items}`,
2186
+ issueKey,
2187
+ at: history.created ?? (/* @__PURE__ */ new Date()).toISOString(),
2188
+ summary: `Jira ${issueKey} ${items}`,
2189
+ body: items,
2190
+ author: history.author?.displayName,
2191
+ actionable: true,
2192
+ terminal
2193
+ };
2194
+ }
2195
+ function appendJiraChangelog(issueKey, cursor, events, histories) {
2196
+ const total = histories.length;
2197
+ if (cursor.changelogCount !== 0 && total > cursor.changelogCount) {
2198
+ for (const history of histories.slice(cursor.changelogCount)) {
2199
+ const event = jiraChangelogEvent(issueKey, history);
2200
+ if (event) events.push(event);
2201
+ }
2202
+ }
2203
+ cursor.changelogCount = total;
2204
+ }
2205
+ function adfText(node) {
2206
+ if (!node || typeof node !== "object") return "";
2207
+ if (typeof node.text === "string") return node.text;
2208
+ if (Array.isArray(node.content)) return node.content.map(adfText).join("");
2209
+ return "";
2210
+ }
2211
+ function appendJiraComments(issueKey, cursor, events, comments) {
2212
+ const freshComments = [];
2213
+ for (const comment of comments) {
2214
+ const id = Number(comment.id);
2215
+ if (cursor.commentIds.includes(id)) continue;
2216
+ freshComments.push(comment);
2217
+ }
2218
+ if (cursor.commentIds.length === 0) {
2219
+ cursor.commentIds = freshComments.map((comment) => Number(comment.id));
2220
+ return;
2221
+ }
2222
+ for (const comment of freshComments) {
2223
+ const body = adfText(comment.body);
2224
+ events.push({
2225
+ kind: "comment",
2226
+ id: `comment:${comment.id}`,
2227
+ issueKey,
2228
+ at: comment.created ?? (/* @__PURE__ */ new Date()).toISOString(),
2229
+ summary: `Jira ${issueKey} comment by ${comment.author?.displayName ?? "someone"}`,
2230
+ body,
2231
+ author: comment.author?.displayName,
2232
+ actionable: true
2233
+ });
2234
+ cursor.commentIds.push(Number(comment.id));
2235
+ }
2236
+ }
2237
+ function appendJiraDescription(issueKey, cursor, events, description) {
2238
+ let descriptionVersion = "none";
2239
+ if (description) descriptionVersion = JSON.stringify(description).slice(0, 40);
2240
+ if (cursor.descriptionVersion !== void 0 && descriptionVersion !== cursor.descriptionVersion) {
2241
+ events.push({
2242
+ kind: "description",
2243
+ id: `description:${descriptionVersion}`,
2244
+ issueKey,
2245
+ at: (/* @__PURE__ */ new Date()).toISOString(),
2246
+ summary: `Jira ${issueKey} description updated`,
2247
+ body: adfText(description),
2248
+ actionable: true
2249
+ });
2250
+ }
2251
+ cursor.descriptionVersion = descriptionVersion;
2252
+ }
2253
+ function emptyJiraCursor() {
2254
+ return { commentIds: [], descriptionVersion: void 0, changelogCount: 0 };
2255
+ }
2256
+ function jiraAuth() {
2257
+ const email = process.env.ATLASSIAN_EMAIL;
2258
+ const key = process.env.ATLASSIAN_API_KEY;
2259
+ if (!email || !key) throw new Error("ATLASSIAN_EMAIL / ATLASSIAN_API_KEY not set");
2260
+ return "Basic " + Buffer.from(`${email}:${key}`).toString("base64");
2261
+ }
2262
+ var JIRA_BASE = process.env.SOURCEFED_JIRA_BASE_URL;
2263
+ async function jiraFetch(path22) {
2264
+ if (!JIRA_BASE) throw new Error("SOURCEFED_JIRA_BASE_URL is required for Jira monitors");
2265
+ const response = await fetch(`${JIRA_BASE}/rest/api/3${path22}`, {
2266
+ headers: { Authorization: jiraAuth(), Accept: "application/json" }
2267
+ });
2268
+ if (!response.ok) throw new Error(`Jira ${response.status} for ${path22}`);
2269
+ return response.json();
2270
+ }
2271
+ async function pollJira(issueKey, cursorRaw) {
2272
+ const parsedCursor = /* @__PURE__ */ safeParse23(JiraCursorSchema, cursorRaw);
2273
+ let cursor = emptyJiraCursor();
2274
+ if (parsedCursor.success) cursor = parsedCursor.output;
2275
+ const events = [];
2276
+ const comments = await jiraFetch(`/issue/${issueKey}/comment?maxResults=50&orderBy=-created`);
2277
+ appendJiraComments(issueKey, cursor, events, comments.comments ?? []);
2278
+ const issue = await jiraFetch(`/issue/${issueKey}?expand=changelog&fields=status,description`);
2279
+ const statusName = issue.fields?.status?.name;
2280
+ appendJiraDescription(issueKey, cursor, events, issue.fields?.description);
2281
+ const changelog = issue.changelog?.histories ?? [];
2282
+ appendJiraChangelog(issueKey, cursor, events, changelog);
2283
+ return { events, cursor, terminal: isTerminalStatus(statusName) };
2284
+ }
2285
+ var jiraPollMonitor = new PollMonitor2({
2286
+ run: async (source, cursor) => {
2287
+ const result = await pollJira(source.issueKey, cursor);
2288
+ return {
2289
+ cursor: result.cursor,
2290
+ events: result.events.map((event) => toMonitorEvent2(event, source)),
2291
+ terminal: result.terminal
2292
+ };
2293
+ }
2294
+ });
2295
+ var JiraMonitor = class extends Monitor2 {
2296
+ poll = jiraPollMonitor;
2297
+ constructor() {
2298
+ super({ type: "jira", schema: JiraSourceSchema });
2299
+ }
2300
+ key = (source) => `jira:${source.issueKey}`;
2301
+ icon = "\u{F0303}";
2302
+ label = (source) => source.issueKey;
2303
+ detail = (source) => ` ${source.issueKey}`;
2304
+ describe = (source) => `Jira ${source.issueKey}`;
2305
+ build(input) {
2306
+ if (input.issueKey) return { type: "jira", issueKey: input.issueKey };
2307
+ return { error: "issueKey is required for a jira monitor" };
2308
+ }
2309
+ };
2310
+ var store$44;
2311
+ var DEFAULT_CONFIG4 = {
2312
+ lang: void 0,
2313
+ message: void 0,
2314
+ abortEarly: void 0,
2315
+ abortPipeEarly: void 0
2316
+ };
2317
+ // @__NO_SIDE_EFFECTS__
2318
+ function getGlobalConfig4(config$1) {
2319
+ if (!config$1 && !store$44) return DEFAULT_CONFIG4;
2320
+ return {
2321
+ lang: config$1?.lang ?? store$44?.lang,
2322
+ message: config$1?.message,
2323
+ abortEarly: config$1?.abortEarly ?? store$44?.abortEarly,
2324
+ abortPipeEarly: config$1?.abortPipeEarly ?? store$44?.abortPipeEarly
2325
+ };
2326
+ }
2327
+ var store$34;
2328
+ // @__NO_SIDE_EFFECTS__
2329
+ function getGlobalMessage4(lang) {
2330
+ return store$34?.get(lang);
2331
+ }
2332
+ var store$24;
2333
+ // @__NO_SIDE_EFFECTS__
2334
+ function getSchemaMessage4(lang) {
2335
+ return store$24?.get(lang);
2336
+ }
2337
+ var store$14;
2338
+ // @__NO_SIDE_EFFECTS__
2339
+ function getSpecificMessage4(reference, lang) {
2340
+ return store$14?.get(reference)?.get(lang);
2341
+ }
2342
+ // @__NO_SIDE_EFFECTS__
2343
+ function _stringify4(input) {
2344
+ const type = typeof input;
2345
+ if (type === "string") return `"${input}"`;
2346
+ if (type === "number" || type === "bigint" || type === "boolean") return `${input}`;
2347
+ if (type === "object" || type === "function") return (input && Object.getPrototypeOf(input)?.constructor?.name) ?? "null";
2348
+ return type;
2349
+ }
2350
+ function _addIssue4(context, label, dataset, config$1, other) {
2351
+ const input = other && "input" in other ? other.input : dataset.value;
2352
+ const expected = other?.expected ?? context.expects ?? null;
2353
+ const received = other?.received ?? /* @__PURE__ */ _stringify4(input);
2354
+ const issue = {
2355
+ kind: context.kind,
2356
+ type: context.type,
2357
+ input,
2358
+ expected,
2359
+ received,
2360
+ message: `Invalid ${label}: ${expected ? `Expected ${expected} but r` : "R"}eceived ${received}`,
2361
+ requirement: context.requirement,
2362
+ path: other?.path,
2363
+ issues: other?.issues,
2364
+ lang: config$1.lang,
2365
+ abortEarly: config$1.abortEarly,
2366
+ abortPipeEarly: config$1.abortPipeEarly
2367
+ };
2368
+ const isSchema = context.kind === "schema";
2369
+ const message$1 = other?.message ?? context.message ?? /* @__PURE__ */ getSpecificMessage4(context.reference, issue.lang) ?? (isSchema ? /* @__PURE__ */ getSchemaMessage4(issue.lang) : null) ?? config$1.message ?? /* @__PURE__ */ getGlobalMessage4(issue.lang);
2370
+ if (message$1 !== void 0) issue.message = typeof message$1 === "function" ? message$1(issue) : message$1;
2371
+ if (isSchema) dataset.typed = false;
2372
+ if (dataset.issues) dataset.issues.push(issue);
2373
+ else dataset.issues = [issue];
2374
+ }
2375
+ var _standardCache4 = /* @__PURE__ */ new WeakMap();
2376
+ // @__NO_SIDE_EFFECTS__
2377
+ function _getStandardProps4(context) {
2378
+ let cached = _standardCache4.get(context);
2379
+ if (!cached) {
2380
+ cached = {
2381
+ version: 1,
2382
+ vendor: "valibot",
2383
+ validate(value$1) {
2384
+ return context["~run"]({ value: value$1 }, /* @__PURE__ */ getGlobalConfig4());
2385
+ }
2386
+ };
2387
+ _standardCache4.set(context, cached);
2388
+ }
2389
+ return cached;
2390
+ }
2391
+ var EMOJI_REGEX4 = new RegExp("^(?:[\\u{1F1E6}-\\u{1F1FF}]{2}|\\u{1F3F4}[\\u{E0061}-\\u{E007A}]{2}[\\u{E0030}-\\u{E0039}\\u{E0061}-\\u{E007A}]{1,3}\\u{E007F}|(?:\\p{Emoji}\\uFE0F\\u20E3?|\\p{Emoji_Modifier_Base}\\p{Emoji_Modifier}?|(?![\\p{Emoji_Modifier_Base}\\u{1F1E6}-\\u{1F1FF}])\\p{Emoji_Presentation})(?:\\u200D(?:\\p{Emoji}\\uFE0F\\u20E3?|\\p{Emoji_Modifier_Base}\\p{Emoji_Modifier}?|(?![\\p{Emoji_Modifier_Base}\\u{1F1E6}-\\u{1F1FF}])\\p{Emoji_Presentation}))*)+$", "u");
2392
+ // @__NO_SIDE_EFFECTS__
2393
+ function getFallback4(schema, dataset, config$1) {
2394
+ return typeof schema.fallback === "function" ? schema.fallback(dataset, config$1) : schema.fallback;
2395
+ }
2396
+ // @__NO_SIDE_EFFECTS__
2397
+ function getDefault4(schema, dataset, config$1) {
2398
+ return typeof schema.default === "function" ? schema.default(dataset, config$1) : schema.default;
2399
+ }
2400
+ // @__NO_SIDE_EFFECTS__
2401
+ function array4(item, message$1) {
2402
+ return {
2403
+ kind: "schema",
2404
+ type: "array",
2405
+ reference: array4,
2406
+ expects: "Array",
2407
+ async: false,
2408
+ item,
2409
+ message: message$1,
2410
+ get "~standard"() {
2411
+ return /* @__PURE__ */ _getStandardProps4(this);
2412
+ },
2413
+ "~run"(dataset, config$1) {
2414
+ const input = dataset.value;
2415
+ if (Array.isArray(input)) {
2416
+ dataset.typed = true;
2417
+ dataset.value = [];
2418
+ for (let key = 0; key < input.length; key++) {
2419
+ const value$1 = input[key];
2420
+ const itemDataset = this.item["~run"]({ value: value$1 }, config$1);
2421
+ if (itemDataset.issues) {
2422
+ const pathItem = {
2423
+ type: "array",
2424
+ origin: "value",
2425
+ input,
2426
+ key,
2427
+ value: value$1
2428
+ };
2429
+ for (const issue of itemDataset.issues) {
2430
+ if (issue.path) issue.path.unshift(pathItem);
2431
+ else issue.path = [pathItem];
2432
+ dataset.issues?.push(issue);
2433
+ }
2434
+ if (!dataset.issues) dataset.issues = itemDataset.issues;
2435
+ if (config$1.abortEarly) {
2436
+ dataset.typed = false;
2437
+ break;
2438
+ }
2439
+ }
2440
+ if (!itemDataset.typed) dataset.typed = false;
2441
+ dataset.value.push(itemDataset.value);
2442
+ }
2443
+ } else _addIssue4(this, "type", dataset, config$1);
2444
+ return dataset;
2445
+ }
2446
+ };
2447
+ }
2448
+ // @__NO_SIDE_EFFECTS__
2449
+ function boolean3(message$1) {
2450
+ return {
2451
+ kind: "schema",
2452
+ type: "boolean",
2453
+ reference: boolean3,
2454
+ expects: "boolean",
2455
+ async: false,
2456
+ message: message$1,
2457
+ get "~standard"() {
2458
+ return /* @__PURE__ */ _getStandardProps4(this);
2459
+ },
2460
+ "~run"(dataset, config$1) {
2461
+ if (typeof dataset.value === "boolean") dataset.typed = true;
2462
+ else _addIssue4(this, "type", dataset, config$1);
2463
+ return dataset;
2464
+ }
2465
+ };
2466
+ }
2467
+ // @__NO_SIDE_EFFECTS__
2468
+ function object4(entries$1, message$1) {
2469
+ return {
2470
+ kind: "schema",
2471
+ type: "object",
2472
+ reference: object4,
2473
+ expects: "Object",
2474
+ async: false,
2475
+ entries: entries$1,
2476
+ message: message$1,
2477
+ get "~standard"() {
2478
+ return /* @__PURE__ */ _getStandardProps4(this);
2479
+ },
2480
+ "~run"(dataset, config$1) {
2481
+ const input = dataset.value;
2482
+ if (input && typeof input === "object") {
2483
+ dataset.typed = true;
2484
+ dataset.value = {};
2485
+ for (const key in this.entries) {
2486
+ const valueSchema = this.entries[key];
2487
+ if (key in input || (valueSchema.type === "exact_optional" || valueSchema.type === "optional" || valueSchema.type === "nullish") && valueSchema.default !== void 0) {
2488
+ const value$1 = key in input ? input[key] : /* @__PURE__ */ getDefault4(valueSchema);
2489
+ const valueDataset = valueSchema["~run"]({ value: value$1 }, config$1);
2490
+ if (valueDataset.issues) {
2491
+ const pathItem = {
2492
+ type: "object",
2493
+ origin: "value",
2494
+ input,
2495
+ key,
2496
+ value: value$1
2497
+ };
2498
+ for (const issue of valueDataset.issues) {
2499
+ if (issue.path) issue.path.unshift(pathItem);
2500
+ else issue.path = [pathItem];
2501
+ dataset.issues?.push(issue);
2502
+ }
2503
+ if (!dataset.issues) dataset.issues = valueDataset.issues;
2504
+ if (config$1.abortEarly) {
2505
+ dataset.typed = false;
2506
+ break;
2507
+ }
2508
+ }
2509
+ if (!valueDataset.typed) dataset.typed = false;
2510
+ dataset.value[key] = valueDataset.value;
2511
+ } else if (valueSchema.fallback !== void 0) dataset.value[key] = /* @__PURE__ */ getFallback4(valueSchema);
2512
+ else if (valueSchema.type !== "exact_optional" && valueSchema.type !== "optional" && valueSchema.type !== "nullish") {
2513
+ _addIssue4(this, "key", dataset, config$1, {
2514
+ input: void 0,
2515
+ expected: `"${key}"`,
2516
+ path: [{
2517
+ type: "object",
2518
+ origin: "key",
2519
+ input,
2520
+ key,
2521
+ value: input[key]
2522
+ }]
2523
+ });
2524
+ if (config$1.abortEarly) break;
2525
+ }
2526
+ }
2527
+ } else _addIssue4(this, "type", dataset, config$1);
2528
+ return dataset;
2529
+ }
2530
+ };
2531
+ }
2532
+ // @__NO_SIDE_EFFECTS__
2533
+ function optional4(wrapped, default_) {
2534
+ return {
2535
+ kind: "schema",
2536
+ type: "optional",
2537
+ reference: optional4,
2538
+ expects: `(${wrapped.expects} | undefined)`,
2539
+ async: false,
2540
+ wrapped,
2541
+ default: default_,
2542
+ get "~standard"() {
2543
+ return /* @__PURE__ */ _getStandardProps4(this);
2544
+ },
2545
+ "~run"(dataset, config$1) {
2546
+ if (dataset.value === void 0) {
2547
+ if (this.default !== void 0) dataset.value = /* @__PURE__ */ getDefault4(this, dataset, config$1);
2548
+ if (dataset.value === void 0) {
2549
+ dataset.typed = true;
2550
+ return dataset;
2551
+ }
2552
+ }
2553
+ return this.wrapped["~run"](dataset, config$1);
2554
+ }
2555
+ };
2556
+ }
2557
+ // @__NO_SIDE_EFFECTS__
2558
+ function string4(message$1) {
2559
+ return {
2560
+ kind: "schema",
2561
+ type: "string",
2562
+ reference: string4,
2563
+ expects: "string",
2564
+ async: false,
2565
+ message: message$1,
2566
+ get "~standard"() {
2567
+ return /* @__PURE__ */ _getStandardProps4(this);
2568
+ },
2569
+ "~run"(dataset, config$1) {
2570
+ if (typeof dataset.value === "string") dataset.typed = true;
2571
+ else _addIssue4(this, "type", dataset, config$1);
2572
+ return dataset;
2573
+ }
2574
+ };
2575
+ }
2576
+ // @__NO_SIDE_EFFECTS__
2577
+ function unknown3() {
2578
+ return {
2579
+ kind: "schema",
2580
+ type: "unknown",
2581
+ reference: unknown3,
2582
+ expects: "unknown",
2583
+ async: false,
2584
+ get "~standard"() {
2585
+ return /* @__PURE__ */ _getStandardProps4(this);
2586
+ },
2587
+ "~run"(dataset) {
2588
+ dataset.typed = true;
2589
+ return dataset;
2590
+ }
2591
+ };
2592
+ }
2593
+ // @__NO_SIDE_EFFECTS__
2594
+ function safeParse4(schema, input, config$1) {
2595
+ const dataset = schema["~run"]({ value: input }, /* @__PURE__ */ getGlobalConfig4(config$1));
2596
+ return {
2597
+ typed: dataset.typed,
2598
+ success: !dataset.issues,
2599
+ output: dataset.value,
2600
+ issues: dataset.issues
2601
+ };
2602
+ }
2603
+ var Monitor3 = class {
2604
+ type;
2605
+ schema;
2606
+ poll;
2607
+ webhook;
2608
+ constructor(options) {
2609
+ this.type = options.type;
2610
+ this.schema = options.schema;
2611
+ }
2612
+ validateSource(source) {
2613
+ const result = /* @__PURE__ */ safeParse4(this.schema, source);
2614
+ if (result.success) return result.output;
2615
+ throw new Error(`invalid ${this.type} monitor source`);
2616
+ }
2617
+ initialDelivery(source) {
2618
+ this.validateSource(source);
2619
+ if (this.webhook && this.webhook.preferred && this.webhook.configured()) return "webhook";
2620
+ if (this.poll) return "poll";
2621
+ if (this.webhook) return "webhook";
2622
+ throw new Error(`source ${this.type} has no configured transport`);
2623
+ }
2624
+ resolveDelivery(monitor) {
2625
+ let webhookHealthy = false;
2626
+ if (this.webhook) webhookHealthy = this.webhook.isHealthy(monitor);
2627
+ if (monitor.delivery === "webhook") {
2628
+ if (this.webhook && webhookHealthy) return "webhook";
2629
+ if (this.poll) return "poll";
2630
+ if (this.webhook) return "webhook";
2631
+ return void 0;
2632
+ }
2633
+ if (this.webhook && webhookHealthy) return "webhook";
2634
+ if (this.poll) return "poll";
2635
+ if (this.webhook) return "webhook";
2636
+ return void 0;
2637
+ }
2638
+ async tick(context, record2) {
2639
+ const fresh = await context.service.get(record2.id);
2640
+ if (!fresh || !fresh.enabled) return 0;
2641
+ if (this.webhook) await this.webhook.retryPending(context, this, fresh);
2642
+ const current = await context.service.get(record2.id);
2643
+ if (!current || !current.enabled) return 0;
2644
+ const nextDelivery = this.resolveDelivery(current);
2645
+ if (!nextDelivery) return 0;
2646
+ if (nextDelivery !== current.delivery) {
2647
+ await context.service.setDelivery(current.id, nextDelivery);
2648
+ current.delivery = nextDelivery;
2649
+ }
2650
+ if (current.delivery !== "poll" || !this.poll) return 0;
2651
+ const lastAttemptAt = Number(current.cursors.__lastAttemptAt ?? current.cursors.__lastPolledAt ?? 0);
2652
+ const pollIntervalSec = current.pollIntervalSec || 60;
2653
+ if (lastAttemptAt && Date.now() - lastAttemptAt < pollIntervalSec * 1e3) return 0;
2654
+ return this.poll.run(context, this, current);
2655
+ }
2656
+ async deliver(context, record2, event, deliveryId) {
2657
+ if (!this.webhook) return 0;
2658
+ return this.webhook.deliver(context, this, record2, event, deliveryId);
2659
+ }
2660
+ unresponsive(record2, now = Date.now()) {
2661
+ if (!record2.enabled) return false;
2662
+ if (record2.delivery === "poll") {
2663
+ if (!this.poll) return true;
2664
+ return this.poll.isUnresponsive(record2, now);
2665
+ }
2666
+ if (!this.webhook) return true;
2667
+ return this.webhook.isUnresponsive(record2, now);
2668
+ }
2669
+ };
2670
+ var RoutedEventCursorSchema3 = /* @__PURE__ */ array4(/* @__PURE__ */ string4());
2671
+ function eventDeliveryId3(event) {
2672
+ if (event.id) return event.id;
2673
+ return `${event.kind}:${event.at}:${event.summary}`;
2674
+ }
2675
+ var activePolls3 = /* @__PURE__ */ new Set();
2676
+ var PollMonitor3 = class {
2677
+ constructor(options) {
2678
+ this.options = options;
2679
+ }
2680
+ async run(context, source, record2) {
2681
+ if (activePolls3.has(record2.id)) return 0;
2682
+ activePolls3.add(record2.id);
2683
+ try {
2684
+ const fresh = await context.service.get(record2.id);
2685
+ if (!fresh) return 0;
2686
+ const sourceRecord = source.validateSource(fresh.source);
2687
+ const cursorKey = source.key(sourceRecord);
2688
+ const result = await this.options.run(sourceRecord, fresh.cursors[cursorKey]);
2689
+ const current = await context.service.get(record2.id);
2690
+ if (!current || !current.enabled) return 0;
2691
+ const parsedRoutedIds = /* @__PURE__ */ safeParse4(RoutedEventCursorSchema3, fresh.cursors.__routedEventIds);
2692
+ let routedIds = [];
2693
+ if (parsedRoutedIds.success) routedIds = parsedRoutedIds.output;
2694
+ let routeFailed = false;
2695
+ for (const event of result.events) {
2696
+ const eventId = eventDeliveryId3(event);
2697
+ if (routedIds.includes(eventId)) continue;
2698
+ const routed = await context.sink.deliver({ monitor: fresh, event });
2699
+ if (!routed.ok) {
2700
+ routeFailed = true;
2701
+ console.error(`[sourcefed] route failed for ${fresh.id}: ${routed.error}`);
2702
+ continue;
2703
+ }
2704
+ if (!routedIds.includes(eventId)) routedIds.push(eventId);
2705
+ }
2706
+ if (routeFailed) {
2707
+ await context.service.updateCursor(fresh, "__routedEventIds", routedIds);
2708
+ return 0;
2709
+ }
2710
+ await context.service.updateCursorValue(fresh, cursorKey, (current2) => {
2711
+ if (this.options.mergeCursor) return this.options.mergeCursor(current2, result.cursor);
2712
+ return result.cursor;
2713
+ });
2714
+ await context.service.updateCursor(fresh, "__routedEventIds", []);
2715
+ if (result.terminal) await context.service.remove(fresh.id);
2716
+ await context.service.updateCursor(fresh, "__lastPolledAt", Date.now());
2717
+ return result.events.length;
2718
+ } finally {
2719
+ activePolls3.delete(record2.id);
2720
+ }
2721
+ }
2722
+ isUnresponsive(record2, now) {
2723
+ const intervalMs = Math.max((record2.pollIntervalSec || 60) * 3e3, 12e4);
2724
+ const lastPolledAt = Number(record2.cursors.__lastPolledAt ?? 0);
2725
+ if (lastPolledAt > 0) return now - lastPolledAt > intervalMs;
2726
+ return now - Date.parse(record2.createdAt) > intervalMs;
2727
+ }
2728
+ };
2729
+ var WEBHOOK_STALE_MS2 = 9e4;
2730
+ var WEBHOOK_STARTUP_GRACE_MS2 = 12e4;
2731
+ var PendingWebhookEventSchema2 = /* @__PURE__ */ object4({
2732
+ deliveryId: /* @__PURE__ */ string4(),
2733
+ event: /* @__PURE__ */ object4({
2734
+ source: /* @__PURE__ */ unknown3(),
2735
+ kind: /* @__PURE__ */ string4(),
2736
+ id: /* @__PURE__ */ optional4(/* @__PURE__ */ string4()),
2737
+ at: /* @__PURE__ */ string4(),
2738
+ summary: /* @__PURE__ */ string4(),
2739
+ body: /* @__PURE__ */ optional4(/* @__PURE__ */ string4()),
2740
+ actionable: /* @__PURE__ */ boolean3(),
2741
+ terminal: /* @__PURE__ */ optional4(/* @__PURE__ */ boolean3())
2742
+ })
2743
+ });
2744
+ var PendingWebhookEventsSchema2 = /* @__PURE__ */ array4(PendingWebhookEventSchema2);
2745
+ var pendingDeliveries2 = /* @__PURE__ */ new Set();
2746
+ var PENDING_WEBHOOK_EVENTS2 = "__pendingWebhookEvents";
2747
+ var WebhookMonitor2 = class {
2748
+ path;
2749
+ preferred;
2750
+ configured;
2751
+ verify;
2752
+ acknowledgeBeforeDelivery;
2753
+ challenge;
2754
+ deliveryId;
2755
+ eventName;
2756
+ parse;
2757
+ updateCursor;
2758
+ constructor(options) {
2759
+ this.path = options.path;
2760
+ this.preferred = options.preferred ?? true;
2761
+ this.configured = options.configured;
2762
+ this.verify = options.verify;
2763
+ this.acknowledgeBeforeDelivery = options.acknowledgeBeforeDelivery ?? false;
2764
+ this.challenge = options.challenge;
2765
+ this.deliveryId = options.deliveryId;
2766
+ this.eventName = options.eventName;
2767
+ this.parse = options.parse;
2768
+ this.updateCursor = options.updateCursor;
2769
+ }
2770
+ isHealthy(record2) {
2771
+ if (!this.preferred || !this.configured()) return false;
2772
+ const heartbeatAt = Number(record2.cursors.__webhookHeartbeatAt ?? 0);
2773
+ if (heartbeatAt > 0) return Date.now() - heartbeatAt <= WEBHOOK_STALE_MS2;
2774
+ return Date.now() - Date.parse(record2.createdAt) <= WEBHOOK_STARTUP_GRACE_MS2;
2775
+ }
2776
+ isUnresponsive(record2, now) {
2777
+ const heartbeatAt = Number(record2.cursors.__webhookHeartbeatAt ?? 0);
2778
+ if (heartbeatAt > 0) return now - heartbeatAt > WEBHOOK_STALE_MS2;
2779
+ return now - Date.parse(record2.createdAt) > WEBHOOK_STARTUP_GRACE_MS2;
2780
+ }
2781
+ async retryPending(context, source, record2) {
2782
+ const fresh = await context.service.get(record2.id);
2783
+ if (!fresh || !fresh.enabled) return 0;
2784
+ const parsed = /* @__PURE__ */ safeParse4(PendingWebhookEventsSchema2, fresh.cursors[PENDING_WEBHOOK_EVENTS2]);
2785
+ if (!parsed.success) return 0;
2786
+ let delivered = 0;
2787
+ for (const pending of parsed.output) {
2788
+ const current = await context.service.get(record2.id);
2789
+ if (!current || !current.enabled) break;
2790
+ const cursorKey = `webhook:${pending.deliveryId}`;
2791
+ if (current.cursors[cursorKey]) {
2792
+ await this.removePending(context, current, pending.deliveryId);
2793
+ continue;
2794
+ }
2795
+ delivered += await this.deliver(context, source, current, pending.event, pending.deliveryId);
2796
+ }
2797
+ return delivered;
2798
+ }
2799
+ async addPending(context, monitor, deliveryId, event) {
2800
+ await context.service.updateCursorValue(monitor, PENDING_WEBHOOK_EVENTS2, (cursor) => {
2801
+ const parsed = /* @__PURE__ */ safeParse4(PendingWebhookEventsSchema2, cursor);
2802
+ let pending = [];
2803
+ if (parsed.success) pending = parsed.output;
2804
+ if (pending.some((entry) => entry.deliveryId === deliveryId)) return pending;
2805
+ pending.push({ deliveryId, event });
2806
+ return pending;
2807
+ });
2808
+ }
2809
+ async removePending(context, monitor, deliveryId) {
2810
+ await context.service.updateCursorValue(monitor, PENDING_WEBHOOK_EVENTS2, (cursor) => {
2811
+ const parsed = /* @__PURE__ */ safeParse4(PendingWebhookEventsSchema2, cursor);
2812
+ if (!parsed.success) return [];
2813
+ return parsed.output.filter((entry) => entry.deliveryId !== deliveryId);
2814
+ });
2815
+ }
2816
+ async deliver(context, source, record2, event, deliveryId) {
2817
+ const fresh = await context.service.get(record2.id);
2818
+ if (!fresh || !fresh.enabled) return 0;
2819
+ const freshSource = source.validateSource(fresh.source);
2820
+ const eventSource = source.validateSource(event.source);
2821
+ if (source.key(freshSource) !== source.key(eventSource)) return 0;
2822
+ if (event.kind === "ci" && !event.actionable) return 0;
2823
+ const cursorKey = `webhook:${deliveryId}`;
2824
+ const reservationKey = `${fresh.id}:${deliveryId}`;
2825
+ if (pendingDeliveries2.has(reservationKey)) return 0;
2826
+ if (fresh.cursors[cursorKey]) {
2827
+ await this.removePending(context, fresh, deliveryId);
2828
+ return 0;
2829
+ }
2830
+ pendingDeliveries2.add(reservationKey);
2831
+ try {
2832
+ await this.addPending(context, fresh, deliveryId, event);
2833
+ if (this.updateCursor) {
2834
+ const sourceCursorKey = source.key(freshSource);
2835
+ await context.service.updateCursorValue(fresh, sourceCursorKey, (cursor) => this.updateCursor(event, cursor));
2836
+ }
2837
+ const result = await context.sink.deliver({ monitor: fresh, event });
2838
+ if (!result.ok) {
2839
+ console.error(`[sourcefed] webhook route failed for ${fresh.id}: ${result.error}`);
2840
+ return 0;
2841
+ }
2842
+ await context.service.setDelivery(fresh.id, "webhook");
2843
+ await context.service.updateCursor(fresh, cursorKey, true);
2844
+ await this.removePending(context, fresh, deliveryId);
2845
+ if (event.terminal) await context.service.remove(fresh.id);
2846
+ return 1;
2847
+ } finally {
2848
+ pendingDeliveries2.delete(reservationKey);
2849
+ }
2850
+ }
2851
+ };
2852
+ var MAX_WEBHOOK_BODY_BYTES4 = 1024 * 1024;
2853
+ function toMonitorEvent3(event, source) {
2854
+ return {
2855
+ source,
2856
+ kind: event.kind,
2857
+ id: event.id,
2858
+ at: event.at,
2859
+ summary: event.summary,
2860
+ body: event.body,
2861
+ actionable: event.actionable,
2862
+ terminal: event.terminal
2863
+ };
2864
+ }
2865
+ var store$424;
2866
+ var DEFAULT_CONFIG24 = {
2867
+ lang: void 0,
2868
+ message: void 0,
2869
+ abortEarly: void 0,
2870
+ abortPipeEarly: void 0
2871
+ };
2872
+ // @__NO_SIDE_EFFECTS__
2873
+ function getGlobalConfig24(config$1) {
2874
+ if (!config$1 && !store$424) return DEFAULT_CONFIG24;
2875
+ return {
2876
+ lang: config$1?.lang ?? store$424?.lang,
2877
+ message: config$1?.message,
2878
+ abortEarly: config$1?.abortEarly ?? store$424?.abortEarly,
2879
+ abortPipeEarly: config$1?.abortPipeEarly ?? store$424?.abortPipeEarly
2880
+ };
2881
+ }
2882
+ var store$324;
2883
+ // @__NO_SIDE_EFFECTS__
2884
+ function getGlobalMessage24(lang) {
2885
+ return store$324?.get(lang);
2886
+ }
2887
+ var store$224;
2888
+ // @__NO_SIDE_EFFECTS__
2889
+ function getSchemaMessage24(lang) {
2890
+ return store$224?.get(lang);
2891
+ }
2892
+ var store$124;
2893
+ // @__NO_SIDE_EFFECTS__
2894
+ function getSpecificMessage24(reference, lang) {
2895
+ return store$124?.get(reference)?.get(lang);
2896
+ }
2897
+ // @__NO_SIDE_EFFECTS__
2898
+ function _stringify24(input) {
2899
+ const type = typeof input;
2900
+ if (type === "string") return `"${input}"`;
2901
+ if (type === "number" || type === "bigint" || type === "boolean") return `${input}`;
2902
+ if (type === "object" || type === "function") return (input && Object.getPrototypeOf(input)?.constructor?.name) ?? "null";
2903
+ return type;
2904
+ }
2905
+ function _addIssue24(context, label, dataset, config$1, other) {
2906
+ const input = other && "input" in other ? other.input : dataset.value;
2907
+ const expected = other?.expected ?? context.expects ?? null;
2908
+ const received = other?.received ?? /* @__PURE__ */ _stringify24(input);
2909
+ const issue = {
2910
+ kind: context.kind,
2911
+ type: context.type,
2912
+ input,
2913
+ expected,
2914
+ received,
2915
+ message: `Invalid ${label}: ${expected ? `Expected ${expected} but r` : "R"}eceived ${received}`,
2916
+ requirement: context.requirement,
2917
+ path: other?.path,
2918
+ issues: other?.issues,
2919
+ lang: config$1.lang,
2920
+ abortEarly: config$1.abortEarly,
2921
+ abortPipeEarly: config$1.abortPipeEarly
2922
+ };
2923
+ const isSchema = context.kind === "schema";
2924
+ const message$1 = other?.message ?? context.message ?? /* @__PURE__ */ getSpecificMessage24(context.reference, issue.lang) ?? (isSchema ? /* @__PURE__ */ getSchemaMessage24(issue.lang) : null) ?? config$1.message ?? /* @__PURE__ */ getGlobalMessage24(issue.lang);
2925
+ if (message$1 !== void 0) issue.message = typeof message$1 === "function" ? message$1(issue) : message$1;
2926
+ if (isSchema) dataset.typed = false;
2927
+ if (dataset.issues) dataset.issues.push(issue);
2928
+ else dataset.issues = [issue];
2929
+ }
2930
+ var _standardCache24 = /* @__PURE__ */ new WeakMap();
2931
+ // @__NO_SIDE_EFFECTS__
2932
+ function _getStandardProps24(context) {
2933
+ let cached = _standardCache24.get(context);
2934
+ if (!cached) {
2935
+ cached = {
2936
+ version: 1,
2937
+ vendor: "valibot",
2938
+ validate(value$1) {
2939
+ return context["~run"]({ value: value$1 }, /* @__PURE__ */ getGlobalConfig24());
2940
+ }
2941
+ };
2942
+ _standardCache24.set(context, cached);
2943
+ }
2944
+ return cached;
2945
+ }
2946
+ var EMOJI_REGEX24 = new RegExp("^(?:[\\u{1F1E6}-\\u{1F1FF}]{2}|\\u{1F3F4}[\\u{E0061}-\\u{E007A}]{2}[\\u{E0030}-\\u{E0039}\\u{E0061}-\\u{E007A}]{1,3}\\u{E007F}|(?:\\p{Emoji}\\uFE0F\\u20E3?|\\p{Emoji_Modifier_Base}\\p{Emoji_Modifier}?|(?![\\p{Emoji_Modifier_Base}\\u{1F1E6}-\\u{1F1FF}])\\p{Emoji_Presentation})(?:\\u200D(?:\\p{Emoji}\\uFE0F\\u20E3?|\\p{Emoji_Modifier_Base}\\p{Emoji_Modifier}?|(?![\\p{Emoji_Modifier_Base}\\u{1F1E6}-\\u{1F1FF}])\\p{Emoji_Presentation}))*)+$", "u");
2947
+ // @__NO_SIDE_EFFECTS__
2948
+ function getFallback23(schema, dataset, config$1) {
2949
+ return typeof schema.fallback === "function" ? schema.fallback(dataset, config$1) : schema.fallback;
2950
+ }
2951
+ // @__NO_SIDE_EFFECTS__
2952
+ function getDefault23(schema, dataset, config$1) {
2953
+ return typeof schema.default === "function" ? schema.default(dataset, config$1) : schema.default;
2954
+ }
2955
+ // @__NO_SIDE_EFFECTS__
2956
+ function array24(item, message$1) {
2957
+ return {
2958
+ kind: "schema",
2959
+ type: "array",
2960
+ reference: array24,
2961
+ expects: "Array",
2962
+ async: false,
2963
+ item,
2964
+ message: message$1,
2965
+ get "~standard"() {
2966
+ return /* @__PURE__ */ _getStandardProps24(this);
2967
+ },
2968
+ "~run"(dataset, config$1) {
2969
+ const input = dataset.value;
2970
+ if (Array.isArray(input)) {
2971
+ dataset.typed = true;
2972
+ dataset.value = [];
2973
+ for (let key = 0; key < input.length; key++) {
2974
+ const value$1 = input[key];
2975
+ const itemDataset = this.item["~run"]({ value: value$1 }, config$1);
2976
+ if (itemDataset.issues) {
2977
+ const pathItem = {
2978
+ type: "array",
2979
+ origin: "value",
2980
+ input,
2981
+ key,
2982
+ value: value$1
2983
+ };
2984
+ for (const issue of itemDataset.issues) {
2985
+ if (issue.path) issue.path.unshift(pathItem);
2986
+ else issue.path = [pathItem];
2987
+ dataset.issues?.push(issue);
2988
+ }
2989
+ if (!dataset.issues) dataset.issues = itemDataset.issues;
2990
+ if (config$1.abortEarly) {
2991
+ dataset.typed = false;
2992
+ break;
2993
+ }
2994
+ }
2995
+ if (!itemDataset.typed) dataset.typed = false;
2996
+ dataset.value.push(itemDataset.value);
2997
+ }
2998
+ } else _addIssue24(this, "type", dataset, config$1);
2999
+ return dataset;
3000
+ }
3001
+ };
3002
+ }
3003
+ // @__NO_SIDE_EFFECTS__
3004
+ function boolean23(message$1) {
3005
+ return {
3006
+ kind: "schema",
3007
+ type: "boolean",
3008
+ reference: boolean23,
3009
+ expects: "boolean",
3010
+ async: false,
3011
+ message: message$1,
3012
+ get "~standard"() {
3013
+ return /* @__PURE__ */ _getStandardProps24(this);
3014
+ },
3015
+ "~run"(dataset, config$1) {
3016
+ if (typeof dataset.value === "boolean") dataset.typed = true;
3017
+ else _addIssue24(this, "type", dataset, config$1);
3018
+ return dataset;
3019
+ }
3020
+ };
3021
+ }
3022
+ // @__NO_SIDE_EFFECTS__
3023
+ function literal3(literal_, message$1) {
3024
+ return {
3025
+ kind: "schema",
3026
+ type: "literal",
3027
+ reference: literal3,
3028
+ expects: /* @__PURE__ */ _stringify24(literal_),
3029
+ async: false,
3030
+ literal: literal_,
3031
+ message: message$1,
3032
+ get "~standard"() {
3033
+ return /* @__PURE__ */ _getStandardProps24(this);
3034
+ },
3035
+ "~run"(dataset, config$1) {
3036
+ if (dataset.value === this.literal) dataset.typed = true;
3037
+ else _addIssue24(this, "type", dataset, config$1);
3038
+ return dataset;
3039
+ }
3040
+ };
3041
+ }
3042
+ // @__NO_SIDE_EFFECTS__
3043
+ function object23(entries$1, message$1) {
3044
+ return {
3045
+ kind: "schema",
3046
+ type: "object",
3047
+ reference: object23,
3048
+ expects: "Object",
3049
+ async: false,
3050
+ entries: entries$1,
3051
+ message: message$1,
3052
+ get "~standard"() {
3053
+ return /* @__PURE__ */ _getStandardProps24(this);
3054
+ },
3055
+ "~run"(dataset, config$1) {
3056
+ const input = dataset.value;
3057
+ if (input && typeof input === "object") {
3058
+ dataset.typed = true;
3059
+ dataset.value = {};
3060
+ for (const key in this.entries) {
3061
+ const valueSchema = this.entries[key];
3062
+ if (key in input || (valueSchema.type === "exact_optional" || valueSchema.type === "optional" || valueSchema.type === "nullish") && valueSchema.default !== void 0) {
3063
+ const value$1 = key in input ? input[key] : /* @__PURE__ */ getDefault23(valueSchema);
3064
+ const valueDataset = valueSchema["~run"]({ value: value$1 }, config$1);
3065
+ if (valueDataset.issues) {
3066
+ const pathItem = {
3067
+ type: "object",
3068
+ origin: "value",
3069
+ input,
3070
+ key,
3071
+ value: value$1
3072
+ };
3073
+ for (const issue of valueDataset.issues) {
3074
+ if (issue.path) issue.path.unshift(pathItem);
3075
+ else issue.path = [pathItem];
3076
+ dataset.issues?.push(issue);
3077
+ }
3078
+ if (!dataset.issues) dataset.issues = valueDataset.issues;
3079
+ if (config$1.abortEarly) {
3080
+ dataset.typed = false;
3081
+ break;
3082
+ }
3083
+ }
3084
+ if (!valueDataset.typed) dataset.typed = false;
3085
+ dataset.value[key] = valueDataset.value;
3086
+ } else if (valueSchema.fallback !== void 0) dataset.value[key] = /* @__PURE__ */ getFallback23(valueSchema);
3087
+ else if (valueSchema.type !== "exact_optional" && valueSchema.type !== "optional" && valueSchema.type !== "nullish") {
3088
+ _addIssue24(this, "key", dataset, config$1, {
3089
+ input: void 0,
3090
+ expected: `"${key}"`,
3091
+ path: [{
3092
+ type: "object",
3093
+ origin: "key",
3094
+ input,
3095
+ key,
3096
+ value: input[key]
3097
+ }]
3098
+ });
3099
+ if (config$1.abortEarly) break;
3100
+ }
3101
+ }
3102
+ } else _addIssue24(this, "type", dataset, config$1);
3103
+ return dataset;
3104
+ }
3105
+ };
3106
+ }
3107
+ // @__NO_SIDE_EFFECTS__
3108
+ function optional22(wrapped, default_) {
3109
+ return {
3110
+ kind: "schema",
3111
+ type: "optional",
3112
+ reference: optional22,
3113
+ expects: `(${wrapped.expects} | undefined)`,
3114
+ async: false,
3115
+ wrapped,
3116
+ default: default_,
3117
+ get "~standard"() {
3118
+ return /* @__PURE__ */ _getStandardProps24(this);
3119
+ },
3120
+ "~run"(dataset, config$1) {
3121
+ if (dataset.value === void 0) {
3122
+ if (this.default !== void 0) dataset.value = /* @__PURE__ */ getDefault23(this, dataset, config$1);
3123
+ if (dataset.value === void 0) {
3124
+ dataset.typed = true;
3125
+ return dataset;
3126
+ }
3127
+ }
3128
+ return this.wrapped["~run"](dataset, config$1);
3129
+ }
3130
+ };
3131
+ }
3132
+ // @__NO_SIDE_EFFECTS__
3133
+ function string24(message$1) {
3134
+ return {
3135
+ kind: "schema",
3136
+ type: "string",
3137
+ reference: string24,
3138
+ expects: "string",
3139
+ async: false,
3140
+ message: message$1,
3141
+ get "~standard"() {
3142
+ return /* @__PURE__ */ _getStandardProps24(this);
3143
+ },
3144
+ "~run"(dataset, config$1) {
3145
+ if (typeof dataset.value === "string") dataset.typed = true;
3146
+ else _addIssue24(this, "type", dataset, config$1);
3147
+ return dataset;
3148
+ }
3149
+ };
3150
+ }
3151
+ // @__NO_SIDE_EFFECTS__
3152
+ function safeParse24(schema, input, config$1) {
3153
+ const dataset = schema["~run"]({ value: input }, /* @__PURE__ */ getGlobalConfig24(config$1));
3154
+ return {
3155
+ typed: dataset.typed,
3156
+ success: !dataset.issues,
3157
+ output: dataset.value,
3158
+ issues: dataset.issues
3159
+ };
3160
+ }
3161
+ var SlackSourceSchema = /* @__PURE__ */ object23({
3162
+ type: /* @__PURE__ */ literal3("slack"),
3163
+ channelId: /* @__PURE__ */ string24(),
3164
+ threadTs: /* @__PURE__ */ string24()
3165
+ });
3166
+ var SlackCursorSchema = /* @__PURE__ */ object23({
3167
+ primed: /* @__PURE__ */ boolean23(),
3168
+ messageIds: /* @__PURE__ */ array24(/* @__PURE__ */ string24()),
3169
+ latestTs: /* @__PURE__ */ optional22(/* @__PURE__ */ string24())
3170
+ });
3171
+ function messageTimestamp(ts) {
3172
+ const value = Number(ts ?? 0);
3173
+ if (Number.isFinite(value)) return value;
3174
+ return 0;
3175
+ }
3176
+ function messageAt(ts) {
3177
+ const value = messageTimestamp(ts);
3178
+ if (value > 0) return new Date(value * 1e3).toISOString();
3179
+ return (/* @__PURE__ */ new Date()).toISOString();
3180
+ }
3181
+ function parseSlackReadResult(result, cursorRaw) {
3182
+ const parsedCursor = /* @__PURE__ */ safeParse24(SlackCursorSchema, cursorRaw);
3183
+ let previous = {};
3184
+ if (parsedCursor.success) previous = parsedCursor.output;
3185
+ const cursor = {
3186
+ primed: previous.primed ?? false,
3187
+ messageIds: previous.messageIds ?? [],
3188
+ latestTs: previous.latestTs
3189
+ };
3190
+ const users = new Map((result.users ?? []).map((user) => [user.id, user.real_name ?? user.name ?? user.id ?? "someone"]));
3191
+ const messages = (result.messages ?? []).filter((message) => message.ts);
3192
+ const freshMessages = messages.filter((message) => {
3193
+ if (cursor.messageIds.includes(message.ts)) return false;
3194
+ if (!cursor.latestTs) return true;
3195
+ return messageTimestamp(message.ts) > messageTimestamp(cursor.latestTs);
3196
+ });
3197
+ let events = [];
3198
+ if (cursor.primed) {
3199
+ events = freshMessages.map((message) => {
3200
+ let text = "[message without text]";
3201
+ if (message.text?.trim()) text = message.text.trim();
3202
+ let author = users.get(message.user);
3203
+ if (!author) author = message.user;
3204
+ if (!author) author = "someone";
3205
+ return {
3206
+ kind: "message",
3207
+ id: `message:${message.ts}`,
3208
+ at: messageAt(message.ts),
3209
+ summary: `Slack thread message by ${author}`,
3210
+ body: text,
3211
+ actionable: true
3212
+ };
3213
+ });
3214
+ }
3215
+ cursor.primed = true;
3216
+ cursor.messageIds = [.../* @__PURE__ */ new Set([...cursor.messageIds, ...messages.map((message) => message.ts)])].slice(-500);
3217
+ let latestTs = cursor.latestTs;
3218
+ for (const message of messages) {
3219
+ if (messageTimestamp(message.ts) > messageTimestamp(latestTs)) latestTs = message.ts;
3220
+ }
3221
+ cursor.latestTs = latestTs;
3222
+ return { events, cursor };
3223
+ }
3224
+ var SLACK_API = "https://slack.com/api";
3225
+ var warnedMissingToken2 = false;
3226
+ function slackToken() {
3227
+ return process.env.SOURCEFED_SLACK_TOKEN;
3228
+ }
3229
+ async function fetchSlackThread(channelId, threadTs) {
3230
+ const messages = [];
3231
+ let cursor = "";
3232
+ let received = false;
3233
+ do {
3234
+ const page = await slackApi("conversations.replies", {
3235
+ channel: channelId,
3236
+ ts: threadTs,
3237
+ limit: "1000",
3238
+ inclusive: "false",
3239
+ ...cursor ? { cursor } : {}
3240
+ });
3241
+ if (!page) break;
3242
+ received = true;
3243
+ if (Array.isArray(page.messages)) messages.push(...page.messages);
3244
+ cursor = page.response_metadata?.next_cursor ?? "";
3245
+ if (messages.length >= 1e4) break;
3246
+ } while (cursor);
3247
+ if (!received) return void 0;
3248
+ const users = [];
3249
+ let usersCursor = "";
3250
+ do {
3251
+ const page = await slackApi("users.list", { limit: "200", ...usersCursor ? { cursor: usersCursor } : {} });
3252
+ if (!page) break;
3253
+ if (Array.isArray(page.members)) {
3254
+ users.push(...page.members.map((member) => ({
3255
+ id: member.id,
3256
+ name: member.name,
3257
+ real_name: member.real_name
3258
+ })));
3259
+ }
3260
+ usersCursor = page.response_metadata?.next_cursor ?? "";
3261
+ } while (usersCursor);
3262
+ const result = { messages, users };
3263
+ return result;
3264
+ }
3265
+ async function slackApi(method, params) {
3266
+ const token = slackToken();
3267
+ if (!token) {
3268
+ if (!warnedMissingToken2) {
3269
+ warnedMissingToken2 = true;
3270
+ console.warn("[sourcefed] SOURCEFED_SLACK_TOKEN is not set; Slack monitors will not poll");
3271
+ }
3272
+ return void 0;
3273
+ }
3274
+ try {
3275
+ const response = await fetch(`${SLACK_API}/${method}`, {
3276
+ method: "POST",
3277
+ headers: { authorization: `Bearer ${token}`, "content-type": "application/x-www-form-urlencoded" },
3278
+ body: new URLSearchParams(params)
3279
+ });
3280
+ const result = await response.json();
3281
+ if (!response.ok || result?.ok !== true) return void 0;
3282
+ return result;
3283
+ } catch {
3284
+ return void 0;
3285
+ }
3286
+ }
3287
+ async function pollSlack(source, cursorRaw) {
3288
+ const result = await fetchSlackThread(source.channelId, source.threadTs);
3289
+ if (!result) throw new Error("slack API request failed; check SOURCEFED_SLACK_TOKEN");
3290
+ const parsed = parseSlackReadResult(result, cursorRaw);
3291
+ return { ...parsed, terminal: false };
3292
+ }
3293
+ var slackPollMonitor = new PollMonitor3({
3294
+ run: async (source, cursor) => {
3295
+ const result = await pollSlack(source, cursor);
3296
+ return {
3297
+ cursor: result.cursor,
3298
+ events: result.events.map((event) => toMonitorEvent3(event, source)),
3299
+ terminal: result.terminal
3300
+ };
3301
+ }
3302
+ });
3303
+ function parseSlackWebhook(payload, _eventName, deliveryId) {
3304
+ const event = payload.event;
3305
+ const channelId = event?.channel;
3306
+ const threadTs = event?.thread_ts;
3307
+ if (payload.type !== "event_callback" || event?.type !== "message" || !channelId || !threadTs) return void 0;
3308
+ let text = "[message without text]";
3309
+ if (event.text?.trim()) text = event.text.trim();
3310
+ let author = event.username;
3311
+ if (!author) author = event.user;
3312
+ if (!author) author = event.bot_id;
3313
+ if (!author) author = "someone";
3314
+ let at = messageAt(event.event_ts);
3315
+ if (!event.event_ts) at = messageAt(event.ts);
3316
+ return {
3317
+ source: { type: "slack", channelId, threadTs },
3318
+ kind: "message",
3319
+ id: event.ts === void 0 ? void 0 : `message:${event.ts}`,
3320
+ at,
3321
+ summary: `Slack thread message by ${author}`,
3322
+ body: `${text}
3323
+
3324
+ Slack event: ${deliveryId}`,
3325
+ actionable: true
3326
+ };
3327
+ }
3328
+ function slackSignature(body, timestamp, secret) {
3329
+ return `v0=${createHmac2("sha256", secret).update(`v0:${timestamp}:${body}`).digest("hex")}`;
3330
+ }
3331
+ function validSlackSignature(body, request, secret = process.env.SOURCEFED_SLACK_SIGNING_SECRET) {
3332
+ const timestamp = request.headers.get("x-slack-request-timestamp");
3333
+ const signature = request.headers.get("x-slack-signature");
3334
+ const timestampValue = Number(timestamp);
3335
+ if (!timestamp || !signature || !secret || !Number.isFinite(timestampValue) || Math.abs(Date.now() / 1e3 - timestampValue) > 300) return false;
3336
+ const expected = slackSignature(body, timestamp, secret);
3337
+ const actual = Buffer.from(signature);
3338
+ const wanted = Buffer.from(expected);
3339
+ return actual.length === wanted.length && timingSafeEqual2(actual, wanted);
3340
+ }
3341
+ function updateSlackCursor(event, cursorRaw) {
3342
+ const parsed = /* @__PURE__ */ safeParse24(SlackCursorSchema, cursorRaw);
3343
+ let cursor = { primed: false, messageIds: [], latestTs: void 0 };
3344
+ if (parsed.success) cursor = parsed.output;
3345
+ if (event.id?.startsWith("message:")) {
3346
+ const messageId = event.id.slice("message:".length);
3347
+ if (!cursor.messageIds.includes(messageId)) cursor.messageIds.push(messageId);
3348
+ if (messageTimestamp(messageId) > messageTimestamp(cursor.latestTs)) cursor.latestTs = messageId;
3349
+ }
3350
+ cursor.primed = true;
3351
+ return cursor;
3352
+ }
3353
+ var slackWebhookMonitor = new WebhookMonitor2({
3354
+ path: "/webhooks/slack",
3355
+ preferred: true,
3356
+ configured: () => Boolean(process.env.SOURCEFED_SLACK_SIGNING_SECRET),
3357
+ verify: (body, request) => validSlackSignature(body, request),
3358
+ acknowledgeBeforeDelivery: true,
3359
+ challenge: (payload) => {
3360
+ if (payload.type === "url_verification" && payload.challenge) return { challenge: payload.challenge };
3361
+ return void 0;
3362
+ },
3363
+ deliveryId: (_request, payload) => payload.event_id,
3364
+ eventName: () => "slack",
3365
+ parse: parseSlackWebhook,
3366
+ updateCursor: updateSlackCursor
3367
+ });
3368
+ var SlackMonitor = class extends Monitor3 {
3369
+ poll = slackPollMonitor;
3370
+ webhook = slackWebhookMonitor;
3371
+ constructor() {
3372
+ super({ type: "slack", schema: SlackSourceSchema });
3373
+ }
3374
+ key = (source) => `slack:${source.channelId}#${source.threadTs}`;
3375
+ icon = "\u{F04B1}";
3376
+ label = () => "thread";
3377
+ detail = (source) => ` ${source.channelId}`;
3378
+ describe = (source) => `Slack ${source.channelId} thread ${source.threadTs}`;
3379
+ build(input) {
3380
+ if (input.channelId && input.threadTs) return { type: "slack", channelId: input.channelId, threadTs: input.threadTs };
3381
+ if (!input.threadUrl) return { error: "channelId + threadTs or threadUrl is required for a slack monitor" };
3382
+ let url;
3383
+ try {
3384
+ url = new URL(input.threadUrl);
3385
+ } catch {
3386
+ return { error: "threadUrl must be a valid Slack URL" };
3387
+ }
3388
+ const channelId = url.pathname.match(/\/archives\/([^/]+)/)?.[1];
3389
+ const pathTimestamp = url.pathname.match(/\/p(\d{10,})/)?.[1];
3390
+ let threadTs = url.searchParams.get("thread_ts") ?? void 0;
3391
+ if (!threadTs && pathTimestamp) threadTs = `${pathTimestamp.slice(0, -6)}.${pathTimestamp.slice(-6)}`;
3392
+ if (channelId && threadTs) return { type: "slack", channelId, threadTs };
3393
+ return { error: "threadUrl does not contain a Slack channel and thread timestamp" };
3394
+ }
3395
+ };
3396
+ var SOURCE_MAP = {
3397
+ jira: new JiraMonitor(),
3398
+ github: new GithubMonitor(),
3399
+ slack: new SlackMonitor()
3400
+ };
3401
+ var SOURCE_TYPES = Object.keys(SOURCE_MAP);
3402
+ var DAEMON_ENVIRONMENT = [
3403
+ "PATH",
3404
+ "HOME",
3405
+ "USER",
3406
+ "TMPDIR",
3407
+ "LANG",
3408
+ "XDG_CONFIG_HOME",
3409
+ "XDG_STATE_HOME",
3410
+ "ATLASSIAN_EMAIL",
3411
+ "ATLASSIAN_API_KEY",
3412
+ "GH_TOKEN",
3413
+ "GITHUB_TOKEN",
3414
+ "SOURCEFED_JIRA_BASE_URL",
3415
+ "SOURCEFED_JIRA_TERMINAL_STATUS",
3416
+ "SOURCEFED_STATE_DIR",
3417
+ "SOURCEFED_DAEMON_TOKEN",
3418
+ "SOURCEFED_SLACK_TOKEN",
3419
+ "SOURCEFED_GITHUB_WEBHOOK_SECRET",
3420
+ "SOURCEFED_SLACK_SIGNING_SECRET",
3421
+ "SOURCEFED_ENABLE_WEBHOOKS",
3422
+ "SOURCEFED_WEBHOOK_HOST",
3423
+ "SOURCEFED_WEBHOOK_PORT"
3424
+ ];
3425
+ function daemonEnvironment(host) {
3426
+ const environment = Object.fromEntries(
3427
+ DAEMON_ENVIRONMENT.flatMap((name) => process.env[name] === void 0 ? [] : [[name, process.env[name]]])
3428
+ );
3429
+ if (host) environment.SOURCEFED_STATE_DIR ??= path5.join(process.env.XDG_STATE_HOME ?? path5.join(os5.homedir(), ".local", "state"), "sourcefed", host);
3430
+ return environment;
3431
+ }
3432
+ async function serveHttp(options) {
3433
+ const active = /* @__PURE__ */ new Set();
3434
+ const server = createServer((incoming, outgoing) => {
3435
+ active.add(outgoing);
3436
+ outgoing.on("close", () => active.delete(outgoing));
3437
+ void handle(incoming, outgoing, options.handler);
3438
+ });
3439
+ server.requestTimeout = 0;
3440
+ server.on("error", (error) => {
3441
+ console.error(`[sourcefed] http server error: ${error instanceof Error ? error.message : String(error)}`);
3442
+ });
3443
+ server.on("clientError", (_error, socket) => {
3444
+ socket.end("HTTP/1.1 400 Bad Request\r\n\r\n");
3445
+ });
3446
+ await new Promise((resolve, reject) => {
3447
+ server.once("error", reject);
3448
+ server.listen(options.port, options.hostname, () => resolve());
3449
+ });
3450
+ const address = server.address();
3451
+ const port = typeof address === "object" && address ? address.port : options.port;
3452
+ return {
3453
+ port,
3454
+ async stop() {
3455
+ for (const outgoing of active) {
3456
+ outgoing.destroy();
3457
+ }
3458
+ active.clear();
3459
+ server.closeIdleConnections?.();
3460
+ await new Promise((resolve) => {
3461
+ server.close(() => resolve());
3462
+ });
3463
+ }
3464
+ };
3465
+ }
3466
+ async function handle(incoming, outgoing, handler) {
3467
+ try {
3468
+ const response = await handler(toRequest(incoming));
3469
+ await writeResponse(outgoing, response);
3470
+ } catch (error) {
3471
+ console.error(`[sourcefed] request failed: ${error instanceof Error ? error.message : String(error)}`);
3472
+ if (!outgoing.headersSent) outgoing.writeHead(500, { "content-type": "text/plain" });
3473
+ outgoing.end("internal error");
3474
+ }
3475
+ }
3476
+ function toRequest(incoming) {
3477
+ const url = new URL(incoming.url ?? "/", `http://${incoming.headers.host ?? "localhost"}`);
3478
+ const method = incoming.method ?? "GET";
3479
+ const hasBody = method !== "GET" && method !== "HEAD";
3480
+ const headers = new Headers();
3481
+ for (const [name, value] of Object.entries(incoming.headers)) {
3482
+ if (value === void 0) continue;
3483
+ if (Array.isArray(value)) {
3484
+ for (const item of value) headers.append(name, item);
3485
+ } else {
3486
+ headers.set(name, value);
3487
+ }
3488
+ }
3489
+ const init = {
3490
+ method,
3491
+ headers,
3492
+ body: hasBody ? Readable.toWeb(incoming) : void 0,
3493
+ duplex: "half"
3494
+ };
3495
+ return new Request(url, init);
3496
+ }
3497
+ async function writeResponse(outgoing, response) {
3498
+ outgoing.writeHead(response.status, Object.fromEntries(response.headers.entries()));
3499
+ if (!response.body) {
3500
+ outgoing.end();
3501
+ return;
3502
+ }
3503
+ const stream = Readable.fromWeb(response.body);
3504
+ stream.on("error", (error) => {
3505
+ console.error(`[sourcefed] response stream error: ${error instanceof Error ? error.message : String(error)}`);
3506
+ });
3507
+ await new Promise((resolve) => {
3508
+ let finished = false;
3509
+ const done = () => {
3510
+ if (finished) return;
3511
+ finished = true;
3512
+ resolve();
3513
+ };
3514
+ outgoing.on("finish", done);
3515
+ outgoing.on("close", () => {
3516
+ stream.unpipe(outgoing);
3517
+ stream.destroy();
3518
+ done();
3519
+ });
3520
+ stream.pipe(outgoing);
3521
+ });
3522
+ }
3523
+ var RequestRouter = class {
3524
+ pending = /* @__PURE__ */ new Map();
3525
+ cursor = 1;
3526
+ nextID() {
3527
+ return this.cursor++;
3528
+ }
3529
+ register(id) {
3530
+ return new Promise((resolve, reject) => {
3531
+ this.pending.set(id, { resolve, reject });
3532
+ });
3533
+ }
3534
+ settle(id, frame) {
3535
+ const pending = this.pending.get(id);
3536
+ if (!pending) return;
3537
+ this.pending.delete(id);
3538
+ if (frame.error) pending.reject(new Error(frame.error));
3539
+ else pending.resolve(frame.result);
3540
+ }
3541
+ rejectAll(error) {
3542
+ for (const { reject } of this.pending.values()) reject(error);
3543
+ this.pending.clear();
3544
+ }
3545
+ };
3546
+ function defaultDaemonUrl() {
3547
+ const port = process.env.SOURCEFED_DAEMON_PORT ?? 18787;
3548
+ return `http://127.0.0.1:${port}`;
3549
+ }
3550
+ function daemonCommand(cliEntryPath) {
3551
+ return {
3552
+ command: nodeExecutable(),
3553
+ args: [cliEntryPath, "daemon"]
3554
+ };
3555
+ }
3556
+ function nodeExecutable() {
3557
+ const base = path6.basename(process.execPath);
3558
+ if (base.includes("node") || base.includes("bun")) return process.execPath;
3559
+ return "node";
3560
+ }
3561
+ async function spawnLocalDaemon(options) {
3562
+ const port = options.port ?? Number(process.env.SOURCEFED_DAEMON_PORT ?? 18787);
3563
+ const url = `http://127.0.0.1:${port}`;
3564
+ const token = options.env?.SOURCEFED_DAEMON_TOKEN ?? process.env.SOURCEFED_DAEMON_TOKEN;
3565
+ if (await daemonReachable(url, 750, token)) return { url };
3566
+ const proc = spawn(options.command, [...options.args, "--port", String(port)], {
3567
+ stdio: ["ignore", "ignore", "ignore"],
3568
+ detached: true,
3569
+ env: { ...process.env, ...options.env }
3570
+ });
3571
+ proc.unref();
3572
+ if (!await waitForDaemon(url, options.timeoutMs ?? 1e4, token)) {
3573
+ proc.kill();
3574
+ throw new Error(`sourcefed daemon did not start at ${url}`);
3575
+ }
3576
+ return { url, proc };
3577
+ }
3578
+ async function daemonReachable(url, timeoutMs, token) {
3579
+ const controller = new AbortController();
3580
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
3581
+ try {
3582
+ const response = await fetch(`${url}/rpc`, {
3583
+ method: "POST",
3584
+ headers: { "content-type": "application/json", ...token ? { authorization: `Bearer ${token}` } : {} },
3585
+ body: JSON.stringify({ method: "daemon.sourceTypes" }),
3586
+ signal: controller.signal
3587
+ });
3588
+ return response.ok;
3589
+ } catch {
3590
+ return false;
3591
+ } finally {
3592
+ clearTimeout(timer);
3593
+ }
3594
+ }
3595
+ async function waitForDaemon(url, timeoutMs, token) {
3596
+ const deadline = Date.now() + timeoutMs;
3597
+ while (Date.now() < deadline) {
3598
+ if (await daemonReachable(url, 750, token)) return true;
3599
+ await sleep(150);
3600
+ }
3601
+ return false;
3602
+ }
3603
+ function sleep(ms) {
3604
+ return new Promise((resolve) => setTimeout(resolve, ms));
3605
+ }
3606
+ async function withTimeout(promise, ms, message) {
3607
+ let timer;
3608
+ try {
3609
+ return await Promise.race([
3610
+ promise,
3611
+ new Promise((_resolve, reject) => {
3612
+ timer = setTimeout(() => reject(new Error(message)), ms);
3613
+ })
3614
+ ]);
3615
+ } finally {
3616
+ if (timer) clearTimeout(timer);
3617
+ }
3618
+ }
3619
+ function encodeTarget(target) {
3620
+ return Buffer.from(JSON.stringify(target), "utf8").toString("base64url");
3621
+ }
3622
+ function decodeTarget(value) {
3623
+ try {
3624
+ const parsed = JSON.parse(Buffer.from(value, "base64url").toString("utf8"));
3625
+ if (!parsed || typeof parsed !== "object") return void 0;
3626
+ const target = parsed;
3627
+ if (typeof target.kind !== "string" || typeof target.id !== "string") return void 0;
3628
+ return { kind: target.kind, id: target.id };
3629
+ } catch {
3630
+ return void 0;
3631
+ }
3632
+ }
3633
+ function targetKey(target) {
3634
+ return JSON.stringify(target);
3635
+ }
3636
+ function parseDaemonFrame(line) {
3637
+ try {
3638
+ return JSON.parse(line);
3639
+ } catch {
3640
+ return void 0;
3641
+ }
3642
+ }
3643
+ async function connectDaemonClient(options) {
3644
+ const url = options.url ?? process.env.SOURCEFED_DAEMON_URL ?? defaultDaemonUrl();
3645
+ return new HttpDaemonClient(url, options.token ?? process.env.SOURCEFED_DAEMON_TOKEN);
3646
+ }
3647
+ var HttpDaemonClient = class {
3648
+ baseURL;
3649
+ token;
3650
+ router = new RequestRouter();
3651
+ listeners = /* @__PURE__ */ new Map();
3652
+ streams = /* @__PURE__ */ new Set();
3653
+ constructor(baseURL, token) {
3654
+ this.baseURL = baseURL.replace(/\/+$/, "");
3655
+ this.token = token;
3656
+ }
3657
+ headers() {
3658
+ return this.token ? { authorization: `Bearer ${this.token}` } : {};
3659
+ }
3660
+ async request(method, params) {
3661
+ const id = this.router.nextID();
3662
+ const promise = this.router.register(id);
3663
+ try {
3664
+ const response = await fetch(`${this.baseURL}/rpc`, {
3665
+ method: "POST",
3666
+ headers: { "content-type": "application/json", ...this.headers() },
3667
+ body: JSON.stringify({ id, method, params })
3668
+ });
3669
+ const frame = await response.json();
3670
+ if (!response.ok || frame.error) throw new Error(frame.error ?? `daemon request failed: ${response.status}`);
3671
+ this.router.settle(id, { result: frame.result });
3672
+ } catch (error) {
3673
+ this.router.settle(id, { error: error instanceof Error ? error.message : String(error) });
3674
+ }
3675
+ return promise;
3676
+ }
3677
+ async subscribe(target, onEvents) {
3678
+ const key = targetKey(target);
3679
+ let callbacks = this.listeners.get(key);
3680
+ if (!callbacks) {
3681
+ callbacks = /* @__PURE__ */ new Set();
3682
+ this.listeners.set(key, callbacks);
3683
+ }
3684
+ callbacks.add(onEvents);
3685
+ const controller = new AbortController();
3686
+ this.streams.add(controller);
3687
+ const url = `${this.baseURL}/events?target=${encodeTarget(target)}`;
3688
+ let markSubscribed;
3689
+ const subscribed = new Promise((resolve) => {
3690
+ markSubscribed = () => resolve();
3691
+ });
3692
+ let drainComplete = false;
3693
+ let draining = false;
3694
+ let redrainRequested = false;
3695
+ let retryTimer;
3696
+ let markDrained;
3697
+ const drained = new Promise((resolve) => {
3698
+ markDrained = resolve;
3699
+ });
3700
+ let buffered = [];
3701
+ let delivering = Promise.resolve();
3702
+ const deliver = (events) => {
3703
+ if (!callbacks?.has(onEvents) || events.length === 0) return Promise.resolve();
3704
+ delivering = delivering.then(() => deliverWithRetry(events, 0)).catch((error) => {
3705
+ console.error(`[sourcefed] event delivery failed: ${error instanceof Error ? error.message : String(error)}`);
3706
+ });
3707
+ return delivering;
3708
+ };
3709
+ const deliverWithRetry = async (events, attempt) => {
3710
+ try {
3711
+ await onEvents(events);
3712
+ } catch (error) {
3713
+ if (attempt >= 2) {
3714
+ console.error(`[sourcefed] event delivery failed after retries; re-draining queue: ${error instanceof Error ? error.message : String(error)}`);
3715
+ redrainRequested = true;
3716
+ void performDrain();
3717
+ return;
3718
+ }
3719
+ await sleep(250 * (attempt + 1));
3720
+ await deliverWithRetry(events, attempt + 1);
3721
+ }
3722
+ };
3723
+ const performDrain = async () => {
3724
+ if (draining) {
3725
+ redrainRequested = true;
3726
+ return;
3727
+ }
3728
+ draining = true;
3729
+ try {
3730
+ let passes = 0;
3731
+ do {
3732
+ redrainRequested = false;
3733
+ passes += 1;
3734
+ try {
3735
+ const drainResult = await this.request("monitor.events", { target });
3736
+ drainComplete = true;
3737
+ const drainedIds = new Set((drainResult.events ?? []).map((event) => event.id));
3738
+ if (drainResult.events && drainResult.events.length > 0) await deliver(drainResult.events);
3739
+ for (const batch of buffered) {
3740
+ const fresh = batch.filter((event) => !drainedIds.has(event.id));
3741
+ if (fresh.length > 0) await deliver(fresh);
3742
+ }
3743
+ buffered = [];
3744
+ markDrained();
3745
+ } catch (error) {
3746
+ console.error(`[sourcefed] event drain failed: ${error instanceof Error ? error.message : String(error)}`);
3747
+ redrainRequested = true;
3748
+ }
3749
+ } while (redrainRequested && passes < 3);
3750
+ if (redrainRequested) markDrained();
3751
+ } finally {
3752
+ draining = false;
3753
+ }
3754
+ };
3755
+ const runStream = async () => {
3756
+ try {
3757
+ const response = await fetch(url, { signal: controller.signal, headers: this.headers() });
3758
+ if (!response.ok || !response.body) throw new Error(`event stream failed: ${response.status}`);
3759
+ const reader = response.body.getReader();
3760
+ const decoder = new TextDecoder();
3761
+ let buffer = "";
3762
+ while (true) {
3763
+ const { done, value } = await reader.read();
3764
+ if (done) break;
3765
+ buffer += decoder.decode(value, { stream: true });
3766
+ const frames = buffer.split("\n\n");
3767
+ buffer = frames.pop() ?? "";
3768
+ for (const chunk of frames) {
3769
+ for (const line of chunk.split("\n")) {
3770
+ if (!line.startsWith("data: ")) continue;
3771
+ const frame = parseDaemonFrame(line.slice(6));
3772
+ if (!frame || !("type" in frame)) continue;
3773
+ if (frame.type === "subscribed") {
3774
+ markSubscribed();
3775
+ drainComplete = false;
3776
+ void performDrain();
3777
+ }
3778
+ if (frame.type === "event") {
3779
+ if (drainComplete) void deliver(frame.events);
3780
+ else buffered.push(frame.events);
3781
+ }
3782
+ }
3783
+ }
3784
+ }
3785
+ consecutiveFailures = 0;
3786
+ } catch (error) {
3787
+ if (!controller.signal.aborted) {
3788
+ consecutiveFailures += 1;
3789
+ if (consecutiveFailures === 1) {
3790
+ console.error(`[sourcefed] event stream ended: ${error instanceof Error ? error.message : String(error)}`);
3791
+ }
3792
+ }
3793
+ }
3794
+ };
3795
+ let consecutiveFailures = 0;
3796
+ void (async () => {
3797
+ while (!controller.signal.aborted && callbacks?.has(onEvents)) {
3798
+ await runStream();
3799
+ if (controller.signal.aborted) break;
3800
+ await sleep(500);
3801
+ }
3802
+ })();
3803
+ try {
3804
+ await withTimeout(subscribed, 15e3, `sourcefed event stream did not subscribe at ${url}`);
3805
+ await withTimeout(drained, 15e3, "sourcefed initial event drain timed out");
3806
+ } catch (error) {
3807
+ if (retryTimer) clearTimeout(retryTimer);
3808
+ const callbacks2 = this.listeners.get(key);
3809
+ callbacks2?.delete(onEvents);
3810
+ if (callbacks2 && callbacks2.size === 0) this.listeners.delete(key);
3811
+ controller.abort();
3812
+ this.streams.delete(controller);
3813
+ throw error;
3814
+ }
3815
+ return {
3816
+ close: async () => {
3817
+ if (retryTimer) clearTimeout(retryTimer);
3818
+ const callbacks2 = this.listeners.get(key);
3819
+ callbacks2?.delete(onEvents);
3820
+ if (callbacks2 && callbacks2.size === 0) this.listeners.delete(key);
3821
+ controller.abort();
3822
+ this.streams.delete(controller);
3823
+ }
3824
+ };
3825
+ }
3826
+ async close() {
3827
+ for (const controller of this.streams) controller.abort();
3828
+ this.streams.clear();
3829
+ this.router.rejectAll(new Error("daemon closed"));
3830
+ this.listeners.clear();
3831
+ }
3832
+ };
3833
+ async function dispatchDaemonRequest(daemon, method, params) {
3834
+ switch (method) {
3835
+ case "monitor.create":
3836
+ return daemon.createMonitor(createTarget(params), createCreateInput(params));
3837
+ case "monitor.list":
3838
+ return daemon.listMonitors(createTarget(params));
3839
+ case "monitor.status":
3840
+ return daemon.getMonitor(createTarget(params), String(params.id));
3841
+ case "monitor.stop":
3842
+ return daemon.stopMonitor(createTarget(params), String(params.id));
3843
+ case "monitor.start":
3844
+ return daemon.startMonitor(createTarget(params), String(params.id));
3845
+ case "monitor.remove":
3846
+ return daemon.removeMonitor(createTarget(params), String(params.id));
3847
+ case "monitor.events":
3848
+ return { events: await daemon.readEvents(createTarget(params)) };
3849
+ case "monitor.logs":
3850
+ return { logs: await daemon.readLogs(createTarget(params)) };
3851
+ case "monitor.ack":
3852
+ return daemon.acknowledgeEvents(createTarget(params), params.eventIDs ?? []);
3853
+ case "daemon.sourceTypes":
3854
+ return { sourceTypes: daemon.sourceTypes };
3855
+ default:
3856
+ throw new Error(`unknown daemon method ${method}`);
3857
+ }
3858
+ }
3859
+ function createTarget(params) {
3860
+ const target = params.target;
3861
+ if (!target || typeof target.kind !== "string" || typeof target.id !== "string") {
3862
+ throw new Error("params.target is required and must be { kind: string, id: string }");
3863
+ }
3864
+ return { kind: target.kind, id: target.id };
3865
+ }
3866
+ function createCreateInput(params) {
3867
+ if (typeof params.name !== "string" || !params.name) throw new Error("monitor.create requires params.name");
3868
+ if (typeof params.sourceType !== "string" || !params.sourceType) throw new Error("monitor.create requires params.sourceType");
3869
+ const input = {
3870
+ name: params.name,
3871
+ sourceType: params.sourceType
3872
+ };
3873
+ for (const key of ["issueKey", "repo", "channelId", "threadTs", "threadUrl"]) {
3874
+ if (typeof params[key] === "string") input[key] = params[key];
3875
+ }
3876
+ if (typeof params.prNumber === "number") input.prNumber = params.prNumber;
3877
+ if (params.pollIntervalSec !== void 0) {
3878
+ if (typeof params.pollIntervalSec !== "number" || !Number.isFinite(params.pollIntervalSec)) {
3879
+ throw new Error("pollIntervalSec must be a number");
3880
+ }
3881
+ input.pollIntervalSec = params.pollIntervalSec;
3882
+ }
3883
+ return input;
3884
+ }
3885
+ async function handleDaemonHttpRequest(daemon, request, options = {}) {
3886
+ const url = new URL(request.url);
3887
+ if (url.pathname === "/rpc" || url.pathname === "/events") {
3888
+ if (!authorized(request, options.token)) return new Response("unauthorized", { status: 401 });
3889
+ }
3890
+ if (url.pathname === "/rpc" && request.method === "POST") {
3891
+ let parsed;
3892
+ try {
3893
+ parsed = await request.json();
3894
+ } catch {
3895
+ return Response.json({ error: "invalid json" }, { status: 400 });
3896
+ }
3897
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed) || typeof parsed.method !== "string") {
3898
+ return Response.json({ error: "invalid request" }, { status: 400 });
3899
+ }
3900
+ const body = parsed;
3901
+ try {
3902
+ const result = await dispatchDaemonRequest(daemon, body.method, body.params ?? {});
3903
+ return Response.json({ id: body.id, result });
3904
+ } catch (error) {
3905
+ return Response.json({ id: body.id, error: error instanceof Error ? error.message : String(error) }, { status: 400 });
3906
+ }
3907
+ }
3908
+ if (url.pathname === "/events" && request.method === "GET") {
3909
+ const target = decodeTarget(url.searchParams.get("target") ?? "");
3910
+ if (!target) return new Response("invalid target", { status: 400 });
3911
+ return eventStream(daemon, target);
3912
+ }
3913
+ return daemon.webhook(request);
3914
+ }
3915
+ function authorized(request, token) {
3916
+ if (!token) return true;
3917
+ return request.headers.get("authorization") === `Bearer ${token}`;
3918
+ }
3919
+ function eventStream(daemon, target) {
3920
+ let unsubscribe;
3921
+ let keepalive;
3922
+ const stream = new ReadableStream({
3923
+ start(controller) {
3924
+ const encoder = new TextEncoder();
3925
+ unsubscribe = daemon.subscribe(target, (events) => {
3926
+ controller.enqueue(encoder.encode(`data: ${JSON.stringify({ type: "event", target, events })}
3927
+
3928
+ `));
3929
+ });
3930
+ controller.enqueue(encoder.encode(`data: ${JSON.stringify({ type: "subscribed", target })}
3931
+
3932
+ `));
3933
+ keepalive = setInterval(() => {
3934
+ controller.enqueue(encoder.encode(": keepalive\n\n"));
3935
+ }, 3e4);
3936
+ keepalive.unref?.();
3937
+ },
3938
+ cancel() {
3939
+ if (keepalive) clearInterval(keepalive);
3940
+ unsubscribe?.();
3941
+ }
3942
+ });
3943
+ return new Response(stream, {
3944
+ headers: {
3945
+ "content-type": "text/event-stream",
3946
+ "cache-control": "no-cache",
3947
+ connection: "keep-alive"
3948
+ }
3949
+ });
3950
+ }
3951
+
3952
+ export {
3953
+ SOURCE_TYPES,
3954
+ daemonEnvironment,
3955
+ serveHttp,
3956
+ defaultDaemonUrl,
3957
+ daemonCommand,
3958
+ spawnLocalDaemon,
3959
+ encodeTarget,
3960
+ decodeTarget,
3961
+ connectDaemonClient,
3962
+ handleDaemonHttpRequest
3963
+ };