@a5c-ai/channels-adapter 5.1.1-staging.5ee7f5f98b91

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 (71) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +661 -0
  3. package/dist/backend.d.ts +7 -0
  4. package/dist/backend.d.ts.map +1 -0
  5. package/dist/backend.js +22 -0
  6. package/dist/backend.js.map +1 -0
  7. package/dist/backends/github.d.ts +16 -0
  8. package/dist/backends/github.d.ts.map +1 -0
  9. package/dist/backends/github.js +357 -0
  10. package/dist/backends/github.js.map +1 -0
  11. package/dist/backends/jira.d.ts +18 -0
  12. package/dist/backends/jira.d.ts.map +1 -0
  13. package/dist/backends/jira.js +256 -0
  14. package/dist/backends/jira.js.map +1 -0
  15. package/dist/backends/webhook.d.ts +25 -0
  16. package/dist/backends/webhook.d.ts.map +1 -0
  17. package/dist/backends/webhook.js +206 -0
  18. package/dist/backends/webhook.js.map +1 -0
  19. package/dist/cli.d.ts +2 -0
  20. package/dist/cli.d.ts.map +1 -0
  21. package/dist/cli.js +28 -0
  22. package/dist/cli.js.map +1 -0
  23. package/dist/config.d.ts +10 -0
  24. package/dist/config.d.ts.map +1 -0
  25. package/dist/config.js +323 -0
  26. package/dist/config.js.map +1 -0
  27. package/dist/dedup.d.ts +26 -0
  28. package/dist/dedup.d.ts.map +1 -0
  29. package/dist/dedup.js +68 -0
  30. package/dist/dedup.js.map +1 -0
  31. package/dist/filter.d.ts +9 -0
  32. package/dist/filter.d.ts.map +1 -0
  33. package/dist/filter.js +115 -0
  34. package/dist/filter.js.map +1 -0
  35. package/dist/index.d.ts +14 -0
  36. package/dist/index.d.ts.map +1 -0
  37. package/dist/index.js +21 -0
  38. package/dist/index.js.map +1 -0
  39. package/dist/poller.d.ts +63 -0
  40. package/dist/poller.d.ts.map +1 -0
  41. package/dist/poller.js +195 -0
  42. package/dist/poller.js.map +1 -0
  43. package/dist/registry.d.ts +22 -0
  44. package/dist/registry.d.ts.map +1 -0
  45. package/dist/registry.js +59 -0
  46. package/dist/registry.js.map +1 -0
  47. package/dist/relay.d.ts +58 -0
  48. package/dist/relay.d.ts.map +1 -0
  49. package/dist/relay.js +172 -0
  50. package/dist/relay.js.map +1 -0
  51. package/dist/runtime.d.ts +27 -0
  52. package/dist/runtime.d.ts.map +1 -0
  53. package/dist/runtime.js +198 -0
  54. package/dist/runtime.js.map +1 -0
  55. package/dist/server.d.ts +64 -0
  56. package/dist/server.d.ts.map +1 -0
  57. package/dist/server.js +217 -0
  58. package/dist/server.js.map +1 -0
  59. package/dist/spawner.d.ts +96 -0
  60. package/dist/spawner.d.ts.map +1 -0
  61. package/dist/spawner.js +368 -0
  62. package/dist/spawner.js.map +1 -0
  63. package/dist/state.d.ts +43 -0
  64. package/dist/state.d.ts.map +1 -0
  65. package/dist/state.js +92 -0
  66. package/dist/state.js.map +1 -0
  67. package/dist/types.d.ts +140 -0
  68. package/dist/types.d.ts.map +1 -0
  69. package/dist/types.js +10 -0
  70. package/dist/types.js.map +1 -0
  71. package/package.json +78 -0
@@ -0,0 +1,256 @@
1
+ // Jira backend (SPEC §4.2 / §5.2, DESIGN §5.2).
2
+ //
3
+ // Poll: POST {baseUrl}/rest/api/3/search with JQL
4
+ // project = "<P>" AND created >= "<cursor>" ORDER BY created ASC
5
+ // (uses `updated` for the issue_updated event), plus any config.jql extra clause.
6
+ // The `created >=` clause is omitted when the cursor is null (first poll).
7
+ //
8
+ // The catch: Jira JQL datetime comparisons are minute-granularity, so two issues
9
+ // created in the same minute as the cursor both re-match `created >= "<cursor>"`
10
+ // on the next poll. The cursor alone cannot guarantee at-most-once, so we combine
11
+ // it with a seen-set keyed by `jira:<key>:<created>` (issue key + full timestamp).
12
+ // Re-matched same-minute issues are recognized as already-seen and dropped.
13
+ //
14
+ // All network access goes through the injected `http` (fetch-like) so tests run
15
+ // offline. Auth is HTTP Basic: base64(email:token).
16
+ import { defineBackend } from '../backend.js';
17
+ import { deriveNew } from '../dedup.js';
18
+ /** Resolve the Jira site base URL. */
19
+ function baseUrlFor(source) {
20
+ return String(source?.auth?.baseUrl || source?.config?.baseUrl || '').replace(/\/+$/, '');
21
+ }
22
+ /** Authenticated request headers for the Jira Cloud REST API (Basic auth). */
23
+ function jiraHeaders(source) {
24
+ const email = source?.auth?.email ?? '';
25
+ const token = source?.auth?.token ?? '';
26
+ const basic = Buffer.from(`${email}:${token}`, 'utf8').toString('base64');
27
+ return {
28
+ authorization: `Basic ${basic}`,
29
+ accept: 'application/json',
30
+ 'content-type': 'application/json'
31
+ };
32
+ }
33
+ /** Which timestamp field this event kind orders/filters on. */
34
+ function timeField(events) {
35
+ return Array.isArray(events) && events.includes('issue_updated') ? 'updated' : 'created';
36
+ }
37
+ /** The event kind label for meta. */
38
+ function kindFor(events) {
39
+ return Array.isArray(events) && events.includes('issue_updated')
40
+ ? 'issue_updated'
41
+ : 'issue_created';
42
+ }
43
+ /** A Jira project key must be a safe identifier so it can't inject into the JQL. */
44
+ const PROJECT_RE = /^[A-Za-z0-9_]+$/;
45
+ /**
46
+ * Format a stored cursor (a full-precision Jira/ISO timestamp) into the
47
+ * JQL-accepted quoted `"yyyy-MM-dd HH:mm"` literal (minute granularity). Jira
48
+ * rejects sub-minute precision in datetime comparisons, so we down-convert for
49
+ * the QUERY only; the full-precision timestamp is kept in the cursor + seen-set
50
+ * key so same-minute dedup still works (SPEC §5.2, finding §10).
51
+ * Returns null if the cursor can't be parsed (the clause is then omitted).
52
+ */
53
+ function jqlDateLiteral(cursor) {
54
+ if (!cursor)
55
+ return null;
56
+ const d = new Date(cursor);
57
+ if (Number.isNaN(d.getTime()))
58
+ return null;
59
+ const p = (n) => String(n).padStart(2, '0');
60
+ // Use UTC so the literal is stable regardless of the host timezone.
61
+ return (`${d.getUTCFullYear()}-${p(d.getUTCMonth() + 1)}-${p(d.getUTCDate())} ` +
62
+ `${p(d.getUTCHours())}:${p(d.getUTCMinutes())}`);
63
+ }
64
+ /**
65
+ * Build the JQL string for this poll. The project is validated against
66
+ * [A-Za-z0-9_]+ (defensive: validateConfig already enforces this) and any
67
+ * config.jql extra clause is wrapped + quote-escaped so neither can inject into
68
+ * the query.
69
+ */
70
+ function buildJql(source, cursor, field) {
71
+ const project = source.config.project;
72
+ if (!PROJECT_RE.test(String(project))) {
73
+ // Defensive: should be unreachable (validateConfig rejects this at load).
74
+ throw new Error(`jira: unsafe project key ${JSON.stringify(project)}`);
75
+ }
76
+ const clauses = [`project = "${project}"`];
77
+ const dateLiteral = jqlDateLiteral(cursor);
78
+ if (dateLiteral)
79
+ clauses.push(`${field} >= "${dateLiteral}"`);
80
+ if (source.config.jql) {
81
+ // config.jql is OPERATOR-supplied input from the trusted YAML config (NOT
82
+ // external/attacker-controlled channel data), so it is trusted JQL-operator
83
+ // input. The quote/backslash/semicolon strip below is therefore
84
+ // DEFENSE-IN-DEPTH only — a belt-and-suspenders guard so an accidental stray
85
+ // quote in a hand-written clause can't terminate the surrounding string and
86
+ // smuggle a trailing clause — not a sanitizer for untrusted input. The
87
+ // fragment is wrapped in parentheses so it composes as a single AND term.
88
+ const jql = String(source.config.jql).replace(/["\\;]/g, '');
89
+ if (jql.trim().length)
90
+ clauses.push(`(${jql})`);
91
+ }
92
+ return `${clauses.join(' AND ')} ORDER BY ${field} ASC`;
93
+ }
94
+ /** Wrap plain reply text in a minimal Atlassian Document Format (ADF) body. */
95
+ function adfBody(text) {
96
+ return {
97
+ body: {
98
+ type: 'doc',
99
+ version: 1,
100
+ content: [
101
+ {
102
+ type: 'paragraph',
103
+ content: [{ type: 'text', text: String(text ?? '') }]
104
+ }
105
+ ]
106
+ }
107
+ };
108
+ }
109
+ /** Pick the lexicographically-max timestamp (ISO-ish strings sort chronologically). */
110
+ function maxTimestamp(values, start) {
111
+ let max = start ?? null;
112
+ for (const v of values) {
113
+ if (v && (max == null || v > max))
114
+ max = v;
115
+ }
116
+ return max;
117
+ }
118
+ /** Build the channel content for an issue (summary + key context). */
119
+ function contentForIssue(issue) {
120
+ const summary = issue?.fields?.summary ?? '';
121
+ return `${summary}\n\n(${issue?.key ?? ''})`;
122
+ }
123
+ /** The Jira event kinds this backend understands. */
124
+ const JIRA_EVENT_KINDS = new Set(['issue_created', 'issue_updated']);
125
+ /** Page size for the paginated /search loop. */
126
+ const PAGE_SIZE = 50;
127
+ /**
128
+ * Run the Jira /search query across ALL pages by looping `startAt` until
129
+ * `startAt + len >= total`, accumulating every issue before the caller computes
130
+ * the cursor (so the cursor never advances past unfetched issues — finding §4).
131
+ * On ANY non-2xx page returns `{ ok:false, issues:[] }` so a failed poll advances
132
+ * nothing (finding §5). A `maxPages` guard bounds a pathological loop.
133
+ */
134
+ async function searchAll(url, jql, source, http, maxPages = 200) {
135
+ const all = [];
136
+ let startAt = 0;
137
+ let pages = 0;
138
+ // total defaults to +Inf until the first page tells us the real count.
139
+ let total = Infinity;
140
+ while (startAt < total && pages < maxPages) {
141
+ const res = (await http(url, {
142
+ method: 'POST',
143
+ headers: jiraHeaders(source),
144
+ body: JSON.stringify({ jql, startAt, maxResults: PAGE_SIZE })
145
+ }));
146
+ if (!res || !res.ok)
147
+ return { ok: false, issues: [] };
148
+ const data = (await res.json().catch(() => null));
149
+ const issues = Array.isArray(data?.issues) ? data.issues : [];
150
+ all.push(...issues);
151
+ total = typeof data?.total === 'number' ? data.total : all.length;
152
+ pages += 1;
153
+ // Advance by what we actually received; stop if a page came back empty.
154
+ if (issues.length === 0)
155
+ break;
156
+ startAt += issues.length;
157
+ }
158
+ return { ok: true, issues: all };
159
+ }
160
+ export default defineBackend({
161
+ type: 'jira',
162
+ /**
163
+ * Validate a Jira source at config-load time so misconfiguration is a clear
164
+ * validation error rather than a crash at poll time (SPEC §4.2, AC-3). Requires
165
+ * config.project (safe identifier), a non-empty events list, and
166
+ * auth.baseUrl/email/token. Also rejects a project value that could inject into
167
+ * the JQL string (SPEC §9 security).
168
+ */
169
+ validateConfig(source) {
170
+ const s = source;
171
+ const errors = [];
172
+ const id = s?.id ?? '(unknown)';
173
+ const cfg = s?.config || {};
174
+ const auth = s?.auth || {};
175
+ const project = cfg.project;
176
+ if (typeof project !== 'string' || project.length === 0) {
177
+ errors.push(`Source "${id}": jira config.project is required.`);
178
+ }
179
+ else if (!PROJECT_RE.test(project)) {
180
+ errors.push(`Source "${id}": jira config.project must match [A-Za-z0-9_]+ ` +
181
+ `(got ${JSON.stringify(project)}).`);
182
+ }
183
+ const events = cfg.events;
184
+ if (!Array.isArray(events) || events.length === 0) {
185
+ errors.push(`Source "${id}": jira config.events must be a non-empty array.`);
186
+ }
187
+ else {
188
+ const bad = events.filter((e) => !JIRA_EVENT_KINDS.has(e));
189
+ if (bad.length) {
190
+ errors.push(`Source "${id}": jira config.events has unknown kind(s) ${JSON.stringify(bad)} ` +
191
+ `(valid: ${[...JIRA_EVENT_KINDS].join(', ')}).`);
192
+ }
193
+ }
194
+ if (!auth.baseUrl)
195
+ errors.push(`Source "${id}": jira auth.baseUrl is required.`);
196
+ if (!auth.email)
197
+ errors.push(`Source "${id}": jira auth.email is required.`);
198
+ if (!auth.token)
199
+ errors.push(`Source "${id}": jira auth.token is required.`);
200
+ return errors;
201
+ },
202
+ async poll(ctx) {
203
+ const { source: srcRaw, state, http } = ctx;
204
+ const source = srcRaw;
205
+ const cursor = state?.cursor ?? null;
206
+ const seen = state?.seen ?? [];
207
+ const events = source?.config?.events;
208
+ const field = timeField(events);
209
+ const kind = kindFor(events);
210
+ const jql = buildJql(source, cursor, field);
211
+ const url = `${baseUrlFor(source)}/rest/api/3/search`;
212
+ // Fetch ALL pages (loop startAt until startAt+len >= total) so issues beyond
213
+ // the first page aren't lost and the cursor never advances past them.
214
+ const { ok, issues } = await searchAll(url, jql, source, http);
215
+ // On a failed poll, advance NOTHING: keep the prior cursor + seen, emit nothing.
216
+ if (!ok)
217
+ return { events: [], state: { cursor, seen } };
218
+ // Seen-set keyed by <key>:<created> (full timestamp) defeats minute-granularity
219
+ // JQL re-matches: a same-minute issue re-returned next poll is already seen.
220
+ const fieldOf = (issue) => issue?.fields?.[field];
221
+ const idOf = (issue) => `jira:${issue.key}:${fieldOf(issue)}`;
222
+ const { fresh, seen: nextSeen } = deriveNew(issues, { idOf, seen });
223
+ const channelEvents = fresh.map((issue) => ({
224
+ id: idOf(issue),
225
+ content: contentForIssue(issue),
226
+ meta: {
227
+ project: issue?.fields?.project?.key ?? source.config.project,
228
+ issue_key: issue.key,
229
+ kind
230
+ },
231
+ payload: issue,
232
+ routing: { key: issue.key }
233
+ }));
234
+ // Cursor = max created/updated observed across the returned issues.
235
+ const nextCursor = maxTimestamp(issues.map(fieldOf), cursor);
236
+ return { events: channelEvents, state: { cursor: nextCursor, seen: nextSeen } };
237
+ },
238
+ /**
239
+ * Post a comment back to the originating Jira issue (ADF body).
240
+ */
241
+ async reply({ routing, text, source, http }) {
242
+ const key = routing?.key;
243
+ const url = `${baseUrlFor(source)}/rest/api/3/issue/${key}/comment`;
244
+ const res = (await http(url, {
245
+ method: 'POST',
246
+ headers: jiraHeaders(source),
247
+ body: JSON.stringify(adfBody(text))
248
+ }));
249
+ if (res && res.ok) {
250
+ const created = (await res.json().catch(() => null));
251
+ return { ok: true, ref: created ? String(created.id ?? '') : undefined };
252
+ }
253
+ return { ok: false };
254
+ }
255
+ });
256
+ //# sourceMappingURL=jira.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"jira.js","sourceRoot":"","sources":["../../src/backends/jira.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,25 @@
1
+ import type { PollContext, PollResult, ReplyArgs, ReplyResult } from '../types.js';
2
+ declare const _default: {
3
+ type: string;
4
+ /**
5
+ * Validate a webhook source at config-load time (SPEC §4 contract). Requires a
6
+ * recognized `config.backend`, and at least one configured queue source
7
+ * (`config.payloads` or `config.dir`). Surfaces clear, per-source messages.
8
+ */
9
+ validateConfig(source: Record<string, unknown>): string[];
10
+ /**
11
+ * Read the configured queue of captured webhook payloads, normalize each via the
12
+ * triggers-adapter `normalizeEvent(backend, eventName, payload)`, and emit a
13
+ * channel event per genuinely-new payload (dedup by stable id). The cursor is
14
+ * unused (the queue source owns ordering); `seen` carries the dedup set.
15
+ */
16
+ poll(ctx: PollContext): Promise<PollResult>;
17
+ /**
18
+ * A webhook is a one-way inbound notification with no generic callback channel,
19
+ * so a reply cannot be routed back. Surface a CLEAR error rather than silently
20
+ * succeeding or no-op'ing (fallbacks are evil): a caller wanting to reply should
21
+ * route through the concrete `github`/`jira` backend the payload originated from.
22
+ */
23
+ reply({ routing }: ReplyArgs): Promise<ReplyResult>;
24
+ };
25
+ export default _default;
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webhook.d.ts","sourceRoot":"","sources":["../../src/backends/webhook.ts"],"names":[],"mappings":"AAmCA,OAAO,KAAK,EAAE,WAAW,EAAE,UAAU,EAAgB,SAAS,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;;;IAqH/F;;;;OAIG;2BACoB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,EAAE;IA2BzD;;;;;OAKG;cACa,WAAW,GAAG,OAAO,CAAC,UAAU,CAAC;IAyCjD;;;;;OAKG;uBACsB,SAAS,GAAG,OAAO,CAAC,WAAW,CAAC;;AAxF3D,wBAgGG"}
@@ -0,0 +1,206 @@
1
+ // Webhook backend (OPTIONAL, additive — SPEC §4 backend contract, DESIGN §5).
2
+ //
3
+ // An ADDITIVE built-in backend alongside `github` and `jira`. It does NOT replace
4
+ // or alter them. It turns INBOUND WEBHOOK PAYLOADS (GitHub / GitLab / Bitbucket /
5
+ // generic) into channel events by delegating the parsing to the genuine
6
+ // `@a5c-ai/triggers-adapter` `normalizeEvent(backend, eventName, payload)` — the
7
+ // same normalizer the triggers-adapter uses for CI/action triggers — so the
8
+ // channels framework reuses that battle-tested webhook-shape knowledge instead of
9
+ // re-deriving it.
10
+ //
11
+ // Channels is POLL-BASED, so this backend reads a QUEUE of already-captured
12
+ // webhook payloads from a configured source and normalizes each on every poll:
13
+ // - `config.payloads`: an inline array of captured webhook entries (the
14
+ // injection seam — used by tests and by a caller that buffers receipts itself).
15
+ // - `config.dir`: a directory of captured `*.json` payload files (read through
16
+ // the injected `readDir`/`readFile` so the suite stays offline; defaults to the
17
+ // real node:fs/promises when unset).
18
+ // Each queue entry is `{ eventName, payload, id? }` (or a bare payload object, in
19
+ // which case `eventName` falls back to `config.eventName`).
20
+ //
21
+ // LIVE HTTP webhook RECEIPT is intentionally OUT OF SCOPE here (it needs a bound
22
+ // HTTP listener / tunnel, mirroring how the spawner documents live agent launch as
23
+ // out of offline-test scope, DESIGN §7.7). This backend only normalizes payloads
24
+ // that some out-of-band receiver has already captured and queued.
25
+ //
26
+ // REPLY: a webhook is a one-way notification; there is no generic callback channel
27
+ // to post back to. `reply()` therefore surfaces a CLEAR, actionable error rather
28
+ // than silently dropping the reply (fallbacks are evil). A caller that wants to
29
+ // reply should route through the concrete backend (`github`/`jira`) the payload
30
+ // originated from, not through `webhook`.
31
+ import { normalizeEvent } from '@a5c-ai/triggers-adapter';
32
+ import { defineBackend } from '../backend.js';
33
+ import { deriveNew } from '../dedup.js';
34
+ /** The webhook backends `normalizeEvent` understands (mirrors TriggerBackend). */
35
+ const WEBHOOK_BACKENDS = new Set([
36
+ 'github',
37
+ 'gitlab',
38
+ 'bitbucket',
39
+ 'generic-webhook'
40
+ ]);
41
+ /** Resolve the configured trigger backend, defaulting to the generic shape. */
42
+ function backendFor(source) {
43
+ const raw = source?.config?.backend;
44
+ return WEBHOOK_BACKENDS.has(raw) ? raw : 'generic-webhook';
45
+ }
46
+ /** Coerce one queue item into a normalized `{ eventName, payload, id? }` envelope. */
47
+ function asEntry(item, fallbackEventName) {
48
+ if (item && typeof item === 'object' && 'payload' in item) {
49
+ const e = item;
50
+ return { eventName: e.eventName ?? fallbackEventName, payload: e.payload, id: e.id };
51
+ }
52
+ // A bare payload object — the eventName comes from config.eventName.
53
+ return { eventName: fallbackEventName, payload: item };
54
+ }
55
+ /** Load the inline `config.payloads` queue (the injection seam). */
56
+ function inlineQueue(source) {
57
+ const payloads = source?.config?.payloads;
58
+ return Array.isArray(payloads) ? payloads : [];
59
+ }
60
+ /** Load + parse the `config.dir` queue of captured `*.json` payload files (sorted
61
+ * by filename so emit order is stable). A file that fails to parse is skipped with
62
+ * a log line rather than failing the whole poll. */
63
+ async function dirQueue(source, ctx, fs) {
64
+ const dir = source?.config?.dir;
65
+ if (typeof dir !== 'string' || dir === '')
66
+ return [];
67
+ let names;
68
+ try {
69
+ names = (await fs.readdir(dir)).filter((n) => n.toLowerCase().endsWith('.json')).sort();
70
+ }
71
+ catch (err) {
72
+ ctx.log(`webhook: failed to read dir "${dir}": ${err?.message || err}`);
73
+ return [];
74
+ }
75
+ const out = [];
76
+ for (const name of names) {
77
+ const full = `${dir.replace(/[/\\]+$/, '')}/${name}`;
78
+ try {
79
+ out.push(JSON.parse(await fs.readFile(full, 'utf8')));
80
+ }
81
+ catch (err) {
82
+ ctx.log(`webhook: skipping unparseable payload "${full}": ${err?.message || err}`);
83
+ }
84
+ }
85
+ return out;
86
+ }
87
+ /**
88
+ * The stable dedup id for a normalized webhook event. Prefer an explicit entry
89
+ * `id` (the receiver's delivery id, e.g. GitHub `X-GitHub-Delivery`), else derive
90
+ * a deterministic id from the normalized shape (backend + event + sha/url/repo).
91
+ */
92
+ function dedupId(entryId, n) {
93
+ if (entryId != null && entryId !== '')
94
+ return `webhook:${n.backend}:${entryId}`;
95
+ const discriminator = n.sha || n.url || `${n.repository ?? ''}#${n.ref ?? ''}`;
96
+ return `webhook:${n.backend}:${n.eventName}:${n.action ?? ''}:${discriminator}`;
97
+ }
98
+ /** Build the channel content from the normalized trigger event (title + body, or
99
+ * the normalizer's collected `text` when neither is present). */
100
+ function contentFor(n) {
101
+ const parts = [n.title, n.body].filter((p) => typeof p === 'string' && p !== '');
102
+ if (parts.length > 0)
103
+ return parts.join('\n\n');
104
+ return n.text ?? '';
105
+ }
106
+ /** meta keys MUST be `[A-Za-z0-9_]+`; values are stringified, empties dropped. */
107
+ function metaFor(n) {
108
+ const meta = {
109
+ backend: n.backend,
110
+ event: n.eventName
111
+ };
112
+ const optional = {
113
+ action: n.action,
114
+ repo: n.repository,
115
+ author: n.actor,
116
+ ref: n.ref,
117
+ sha: n.sha,
118
+ source_branch: n.sourceBranch,
119
+ target_branch: n.targetBranch,
120
+ url: n.url
121
+ };
122
+ for (const [k, v] of Object.entries(optional)) {
123
+ if (v != null && v !== '')
124
+ meta[k] = String(v);
125
+ }
126
+ return meta;
127
+ }
128
+ export default defineBackend({
129
+ type: 'webhook',
130
+ /**
131
+ * Validate a webhook source at config-load time (SPEC §4 contract). Requires a
132
+ * recognized `config.backend`, and at least one configured queue source
133
+ * (`config.payloads` or `config.dir`). Surfaces clear, per-source messages.
134
+ */
135
+ validateConfig(source) {
136
+ const s = source;
137
+ const errors = [];
138
+ const id = s?.id ?? '(unknown)';
139
+ const cfg = s?.config || {};
140
+ if (!WEBHOOK_BACKENDS.has(cfg.backend)) {
141
+ errors.push(`Source "${id}": webhook config.backend must be one of ${[...WEBHOOK_BACKENDS].join(', ')}.`);
142
+ }
143
+ const hasInline = Array.isArray(cfg.payloads);
144
+ const hasDir = typeof cfg.dir === 'string' && cfg.dir !== '';
145
+ if (!hasInline && !hasDir) {
146
+ errors.push(`Source "${id}": webhook needs a queue source — set config.payloads (array) or config.dir (path).`);
147
+ }
148
+ if (cfg.eventName != null && typeof cfg.eventName !== 'string') {
149
+ errors.push(`Source "${id}": webhook config.eventName must be a string when set.`);
150
+ }
151
+ return errors;
152
+ },
153
+ /**
154
+ * Read the configured queue of captured webhook payloads, normalize each via the
155
+ * triggers-adapter `normalizeEvent(backend, eventName, payload)`, and emit a
156
+ * channel event per genuinely-new payload (dedup by stable id). The cursor is
157
+ * unused (the queue source owns ordering); `seen` carries the dedup set.
158
+ */
159
+ async poll(ctx) {
160
+ const source = ctx.source;
161
+ const state = ctx.state || { cursor: null, seen: [] };
162
+ const seen = state.seen ?? [];
163
+ const backend = backendFor(source);
164
+ const fallbackEventName = String(source?.config?.eventName ?? backend);
165
+ // The fs seam defaults to node:fs/promises; tests inject `config.fs`.
166
+ const fs = source?.config?.fs || (await import('node:fs/promises'));
167
+ const raw = [...inlineQueue(source), ...(await dirQueue(source, ctx, fs))];
168
+ // Normalize each queued payload into a (entry, normalized) pair.
169
+ const normalized = raw.map((item) => {
170
+ const entry = asEntry(item, fallbackEventName);
171
+ const n = normalizeEvent(backend, String(entry.eventName ?? backend), entry.payload);
172
+ return { id: dedupId(entry.id, n), n };
173
+ });
174
+ const { fresh, seen: nextSeen } = deriveNew(normalized, { idOf: (x) => x.id, seen });
175
+ const events = fresh.map(({ id, n }) => ({
176
+ id,
177
+ content: contentFor(n),
178
+ meta: metaFor(n),
179
+ // The raw upstream payload, so declarative dot-path filters can match.
180
+ payload: (n.raw && typeof n.raw === 'object' ? n.raw : { raw: n.raw }),
181
+ // Origin coordinates for a reply: the normalized identity. There is no live
182
+ // callback channel, so reply() is unsupported (see below) — this is recorded
183
+ // for diagnostics / downstream routing only.
184
+ routing: {
185
+ backend: n.backend,
186
+ eventName: n.eventName,
187
+ repository: n.repository,
188
+ url: n.url
189
+ }
190
+ }));
191
+ return { events, state: { cursor: state.cursor ?? null, seen: nextSeen } };
192
+ },
193
+ /**
194
+ * A webhook is a one-way inbound notification with no generic callback channel,
195
+ * so a reply cannot be routed back. Surface a CLEAR error rather than silently
196
+ * succeeding or no-op'ing (fallbacks are evil): a caller wanting to reply should
197
+ * route through the concrete `github`/`jira` backend the payload originated from.
198
+ */
199
+ async reply({ routing }) {
200
+ const r = routing;
201
+ throw new Error(`webhook backend does not support reply: a webhook is a one-way inbound ` +
202
+ `notification with no callback channel (origin backend="${r?.backend ?? 'unknown'}", ` +
203
+ `event="${r?.eventName ?? 'unknown'}"). Reply through the concrete backend instead.`);
204
+ }
205
+ });
206
+ //# sourceMappingURL=webhook.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webhook.js","sourceRoot":"","sources":["../../src/backends/webhook.ts"],"names":[],"mappings":"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"}
package/dist/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":""}
package/dist/cli.js ADDED
@@ -0,0 +1,28 @@
1
+ #!/usr/bin/env node
2
+ // mcp-channels CLI entry (stdio MCP server bootstrap).
3
+ //
4
+ // Usage: mcp-channels <path/to/channels.yml>
5
+ // node dist/cli.js examples/channels.yml
6
+ //
7
+ // Thin wrapper: resolve the config path from argv, build the runtime, and start
8
+ // it. All real logic lives in runtime.js (and the modules it composes). This
9
+ // file is intentionally trivial and is excluded from coverage.
10
+ import { createRuntime } from './runtime.js';
11
+ const configPath = process.argv[2];
12
+ if (!configPath) {
13
+ process.stderr.write('Usage: mcp-channels <config.yml>\n' +
14
+ ' e.g. mcp-channels examples/channels.yml\n');
15
+ process.exit(1);
16
+ }
17
+ // Wrap the bootstrap so a misconfig (e.g. aggregated validation errors from
18
+ // createRuntime, or a bad custom-backend import) prints a clean message to stderr
19
+ // and exits non-zero, rather than surfacing as an unhandled promise rejection.
20
+ try {
21
+ const runtime = await createRuntime(configPath);
22
+ await runtime.start();
23
+ }
24
+ catch (err) {
25
+ process.stderr.write(`${err?.message || String(err)}\n`);
26
+ process.exit(1);
27
+ }
28
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA,uDAAuD;AACvD,EAAE;AACF,6CAA6C;AAC7C,gDAAgD;AAChD,EAAE;AACF,gFAAgF;AAChF,6EAA6E;AAC7E,+DAA+D;AAE/D,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAE7C,MAAM,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAEnC,IAAI,CAAC,UAAU,EAAE,CAAC;IAChB,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,oCAAoC;QAClC,6CAA6C,CAChD,CAAC;IACF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC;AAED,4EAA4E;AAC5E,kFAAkF;AAClF,+EAA+E;AAC/E,IAAI,CAAC;IACH,MAAM,OAAO,GAAG,MAAM,aAAa,CAAC,UAAU,CAAC,CAAC;IAChD,MAAM,OAAO,CAAC,KAAK,EAAE,CAAC;AACxB,CAAC;AAAC,OAAO,GAAG,EAAE,CAAC;IACb,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAI,GAAa,EAAE,OAAO,IAAI,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IACpE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC"}
@@ -0,0 +1,10 @@
1
+ import type { ChannelsConfig } from './types.js';
2
+ /**
3
+ * Load and validate a channels config.
4
+ */
5
+ export declare function loadConfig(input: string, opts?: {
6
+ env?: Record<string, string>;
7
+ }): {
8
+ config: ChannelsConfig;
9
+ errors: string[];
10
+ };
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,cAAc,EAAiC,MAAM,YAAY,CAAC;AA6KhF;;GAEG;AACH,wBAAgB,UAAU,CACxB,KAAK,EAAE,MAAM,EACb,IAAI,GAAE;IAAE,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAAO,GAC1C;IAAE,MAAM,EAAE,cAAc,CAAC;IAAC,MAAM,EAAE,MAAM,EAAE,CAAA;CAAE,CAoK9C"}