@fdcn/sourcefed 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (220) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +51 -0
  3. package/dist/chunk-B7T6IZYS.js +5155 -0
  4. package/dist/chunk-MLKGABMK.js +9 -0
  5. package/dist/chunk-NGJ6F5ST.js +766 -0
  6. package/dist/cli.js +30491 -0
  7. package/dist/core-storage.js +13 -0
  8. package/dist/core.js +643 -0
  9. package/dist/daemon.js +5190 -0
  10. package/dist/data/core/SKILL.md +71 -0
  11. package/dist/data/core/references/commands.md +75 -0
  12. package/dist/data/sourcefed/SKILL.md +31 -0
  13. package/dist/guidance.md +71 -0
  14. package/dist/mcp.js +4800 -0
  15. package/dist/opencode.js +12646 -0
  16. package/dist/pi.js +249 -0
  17. package/dist/tui-compiled.mjs +4096 -0
  18. package/dist.dts/adapters/opencode/bridge.d.ts +20 -0
  19. package/dist.dts/adapters/opencode/index.d.ts +3 -0
  20. package/dist.dts/adapters/opencode/tool-result.d.ts +1 -0
  21. package/dist.dts/adapters/opencode/tools/monitor_create.d.ts +3 -0
  22. package/dist.dts/adapters/opencode/tools/monitor_list.d.ts +3 -0
  23. package/dist.dts/adapters/opencode/tools/monitor_start.d.ts +3 -0
  24. package/dist.dts/adapters/opencode/tools/monitor_status.d.ts +3 -0
  25. package/dist.dts/adapters/opencode/tools/monitor_stop.d.ts +3 -0
  26. package/dist.dts/adapters/opencode/tui/plugin.d.ts +3 -0
  27. package/dist.dts/adapters/opencode/tui/sidebar.d.ts +13 -0
  28. package/dist.dts/adapters/opencode/tui.d.ts +7 -0
  29. package/dist.dts/adapters/pi/src/dialog.d.ts +12 -0
  30. package/dist.dts/adapters/pi/src/index.d.ts +2 -0
  31. package/dist.dts/index.d.ts +1 -0
  32. package/dist.dts/packages/cli/src/index.d.ts +2 -0
  33. package/dist.dts/packages/cli/src/skills/command.d.ts +1 -0
  34. package/dist.dts/packages/cli/src/skills/discovery.d.ts +7 -0
  35. package/dist.dts/packages/cli/src/skills/frontmatter.d.ts +5 -0
  36. package/dist.dts/packages/cli/src/skills/index.d.ts +1 -0
  37. package/dist.dts/packages/core/src/events.d.ts +24 -0
  38. package/dist.dts/packages/core/src/events.test.d.ts +1 -0
  39. package/dist.dts/packages/core/src/index.d.ts +9 -0
  40. package/dist.dts/packages/core/src/monitors/index.d.ts +6 -0
  41. package/dist.dts/packages/core/src/monitors/monitor.d.ts +26 -0
  42. package/dist.dts/packages/core/src/monitors/poll/index.d.ts +1 -0
  43. package/dist.dts/packages/core/src/monitors/poll/monitor.d.ts +12 -0
  44. package/dist.dts/packages/core/src/monitors/poll/types/index.d.ts +2 -0
  45. package/dist.dts/packages/core/src/monitors/poll/types/poll-monitor-options.d.ts +6 -0
  46. package/dist.dts/packages/core/src/monitors/poll/types/poll-runner.d.ts +3 -0
  47. package/dist.dts/packages/core/src/monitors/poll/types/routed-event-cursor.d.ts +2 -0
  48. package/dist.dts/packages/core/src/monitors/poll/utils/event-delivery-id.d.ts +2 -0
  49. package/dist.dts/packages/core/src/monitors/poll/utils/tick.d.ts +2 -0
  50. package/dist.dts/packages/core/src/monitors/schema.d.ts +37 -0
  51. package/dist.dts/packages/core/src/monitors/service.d.ts +35 -0
  52. package/dist.dts/packages/core/src/monitors/service.test.d.ts +1 -0
  53. package/dist.dts/packages/core/src/monitors/transport.test.d.ts +1 -0
  54. package/dist.dts/packages/core/src/monitors/types/index.d.ts +2 -0
  55. package/dist.dts/packages/core/src/monitors/types/monitor-record.d.ts +3 -0
  56. package/dist.dts/packages/core/src/monitors/types/monitor-registry.d.ts +3 -0
  57. package/dist.dts/packages/core/src/monitors/utils/monitor-identity.d.ts +5 -0
  58. package/dist.dts/packages/core/src/monitors/utils/monitor-identity.test.d.ts +1 -0
  59. package/dist.dts/packages/core/src/monitors/webhook/index.d.ts +1 -0
  60. package/dist.dts/packages/core/src/monitors/webhook/monitor.d.ts +24 -0
  61. package/dist.dts/packages/core/src/monitors/webhook/types/index.d.ts +3 -0
  62. package/dist.dts/packages/core/src/monitors/webhook/types/pending-webhook-event.d.ts +19 -0
  63. package/dist.dts/packages/core/src/monitors/webhook/types/webhook-monitor-options.d.ts +13 -0
  64. package/dist.dts/packages/core/src/monitors/webhook/utils/constants.d.ts +2 -0
  65. package/dist.dts/packages/core/src/monitors/webhook/utils/deliver-webhook.d.ts +3 -0
  66. package/dist.dts/packages/core/src/monitors/webhook/utils/handle-webhook.d.ts +2 -0
  67. package/dist.dts/packages/core/src/monitors/webhook/utils/handle-webhook.test.d.ts +1 -0
  68. package/dist.dts/packages/core/src/monitors/webhook/utils/heartbeat-webhooks.d.ts +3 -0
  69. package/dist.dts/packages/core/src/runtime.d.ts +23 -0
  70. package/dist.dts/packages/core/src/runtime.test.d.ts +1 -0
  71. package/dist.dts/packages/core/src/sources/index.d.ts +1 -0
  72. package/dist.dts/packages/core/src/sources/schema.d.ts +4 -0
  73. package/dist.dts/packages/core/src/sources/types/index.d.ts +3 -0
  74. package/dist.dts/packages/core/src/sources/types/monitor-source.d.ts +3 -0
  75. package/dist.dts/packages/core/src/sources/types/source-input.d.ts +4 -0
  76. package/dist.dts/packages/core/src/sources/types/source-registry.d.ts +2 -0
  77. package/dist.dts/packages/core/src/storage/in-memory-monitor-store.d.ts +8 -0
  78. package/dist.dts/packages/core/src/storage/index.d.ts +2 -0
  79. package/dist.dts/packages/core/src/storage/json.d.ts +30 -0
  80. package/dist.dts/packages/core/src/storage/json.test.d.ts +1 -0
  81. package/dist.dts/packages/core/src/types/delivery.d.ts +1 -0
  82. package/dist.dts/packages/core/src/types/event-sink.d.ts +12 -0
  83. package/dist.dts/packages/core/src/types/index.d.ts +11 -0
  84. package/dist.dts/packages/core/src/types/monitor-context.d.ts +10 -0
  85. package/dist.dts/packages/core/src/types/monitor-create-input.d.ts +8 -0
  86. package/dist.dts/packages/core/src/types/monitor-event-queue.d.ts +18 -0
  87. package/dist.dts/packages/core/src/types/monitor-event.d.ts +10 -0
  88. package/dist.dts/packages/core/src/types/monitor-options.d.ts +7 -0
  89. package/dist.dts/packages/core/src/types/monitor-store.d.ts +6 -0
  90. package/dist.dts/packages/core/src/types/monitor-target.d.ts +4 -0
  91. package/dist.dts/packages/core/src/types/source-event.d.ts +9 -0
  92. package/dist.dts/packages/core/src/types/source-poll-result.d.ts +6 -0
  93. package/dist.dts/packages/core/src/utils/event-to-text.d.ts +2 -0
  94. package/dist.dts/packages/core/src/utils/to-monitor-event.d.ts +3 -0
  95. package/dist.dts/packages/daemon/src/client.d.ts +2 -0
  96. package/dist.dts/packages/daemon/src/client.test.d.ts +1 -0
  97. package/dist.dts/packages/daemon/src/daemon.d.ts +36 -0
  98. package/dist.dts/packages/daemon/src/daemon.test.d.ts +1 -0
  99. package/dist.dts/packages/daemon/src/dispatch.d.ts +2 -0
  100. package/dist.dts/packages/daemon/src/http.d.ts +5 -0
  101. package/dist.dts/packages/daemon/src/index.d.ts +8 -0
  102. package/dist.dts/packages/daemon/src/registry.d.ts +14 -0
  103. package/dist.dts/packages/daemon/src/types/client.d.ts +21 -0
  104. package/dist.dts/packages/daemon/src/types/daemon.d.ts +58 -0
  105. package/dist.dts/packages/daemon/src/types/index.d.ts +3 -0
  106. package/dist.dts/packages/daemon/src/types/protocol.d.ts +21 -0
  107. package/dist.dts/packages/daemon/src/utils/env.d.ts +1 -0
  108. package/dist.dts/packages/daemon/src/utils/http-server.d.ts +10 -0
  109. package/dist.dts/packages/daemon/src/utils/index.d.ts +6 -0
  110. package/dist.dts/packages/daemon/src/utils/request-router.d.ts +11 -0
  111. package/dist.dts/packages/daemon/src/utils/sink.d.ts +13 -0
  112. package/dist.dts/packages/daemon/src/utils/spawn.d.ts +19 -0
  113. package/dist.dts/packages/daemon/src/utils/target.d.ts +4 -0
  114. package/dist.dts/packages/mcp/src/index.d.ts +2 -0
  115. package/dist.dts/packages/mcp/src/server.d.ts +14 -0
  116. package/dist.dts/packages/mcp/src/server.test.d.ts +1 -0
  117. package/dist.dts/packages/mcp/src/uris.d.ts +3 -0
  118. package/dist.dts/packages/sourcefed/src/server.d.ts +7 -0
  119. package/dist.dts/providers/github/src/api/client.d.ts +3 -0
  120. package/dist.dts/providers/github/src/api/comments.d.ts +1 -0
  121. package/dist.dts/providers/github/src/api/github-api.test.d.ts +1 -0
  122. package/dist.dts/providers/github/src/api/index.d.ts +4 -0
  123. package/dist.dts/providers/github/src/api/logs.d.ts +5 -0
  124. package/dist.dts/providers/github/src/api/pr.d.ts +8 -0
  125. package/dist.dts/providers/github/src/github.test.d.ts +1 -0
  126. package/dist.dts/providers/github/src/index.d.ts +2 -0
  127. package/dist.dts/providers/github/src/monitor.d.ts +15 -0
  128. package/dist.dts/providers/github/src/poll/index.d.ts +2 -0
  129. package/dist.dts/providers/github/src/poll/monitor.d.ts +3 -0
  130. package/dist.dts/providers/github/src/poll/utils/append-comments.d.ts +2 -0
  131. package/dist.dts/providers/github/src/poll/utils/append-github-ci-event.d.ts +2 -0
  132. package/dist.dts/providers/github/src/poll/utils/append-github-conflict-event.d.ts +2 -0
  133. package/dist.dts/providers/github/src/poll/utils/append-github-reviews.d.ts +2 -0
  134. package/dist.dts/providers/github/src/poll/utils/append-github-state-event.d.ts +2 -0
  135. package/dist.dts/providers/github/src/poll/utils/ci-failure-detail.d.ts +1 -0
  136. package/dist.dts/providers/github/src/poll/utils/clean-ci-log.d.ts +1 -0
  137. package/dist.dts/providers/github/src/poll/utils/confirm-still-failing.d.ts +1 -0
  138. package/dist.dts/providers/github/src/poll/utils/empty-cursor.d.ts +2 -0
  139. package/dist.dts/providers/github/src/poll/utils/failure-run-ids.d.ts +1 -0
  140. package/dist.dts/providers/github/src/poll/utils/failure-run-log.d.ts +1 -0
  141. package/dist.dts/providers/github/src/poll/utils/format-failed-check.d.ts +1 -0
  142. package/dist.dts/providers/github/src/poll/utils/index.d.ts +1 -0
  143. package/dist.dts/providers/github/src/poll/utils/poll-github.d.ts +6 -0
  144. package/dist.dts/providers/github/src/poll/utils/poll-helpers.test.d.ts +1 -0
  145. package/dist.dts/providers/github/src/schema.d.ts +17 -0
  146. package/dist.dts/providers/github/src/types/cursor.d.ts +11 -0
  147. package/dist.dts/providers/github/src/types/event.d.ts +11 -0
  148. package/dist.dts/providers/github/src/types/index.d.ts +3 -0
  149. package/dist.dts/providers/github/src/types/source-record.d.ts +5 -0
  150. package/dist.dts/providers/github/src/webhook/index.d.ts +3 -0
  151. package/dist.dts/providers/github/src/webhook/monitor.d.ts +3 -0
  152. package/dist.dts/providers/github/src/webhook/types/context.d.ts +7 -0
  153. package/dist.dts/providers/github/src/webhook/types/event-handler.d.ts +3 -0
  154. package/dist.dts/providers/github/src/webhook/types/event-shape.d.ts +7 -0
  155. package/dist.dts/providers/github/src/webhook/types/index.d.ts +1 -0
  156. package/dist.dts/providers/github/src/webhook/utils/fallback-github-event.d.ts +3 -0
  157. package/dist.dts/providers/github/src/webhook/utils/github-event-handlers.d.ts +2 -0
  158. package/dist.dts/providers/github/src/webhook/utils/index.d.ts +2 -0
  159. package/dist.dts/providers/github/src/webhook/utils/parse-check-run.d.ts +3 -0
  160. package/dist.dts/providers/github/src/webhook/utils/parse-check-suite.d.ts +3 -0
  161. package/dist.dts/providers/github/src/webhook/utils/parse-github-comment.d.ts +3 -0
  162. package/dist.dts/providers/github/src/webhook/utils/parse-github-webhook.d.ts +2 -0
  163. package/dist.dts/providers/github/src/webhook/utils/parse-pull-request-review.d.ts +3 -0
  164. package/dist.dts/providers/github/src/webhook/utils/parse-pull-request.d.ts +3 -0
  165. package/dist.dts/providers/github/src/webhook/utils/pull-request-number.d.ts +1 -0
  166. package/dist.dts/providers/github/src/webhook/utils/update-github-cursor.d.ts +3 -0
  167. package/dist.dts/providers/github/src/webhook/utils/verify-github-webhook.d.ts +1 -0
  168. package/dist.dts/providers/github/src/webhook/utils/verify-hmac.d.ts +1 -0
  169. package/dist.dts/providers/jira/src/index.d.ts +2 -0
  170. package/dist.dts/providers/jira/src/monitor.d.ts +14 -0
  171. package/dist.dts/providers/jira/src/poll/index.d.ts +2 -0
  172. package/dist.dts/providers/jira/src/poll/monitor.d.ts +3 -0
  173. package/dist.dts/providers/jira/src/poll/utils/adf-text.d.ts +1 -0
  174. package/dist.dts/providers/jira/src/poll/utils/append-jira-changelog.d.ts +2 -0
  175. package/dist.dts/providers/jira/src/poll/utils/append-jira-comments.d.ts +2 -0
  176. package/dist.dts/providers/jira/src/poll/utils/append-jira-description.d.ts +2 -0
  177. package/dist.dts/providers/jira/src/poll/utils/empty-cursor.d.ts +2 -0
  178. package/dist.dts/providers/jira/src/poll/utils/format-jira-changelog-item.d.ts +1 -0
  179. package/dist.dts/providers/jira/src/poll/utils/index.d.ts +1 -0
  180. package/dist.dts/providers/jira/src/poll/utils/is-terminal-status.d.ts +1 -0
  181. package/dist.dts/providers/jira/src/poll/utils/jira-auth.d.ts +1 -0
  182. package/dist.dts/providers/jira/src/poll/utils/jira-change-value.d.ts +1 -0
  183. package/dist.dts/providers/jira/src/poll/utils/jira-changelog-event.d.ts +2 -0
  184. package/dist.dts/providers/jira/src/poll/utils/jira-fetch.d.ts +1 -0
  185. package/dist.dts/providers/jira/src/poll/utils/meaningful-changelog-items.d.ts +1 -0
  186. package/dist.dts/providers/jira/src/poll/utils/poll-helpers.test.d.ts +1 -0
  187. package/dist.dts/providers/jira/src/poll/utils/poll-jira.d.ts +6 -0
  188. package/dist.dts/providers/jira/src/schema.d.ts +10 -0
  189. package/dist.dts/providers/jira/src/types/cursor.d.ts +5 -0
  190. package/dist.dts/providers/jira/src/types/event.d.ts +11 -0
  191. package/dist.dts/providers/jira/src/types/index.d.ts +3 -0
  192. package/dist.dts/providers/jira/src/types/source-record.d.ts +4 -0
  193. package/dist.dts/providers/slack/src/index.d.ts +2 -0
  194. package/dist.dts/providers/slack/src/monitor.d.ts +15 -0
  195. package/dist.dts/providers/slack/src/poll/index.d.ts +2 -0
  196. package/dist.dts/providers/slack/src/poll/monitor.d.ts +3 -0
  197. package/dist.dts/providers/slack/src/poll/utils/index.d.ts +2 -0
  198. package/dist.dts/providers/slack/src/poll/utils/parse-slack-read-result.d.ts +5 -0
  199. package/dist.dts/providers/slack/src/poll/utils/poll-slack.d.ts +6 -0
  200. package/dist.dts/providers/slack/src/poll/utils/slack-api.d.ts +4 -0
  201. package/dist.dts/providers/slack/src/schema.d.ts +11 -0
  202. package/dist.dts/providers/slack/src/slack.test.d.ts +1 -0
  203. package/dist.dts/providers/slack/src/types/cursor.d.ts +5 -0
  204. package/dist.dts/providers/slack/src/types/event.d.ts +8 -0
  205. package/dist.dts/providers/slack/src/types/index.d.ts +4 -0
  206. package/dist.dts/providers/slack/src/types/message.d.ts +6 -0
  207. package/dist.dts/providers/slack/src/types/read-result.d.ts +10 -0
  208. package/dist.dts/providers/slack/src/types/source-record.d.ts +5 -0
  209. package/dist.dts/providers/slack/src/utils/index.d.ts +2 -0
  210. package/dist.dts/providers/slack/src/utils/message-at.d.ts +1 -0
  211. package/dist.dts/providers/slack/src/utils/message-timestamp.d.ts +1 -0
  212. package/dist.dts/providers/slack/src/webhook/index.d.ts +2 -0
  213. package/dist.dts/providers/slack/src/webhook/monitor.d.ts +3 -0
  214. package/dist.dts/providers/slack/src/webhook/utils/index.d.ts +3 -0
  215. package/dist.dts/providers/slack/src/webhook/utils/parse-slack-webhook.d.ts +2 -0
  216. package/dist.dts/providers/slack/src/webhook/utils/slack-signature.d.ts +1 -0
  217. package/dist.dts/providers/slack/src/webhook/utils/update-slack-cursor.d.ts +3 -0
  218. package/dist.dts/providers/slack/src/webhook/utils/valid-slack-signature.d.ts +1 -0
  219. package/dist.dts/tui.d.ts +1 -0
  220. package/package.json +68 -0
@@ -0,0 +1,13 @@
1
+ import {
2
+ InMemoryMonitorStore,
3
+ JsonMonitorEventQueue,
4
+ JsonMonitorStore,
5
+ defaultJsonMonitorStore
6
+ } from "./chunk-NGJ6F5ST.js";
7
+ import "./chunk-MLKGABMK.js";
8
+ export {
9
+ InMemoryMonitorStore,
10
+ JsonMonitorEventQueue,
11
+ JsonMonitorStore,
12
+ defaultJsonMonitorStore
13
+ };
package/dist/core.js ADDED
@@ -0,0 +1,643 @@
1
+ import {
2
+ InMemoryMonitorEventQueue,
3
+ InMemoryMonitorStore,
4
+ JsonMonitorEventQueue,
5
+ JsonMonitorStore,
6
+ MonitorRecordSchema,
7
+ MonitorRegistrySchema,
8
+ QueueMonitorEventSink,
9
+ array,
10
+ boolean,
11
+ createQueuedMonitorEvent,
12
+ defaultJsonMonitorStore,
13
+ monitorEventID,
14
+ object,
15
+ optional,
16
+ safeParse,
17
+ string,
18
+ unknown
19
+ } from "./chunk-NGJ6F5ST.js";
20
+ import "./chunk-MLKGABMK.js";
21
+
22
+ // packages/core/src/monitors/monitor.ts
23
+ var Monitor = class {
24
+ type;
25
+ schema;
26
+ poll;
27
+ webhook;
28
+ constructor(options) {
29
+ this.type = options.type;
30
+ this.schema = options.schema;
31
+ }
32
+ validateSource(source) {
33
+ const result = safeParse(this.schema, source);
34
+ if (result.success) return result.output;
35
+ throw new Error(`invalid ${this.type} monitor source`);
36
+ }
37
+ initialDelivery(source) {
38
+ this.validateSource(source);
39
+ if (this.webhook && this.webhook.preferred && this.webhook.configured()) return "webhook";
40
+ if (this.poll) return "poll";
41
+ if (this.webhook) return "webhook";
42
+ throw new Error(`source ${this.type} has no configured transport`);
43
+ }
44
+ resolveDelivery(monitor) {
45
+ let webhookHealthy = false;
46
+ if (this.webhook) webhookHealthy = this.webhook.isHealthy(monitor);
47
+ if (monitor.delivery === "webhook") {
48
+ if (this.webhook && webhookHealthy) return "webhook";
49
+ if (this.poll) return "poll";
50
+ if (this.webhook) return "webhook";
51
+ return void 0;
52
+ }
53
+ if (this.webhook && webhookHealthy) return "webhook";
54
+ if (this.poll) return "poll";
55
+ if (this.webhook) return "webhook";
56
+ return void 0;
57
+ }
58
+ async tick(context, record) {
59
+ const fresh = await context.service.get(record.id);
60
+ if (!fresh || !fresh.enabled) return 0;
61
+ if (this.webhook) await this.webhook.retryPending(context, this, fresh);
62
+ const current = await context.service.get(record.id);
63
+ if (!current || !current.enabled) return 0;
64
+ const nextDelivery = this.resolveDelivery(current);
65
+ if (!nextDelivery) return 0;
66
+ if (nextDelivery !== current.delivery) {
67
+ await context.service.setDelivery(current.id, nextDelivery);
68
+ current.delivery = nextDelivery;
69
+ }
70
+ if (current.delivery !== "poll" || !this.poll) return 0;
71
+ const lastAttemptAt = Number(current.cursors.__lastAttemptAt ?? current.cursors.__lastPolledAt ?? 0);
72
+ const pollIntervalSec = current.pollIntervalSec || 60;
73
+ if (lastAttemptAt && Date.now() - lastAttemptAt < pollIntervalSec * 1e3) return 0;
74
+ return this.poll.run(context, this, current);
75
+ }
76
+ async deliver(context, record, event, deliveryId) {
77
+ if (!this.webhook) return 0;
78
+ return this.webhook.deliver(context, this, record, event, deliveryId);
79
+ }
80
+ unresponsive(record, now = Date.now()) {
81
+ if (!record.enabled) return false;
82
+ if (record.delivery === "poll") {
83
+ if (!this.poll) return true;
84
+ return this.poll.isUnresponsive(record, now);
85
+ }
86
+ if (!this.webhook) return true;
87
+ return this.webhook.isUnresponsive(record, now);
88
+ }
89
+ };
90
+
91
+ // packages/core/src/monitors/utils/monitor-identity.ts
92
+ function monitorSourceIdentity(source) {
93
+ return JSON.stringify(stableValue(source));
94
+ }
95
+ function stableValue(value) {
96
+ if (Array.isArray(value)) return value.map(stableValue);
97
+ if (!value || typeof value !== "object") return value;
98
+ return Object.fromEntries(
99
+ Object.entries(value).sort(([left], [right]) => left.localeCompare(right)).map(([key, entry]) => [key, stableValue(entry)])
100
+ );
101
+ }
102
+ function monitorIdentity(target, source) {
103
+ return JSON.stringify([target.kind, target.id, monitorSourceIdentity(source)]);
104
+ }
105
+ function dedupeActiveMonitors(monitors) {
106
+ const seen = /* @__PURE__ */ new Set();
107
+ const result = [];
108
+ for (const monitor of monitors) {
109
+ if (!monitor.enabled) {
110
+ result.push(monitor);
111
+ continue;
112
+ }
113
+ const identity = monitorIdentity(monitor.target, monitor.source);
114
+ if (seen.has(identity)) continue;
115
+ seen.add(identity);
116
+ result.push(monitor);
117
+ }
118
+ return result;
119
+ }
120
+
121
+ // packages/core/src/monitors/service.ts
122
+ var MonitorService = class {
123
+ constructor(store) {
124
+ this.store = store;
125
+ }
126
+ registryQueue = Promise.resolve();
127
+ async list() {
128
+ return (await this.store.load()).monitors;
129
+ }
130
+ async get(id) {
131
+ return (await this.store.load()).monitors.find((monitor) => monitor.id === id);
132
+ }
133
+ async create(input) {
134
+ return this.transact((registry) => {
135
+ registry.monitors = dedupeActiveMonitors(registry.monitors);
136
+ const identity = monitorIdentity(input.target, input.source);
137
+ const existing = registry.monitors.find(
138
+ (monitor2) => monitor2.enabled && monitorIdentity(monitor2.target, monitor2.source) === identity
139
+ );
140
+ if (existing) return { monitor: existing, created: false };
141
+ const now = (/* @__PURE__ */ new Date()).toISOString();
142
+ const monitor = {
143
+ id: `m-${Date.now().toString(36)}-${crypto.randomUUID().slice(0, 6)}`,
144
+ name: input.name,
145
+ source: input.source,
146
+ delivery: input.delivery,
147
+ target: input.target,
148
+ pollIntervalSec: Math.max(15, input.pollIntervalSec ?? 60),
149
+ enabled: true,
150
+ createdAt: now,
151
+ updatedAt: now,
152
+ cursors: {}
153
+ };
154
+ registry.monitors.push(monitor);
155
+ return { monitor, created: true };
156
+ });
157
+ }
158
+ async stop(id) {
159
+ return this.transact((registry) => {
160
+ const monitor = registry.monitors.find((entry) => entry.id === id);
161
+ if (!monitor) return { error: `monitor ${id} was not found` };
162
+ monitor.enabled = false;
163
+ monitor.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
164
+ return monitor;
165
+ });
166
+ }
167
+ async start(id) {
168
+ return this.transact((registry) => {
169
+ const monitor = registry.monitors.find((entry) => entry.id === id);
170
+ if (!monitor) return { error: `monitor ${id} was not found` };
171
+ monitor.enabled = true;
172
+ monitor.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
173
+ return monitor;
174
+ });
175
+ }
176
+ async remove(id) {
177
+ await this.transact((registry) => {
178
+ registry.monitors = registry.monitors.filter((monitor) => monitor.id !== id);
179
+ });
180
+ }
181
+ async setDelivery(id, delivery) {
182
+ await this.updateMonitor(id, (monitor) => {
183
+ monitor.delivery = delivery;
184
+ });
185
+ }
186
+ async updateCursor(monitor, key, value) {
187
+ await this.updateMonitor(monitor.id, (current) => {
188
+ current.cursors[key] = value;
189
+ });
190
+ }
191
+ async updateCursorValue(monitor, key, update) {
192
+ await this.updateMonitor(monitor.id, (current) => {
193
+ current.cursors[key] = update(current.cursors[key]);
194
+ });
195
+ }
196
+ async dedupe() {
197
+ return this.transact((registry) => {
198
+ const deduped = dedupeActiveMonitors(registry.monitors);
199
+ const removed = registry.monitors.length - deduped.length;
200
+ registry.monitors = deduped;
201
+ return removed;
202
+ });
203
+ }
204
+ async updateMonitor(id, update) {
205
+ await this.transact((registry) => {
206
+ const monitor = registry.monitors.find((entry) => entry.id === id);
207
+ if (!monitor) return;
208
+ update(monitor);
209
+ monitor.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
210
+ });
211
+ }
212
+ transact(operation) {
213
+ const next = this.registryQueue.then(async () => {
214
+ const registry = await this.store.load();
215
+ const result = await operation(registry);
216
+ await this.store.save(registry);
217
+ return result;
218
+ });
219
+ this.registryQueue = next.then(() => void 0, () => void 0);
220
+ return next;
221
+ }
222
+ };
223
+
224
+ // packages/core/src/monitors/poll/types/routed-event-cursor.ts
225
+ var RoutedEventCursorSchema = array(string());
226
+
227
+ // packages/core/src/monitors/poll/utils/event-delivery-id.ts
228
+ function eventDeliveryId(event) {
229
+ if (event.id) return event.id;
230
+ return `${event.kind}:${event.at}:${event.summary}`;
231
+ }
232
+
233
+ // packages/core/src/monitors/poll/monitor.ts
234
+ var activePolls = /* @__PURE__ */ new Set();
235
+ var PollMonitor = class {
236
+ constructor(options) {
237
+ this.options = options;
238
+ }
239
+ async run(context, source, record) {
240
+ if (activePolls.has(record.id)) return 0;
241
+ activePolls.add(record.id);
242
+ try {
243
+ const fresh = await context.service.get(record.id);
244
+ if (!fresh) return 0;
245
+ const sourceRecord = source.validateSource(fresh.source);
246
+ const cursorKey = source.key(sourceRecord);
247
+ const result = await this.options.run(sourceRecord, fresh.cursors[cursorKey]);
248
+ const current = await context.service.get(record.id);
249
+ if (!current || !current.enabled) return 0;
250
+ const parsedRoutedIds = safeParse(RoutedEventCursorSchema, fresh.cursors.__routedEventIds);
251
+ let routedIds = [];
252
+ if (parsedRoutedIds.success) routedIds = parsedRoutedIds.output;
253
+ let routeFailed = false;
254
+ for (const event of result.events) {
255
+ const eventId = eventDeliveryId(event);
256
+ if (routedIds.includes(eventId)) continue;
257
+ const routed = await context.sink.deliver({ monitor: fresh, event });
258
+ if (!routed.ok) {
259
+ routeFailed = true;
260
+ console.error(`[sourcefed] route failed for ${fresh.id}: ${routed.error}`);
261
+ continue;
262
+ }
263
+ if (!routedIds.includes(eventId)) routedIds.push(eventId);
264
+ }
265
+ if (routeFailed) {
266
+ await context.service.updateCursor(fresh, "__routedEventIds", routedIds);
267
+ return 0;
268
+ }
269
+ await context.service.updateCursorValue(fresh, cursorKey, (current2) => {
270
+ if (this.options.mergeCursor) return this.options.mergeCursor(current2, result.cursor);
271
+ return result.cursor;
272
+ });
273
+ await context.service.updateCursor(fresh, "__routedEventIds", []);
274
+ if (result.terminal) await context.service.remove(fresh.id);
275
+ await context.service.updateCursor(fresh, "__lastPolledAt", Date.now());
276
+ return result.events.length;
277
+ } finally {
278
+ activePolls.delete(record.id);
279
+ }
280
+ }
281
+ isUnresponsive(record, now) {
282
+ const intervalMs = Math.max((record.pollIntervalSec || 60) * 3e3, 12e4);
283
+ const lastPolledAt = Number(record.cursors.__lastPolledAt ?? 0);
284
+ if (lastPolledAt > 0) return now - lastPolledAt > intervalMs;
285
+ return now - Date.parse(record.createdAt) > intervalMs;
286
+ }
287
+ };
288
+ function isRecord(value) {
289
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
290
+ }
291
+ function mergeCursors(current, result) {
292
+ if (Array.isArray(current) && Array.isArray(result)) {
293
+ return [.../* @__PURE__ */ new Set([...current, ...result])];
294
+ }
295
+ if (isRecord(current) && isRecord(result)) {
296
+ const merged = { ...current };
297
+ for (const [key, value] of Object.entries(result)) {
298
+ if (value === void 0) continue;
299
+ merged[key] = key in current ? mergeCursors(current[key], value) : value;
300
+ }
301
+ return merged;
302
+ }
303
+ return result === void 0 ? current : result;
304
+ }
305
+
306
+ // packages/core/src/monitors/webhook/utils/constants.ts
307
+ var WEBHOOK_STALE_MS = 9e4;
308
+ var WEBHOOK_STARTUP_GRACE_MS = 12e4;
309
+
310
+ // packages/core/src/monitors/webhook/types/pending-webhook-event.ts
311
+ var PendingWebhookEventSchema = object({
312
+ deliveryId: string(),
313
+ event: object({
314
+ source: unknown(),
315
+ kind: string(),
316
+ id: optional(string()),
317
+ at: string(),
318
+ summary: string(),
319
+ body: optional(string()),
320
+ actionable: boolean(),
321
+ terminal: optional(boolean())
322
+ })
323
+ });
324
+ var PendingWebhookEventsSchema = array(PendingWebhookEventSchema);
325
+
326
+ // packages/core/src/monitors/webhook/monitor.ts
327
+ var pendingDeliveries = /* @__PURE__ */ new Set();
328
+ var PENDING_WEBHOOK_EVENTS = "__pendingWebhookEvents";
329
+ var WebhookMonitor = class {
330
+ path;
331
+ preferred;
332
+ configured;
333
+ verify;
334
+ acknowledgeBeforeDelivery;
335
+ challenge;
336
+ deliveryId;
337
+ eventName;
338
+ parse;
339
+ updateCursor;
340
+ constructor(options) {
341
+ this.path = options.path;
342
+ this.preferred = options.preferred ?? true;
343
+ this.configured = options.configured;
344
+ this.verify = options.verify;
345
+ this.acknowledgeBeforeDelivery = options.acknowledgeBeforeDelivery ?? false;
346
+ this.challenge = options.challenge;
347
+ this.deliveryId = options.deliveryId;
348
+ this.eventName = options.eventName;
349
+ this.parse = options.parse;
350
+ this.updateCursor = options.updateCursor;
351
+ }
352
+ isHealthy(record) {
353
+ if (!this.preferred || !this.configured()) return false;
354
+ const heartbeatAt = Number(record.cursors.__webhookHeartbeatAt ?? 0);
355
+ if (heartbeatAt > 0) return Date.now() - heartbeatAt <= WEBHOOK_STALE_MS;
356
+ return Date.now() - Date.parse(record.createdAt) <= WEBHOOK_STARTUP_GRACE_MS;
357
+ }
358
+ isUnresponsive(record, now) {
359
+ const heartbeatAt = Number(record.cursors.__webhookHeartbeatAt ?? 0);
360
+ if (heartbeatAt > 0) return now - heartbeatAt > WEBHOOK_STALE_MS;
361
+ return now - Date.parse(record.createdAt) > WEBHOOK_STARTUP_GRACE_MS;
362
+ }
363
+ async retryPending(context, source, record) {
364
+ const fresh = await context.service.get(record.id);
365
+ if (!fresh || !fresh.enabled) return 0;
366
+ const parsed = safeParse(PendingWebhookEventsSchema, fresh.cursors[PENDING_WEBHOOK_EVENTS]);
367
+ if (!parsed.success) return 0;
368
+ let delivered = 0;
369
+ for (const pending of parsed.output) {
370
+ const current = await context.service.get(record.id);
371
+ if (!current || !current.enabled) break;
372
+ const cursorKey = `webhook:${pending.deliveryId}`;
373
+ if (current.cursors[cursorKey]) {
374
+ await this.removePending(context, current, pending.deliveryId);
375
+ continue;
376
+ }
377
+ delivered += await this.deliver(context, source, current, pending.event, pending.deliveryId);
378
+ }
379
+ return delivered;
380
+ }
381
+ async addPending(context, monitor, deliveryId, event) {
382
+ await context.service.updateCursorValue(monitor, PENDING_WEBHOOK_EVENTS, (cursor) => {
383
+ const parsed = safeParse(PendingWebhookEventsSchema, cursor);
384
+ let pending = [];
385
+ if (parsed.success) pending = parsed.output;
386
+ if (pending.some((entry) => entry.deliveryId === deliveryId)) return pending;
387
+ pending.push({ deliveryId, event });
388
+ return pending;
389
+ });
390
+ }
391
+ async removePending(context, monitor, deliveryId) {
392
+ await context.service.updateCursorValue(monitor, PENDING_WEBHOOK_EVENTS, (cursor) => {
393
+ const parsed = safeParse(PendingWebhookEventsSchema, cursor);
394
+ if (!parsed.success) return [];
395
+ return parsed.output.filter((entry) => entry.deliveryId !== deliveryId);
396
+ });
397
+ }
398
+ async deliver(context, source, record, event, deliveryId) {
399
+ const fresh = await context.service.get(record.id);
400
+ if (!fresh || !fresh.enabled) return 0;
401
+ const freshSource = source.validateSource(fresh.source);
402
+ const eventSource = source.validateSource(event.source);
403
+ if (source.key(freshSource) !== source.key(eventSource)) return 0;
404
+ if (event.kind === "ci" && !event.actionable) return 0;
405
+ const cursorKey = `webhook:${deliveryId}`;
406
+ const reservationKey = `${fresh.id}:${deliveryId}`;
407
+ if (pendingDeliveries.has(reservationKey)) return 0;
408
+ if (fresh.cursors[cursorKey]) {
409
+ await this.removePending(context, fresh, deliveryId);
410
+ return 0;
411
+ }
412
+ pendingDeliveries.add(reservationKey);
413
+ try {
414
+ await this.addPending(context, fresh, deliveryId, event);
415
+ if (this.updateCursor) {
416
+ const sourceCursorKey = source.key(freshSource);
417
+ await context.service.updateCursorValue(fresh, sourceCursorKey, (cursor) => this.updateCursor(event, cursor));
418
+ }
419
+ const result = await context.sink.deliver({ monitor: fresh, event });
420
+ if (!result.ok) {
421
+ console.error(`[sourcefed] webhook route failed for ${fresh.id}: ${result.error}`);
422
+ return 0;
423
+ }
424
+ await context.service.setDelivery(fresh.id, "webhook");
425
+ await context.service.updateCursor(fresh, cursorKey, true);
426
+ await this.removePending(context, fresh, deliveryId);
427
+ if (event.terminal) await context.service.remove(fresh.id);
428
+ return 1;
429
+ } finally {
430
+ pendingDeliveries.delete(reservationKey);
431
+ }
432
+ }
433
+ };
434
+
435
+ // packages/core/src/monitors/poll/utils/tick.ts
436
+ async function tick(context) {
437
+ const monitors = await context.service.list();
438
+ for (const record of monitors) {
439
+ if (!record.enabled) continue;
440
+ const source = context.sources[record.source.type];
441
+ if (!source) {
442
+ console.error(`[sourcefed] no monitor provider for source type ${record.source.type}`);
443
+ continue;
444
+ }
445
+ try {
446
+ await source.tick(context, record);
447
+ } catch (error) {
448
+ console.error(`[sourcefed] monitor ${record.id} poll failed:`, error);
449
+ await context.service.updateCursor(record, "__lastAttemptAt", Date.now()).catch((updateError) => {
450
+ console.error(`[sourcefed] monitor ${record.id} poll timestamp update failed:`, updateError);
451
+ });
452
+ }
453
+ }
454
+ }
455
+
456
+ // packages/core/src/monitors/webhook/utils/deliver-webhook.ts
457
+ async function deliverWebhook(context, source, event, deliveryId) {
458
+ let delivered = 0;
459
+ const monitors = await context.service.list();
460
+ for (const monitor of monitors) {
461
+ const monitorSource = context.sources[monitor.source.type];
462
+ if (!monitorSource) continue;
463
+ if (monitorSource.type !== source.type) continue;
464
+ delivered += await monitorSource.deliver(context, monitor, event, deliveryId);
465
+ }
466
+ return delivered;
467
+ }
468
+
469
+ // packages/core/src/monitors/webhook/utils/heartbeat-webhooks.ts
470
+ async function heartbeatWebhooks(context, source, eventSource) {
471
+ const sourceKey = source.key(source.validateSource(eventSource));
472
+ const monitors = await context.service.list();
473
+ for (const monitor of monitors) {
474
+ if (!monitor.enabled || monitor.source.type !== source.type) continue;
475
+ if (source.key(source.validateSource(monitor.source)) !== sourceKey) continue;
476
+ await context.service.updateCursor(monitor, "__webhookHeartbeatAt", Date.now());
477
+ }
478
+ }
479
+
480
+ // packages/core/src/monitors/webhook/utils/handle-webhook.ts
481
+ var MAX_WEBHOOK_BODY_BYTES = 1024 * 1024;
482
+ async function handleWebhook(request, context) {
483
+ const source = context.sourceForWebhookPath(new URL(request.url).pathname);
484
+ if (!source || !source.webhook) return new Response("not found", { status: 404 });
485
+ const body = await readBody(request);
486
+ if (body === void 0) return new Response("payload too large", { status: 413 });
487
+ if (!source.webhook.verify(body, request)) return new Response("invalid signature", { status: 401 });
488
+ let payload;
489
+ try {
490
+ payload = JSON.parse(body);
491
+ } catch {
492
+ return new Response("invalid json", { status: 400 });
493
+ }
494
+ const challenge = source.webhook.challenge?.(payload);
495
+ if (challenge) return Response.json(challenge);
496
+ const deliveryId = source.webhook.deliveryId(request, payload);
497
+ if (!deliveryId) return new Response("missing delivery id", { status: 400 });
498
+ const eventName = source.webhook.eventName(request, payload);
499
+ const event = source.webhook.parse(payload, eventName, deliveryId);
500
+ if (!event) return new Response("ignored", { status: 202 });
501
+ void heartbeatWebhooks(context, source, event.source).catch((error) => console.error("[sourcefed] webhook heartbeat failed:", error));
502
+ if (source.webhook.acknowledgeBeforeDelivery) {
503
+ void deliverWebhook(context, source, event, deliveryId).catch((error) => console.error("[sourcefed] webhook delivery failed:", error));
504
+ return new Response("accepted", { status: 202 });
505
+ }
506
+ await deliverWebhook(context, source, event, deliveryId);
507
+ return new Response("ok");
508
+ }
509
+ async function readBody(request) {
510
+ const contentLength = Number(request.headers.get("content-length"));
511
+ if (Number.isFinite(contentLength) && contentLength > MAX_WEBHOOK_BODY_BYTES) return void 0;
512
+ const reader = request.body?.getReader();
513
+ if (!reader) return "";
514
+ const chunks = [];
515
+ let length = 0;
516
+ try {
517
+ while (true) {
518
+ const { done, value } = await reader.read();
519
+ if (done) break;
520
+ length += value.byteLength;
521
+ if (length > MAX_WEBHOOK_BODY_BYTES) {
522
+ await reader.cancel();
523
+ return void 0;
524
+ }
525
+ chunks.push(value);
526
+ }
527
+ } finally {
528
+ reader.releaseLock();
529
+ }
530
+ const bytes = new Uint8Array(length);
531
+ let offset = 0;
532
+ for (const chunk of chunks) {
533
+ bytes.set(chunk, offset);
534
+ offset += chunk.byteLength;
535
+ }
536
+ return new TextDecoder().decode(bytes);
537
+ }
538
+
539
+ // packages/core/src/runtime.ts
540
+ var MonitorRuntime = class {
541
+ service;
542
+ context;
543
+ timer;
544
+ activeTicks = /* @__PURE__ */ new Set();
545
+ constructor(options) {
546
+ this.service = new MonitorService(options.store);
547
+ this.context = {
548
+ service: this.service,
549
+ sink: options.sink,
550
+ sources: options.sources,
551
+ sourceForWebhookPath: options.sourceForWebhookPath
552
+ };
553
+ this.pollLoopSec = Math.max(1, options.pollLoopSec ?? 5);
554
+ }
555
+ pollLoopSec;
556
+ async start() {
557
+ await this.service.dedupe();
558
+ if (this.timer) return;
559
+ this.timer = setInterval(() => {
560
+ const tick2 = this.tick().catch((error) => console.error("[sourcefed] monitor tick error:", error));
561
+ this.activeTicks.add(tick2);
562
+ void tick2.finally(() => this.activeTicks.delete(tick2));
563
+ }, this.pollLoopSec * 1e3);
564
+ const initial = this.tick().catch((error) => console.error("[sourcefed] monitor tick error:", error));
565
+ this.activeTicks.add(initial);
566
+ void initial.finally(() => this.activeTicks.delete(initial));
567
+ }
568
+ async tick() {
569
+ await tick(this.context);
570
+ }
571
+ async webhook(request) {
572
+ return handleWebhook(request, this.context);
573
+ }
574
+ async stop() {
575
+ if (this.timer) {
576
+ clearInterval(this.timer);
577
+ this.timer = void 0;
578
+ }
579
+ const ticks = [...this.activeTicks];
580
+ if (ticks.length === 0) return true;
581
+ let timer;
582
+ const settled = await Promise.race([
583
+ Promise.allSettled(ticks).then(() => true),
584
+ new Promise((resolve) => {
585
+ timer = setTimeout(() => {
586
+ console.warn("[sourcefed] stop waited 30s for in-flight monitor ticks; retaining state lock");
587
+ resolve(false);
588
+ }, 3e4);
589
+ })
590
+ ]);
591
+ if (timer) clearTimeout(timer);
592
+ return settled;
593
+ }
594
+ };
595
+
596
+ // packages/core/src/utils/event-to-text.ts
597
+ function eventToText(event) {
598
+ const summary = event.summary.trim();
599
+ let detail = "";
600
+ if (event.kind !== "merged" && event.body) detail = event.body.trim();
601
+ let duplicateDetail = detail;
602
+ if (detail && summary.includes(detail)) duplicateDetail = "";
603
+ const lines = [`[sourcefed monitor] ${summary}`];
604
+ if (duplicateDetail) lines.push(`
605
+ ${duplicateDetail}`);
606
+ return lines.filter(Boolean).join("\n");
607
+ }
608
+
609
+ // packages/core/src/utils/to-monitor-event.ts
610
+ function toMonitorEvent(event, source) {
611
+ return {
612
+ source,
613
+ kind: event.kind,
614
+ id: event.id,
615
+ at: event.at,
616
+ summary: event.summary,
617
+ body: event.body,
618
+ actionable: event.actionable,
619
+ terminal: event.terminal
620
+ };
621
+ }
622
+ export {
623
+ InMemoryMonitorEventQueue,
624
+ InMemoryMonitorStore,
625
+ JsonMonitorEventQueue,
626
+ JsonMonitorStore,
627
+ Monitor,
628
+ MonitorRecordSchema,
629
+ MonitorRegistrySchema,
630
+ MonitorRuntime,
631
+ MonitorService,
632
+ PollMonitor,
633
+ QueueMonitorEventSink,
634
+ WebhookMonitor,
635
+ createQueuedMonitorEvent,
636
+ dedupeActiveMonitors,
637
+ defaultJsonMonitorStore,
638
+ eventToText,
639
+ mergeCursors,
640
+ monitorEventID,
641
+ monitorIdentity,
642
+ toMonitorEvent
643
+ };