@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.
- package/LICENSE +21 -0
- package/README.md +661 -0
- package/dist/backend.d.ts +7 -0
- package/dist/backend.d.ts.map +1 -0
- package/dist/backend.js +22 -0
- package/dist/backend.js.map +1 -0
- package/dist/backends/github.d.ts +16 -0
- package/dist/backends/github.d.ts.map +1 -0
- package/dist/backends/github.js +357 -0
- package/dist/backends/github.js.map +1 -0
- package/dist/backends/jira.d.ts +18 -0
- package/dist/backends/jira.d.ts.map +1 -0
- package/dist/backends/jira.js +256 -0
- package/dist/backends/jira.js.map +1 -0
- package/dist/backends/webhook.d.ts +25 -0
- package/dist/backends/webhook.d.ts.map +1 -0
- package/dist/backends/webhook.js +206 -0
- package/dist/backends/webhook.js.map +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +28 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +10 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +323 -0
- package/dist/config.js.map +1 -0
- package/dist/dedup.d.ts +26 -0
- package/dist/dedup.d.ts.map +1 -0
- package/dist/dedup.js +68 -0
- package/dist/dedup.js.map +1 -0
- package/dist/filter.d.ts +9 -0
- package/dist/filter.d.ts.map +1 -0
- package/dist/filter.js +115 -0
- package/dist/filter.js.map +1 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +21 -0
- package/dist/index.js.map +1 -0
- package/dist/poller.d.ts +63 -0
- package/dist/poller.d.ts.map +1 -0
- package/dist/poller.js +195 -0
- package/dist/poller.js.map +1 -0
- package/dist/registry.d.ts +22 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/registry.js +59 -0
- package/dist/registry.js.map +1 -0
- package/dist/relay.d.ts +58 -0
- package/dist/relay.d.ts.map +1 -0
- package/dist/relay.js +172 -0
- package/dist/relay.js.map +1 -0
- package/dist/runtime.d.ts +27 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/runtime.js +198 -0
- package/dist/runtime.js.map +1 -0
- package/dist/server.d.ts +64 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +217 -0
- package/dist/server.js.map +1 -0
- package/dist/spawner.d.ts +96 -0
- package/dist/spawner.d.ts.map +1 -0
- package/dist/spawner.js +368 -0
- package/dist/spawner.js.map +1 -0
- package/dist/state.d.ts +43 -0
- package/dist/state.d.ts.map +1 -0
- package/dist/state.js +92 -0
- package/dist/state.js.map +1 -0
- package/dist/types.d.ts +140 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +10 -0
- package/dist/types.js.map +1 -0
- 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 @@
|
|
|
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
|
package/dist/cli.js.map
ADDED
|
@@ -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"}
|
package/dist/config.d.ts
ADDED
|
@@ -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"}
|