@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
package/dist/poller.d.ts
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type { Backend, ChannelEvent, ReplyToken } from './types.js';
|
|
2
|
+
import type { StateStoreLike } from './state.js';
|
|
3
|
+
type AnySource = Record<string, any>;
|
|
4
|
+
/** A spawner-like seam the poller dispatches surviving events to. */
|
|
5
|
+
export interface SpawnerLike {
|
|
6
|
+
spawn(source: AnySource, event: ChannelEvent): unknown;
|
|
7
|
+
}
|
|
8
|
+
/** A server-like seam the poller emits surviving events to. */
|
|
9
|
+
export interface EmitTargetLike {
|
|
10
|
+
emit(event: {
|
|
11
|
+
content: string;
|
|
12
|
+
meta: Record<string, unknown>;
|
|
13
|
+
}): void | Promise<void>;
|
|
14
|
+
}
|
|
15
|
+
export interface PollerDeps {
|
|
16
|
+
sources: AnySource[];
|
|
17
|
+
resolveBackend: (source: AnySource) => Backend | undefined | Promise<Backend | undefined>;
|
|
18
|
+
stateStore: StateStoreLike;
|
|
19
|
+
server: EmitTargetLike;
|
|
20
|
+
spawner?: SpawnerLike | null;
|
|
21
|
+
encodeReplyTo?: (record: ReplyToken) => string;
|
|
22
|
+
http: (url: string | URL, opts?: Record<string, unknown>) => Promise<unknown>;
|
|
23
|
+
now?: () => Date;
|
|
24
|
+
log?: (...args: unknown[]) => void;
|
|
25
|
+
}
|
|
26
|
+
export declare class Poller {
|
|
27
|
+
sources: AnySource[];
|
|
28
|
+
resolveBackend: PollerDeps['resolveBackend'];
|
|
29
|
+
stateStore: StateStoreLike;
|
|
30
|
+
server: EmitTargetLike;
|
|
31
|
+
spawner: SpawnerLike | null;
|
|
32
|
+
encodeReplyTo: (record: ReplyToken) => string;
|
|
33
|
+
http: PollerDeps['http'];
|
|
34
|
+
now: () => Date;
|
|
35
|
+
log: (...args: unknown[]) => void;
|
|
36
|
+
_byId: Map<string, AnySource>;
|
|
37
|
+
_timers: Map<string, ReturnType<typeof setInterval>>;
|
|
38
|
+
_initialized: Set<string>;
|
|
39
|
+
_active: Map<string, Promise<void>>;
|
|
40
|
+
_pending: Map<string, Promise<void>>;
|
|
41
|
+
constructor({ sources, resolveBackend, stateStore, server, spawner, encodeReplyTo, http, now, log }: PollerDeps);
|
|
42
|
+
/**
|
|
43
|
+
* Run a single poll for one source. Ticks for the SAME source are SERIALIZED
|
|
44
|
+
* AND COALESCED:
|
|
45
|
+
* - if no tick is in flight, this one runs immediately;
|
|
46
|
+
* - if a tick is in flight and NONE is pending, exactly ONE follow-up is
|
|
47
|
+
* scheduled to run after the active one settles;
|
|
48
|
+
* - if a tick is in flight and a follow-up is ALREADY pending, this call is
|
|
49
|
+
* COALESCED onto that single pending tick.
|
|
50
|
+
* Ticks for DIFFERENT sources run concurrently. Errors are isolated so one
|
|
51
|
+
* failing tick doesn't poison the next.
|
|
52
|
+
*/
|
|
53
|
+
tick(sourceId: string): Promise<void>;
|
|
54
|
+
/**
|
|
55
|
+
* The actual single-poll body (no serialization). Use `tick()` externally.
|
|
56
|
+
*/
|
|
57
|
+
_runTick(sourceId: string): Promise<void>;
|
|
58
|
+
/** Start interval polling for every source. */
|
|
59
|
+
start(): void;
|
|
60
|
+
/** Stop all interval polling. */
|
|
61
|
+
stop(): void;
|
|
62
|
+
}
|
|
63
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"poller.d.ts","sourceRoot":"","sources":["../src/poller.ts"],"names":[],"mappings":"AAkBA,OAAO,KAAK,EAAE,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AACpE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAEjD,KAAK,SAAS,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;AAErC,qEAAqE;AACrE,MAAM,WAAW,WAAW;IAC1B,KAAK,CAAC,MAAM,EAAE,SAAS,EAAE,KAAK,EAAE,YAAY,GAAG,OAAO,CAAC;CACxD;AAED,+DAA+D;AAC/D,MAAM,WAAW,cAAc;IAC7B,IAAI,CAAC,KAAK,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KAAE,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACvF;AAED,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,SAAS,EAAE,CAAC;IACrB,cAAc,EAAE,CAAC,MAAM,EAAE,SAAS,KAAK,OAAO,GAAG,SAAS,GAAG,OAAO,CAAC,OAAO,GAAG,SAAS,CAAC,CAAC;IAC1F,UAAU,EAAE,cAAc,CAAC;IAC3B,MAAM,EAAE,cAAc,CAAC;IACvB,OAAO,CAAC,EAAE,WAAW,GAAG,IAAI,CAAC;IAC7B,aAAa,CAAC,EAAE,CAAC,MAAM,EAAE,UAAU,KAAK,MAAM,CAAC;IAC/C,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IAC9E,GAAG,CAAC,EAAE,MAAM,IAAI,CAAC;IACjB,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,CAAC;CACpC;AAED,qBAAa,MAAM;IACjB,OAAO,EAAE,SAAS,EAAE,CAAC;IACrB,cAAc,EAAE,UAAU,CAAC,gBAAgB,CAAC,CAAC;IAC7C,UAAU,EAAE,cAAc,CAAC;IAC3B,MAAM,EAAE,cAAc,CAAC;IACvB,OAAO,EAAE,WAAW,GAAG,IAAI,CAAC;IAC5B,aAAa,EAAE,CAAC,MAAM,EAAE,UAAU,KAAK,MAAM,CAAC;IAC9C,IAAI,EAAE,UAAU,CAAC,MAAM,CAAC,CAAC;IACzB,GAAG,EAAE,MAAM,IAAI,CAAC;IAChB,GAAG,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,CAAC;IAClC,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;IAC9B,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC,CAAC;IACrD,YAAY,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC1B,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;IACpC,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;gBAEzB,EACV,OAAO,EACP,cAAc,EACd,UAAU,EACV,MAAM,EACN,OAAO,EACP,aAAa,EACb,IAAI,EACJ,GAAG,EACH,GAAG,EACJ,EAAE,UAAU;IAqBb;;;;;;;;;;OAUG;IACH,IAAI,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAsCrC;;OAEG;IACG,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAsF/C,+CAA+C;IAC/C,KAAK,IAAI,IAAI;IAcb,iCAAiC;IACjC,IAAI,IAAI,IAAI;CAMb"}
|
package/dist/poller.js
ADDED
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
// Poller — per-source scheduling + the inbound pipeline (SPEC §3/§6 R5,
|
|
2
|
+
// DESIGN §1/§3.1).
|
|
3
|
+
//
|
|
4
|
+
// For each source, on its scheduled tick (or a test's explicit tick(id)):
|
|
5
|
+
// 1. load state StateStore.get(id) -> { cursor, seen }
|
|
6
|
+
// 2. poll the backend backend.poll({ source, state, http, log, now })
|
|
7
|
+
// 3. FILTER (core) keep events whose payload satisfies source.filter
|
|
8
|
+
// 4. DEDUP (core) drop events whose id is already in seen
|
|
9
|
+
// 5. mint reply_to attach the opaque routing token to event.meta
|
|
10
|
+
// 6. persist state StateStore.set(id, { cursor', seen ∪ survivor ids })
|
|
11
|
+
// 7. EMIT server.emit({ content, meta }) per survivor
|
|
12
|
+
//
|
|
13
|
+
// Filtering happens BEFORE dedup so a non-matching event is simply ignored that
|
|
14
|
+
// tick (not recorded as seen). Dedup is the authoritative at-most-once gate.
|
|
15
|
+
import { compileFilter } from './filter.js';
|
|
16
|
+
import { deriveNew } from './dedup.js';
|
|
17
|
+
import { encodeReplyTo as defaultEncodeReplyTo } from './relay.js';
|
|
18
|
+
export class Poller {
|
|
19
|
+
sources;
|
|
20
|
+
resolveBackend;
|
|
21
|
+
stateStore;
|
|
22
|
+
server;
|
|
23
|
+
spawner;
|
|
24
|
+
encodeReplyTo;
|
|
25
|
+
http;
|
|
26
|
+
now;
|
|
27
|
+
log;
|
|
28
|
+
_byId;
|
|
29
|
+
_timers;
|
|
30
|
+
_initialized;
|
|
31
|
+
_active;
|
|
32
|
+
_pending;
|
|
33
|
+
constructor({ sources, resolveBackend, stateStore, server, spawner, encodeReplyTo, http, now, log }) {
|
|
34
|
+
this.sources = sources || [];
|
|
35
|
+
this.resolveBackend = resolveBackend;
|
|
36
|
+
this.stateStore = stateStore;
|
|
37
|
+
this.server = server;
|
|
38
|
+
this.spawner = spawner || null;
|
|
39
|
+
// The reply_to minter. Injected (secret-bound) by the runtime when a shared
|
|
40
|
+
// replySecret is configured; otherwise the module's per-process function.
|
|
41
|
+
this.encodeReplyTo = typeof encodeReplyTo === 'function' ? encodeReplyTo : defaultEncodeReplyTo;
|
|
42
|
+
this.http = http;
|
|
43
|
+
this.now = now || (() => new Date());
|
|
44
|
+
this.log = log || (() => { });
|
|
45
|
+
this._byId = new Map(this.sources.map((s) => [s.id, s]));
|
|
46
|
+
this._timers = new Map();
|
|
47
|
+
this._initialized = new Set();
|
|
48
|
+
/** the in-flight tick per source. */
|
|
49
|
+
this._active = new Map();
|
|
50
|
+
/** the single COALESCED follow-up per source. */
|
|
51
|
+
this._pending = new Map();
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Run a single poll for one source. Ticks for the SAME source are SERIALIZED
|
|
55
|
+
* AND COALESCED:
|
|
56
|
+
* - if no tick is in flight, this one runs immediately;
|
|
57
|
+
* - if a tick is in flight and NONE is pending, exactly ONE follow-up is
|
|
58
|
+
* scheduled to run after the active one settles;
|
|
59
|
+
* - if a tick is in flight and a follow-up is ALREADY pending, this call is
|
|
60
|
+
* COALESCED onto that single pending tick.
|
|
61
|
+
* Ticks for DIFFERENT sources run concurrently. Errors are isolated so one
|
|
62
|
+
* failing tick doesn't poison the next.
|
|
63
|
+
*/
|
|
64
|
+
tick(sourceId) {
|
|
65
|
+
// No tick in flight → run now and record it as the active one.
|
|
66
|
+
if (!this._active.has(sourceId)) {
|
|
67
|
+
const run = this._runTick(sourceId)
|
|
68
|
+
.catch(() => { })
|
|
69
|
+
.finally(() => {
|
|
70
|
+
if (this._active.get(sourceId) === run)
|
|
71
|
+
this._active.delete(sourceId);
|
|
72
|
+
});
|
|
73
|
+
this._active.set(sourceId, run);
|
|
74
|
+
return run;
|
|
75
|
+
}
|
|
76
|
+
// A tick is in flight. If one is already pending, coalesce onto it.
|
|
77
|
+
const pending = this._pending.get(sourceId);
|
|
78
|
+
if (pending)
|
|
79
|
+
return pending;
|
|
80
|
+
// Otherwise schedule EXACTLY ONE follow-up after the active tick settles.
|
|
81
|
+
const active = this._active.get(sourceId);
|
|
82
|
+
const next = active
|
|
83
|
+
.then(() => { }, () => { })
|
|
84
|
+
.then(() => {
|
|
85
|
+
// Promote the pending tick to active before running it.
|
|
86
|
+
this._pending.delete(sourceId);
|
|
87
|
+
const run = this._runTick(sourceId)
|
|
88
|
+
.catch(() => { })
|
|
89
|
+
.finally(() => {
|
|
90
|
+
if (this._active.get(sourceId) === run)
|
|
91
|
+
this._active.delete(sourceId);
|
|
92
|
+
});
|
|
93
|
+
this._active.set(sourceId, run);
|
|
94
|
+
return run;
|
|
95
|
+
});
|
|
96
|
+
this._pending.set(sourceId, next);
|
|
97
|
+
return next;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* The actual single-poll body (no serialization). Use `tick()` externally.
|
|
101
|
+
*/
|
|
102
|
+
async _runTick(sourceId) {
|
|
103
|
+
const source = this._byId.get(sourceId);
|
|
104
|
+
if (!source)
|
|
105
|
+
return;
|
|
106
|
+
const backend = await this.resolveBackend(source);
|
|
107
|
+
if (!backend) {
|
|
108
|
+
this.log(`poller: no backend for source "${sourceId}"`);
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
// One-time init before the first poll of this source.
|
|
112
|
+
if (!this._initialized.has(sourceId)) {
|
|
113
|
+
this._initialized.add(sourceId);
|
|
114
|
+
if (typeof backend.init === 'function') {
|
|
115
|
+
await backend.init(source);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
const state = this.stateStore.get(sourceId);
|
|
119
|
+
const polled = await backend.poll({
|
|
120
|
+
source,
|
|
121
|
+
state,
|
|
122
|
+
http: this.http,
|
|
123
|
+
log: this.log,
|
|
124
|
+
now: this.now()
|
|
125
|
+
});
|
|
126
|
+
const events = Array.isArray(polled?.events) ? polled.events : [];
|
|
127
|
+
// (3) FILTER — core is the authoritative gate.
|
|
128
|
+
const predicate = compileFilter(source.filter);
|
|
129
|
+
const matched = events.filter((e) => predicate(e?.payload));
|
|
130
|
+
// (4) DEDUP — at-most-once across overlapping windows.
|
|
131
|
+
const { fresh, seen } = deriveNew(matched, {
|
|
132
|
+
idOf: (e) => e.id,
|
|
133
|
+
seen: state.seen || []
|
|
134
|
+
});
|
|
135
|
+
// (6) persist: cursor advances; seen is the union (store enforces the bound).
|
|
136
|
+
// Protect this tick's window ids from FIFO eviction so a boundary id still
|
|
137
|
+
// inside the cursor window can't be pruned and then re-emitted next poll
|
|
138
|
+
// (finding §3). The boundary bucket is a subset of the current window, so
|
|
139
|
+
// passing every matched id is a safe superset to keep.
|
|
140
|
+
const nextCursor = polled?.state?.cursor ?? null;
|
|
141
|
+
const keepSeen = matched.map((e) => e.id);
|
|
142
|
+
await this.stateStore.set(sourceId, { cursor: nextCursor, seen, keepSeen });
|
|
143
|
+
// (5) mint reply_to + (7) DISPATCH each survivor per the source's effective
|
|
144
|
+
// onEvent (DESIGN §7.6): emit -> server.emit, spawn -> spawner.spawn, both ->
|
|
145
|
+
// both. The SAME minted reply_to is shared between the emitted meta and the
|
|
146
|
+
// spawned event. Default is 'emit' (today's behavior) so existing sources
|
|
147
|
+
// never spawn.
|
|
148
|
+
const action = source.onEvent || 'emit';
|
|
149
|
+
const doEmit = action === 'emit' || action === 'both';
|
|
150
|
+
const doSpawn = (action === 'spawn' || action === 'both') && this.spawner;
|
|
151
|
+
for (const ev of fresh) {
|
|
152
|
+
const reply_to = this.encodeReplyTo({
|
|
153
|
+
sourceId,
|
|
154
|
+
backendType: backend.type || source.backend,
|
|
155
|
+
routing: ev.routing || {}
|
|
156
|
+
});
|
|
157
|
+
// The event carrying the minted reply_to in its meta (shared by both paths).
|
|
158
|
+
const meta = { ...(ev.meta || {}), reply_to };
|
|
159
|
+
if (doEmit) {
|
|
160
|
+
await this.server.emit({ content: ev.content, meta });
|
|
161
|
+
}
|
|
162
|
+
if (doSpawn) {
|
|
163
|
+
// Spawn is dispatched but NOT awaited to completion (the spawner bounds
|
|
164
|
+
// concurrency + isolates errors internally), so a slow/failing launch
|
|
165
|
+
// never stalls the tick or blocks sibling events.
|
|
166
|
+
const spawnEvent = { ...ev, meta };
|
|
167
|
+
const p = this.spawner.spawn(source, spawnEvent);
|
|
168
|
+
if (p && typeof p.catch === 'function') {
|
|
169
|
+
p.catch((err) => this.log(`poller: spawn dispatch failed for "${sourceId}": ${err?.message || err}`));
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
/** Start interval polling for every source. */
|
|
175
|
+
start() {
|
|
176
|
+
for (const source of this.sources) {
|
|
177
|
+
const ms = Math.max(1, Number(source.pollIntervalSeconds) || 60) * 1000;
|
|
178
|
+
const timer = setInterval(() => {
|
|
179
|
+
this.tick(source.id).catch((err) => this.log(`poller: tick("${source.id}") failed: ${err?.message || err}`));
|
|
180
|
+
}, ms);
|
|
181
|
+
// Don't keep the process alive solely for polling.
|
|
182
|
+
if (typeof timer.unref === 'function')
|
|
183
|
+
timer.unref();
|
|
184
|
+
this._timers.set(source.id, timer);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
/** Stop all interval polling. */
|
|
188
|
+
stop() {
|
|
189
|
+
for (const timer of this._timers.values()) {
|
|
190
|
+
clearInterval(timer);
|
|
191
|
+
}
|
|
192
|
+
this._timers.clear();
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
//# sourceMappingURL=poller.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"poller.js","sourceRoot":"","sources":["../src/poller.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { Backend } from './types.js';
|
|
2
|
+
declare class Registry {
|
|
3
|
+
_map: Map<string, Backend>;
|
|
4
|
+
constructor();
|
|
5
|
+
/**
|
|
6
|
+
* Register a backend programmatically.
|
|
7
|
+
*/
|
|
8
|
+
register(type: string, backend: Backend): Backend;
|
|
9
|
+
/**
|
|
10
|
+
* Look up a backend by type. Returns `undefined` for an unknown type (never
|
|
11
|
+
* throws).
|
|
12
|
+
*/
|
|
13
|
+
get(type: string): Backend | undefined;
|
|
14
|
+
/**
|
|
15
|
+
* Dynamically import a custom backend referenced by (relative) path, resolving
|
|
16
|
+
* it against `baseDir`. Validates the module exposes the hook interface.
|
|
17
|
+
*/
|
|
18
|
+
load(path: string, baseDir: string): Promise<Backend>;
|
|
19
|
+
}
|
|
20
|
+
/** The shared registry instance (built-ins pre-registered). */
|
|
21
|
+
export declare const registry: Registry;
|
|
22
|
+
export { Registry };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../src/registry.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAS1C,cAAM,QAAQ;IACZ,IAAI,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;;IAS3B;;OAEG;IACH,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO;IAKjD;;;OAGG;IACH,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,GAAG,SAAS;IAItC;;;OAGG;IACG,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;CAO5D;AAED,+DAA+D;AAC/D,eAAO,MAAM,QAAQ,UAAiB,CAAC;AACvC,OAAO,EAAE,QAAQ,EAAE,CAAC"}
|
package/dist/registry.js
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
// Backend registry (SPEC §3/§6 R2, DESIGN §1).
|
|
2
|
+
//
|
|
3
|
+
// Maps a backend `type` -> backend module. Built-ins `github` and `jira` are
|
|
4
|
+
// pre-registered as the REAL backend modules (statically imported), so a single
|
|
5
|
+
// module instance is shared everywhere: `registry.get('github')` returns the same
|
|
6
|
+
// object that `import github from './backends/github.js'` yields.
|
|
7
|
+
//
|
|
8
|
+
// Custom backends referenced from YAML by relative path are loaded on demand via
|
|
9
|
+
// `registry.load(path, baseDir)` (dynamic import, resolved against the config dir).
|
|
10
|
+
import { pathToFileURL } from 'node:url';
|
|
11
|
+
import { resolve, isAbsolute } from 'node:path';
|
|
12
|
+
import { defineBackend } from './backend.js';
|
|
13
|
+
import githubBackend from './backends/github.js';
|
|
14
|
+
import jiraBackend from './backends/jira.js';
|
|
15
|
+
import webhookBackend from './backends/webhook.js';
|
|
16
|
+
/** Built-in backend type -> backend module. */
|
|
17
|
+
const BUILTINS = {
|
|
18
|
+
github: githubBackend,
|
|
19
|
+
jira: jiraBackend,
|
|
20
|
+
webhook: webhookBackend
|
|
21
|
+
};
|
|
22
|
+
class Registry {
|
|
23
|
+
_map;
|
|
24
|
+
constructor() {
|
|
25
|
+
this._map = new Map();
|
|
26
|
+
for (const [type, backend] of Object.entries(BUILTINS)) {
|
|
27
|
+
this._map.set(type, backend);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Register a backend programmatically.
|
|
32
|
+
*/
|
|
33
|
+
register(type, backend) {
|
|
34
|
+
this._map.set(type, backend);
|
|
35
|
+
return backend;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Look up a backend by type. Returns `undefined` for an unknown type (never
|
|
39
|
+
* throws).
|
|
40
|
+
*/
|
|
41
|
+
get(type) {
|
|
42
|
+
return this._map.get(type);
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Dynamically import a custom backend referenced by (relative) path, resolving
|
|
46
|
+
* it against `baseDir`. Validates the module exposes the hook interface.
|
|
47
|
+
*/
|
|
48
|
+
async load(path, baseDir) {
|
|
49
|
+
const abs = isAbsolute(path) ? path : resolve(baseDir || process.cwd(), path);
|
|
50
|
+
const mod = (await import(pathToFileURL(abs).href));
|
|
51
|
+
const backend = (mod.default ?? mod);
|
|
52
|
+
// Validate it really is a backend (throws a clear error if not).
|
|
53
|
+
return defineBackend(backend);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
/** The shared registry instance (built-ins pre-registered). */
|
|
57
|
+
export const registry = new Registry();
|
|
58
|
+
export { Registry };
|
|
59
|
+
//# sourceMappingURL=registry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry.js","sourceRoot":"","sources":["../src/registry.ts"],"names":[],"mappings":"AAAA,+CAA+C;AAC/C,EAAE;AACF,6EAA6E;AAC7E,gFAAgF;AAChF,kFAAkF;AAClF,kEAAkE;AAClE,EAAE;AACF,iFAAiF;AACjF,oFAAoF;AAEpF,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAChD,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAC7C,OAAO,aAAa,MAAM,sBAAsB,CAAC;AACjD,OAAO,WAAW,MAAM,oBAAoB,CAAC;AAC7C,OAAO,cAAc,MAAM,uBAAuB,CAAC;AAGnD,+CAA+C;AAC/C,MAAM,QAAQ,GAA4B;IACxC,MAAM,EAAE,aAAa;IACrB,IAAI,EAAE,WAAW;IACjB,OAAO,EAAE,cAAc;CACxB,CAAC;AAEF,MAAM,QAAQ;IACZ,IAAI,CAAuB;IAE3B;QACE,IAAI,CAAC,IAAI,GAAG,IAAI,GAAG,EAAE,CAAC;QACtB,KAAK,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAC;YACvD,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QAC/B,CAAC;IACH,CAAC;IAED;;OAEG;IACH,QAAQ,CAAC,IAAY,EAAE,OAAgB;QACrC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QAC7B,OAAO,OAAO,CAAC;IACjB,CAAC;IAED;;;OAGG;IACH,GAAG,CAAC,IAAY;QACd,OAAO,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAC7B,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,IAAI,CAAC,IAAY,EAAE,OAAe;QACtC,MAAM,GAAG,GAAG,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,IAAI,OAAO,CAAC,GAAG,EAAE,EAAE,IAAI,CAAC,CAAC;QAC9E,MAAM,GAAG,GAAG,CAAC,MAAM,MAAM,CAAC,aAAa,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,CAA6C,CAAC;QAChG,MAAM,OAAO,GAAG,CAAC,GAAG,CAAC,OAAO,IAAI,GAAG,CAAqB,CAAC;QACzD,iEAAiE;QACjE,OAAO,aAAa,CAAC,OAAO,CAAY,CAAC;IAC3C,CAAC;CACF;AAED,+DAA+D;AAC/D,MAAM,CAAC,MAAM,QAAQ,GAAG,IAAI,QAAQ,EAAE,CAAC;AACvC,OAAO,EAAE,QAAQ,EAAE,CAAC"}
|
package/dist/relay.d.ts
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import type { ReplyToken, ReplyResult } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Encode a routing record into an opaque, URL-safe, tamper-evident token of the
|
|
4
|
+
* form `<payload>.<sig>` where `payload = base64url(JSON(record))` and
|
|
5
|
+
* `sig = base64url(HMAC-SHA256(payload))`.
|
|
6
|
+
*
|
|
7
|
+
* The optional trailing `secret` (DESIGN §7.4) derives the signing key so a
|
|
8
|
+
* second process with the SAME secret can verify the token. Omitted ⇒ the
|
|
9
|
+
* per-process random key (existing single-arg behavior is unchanged).
|
|
10
|
+
*/
|
|
11
|
+
export declare function encodeReplyTo(record: ReplyToken | unknown, secret?: string): string;
|
|
12
|
+
/**
|
|
13
|
+
* Decode an opaque token back into the routing record, VERIFYING the HMAC first.
|
|
14
|
+
* Returns `null` for any garbage (missing/invalid signature, bad base64, non-JSON,
|
|
15
|
+
* empty/nullish, or a tampered payload) — NEVER throws (AC-16).
|
|
16
|
+
*
|
|
17
|
+
* The optional trailing `secret` must match the one used to encode (DESIGN §7.4):
|
|
18
|
+
* a token minted under secret A does not verify under secret B (or the default),
|
|
19
|
+
* yielding `null`. Omitted ⇒ the per-process random key (unchanged behavior).
|
|
20
|
+
*/
|
|
21
|
+
export declare function decodeReplyTo(token: unknown, secret?: string): ReplyToken | null;
|
|
22
|
+
/** Arguments to `dispatchReply`. */
|
|
23
|
+
export interface DispatchArgs {
|
|
24
|
+
reply_to: string;
|
|
25
|
+
text: string;
|
|
26
|
+
resolveSource: (id: string) => Record<string, unknown> | undefined;
|
|
27
|
+
resolveBackend: (type: string, sourceId?: string) => Record<string, unknown> | undefined | Promise<Record<string, unknown> | undefined>;
|
|
28
|
+
http: (url: string | URL, opts?: Record<string, unknown>) => Promise<unknown>;
|
|
29
|
+
/** Shared secret to decode under (DESIGN §7.4). */
|
|
30
|
+
secret?: string;
|
|
31
|
+
}
|
|
32
|
+
/** A relay bound to an optional shared secret. */
|
|
33
|
+
export interface BoundRelay {
|
|
34
|
+
encodeReplyTo: (record: ReplyToken) => string;
|
|
35
|
+
decodeReplyTo: (token: unknown) => ReplyToken | null;
|
|
36
|
+
dispatchReply: (args: Omit<DispatchArgs, 'secret'>) => Promise<ReplyResult>;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Bind a shared `secret` to a relay instance (DESIGN §7.4). Returns
|
|
40
|
+
* `{ encodeReplyTo, decodeReplyTo, dispatchReply }` whose encode/decode use the
|
|
41
|
+
* derived key (and dispatchReply decodes under it), so the running instance signs
|
|
42
|
+
* AND verifies with the shared key. With no secret, this is exactly the
|
|
43
|
+
* per-process behavior of the module-level functions.
|
|
44
|
+
*/
|
|
45
|
+
export declare function createRelay(secret?: string): BoundRelay;
|
|
46
|
+
/**
|
|
47
|
+
* Decode a reply token, look up the owning source + backend, and dispatch the
|
|
48
|
+
* reply to that backend. The single source of truth for the reply path; the
|
|
49
|
+
* runtime's reply handler delegates here. Returns the backend's `{ ok, ref? }`;
|
|
50
|
+
* an unknown/garbled/forged token or a missing source/backend yields
|
|
51
|
+
* `{ ok: false }` WITHOUT throwing (AC-16).
|
|
52
|
+
*
|
|
53
|
+
* The token's `sourceId` is passed as the second argument to `resolveBackend` so
|
|
54
|
+
* the caller can route to the EXACT owning source's backend (rather than the
|
|
55
|
+
* first backend matching `type`), which keeps two custom backends that share a
|
|
56
|
+
* `type` from cross-dispatching.
|
|
57
|
+
*/
|
|
58
|
+
export declare function dispatchReply({ reply_to, text, resolveSource, resolveBackend, http, secret }: DispatchArgs): Promise<ReplyResult>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"relay.d.ts","sourceRoot":"","sources":["../src/relay.ts"],"names":[],"mappings":"AAqBA,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AA8C1D;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,UAAU,GAAG,OAAO,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAInF;AAED;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI,CA6BhF;AAED,oCAAoC;AACpC,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,aAAa,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAAC;IACnE,cAAc,EAAE,CACd,IAAI,EAAE,MAAM,EACZ,QAAQ,CAAC,EAAE,MAAM,KAEf,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GACvB,SAAS,GACT,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAAC,CAAC;IACjD,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IAC9E,mDAAmD;IACnD,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,kDAAkD;AAClD,MAAM,WAAW,UAAU;IACzB,aAAa,EAAE,CAAC,MAAM,EAAE,UAAU,KAAK,MAAM,CAAC;IAC9C,aAAa,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,UAAU,GAAG,IAAI,CAAC;IACrD,aAAa,EAAE,CAAC,IAAI,EAAE,IAAI,CAAC,YAAY,EAAE,QAAQ,CAAC,KAAK,OAAO,CAAC,WAAW,CAAC,CAAC;CAC7E;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,UAAU,CAMvD;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,aAAa,CAAC,EAClC,QAAQ,EACR,IAAI,EACJ,aAAa,EACb,cAAc,EACd,IAAI,EACJ,MAAM,EACP,EAAE,YAAY,GAAG,OAAO,CAAC,WAAW,CAAC,CA+BrC"}
|
package/dist/relay.js
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
// The opaque, TAMPER-EVIDENT `reply_to` routing token + reply dispatch
|
|
2
|
+
// (SPEC §4, DESIGN §4).
|
|
3
|
+
//
|
|
4
|
+
// A reply must reach the EXACT origin (this repo + issue number, or this Jira
|
|
5
|
+
// key). Claude forwards only one small attribute back to us and inbound channel
|
|
6
|
+
// text is an untrusted prompt-injection surface, so we must not let model-authored
|
|
7
|
+
// text dictate where a write goes. The framework mints a single opaque token per
|
|
8
|
+
// event (`meta.reply_to`); Claude treats it as a black box and echoes it back
|
|
9
|
+
// when calling the `reply` tool. Only the framework interprets it.
|
|
10
|
+
//
|
|
11
|
+
// The token is `<base64url(JSON)>.<base64url(HMAC-SHA256)>` — URL-safe, a valid
|
|
12
|
+
// meta identifier value (so it survives the meta-key sanitizer), and OPAQUE
|
|
13
|
+
// (Claude can't read or mutate the routing target). The HMAC is computed over the
|
|
14
|
+
// encoded payload with a PER-PROCESS random secret generated at startup, so a
|
|
15
|
+
// forged/tampered token (e.g. a flipped char, or a hand-rolled base64url(JSON))
|
|
16
|
+
// fails verification and is rejected. It is a *routing* token, not a bearer
|
|
17
|
+
// secret: it carries no credentials (auth lives only in the loaded config, keyed
|
|
18
|
+
// by source id) — the HMAC exists purely so the runtime never POSTs under real
|
|
19
|
+
// credentials to a routing target it didn't itself mint.
|
|
20
|
+
import { createHash, createHmac, randomBytes, timingSafeEqual } from 'node:crypto';
|
|
21
|
+
// Per-process signing secret, generated once at module load. It never leaves the
|
|
22
|
+
// process and is not derivable from a token, so tokens minted by this process
|
|
23
|
+
// cannot be forged by anyone who hasn't seen the secret. A fresh secret each run
|
|
24
|
+
// is fine for the single-process model: reply_to tokens are only meaningful
|
|
25
|
+
// within the lifetime of the running server (the in-memory source/backend tables
|
|
26
|
+
// they reference are process-local).
|
|
27
|
+
//
|
|
28
|
+
// SPEC §10 AC-21 / DESIGN §7.4: a SHARED secret makes a token portable across
|
|
29
|
+
// processes (a spawned child must verify a token its parent minted). When a
|
|
30
|
+
// non-empty secret is supplied, the HMAC key is DERIVED from it deterministically
|
|
31
|
+
// (sha256(secret) -> 32 bytes) so two processes with the same secret produce
|
|
32
|
+
// identical signatures. When no secret is supplied, this per-process random key
|
|
33
|
+
// is used and behavior is identical to before (backward compatible).
|
|
34
|
+
const REPLY_SECRET = randomBytes(32);
|
|
35
|
+
/** Cache of derived 32-byte keys per shared secret string (avoid re-hashing). */
|
|
36
|
+
const KEY_CACHE = new Map();
|
|
37
|
+
/**
|
|
38
|
+
* Resolve the HMAC key: the derived key from a non-empty `secret`, else the
|
|
39
|
+
* per-process random key. A derived key is `sha256(secret)` (32 bytes), cached.
|
|
40
|
+
*/
|
|
41
|
+
function keyFor(secret) {
|
|
42
|
+
if (typeof secret === 'string' && secret.length > 0) {
|
|
43
|
+
let key = KEY_CACHE.get(secret);
|
|
44
|
+
if (!key) {
|
|
45
|
+
key = createHash('sha256').update(secret, 'utf8').digest();
|
|
46
|
+
KEY_CACHE.set(secret, key);
|
|
47
|
+
}
|
|
48
|
+
return key;
|
|
49
|
+
}
|
|
50
|
+
return REPLY_SECRET;
|
|
51
|
+
}
|
|
52
|
+
/** base64url-encode a Buffer/string without padding ambiguity. */
|
|
53
|
+
function b64url(buf) {
|
|
54
|
+
return Buffer.from(buf).toString('base64url');
|
|
55
|
+
}
|
|
56
|
+
/** Compute the HMAC-SHA256 of `encodedPayload` (under `key`) as base64url. */
|
|
57
|
+
function sign(encodedPayload, key) {
|
|
58
|
+
return createHmac('sha256', key).update(encodedPayload).digest('base64url');
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Encode a routing record into an opaque, URL-safe, tamper-evident token of the
|
|
62
|
+
* form `<payload>.<sig>` where `payload = base64url(JSON(record))` and
|
|
63
|
+
* `sig = base64url(HMAC-SHA256(payload))`.
|
|
64
|
+
*
|
|
65
|
+
* The optional trailing `secret` (DESIGN §7.4) derives the signing key so a
|
|
66
|
+
* second process with the SAME secret can verify the token. Omitted ⇒ the
|
|
67
|
+
* per-process random key (existing single-arg behavior is unchanged).
|
|
68
|
+
*/
|
|
69
|
+
export function encodeReplyTo(record, secret) {
|
|
70
|
+
const json = JSON.stringify(record);
|
|
71
|
+
const payload = b64url(Buffer.from(json, 'utf8'));
|
|
72
|
+
return `${payload}.${sign(payload, keyFor(secret))}`;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Decode an opaque token back into the routing record, VERIFYING the HMAC first.
|
|
76
|
+
* Returns `null` for any garbage (missing/invalid signature, bad base64, non-JSON,
|
|
77
|
+
* empty/nullish, or a tampered payload) — NEVER throws (AC-16).
|
|
78
|
+
*
|
|
79
|
+
* The optional trailing `secret` must match the one used to encode (DESIGN §7.4):
|
|
80
|
+
* a token minted under secret A does not verify under secret B (or the default),
|
|
81
|
+
* yielding `null`. Omitted ⇒ the per-process random key (unchanged behavior).
|
|
82
|
+
*/
|
|
83
|
+
export function decodeReplyTo(token, secret) {
|
|
84
|
+
if (typeof token !== 'string' || token.length === 0)
|
|
85
|
+
return null;
|
|
86
|
+
const dot = token.lastIndexOf('.');
|
|
87
|
+
if (dot <= 0 || dot === token.length - 1)
|
|
88
|
+
return null;
|
|
89
|
+
const payload = token.slice(0, dot);
|
|
90
|
+
const provided = token.slice(dot + 1);
|
|
91
|
+
// Verify the signature in constant time. A flipped char in EITHER half makes
|
|
92
|
+
// the buffers differ (or differ in length) and the token is rejected.
|
|
93
|
+
let expectedBuf;
|
|
94
|
+
let providedBuf;
|
|
95
|
+
try {
|
|
96
|
+
expectedBuf = Buffer.from(sign(payload, keyFor(secret)), 'utf8');
|
|
97
|
+
providedBuf = Buffer.from(provided, 'utf8');
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
if (expectedBuf.length !== providedBuf.length)
|
|
103
|
+
return null;
|
|
104
|
+
if (!timingSafeEqual(expectedBuf, providedBuf))
|
|
105
|
+
return null;
|
|
106
|
+
try {
|
|
107
|
+
const json = Buffer.from(payload, 'base64url').toString('utf8');
|
|
108
|
+
const obj = JSON.parse(json);
|
|
109
|
+
if (obj == null || typeof obj !== 'object')
|
|
110
|
+
return null;
|
|
111
|
+
return obj;
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Bind a shared `secret` to a relay instance (DESIGN §7.4). Returns
|
|
119
|
+
* `{ encodeReplyTo, decodeReplyTo, dispatchReply }` whose encode/decode use the
|
|
120
|
+
* derived key (and dispatchReply decodes under it), so the running instance signs
|
|
121
|
+
* AND verifies with the shared key. With no secret, this is exactly the
|
|
122
|
+
* per-process behavior of the module-level functions.
|
|
123
|
+
*/
|
|
124
|
+
export function createRelay(secret) {
|
|
125
|
+
return {
|
|
126
|
+
encodeReplyTo: (record) => encodeReplyTo(record, secret),
|
|
127
|
+
decodeReplyTo: (token) => decodeReplyTo(token, secret),
|
|
128
|
+
dispatchReply: (args) => dispatchReply({ ...args, secret })
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Decode a reply token, look up the owning source + backend, and dispatch the
|
|
133
|
+
* reply to that backend. The single source of truth for the reply path; the
|
|
134
|
+
* runtime's reply handler delegates here. Returns the backend's `{ ok, ref? }`;
|
|
135
|
+
* an unknown/garbled/forged token or a missing source/backend yields
|
|
136
|
+
* `{ ok: false }` WITHOUT throwing (AC-16).
|
|
137
|
+
*
|
|
138
|
+
* The token's `sourceId` is passed as the second argument to `resolveBackend` so
|
|
139
|
+
* the caller can route to the EXACT owning source's backend (rather than the
|
|
140
|
+
* first backend matching `type`), which keeps two custom backends that share a
|
|
141
|
+
* `type` from cross-dispatching.
|
|
142
|
+
*/
|
|
143
|
+
export async function dispatchReply({ reply_to, text, resolveSource, resolveBackend, http, secret }) {
|
|
144
|
+
const decoded = decodeReplyTo(reply_to, secret);
|
|
145
|
+
if (!decoded || !decoded.backendType)
|
|
146
|
+
return { ok: false };
|
|
147
|
+
const source = resolveSource(decoded.sourceId);
|
|
148
|
+
if (!source)
|
|
149
|
+
return { ok: false };
|
|
150
|
+
let backend;
|
|
151
|
+
try {
|
|
152
|
+
backend = await resolveBackend(decoded.backendType, decoded.sourceId);
|
|
153
|
+
}
|
|
154
|
+
catch {
|
|
155
|
+
return { ok: false };
|
|
156
|
+
}
|
|
157
|
+
if (!backend || typeof backend.reply !== 'function')
|
|
158
|
+
return { ok: false };
|
|
159
|
+
try {
|
|
160
|
+
const result = await backend.reply({
|
|
161
|
+
routing: decoded.routing,
|
|
162
|
+
text,
|
|
163
|
+
source,
|
|
164
|
+
http
|
|
165
|
+
});
|
|
166
|
+
return result || { ok: false };
|
|
167
|
+
}
|
|
168
|
+
catch {
|
|
169
|
+
return { ok: false };
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
//# sourceMappingURL=relay.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"relay.js","sourceRoot":"","sources":["../src/relay.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { ChannelServer } from './server.js';
|
|
2
|
+
import { Poller } from './poller.js';
|
|
3
|
+
import { type AdaptersClientLike } from './spawner.js';
|
|
4
|
+
import type { ChannelsConfig } from './types.js';
|
|
5
|
+
import type { StateStoreLike } from './state.js';
|
|
6
|
+
import type { Transport } from '@modelcontextprotocol/sdk/shared/transport.js';
|
|
7
|
+
export interface RuntimeDeps {
|
|
8
|
+
http?: (url: string | URL, opts?: Record<string, unknown>) => Promise<unknown>;
|
|
9
|
+
stateStore?: StateStoreLike;
|
|
10
|
+
now?: () => Date;
|
|
11
|
+
transport?: Transport;
|
|
12
|
+
env?: Record<string, string>;
|
|
13
|
+
client?: AdaptersClientLike;
|
|
14
|
+
loadClient?: () => Promise<AdaptersClientLike> | AdaptersClientLike;
|
|
15
|
+
resolveCliPath?: () => string;
|
|
16
|
+
permissionHandler?: (req: unknown) => 'allow' | 'deny' | Promise<'allow' | 'deny'>;
|
|
17
|
+
log?: (...args: unknown[]) => void;
|
|
18
|
+
}
|
|
19
|
+
export interface Runtime {
|
|
20
|
+
server: ChannelServer;
|
|
21
|
+
poller: Poller;
|
|
22
|
+
config: ChannelsConfig;
|
|
23
|
+
errors: string[];
|
|
24
|
+
start(): Promise<void>;
|
|
25
|
+
stop(): Promise<void>;
|
|
26
|
+
}
|
|
27
|
+
export declare function createRuntime(configPathOrYaml: string, deps?: RuntimeDeps): Promise<Runtime>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"AAiBA,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAIrC,OAAO,EAAkB,KAAK,kBAAkB,EAAE,MAAM,cAAc,CAAC;AACvE,OAAO,KAAK,EAAW,cAAc,EAAE,MAAM,YAAY,CAAC;AAC1D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AACjD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,+CAA+C,CAAC;AAI/E,MAAM,WAAW,WAAW;IAC1B,IAAI,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IAC/E,UAAU,CAAC,EAAE,cAAc,CAAC;IAC5B,GAAG,CAAC,EAAE,MAAM,IAAI,CAAC;IACjB,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7B,MAAM,CAAC,EAAE,kBAAkB,CAAC;IAC5B,UAAU,CAAC,EAAE,MAAM,OAAO,CAAC,kBAAkB,CAAC,GAAG,kBAAkB,CAAC;IACpE,cAAc,CAAC,EAAE,MAAM,MAAM,CAAC;IAC9B,iBAAiB,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,OAAO,GAAG,MAAM,GAAG,OAAO,CAAC,OAAO,GAAG,MAAM,CAAC,CAAC;IACnF,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,CAAC;CACpC;AAED,MAAM,WAAW,OAAO;IACtB,MAAM,EAAE,aAAa,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,cAAc,CAAC;IACvB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACvB;AAcD,wBAAsB,aAAa,CACjC,gBAAgB,EAAE,MAAM,EACxB,IAAI,GAAE,WAAgB,GACrB,OAAO,CAAC,OAAO,CAAC,CA2JlB"}
|