@chinchillaenterprises/mcp-claude-channel 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -100,6 +100,49 @@ claude --dangerously-load-development-channels server:claude-channel
100
100
  }
101
101
  ```
102
102
 
103
+ ## v0.2 — inbound Slack → lane router (the `--ingest` bridge)
104
+
105
+ v0.2 adds a second run mode that brings messages **from Slack into the lanes**. It runs
106
+ as its own resident process (`node dist/index.js --ingest`), separate from the per-lane
107
+ stdio server above, and keeps three layers clean:
108
+
109
+ 1. **Transport / ingest (this package).** A pluggable **`InboundAdapter`** seam. Adapter
110
+ #1 is `SlackAdapter` — polls watched channels via the Slack Web API (reusing
111
+ `SLACK_BOT_TOKEN`, no public webhook), normalizes each new human message to a
112
+ transport-agnostic **envelope**, and stages it. A GitHub-issues webhook is the planned
113
+ adapter #2: same envelope, same staging log, same router — `envelope.source`
114
+ (`slack` | `github`) is the only distinguisher.
115
+ 2. **Router (Coordinator-owned policy).** Reads a hot-reloaded `routing-policy.yaml`
116
+ (`(channel, person) → suggested_domain + suggested_lane`) — **never hardcoded**.
117
+ `suggested_lane` is a free-form target id that may name a bare lane (`alpha`) or a
118
+ **formation seat** (`squad-alpha`). The router classifies and tags the hint only.
119
+ 3. **Delivery.** The **Coordinator (omega) is the sole receiver**: every inbound is
120
+ delivered to `chan-<COORDINATOR_LANE>-in.jsonl` carrying the routing hint plus the
121
+ **reply-back return address** (`reply_channel`, `requester`, `thread_ts`,
122
+ `envelope_id`) so a reply routes home even when source channel ≠ handling domain.
123
+
124
+ **Pipeline:** `adapter.collect()` → staging log `chan-inbound-raw.jsonl` (the one new
125
+ buffer, append-only JSONL, the proven side-door idiom) → router tails it from a journaled
126
+ offset (no-loss / no-double-process) → Coordinator inbox.
127
+
128
+ **Outbound mirror.** Lanes MAY reply to a Slack requester directly (low latency), but must
129
+ call the **`mirror_to_coordinator`** tool once per reply so the Coordinator keeps the full
130
+ conversation map. Lane owns the answer; Coordinator owns the routing picture.
131
+
132
+ ### Ingest configuration
133
+
134
+ | Env var | Default | Notes |
135
+ | --- | --- | --- |
136
+ | `ROUTING_POLICY_PATH` | _(required)_ | Coordinator-owned routing-policy YAML (hot-reloaded). |
137
+ | `SLACK_BOT_TOKEN` | _(required for `--ingest`)_ | Existing bot token; the Slack poll adapter. |
138
+ | `LANE_INBOX_DIR` | `/tmp` | Holds the staging log, cursor bookmark, and lane inboxes. |
139
+ | `COORDINATOR_LANE` | `omega` | Sole-receiver lane; also the `mirror_to_coordinator` target. |
140
+
141
+ ```bash
142
+ ROUTING_POLICY_PATH=/path/routing-policy.yaml SLACK_BOT_TOKEN=xoxb-… \
143
+ LANE_INBOX_DIR=/path/inboxes node dist/index.js --ingest
144
+ ```
145
+
103
146
  ## Build
104
147
 
105
148
  ```bash
@@ -112,6 +155,7 @@ Published via the repo's OIDC `publish.yml`. Bump the version in **both**
112
155
 
113
156
  ## Roadmap
114
157
 
115
- - **v1 (this):** lane-to-lane nudges, file-inbox transport, no Slack.
116
- - **v2 (deferred, not deleted):** the Slack-bridge half one app posting a remote
117
- readout when the human walks away. See ChillMCP issues #8 / #13.
158
+ - **v0.1:** lane-to-lane nudges, file-inbox transport, no Slack.
159
+ - **v0.2 (this):** inbound Slack lane router (`--ingest` bridge), pluggable
160
+ `InboundAdapter` seam, Coordinator-owned routing policy, `mirror_to_coordinator`.
161
+ - **next:** GitHub-issues webhook as adapter #2 (same envelope / staging log / router).
@@ -0,0 +1,21 @@
1
+ /**
2
+ * adapter.ts — the pluggable INBOUND adapter seam.
3
+ *
4
+ * An adapter's only job: produce normalized Envelopes from some source. The staging
5
+ * writer, the queue (chan-inbound-raw.jsonl), and the router know NOTHING about the
6
+ * source — they branch only on `envelope.source` if ever needed. Slack-poll is
7
+ * adapter #1; a GitHub-issues webhook is adapter #2 (same envelope, same staging log,
8
+ * same router). New sources = a new adapter, zero changes downstream.
9
+ *
10
+ * `collect()` is pull-shaped (poll adapters return their new batch each tick). A
11
+ * push adapter (webhook) can implement it as "drain whatever I've buffered since the
12
+ * last call", keeping the bridge's tick loop uniform across adapter kinds.
13
+ */
14
+ import type { Envelope } from "./envelope.js";
15
+ export interface InboundAdapter {
16
+ /** Source tag stamped on every envelope this adapter emits ("slack" | "github" | …). */
17
+ readonly source: string;
18
+ /** Return the batch of new normalized envelopes since the last call. */
19
+ collect(): Promise<Envelope[]>;
20
+ }
21
+ //# sourceMappingURL=adapter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../src/adapter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAE9C,MAAM,WAAW,cAAc;IAC7B,wFAAwF;IACxF,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,wEAAwE;IACxE,OAAO,IAAI,OAAO,CAAC,QAAQ,EAAE,CAAC,CAAC;CAChC"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * adapter.ts — the pluggable INBOUND adapter seam.
3
+ *
4
+ * An adapter's only job: produce normalized Envelopes from some source. The staging
5
+ * writer, the queue (chan-inbound-raw.jsonl), and the router know NOTHING about the
6
+ * source — they branch only on `envelope.source` if ever needed. Slack-poll is
7
+ * adapter #1; a GitHub-issues webhook is adapter #2 (same envelope, same staging log,
8
+ * same router). New sources = a new adapter, zero changes downstream.
9
+ *
10
+ * `collect()` is pull-shaped (poll adapters return their new batch each tick). A
11
+ * push adapter (webhook) can implement it as "drain whatever I've buffered since the
12
+ * last call", keeping the bridge's tick loop uniform across adapter kinds.
13
+ */
14
+ export {};
15
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYWRhcHRlci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3NyYy9hZGFwdGVyLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOzs7Ozs7Ozs7Ozs7R0FZRyJ9
@@ -0,0 +1,47 @@
1
+ /**
2
+ * envelope.ts — the normalized inbound-message envelope (transport-agnostic).
3
+ *
4
+ * Slack is adapter #1; Teams / in-app BeonIQ plug into the same shape later. The
5
+ * ingest leg produces everything down to `text/ts/thread_ts`; the ROUTER fills
6
+ * `suggested_domain` + `suggested_lane` from Coordinator-owned policy (never
7
+ * hardcoded). Delivery model: the Coordinator (omega) is the SOLE receiver — every
8
+ * inbound goes to its inbox carrying those two fields as HINTS; omega makes the
9
+ * actual lane dispatch.
10
+ *
11
+ * The `channel` + `requester` + `thread_ts` + `id` fields are the REPLY-BACK
12
+ * return address: a lane's answer routes home to the original asker even when the
13
+ * source channel ≠ the handling domain (the "Tuck missing a tool" Beon→echelon case).
14
+ */
15
+ export interface Party {
16
+ id: string;
17
+ name: string;
18
+ display: string;
19
+ }
20
+ export interface ChannelRef {
21
+ id: string;
22
+ name: string;
23
+ }
24
+ export type Domain = "beon" | "echelon" | "triage" | null;
25
+ export interface Envelope {
26
+ v: 1;
27
+ id: string;
28
+ source: string;
29
+ channel: ChannelRef;
30
+ requester: Party;
31
+ text: string;
32
+ ts: string;
33
+ thread_ts: string | null;
34
+ suggested_domain: Domain;
35
+ suggested_lane: string | null;
36
+ }
37
+ export declare function makeEnvelopeId(source: string, ts: string, channelId: string): string;
38
+ /** Build an ingest-stage envelope (domain/routed_lane left null for the router). */
39
+ export declare function newEnvelope(params: {
40
+ source: string;
41
+ channel: ChannelRef;
42
+ requester: Party;
43
+ text: string;
44
+ ts: string;
45
+ thread_ts?: string | null;
46
+ }): Envelope;
47
+ //# sourceMappingURL=envelope.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"envelope.d.ts","sourceRoot":"","sources":["../src/envelope.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,MAAM,WAAW,KAAK;IACpB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,MAAM,MAAM,GAAG,MAAM,GAAG,SAAS,GAAG,QAAQ,GAAG,IAAI,CAAC;AAE1D,MAAM,WAAW,QAAQ;IACvB,CAAC,EAAE,CAAC,CAAC;IACL,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,UAAU,CAAC;IACpB,SAAS,EAAE,KAAK,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,gBAAgB,EAAE,MAAM,CAAC;IAKzB,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;CAC/B;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAEpF;AAED,oFAAoF;AACpF,wBAAgB,WAAW,CAAC,MAAM,EAAE;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,UAAU,CAAC;IACpB,SAAS,EAAE,KAAK,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B,GAAG,QAAQ,CAaX"}
@@ -0,0 +1,33 @@
1
+ /**
2
+ * envelope.ts — the normalized inbound-message envelope (transport-agnostic).
3
+ *
4
+ * Slack is adapter #1; Teams / in-app BeonIQ plug into the same shape later. The
5
+ * ingest leg produces everything down to `text/ts/thread_ts`; the ROUTER fills
6
+ * `suggested_domain` + `suggested_lane` from Coordinator-owned policy (never
7
+ * hardcoded). Delivery model: the Coordinator (omega) is the SOLE receiver — every
8
+ * inbound goes to its inbox carrying those two fields as HINTS; omega makes the
9
+ * actual lane dispatch.
10
+ *
11
+ * The `channel` + `requester` + `thread_ts` + `id` fields are the REPLY-BACK
12
+ * return address: a lane's answer routes home to the original asker even when the
13
+ * source channel ≠ the handling domain (the "Tuck missing a tool" Beon→echelon case).
14
+ */
15
+ export function makeEnvelopeId(source, ts, channelId) {
16
+ return `in-${source}-${ts}-${channelId}`;
17
+ }
18
+ /** Build an ingest-stage envelope (domain/routed_lane left null for the router). */
19
+ export function newEnvelope(params) {
20
+ return {
21
+ v: 1,
22
+ id: makeEnvelopeId(params.source, params.ts, params.channel.id),
23
+ source: params.source,
24
+ channel: params.channel,
25
+ requester: params.requester,
26
+ text: params.text,
27
+ ts: params.ts,
28
+ thread_ts: params.thread_ts ?? null,
29
+ suggested_domain: null,
30
+ suggested_lane: null,
31
+ };
32
+ }
33
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZW52ZWxvcGUuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvZW52ZWxvcGUudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7Ozs7Ozs7Ozs7Ozs7R0FhRztBQWdDSCxNQUFNLFVBQVUsY0FBYyxDQUFDLE1BQWMsRUFBRSxFQUFVLEVBQUUsU0FBaUI7SUFDMUUsT0FBTyxNQUFNLE1BQU0sSUFBSSxFQUFFLElBQUksU0FBUyxFQUFFLENBQUM7QUFDM0MsQ0FBQztBQUVELG9GQUFvRjtBQUNwRixNQUFNLFVBQVUsV0FBVyxDQUFDLE1BTzNCO0lBQ0MsT0FBTztRQUNMLENBQUMsRUFBRSxDQUFDO1FBQ0osRUFBRSxFQUFFLGNBQWMsQ0FBQyxNQUFNLENBQUMsTUFBTSxFQUFFLE1BQU0sQ0FBQyxFQUFFLEVBQUUsTUFBTSxDQUFDLE9BQU8sQ0FBQyxFQUFFLENBQUM7UUFDL0QsTUFBTSxFQUFFLE1BQU0sQ0FBQyxNQUFNO1FBQ3JCLE9BQU8sRUFBRSxNQUFNLENBQUMsT0FBTztRQUN2QixTQUFTLEVBQUUsTUFBTSxDQUFDLFNBQVM7UUFDM0IsSUFBSSxFQUFFLE1BQU0sQ0FBQyxJQUFJO1FBQ2pCLEVBQUUsRUFBRSxNQUFNLENBQUMsRUFBRTtRQUNiLFNBQVMsRUFBRSxNQUFNLENBQUMsU0FBUyxJQUFJLElBQUk7UUFDbkMsZ0JBQWdCLEVBQUUsSUFBSTtRQUN0QixjQUFjLEVBQUUsSUFBSTtLQUNyQixDQUFDO0FBQ0osQ0FBQyJ9
@@ -0,0 +1,28 @@
1
+ /**
2
+ * fileio.ts — append-only JSONL helpers + a resumable line tailer.
3
+ *
4
+ * The whole bus is append-only JSONL (the proven side-door idiom). The router is
5
+ * "just another tailer" of chan-inbound-raw.jsonl, with an offset bookmark on disk
6
+ * so nothing is lost or double-processed across restarts.
7
+ */
8
+ /** Atomically append one JSON record as a line. */
9
+ export declare function appendLine(path: string, obj: unknown): void;
10
+ /** Read a tiny JSON bookmark file; return fallback if missing/corrupt. */
11
+ export declare function readJsonFile<T>(path: string, fallback: T): T;
12
+ /** Write a tiny JSON bookmark file (whole-file overwrite — these are small). */
13
+ export declare function writeJsonFile(path: string, obj: unknown): void;
14
+ /**
15
+ * Tail a JSONL file from a persisted byte offset, invoking `onLine` per new line.
16
+ * The offset is journaled to `<path>.offset` after each batch so a restart resumes
17
+ * exactly where it left off (no-loss, no double-process).
18
+ */
19
+ export declare class Tailer {
20
+ private path;
21
+ private offsetPath;
22
+ private offset;
23
+ private buffer;
24
+ constructor(path: string, offsetPath?: string);
25
+ /** Drain newly-appended whole lines. Returns the number of lines processed. */
26
+ drain(onLine: (line: string) => void): number;
27
+ }
28
+ //# sourceMappingURL=fileio.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fileio.d.ts","sourceRoot":"","sources":["../src/fileio.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,mDAAmD;AACnD,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,GAAG,IAAI,CAO3D;AAED,0EAA0E;AAC1E,wBAAgB,YAAY,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,GAAG,CAAC,CAM5D;AAED,gFAAgF;AAChF,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,GAAG,IAAI,CAO9D;AAED;;;;GAIG;AACH,qBAAa,MAAM;IACjB,OAAO,CAAC,IAAI,CAAS;IACrB,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,MAAM,CAAM;gBAER,IAAI,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM;IAa7C,+EAA+E;IAC/E,KAAK,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,GAAG,MAAM;CAqC9C"}
package/dist/fileio.js ADDED
@@ -0,0 +1,104 @@
1
+ /**
2
+ * fileio.ts — append-only JSONL helpers + a resumable line tailer.
3
+ *
4
+ * The whole bus is append-only JSONL (the proven side-door idiom). The router is
5
+ * "just another tailer" of chan-inbound-raw.jsonl, with an offset bookmark on disk
6
+ * so nothing is lost or double-processed across restarts.
7
+ */
8
+ import { existsSync, openSync, closeSync, statSync, readSync, writeSync, readFileSync } from "fs";
9
+ /** Atomically append one JSON record as a line. */
10
+ export function appendLine(path, obj) {
11
+ const fd = openSync(path, "a");
12
+ try {
13
+ writeSync(fd, JSON.stringify(obj) + "\n");
14
+ }
15
+ finally {
16
+ closeSync(fd);
17
+ }
18
+ }
19
+ /** Read a tiny JSON bookmark file; return fallback if missing/corrupt. */
20
+ export function readJsonFile(path, fallback) {
21
+ try {
22
+ return JSON.parse(readFileSync(path, "utf8"));
23
+ }
24
+ catch {
25
+ return fallback;
26
+ }
27
+ }
28
+ /** Write a tiny JSON bookmark file (whole-file overwrite — these are small). */
29
+ export function writeJsonFile(path, obj) {
30
+ const fd = openSync(path, "w");
31
+ try {
32
+ writeSync(fd, JSON.stringify(obj, null, 2));
33
+ }
34
+ finally {
35
+ closeSync(fd);
36
+ }
37
+ }
38
+ /**
39
+ * Tail a JSONL file from a persisted byte offset, invoking `onLine` per new line.
40
+ * The offset is journaled to `<path>.offset` after each batch so a restart resumes
41
+ * exactly where it left off (no-loss, no double-process).
42
+ */
43
+ export class Tailer {
44
+ path;
45
+ offsetPath;
46
+ offset;
47
+ buffer = "";
48
+ constructor(path, offsetPath) {
49
+ this.path = path;
50
+ this.offsetPath = offsetPath || `${path}.offset`;
51
+ if (!existsSync(path))
52
+ closeSync(openSync(path, "a"));
53
+ this.offset = readJsonFile(this.offsetPath, { offset: 0 }).offset;
54
+ // Guard against a bookmark pointing past a truncated/rotated file.
55
+ try {
56
+ if (statSync(path).size < this.offset)
57
+ this.offset = 0;
58
+ }
59
+ catch {
60
+ this.offset = 0;
61
+ }
62
+ }
63
+ /** Drain newly-appended whole lines. Returns the number of lines processed. */
64
+ drain(onLine) {
65
+ let size;
66
+ try {
67
+ size = statSync(this.path).size;
68
+ }
69
+ catch {
70
+ return 0;
71
+ }
72
+ if (size < this.offset) {
73
+ this.offset = 0;
74
+ this.buffer = "";
75
+ }
76
+ if (size === this.offset)
77
+ return 0;
78
+ const fd = openSync(this.path, "r");
79
+ try {
80
+ const len = size - this.offset;
81
+ const buf = Buffer.alloc(len);
82
+ const read = readSync(fd, buf, 0, len, this.offset);
83
+ this.offset += read;
84
+ this.buffer += buf.toString("utf8", 0, read);
85
+ }
86
+ finally {
87
+ closeSync(fd);
88
+ }
89
+ let count = 0;
90
+ let nl;
91
+ while ((nl = this.buffer.indexOf("\n")) !== -1) {
92
+ const line = this.buffer.slice(0, nl);
93
+ this.buffer = this.buffer.slice(nl + 1);
94
+ if (line.trim()) {
95
+ onLine(line);
96
+ count++;
97
+ }
98
+ }
99
+ if (count > 0)
100
+ writeJsonFile(this.offsetPath, { offset: this.offset });
101
+ return count;
102
+ }
103
+ }
104
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZmlsZWlvLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vc3JjL2ZpbGVpby50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTs7Ozs7O0dBTUc7QUFFSCxPQUFPLEVBQUUsVUFBVSxFQUFFLFFBQVEsRUFBRSxTQUFTLEVBQUUsUUFBUSxFQUFFLFFBQVEsRUFBRSxTQUFTLEVBQUUsWUFBWSxFQUFFLE1BQU0sSUFBSSxDQUFDO0FBRWxHLG1EQUFtRDtBQUNuRCxNQUFNLFVBQVUsVUFBVSxDQUFDLElBQVksRUFBRSxHQUFZO0lBQ25ELE1BQU0sRUFBRSxHQUFHLFFBQVEsQ0FBQyxJQUFJLEVBQUUsR0FBRyxDQUFDLENBQUM7SUFDL0IsSUFBSSxDQUFDO1FBQ0gsU0FBUyxDQUFDLEVBQUUsRUFBRSxJQUFJLENBQUMsU0FBUyxDQUFDLEdBQUcsQ0FBQyxHQUFHLElBQUksQ0FBQyxDQUFDO0lBQzVDLENBQUM7WUFBUyxDQUFDO1FBQ1QsU0FBUyxDQUFDLEVBQUUsQ0FBQyxDQUFDO0lBQ2hCLENBQUM7QUFDSCxDQUFDO0FBRUQsMEVBQTBFO0FBQzFFLE1BQU0sVUFBVSxZQUFZLENBQUksSUFBWSxFQUFFLFFBQVc7SUFDdkQsSUFBSSxDQUFDO1FBQ0gsT0FBTyxJQUFJLENBQUMsS0FBSyxDQUFDLFlBQVksQ0FBQyxJQUFJLEVBQUUsTUFBTSxDQUFDLENBQU0sQ0FBQztJQUNyRCxDQUFDO0lBQUMsTUFBTSxDQUFDO1FBQ1AsT0FBTyxRQUFRLENBQUM7SUFDbEIsQ0FBQztBQUNILENBQUM7QUFFRCxnRkFBZ0Y7QUFDaEYsTUFBTSxVQUFVLGFBQWEsQ0FBQyxJQUFZLEVBQUUsR0FBWTtJQUN0RCxNQUFNLEVBQUUsR0FBRyxRQUFRLENBQUMsSUFBSSxFQUFFLEdBQUcsQ0FBQyxDQUFDO0lBQy9CLElBQUksQ0FBQztRQUNILFNBQVMsQ0FBQyxFQUFFLEVBQUUsSUFBSSxDQUFDLFNBQVMsQ0FBQyxHQUFHLEVBQUUsSUFBSSxFQUFFLENBQUMsQ0FBQyxDQUFD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
package/dist/index.d.ts CHANGED
@@ -51,6 +51,7 @@ declare class ClaudeChannelServer {
51
51
  /** Tail our own inbox file; push each newly-appended line. */
52
52
  private startInboxTailer;
53
53
  private sendToLane;
54
+ private mirrorToCoordinator;
54
55
  private setupHandlers;
55
56
  run(): Promise<void>;
56
57
  }
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAqEH,cAAM,mBAAmB;IACvB,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,SAAS,CAAqC;IACtD,OAAO,CAAC,MAAM,CAAa;IAC3B,OAAO,CAAC,MAAM,CAAK;IACnB,OAAO,CAAC,MAAM,CAAM;IACpB,OAAO,CAAC,SAAS,CAA+C;;IAqBhE;;;;;;OAMG;YACW,aAAa;IAqB3B,6EAA6E;YAC/D,eAAe;IA8B7B,8DAA8D;IAC9D,OAAO,CAAC,gBAAgB;YAsDV,UAAU;IAwCxB,OAAO,CAAC,aAAa;IAgDf,GAAG;CAgBV;AAmBD,OAAO,EAAE,mBAAmB,EAAE,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AA6FH,cAAM,mBAAmB;IACvB,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,SAAS,CAAqC;IACtD,OAAO,CAAC,MAAM,CAAa;IAC3B,OAAO,CAAC,MAAM,CAAK;IACnB,OAAO,CAAC,MAAM,CAAM;IACpB,OAAO,CAAC,SAAS,CAA+C;;IAqBhE;;;;;;OAMG;YACW,aAAa;IAqB3B,6EAA6E;YAC/D,eAAe;IA8B7B,8DAA8D;IAC9D,OAAO,CAAC,gBAAgB;YAsDV,UAAU;YAwCV,mBAAmB;IAwCjC,OAAO,CAAC,aAAa;IAkEf,GAAG;CAgBV;AAkGD,OAAO,EAAE,mBAAmB,EAAE,CAAC"}
package/dist/index.js CHANGED
@@ -37,7 +37,13 @@ import { z } from "zod";
37
37
  import { fileURLToPath } from "url";
38
38
  import { realpathSync, existsSync, closeSync, openSync, statSync, readSync, writeSync } from "fs";
39
39
  import { join } from "path";
40
- const VERSION = "0.1.0";
40
+ import { PolicyStore } from "./policy.js";
41
+ import { SlackClient } from "./slack.js";
42
+ import { SlackAdapter } from "./slack-adapter.js";
43
+ import { Ingestor } from "./ingest.js";
44
+ import { Router } from "./router.js";
45
+ import { appendLine } from "./fileio.js";
46
+ const VERSION = "0.2.0";
41
47
  function inboxPathFor(dir, lane) {
42
48
  return join(dir, `chan-${lane}-in.jsonl`);
43
49
  }
@@ -50,7 +56,10 @@ function loadConfigFromEnv() {
50
56
  .split(/[,\s]+/)
51
57
  .map((s) => s.trim())
52
58
  .filter(Boolean));
53
- return { lane, inboxDir, inboxPath: inboxPathFor(inboxDir, lane), allowedSenders };
59
+ // The Coordinator lane that owns the routing picture. Lanes mirror every outbound
60
+ // Slack reply here so the Coordinator keeps the full conversation map.
61
+ const coordinatorLane = (process.env.COORDINATOR_LANE || "omega").trim();
62
+ return { lane, inboxDir, inboxPath: inboxPathFor(inboxDir, lane), allowedSenders, coordinatorLane };
54
63
  }
55
64
  // ---------------------------------------------------------------------------
56
65
  // Tool schema
@@ -66,6 +75,17 @@ const SendToLaneArgsSchema = z.object({
66
75
  .optional()
67
76
  .describe("Structured routing payload: round_id, stage, baton_path, to_lane, etc. from_lane is stamped automatically."),
68
77
  });
78
+ // Outbound mirror: when a lane replies to a Slack requester directly (low latency),
79
+ // it MUST mirror that reply to the Coordinator so the full conversation map stays
80
+ // complete. One call, can't be forgotten. Lane owns the answer; Coordinator owns the
81
+ // routing picture.
82
+ const MirrorToCoordinatorArgsSchema = z.object({
83
+ reply_text: z.string().min(1).describe("The reply this lane sent (or is about to send) to the Slack requester."),
84
+ reply_channel: z.string().min(1).describe("Slack channel id the reply went to (the requester's channel)."),
85
+ requester: z.string().optional().describe("Display name / handle of the original requester."),
86
+ envelope_id: z.string().optional().describe("The inbound envelope id this reply answers (for correlation)."),
87
+ meta: z.record(z.any()).optional().describe("Any extra context (thread_ts, suggested_lane, etc.)."),
88
+ });
69
89
  // ---------------------------------------------------------------------------
70
90
  // Server
71
91
  // ---------------------------------------------------------------------------
@@ -233,6 +253,40 @@ class ClaudeChannelServer {
233
253
  };
234
254
  }
235
255
  }
256
+ // --- outbound mirror: copy a lane's Slack reply to the Coordinator ----------
257
+ async mirrorToCoordinator(args) {
258
+ const v = MirrorToCoordinatorArgsSchema.parse(args);
259
+ const targetPath = inboxPathFor(this.config.inboxDir, this.config.coordinatorLane);
260
+ const meta = {
261
+ to_lane: this.config.coordinatorLane,
262
+ mirror: true,
263
+ kind: "outbound_reply",
264
+ reply_channel: v.reply_channel,
265
+ requester: v.requester,
266
+ envelope_id: v.envelope_id,
267
+ ...(v.meta || {}),
268
+ from_lane: this.config.lane,
269
+ };
270
+ const content = `↩️ ${this.config.lane} replied to ${v.requester || "requester"} in ${v.reply_channel}: ` +
271
+ `"${v.reply_text.replace(/\s+/g, " ").trim().slice(0, 160)}"`;
272
+ try {
273
+ appendLine(targetPath, { content, meta });
274
+ return {
275
+ content: [
276
+ {
277
+ type: "text",
278
+ text: JSON.stringify({ ok: true, mirrored_to: this.config.coordinatorLane, inbox: targetPath, envelope_id: v.envelope_id }, null, 2),
279
+ },
280
+ ],
281
+ };
282
+ }
283
+ catch (err) {
284
+ return {
285
+ content: [{ type: "text", text: `Error mirroring to coordinator '${this.config.coordinatorLane}': ${err?.message || err}` }],
286
+ isError: true,
287
+ };
288
+ }
289
+ }
236
290
  // --- handlers ---------------------------------------------------------------
237
291
  setupHandlers() {
238
292
  this.server.setRequestHandler(ListToolsRequestSchema, async () => {
@@ -259,6 +313,21 @@ class ClaudeChannelServer {
259
313
  required: ["target", "text"],
260
314
  },
261
315
  },
316
+ {
317
+ name: "mirror_to_coordinator",
318
+ description: "Mirror an outbound Slack reply to the Coordinator's inbox. Lanes MAY answer a Slack requester directly (low latency — matters for customers), but EVERY such reply MUST be mirrored here so the Coordinator keeps the full conversation map. Rule of thumb: the lane owns the answer, the Coordinator owns the routing picture. Call this once per outbound reply — it's the can't-forget-it copy.",
319
+ inputSchema: {
320
+ type: "object",
321
+ properties: {
322
+ reply_text: { type: "string", description: "The reply this lane sent to the Slack requester." },
323
+ reply_channel: { type: "string", description: "Slack channel id the reply went to." },
324
+ requester: { type: "string", description: "Display name / handle of the original requester." },
325
+ envelope_id: { type: "string", description: "Inbound envelope id this reply answers (for correlation)." },
326
+ meta: { type: "object", description: "Extra context (thread_ts, suggested_lane, etc.)." },
327
+ },
328
+ required: ["reply_text", "reply_channel"],
329
+ },
330
+ },
262
331
  ];
263
332
  return { tools };
264
333
  });
@@ -268,6 +337,8 @@ class ClaudeChannelServer {
268
337
  switch (name) {
269
338
  case "send_to_lane":
270
339
  return await this.sendToLane(args);
340
+ case "mirror_to_coordinator":
341
+ return await this.mirrorToCoordinator(args);
271
342
  default:
272
343
  throw new Error(`Unknown tool: ${name}`);
273
344
  }
@@ -294,6 +365,71 @@ class ClaudeChannelServer {
294
365
  process.once("SIGTERM", shutdown);
295
366
  }
296
367
  }
368
+ // ---------------------------------------------------------------------------
369
+ // Bridge mode (`--ingest`): poll Slack → staging log → router → Coordinator inbox.
370
+ //
371
+ // This is the inbound Slack→lane router. It runs as its OWN resident process
372
+ // (not the per-lane MCP server above). The two legs share one tick:
373
+ // 1. Ingestor.poll() — pull new messages from watched channels into the staging
374
+ // log (chan-inbound-raw.jsonl), bookmarked per-channel for no-loss/no-replay.
375
+ // 2. Router.drain() — classify each staged envelope against Coordinator-owned
376
+ // policy and deliver to the Coordinator inbox (omega is the sole receiver).
377
+ // Policy (watch list, suggested domain/lane, person overrides) is hot-reloaded.
378
+ // ---------------------------------------------------------------------------
379
+ async function runBridge() {
380
+ const inboxDir = process.env.LANE_INBOX_DIR || "/tmp";
381
+ const coordinatorLane = (process.env.COORDINATOR_LANE || "omega").trim();
382
+ const policyPath = process.env.ROUTING_POLICY_PATH;
383
+ const token = process.env.SLACK_BOT_TOKEN;
384
+ const rawLogPath = join(inboxDir, "chan-inbound-raw.jsonl");
385
+ const cursorsPath = join(inboxDir, "ingest-cursors.json");
386
+ if (!policyPath) {
387
+ console.error("[bridge] FATAL: ROUTING_POLICY_PATH is required (Coordinator-owned routing policy).");
388
+ process.exit(1);
389
+ }
390
+ if (!token) {
391
+ console.error("[bridge] FATAL: SLACK_BOT_TOKEN is required for the ingest poll leg.");
392
+ process.exit(1);
393
+ }
394
+ const policy = new PolicyStore(policyPath);
395
+ // Pluggable inbound adapters. Slack-poll is #1; a GitHub-issues webhook is the
396
+ // planned #2 — it would register here, emit the SAME envelope into the SAME
397
+ // staging log, and the writer/router never change.
398
+ const adapters = [
399
+ new SlackAdapter({ slack: new SlackClient(token), policy, cursorsPath }),
400
+ ];
401
+ const ingestor = new Ingestor({ adapters, rawLogPath });
402
+ const router = new Router({ policy, rawLogPath, inboxDir, coordinatorLane });
403
+ const intervalMs = Math.max(1, policy.current().pollIntervalSeconds) * 1000;
404
+ console.error(`[bridge] mcp-claude-channel v${VERSION} INGEST mode — poll ${policy.current().pollIntervalSeconds}s, ` +
405
+ `staging ${rawLogPath}, sole receiver '${coordinatorLane}'`);
406
+ let ticking = false;
407
+ const tick = async () => {
408
+ if (ticking)
409
+ return; // skip if a slow Slack pass overruns the interval
410
+ ticking = true;
411
+ try {
412
+ const staged = await ingestor.poll();
413
+ const delivered = router.drain();
414
+ if (staged || delivered)
415
+ console.error(`[bridge] tick: +${staged} staged, ${delivered} delivered`);
416
+ }
417
+ catch (err) {
418
+ console.error(`[bridge] tick error: ${err?.message || err}`);
419
+ }
420
+ finally {
421
+ ticking = false;
422
+ }
423
+ };
424
+ const timer = setInterval(tick, intervalMs);
425
+ void tick();
426
+ const shutdown = () => {
427
+ clearInterval(timer);
428
+ process.exit(0);
429
+ };
430
+ process.once("SIGINT", shutdown);
431
+ process.once("SIGTERM", shutdown);
432
+ }
297
433
  // Boot guard — only run when invoked directly (mirrors mcp-slack).
298
434
  const isMain = (() => {
299
435
  const entry = process.argv[1];
@@ -308,8 +444,16 @@ const isMain = (() => {
308
444
  }
309
445
  })();
310
446
  if (isMain) {
311
- const server = new ClaudeChannelServer();
312
- server.run().catch(console.error);
447
+ if (process.argv.includes("--ingest")) {
448
+ runBridge().catch((err) => {
449
+ console.error("[bridge] fatal:", err);
450
+ process.exit(1);
451
+ });
452
+ }
453
+ else {
454
+ const server = new ClaudeChannelServer();
455
+ server.run().catch(console.error);
456
+ }
313
457
  }
314
458
  export { ClaudeChannelServer };
315
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvaW5kZXgudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IjtBQUNBOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7R0E4Qkc7QUFFSCxPQUFPLEVBQUUsTUFBTSxFQUFFLE1BQU0sMkNBQTJDLENBQUM7QUFDbkUsT0FBTyxFQUFFLG9CQUFvQixFQUFFLE1BQU0sMkNBQTJDLENBQUM7QUFDakYsT0FBTyxFQUNMLHNCQUFzQixFQUN0QixxQkFBcUIsR0FFdEIsTUFBTSxvQ0FBb0MsQ0FBQztBQUM1QyxPQUFPLEVBQUUsQ0FBQyxFQUFFLE1BQU0sS0FBSyxDQUFDO0FBQ3hCLE9BQU8sRUFBRSxhQUFhLEVBQUUsTUFBTSxLQUFLLENBQUM7QUFDcEMsT0FBTyxFQUFFLFlBQVksRUFBRSxVQUFVLEVBQUUsU0FBUyxFQUFFLFFBQVEsRUFBRSxRQUFRLEVBQUUsUUFBUSxFQUFFLFNBQVMsRUFBRSxNQUFNLElBQUksQ0FBQztBQUNsRyxPQUFPLEVBQUUsSUFBSSxFQUFFLE1BQU0sTUFBTSxDQUFDO0FBRTVCLE1BQU0sT0FBTyxHQUFHLE9BQU8sQ0FBQztBQWF4QixTQUFTLFlBQVksQ0FBQyxHQUFXLEVBQUUsSUFBWTtJQUM3QyxPQUFPLElBQUksQ0FBQyxHQUFHLEVBQUUsUUFBUSxJQUFJLFdBQVcsQ0FBQyxDQUFDO0FBQzVDLENBQUM7QUFFRCxTQUFTLGlCQUFpQjtJQUN4QixNQUFNLElBQUksR0FBRyxDQUFDLE9BQU8sQ0FBQyxHQUFHLENBQUMsU0FBUyxJQUFJLFVBQVUsQ0FBQyxDQUFDLElBQUksRUFBRSxDQUFDO0lBQzFELE1BQU0sUUFBUSxHQUFHLE9BQU8sQ0FBQyxHQUFHLENBQUMsY0FBYyxJQUFJLE1BQU0sQ0FBQztJQUV0RCwyRUFBMkU7SUFDM0UsOEVBQThFO0lBQzlFLE1BQU0sY0FBYyxHQUFHLElBQUksR0FBRyxDQUM1QixDQUFDLE9BQU8sQ0FBQyxHQUFHLENBQUMsb0JB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
459
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvaW5kZXgudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IjtBQUNBOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7R0E4Qkc7QUFFSCxPQUFPLEVBQUUsTUFBTSxFQUFFLE1BQU0sMkNBQTJDLENBQUM7QUFDbkUsT0FBTyxFQUFFLG9CQUFvQixFQUFFLE1BQU0sMkNBQTJDLENBQUM7QUFDakYsT0FBTyxFQUNMLHNCQUFzQixFQUN0QixxQkFBcUIsR0FFdEIsTUFBTSxvQ0FBb0MsQ0FBQztBQUM1QyxPQUFPLEVBQUUsQ0FBQyxFQUFFLE1BQU0sS0FBSyxDQUFDO0FBQ3hCLE9BQU8sRUFBRSxhQUFhLEVBQUUsTUFBTSxLQUFLLENBQUM7QUFDcEMsT0FBTyxFQUFFLFlBQVksRUFBRSxVQUFVLEVBQUUsU0FBUyxFQUFFLFFBQVEsRUFBRSxRQUFRLEVBQUUsUUFBUSxFQUFFLFNBQVMsRUFBRSxNQUFNLElBQUksQ0FBQztBQUNsRyxPQUFPLEVBQUUsSUFBSSxFQUFFLE1BQU0sTUFBTSxDQUFDO0FBQzVCLE9BQU8sRUFBRSxXQUFXLEVBQUUsTUFBTSxhQUFhLENBQUM7QUFDMUMsT0FBTyxFQUFFLFdBQVcsRUFBRSxNQUFNLFlBQVksQ0FBQztBQUN6QyxPQUFPLEVBQUUsWUFBWSxFQUFFLE1BQU0sb0JBQW9CLENBQUM7QUFDbEQsT0FBTyxFQUFFLFFBQVEsRUFBRSxNQUFNLGFBQWEsQ0FBQztBQUN2QyxPQUFPLEVBQUUsTUFBTSxFQUFFLE1BQU0sYUFBYSxDQUFDO0FBQ3JDLE9BQU8sRUFBRSxVQUFVLEVBQUUsTUFBTSxhQUFhLENBQUM7QUFHekMsTUFBTSxPQUFPLEdBQUcsT0FBTyxDQUFDO0FBY3hCLFNBQVMsWUFBWSxDQUFDLEdBQVcsRUFBRSxJQUFZO0lBQzdDLE9BQU8sSUFBSSxDQUFDLEdBQUcsRUFBRSxRQUFRLElBQUksV0FBVyxDQUFDLENBQUM7QUFDNUMsQ0FBQztBQUVELFNBQVMsaUJBQWlCO0lBQ3hCLE1BQU0sSUFBSSxHQUFHLENBQUMsT0FBTyxDQUFDLEdBQUcsQ0FBQyxTQUFTLElBQUksVUFBVSxDQUFDLENBQUMsSUFBSSxFQUFFLENBQUM7SUFDMUQsTUFBTSxRQUFRLEdBQUcsT0FBTyxDQUFDLEdBQUcsQ0FBQyxjQUFjLElBQUksTUFBTSxDQUFDO0lBRXRELDJFQUEyRTtJQUMzRSw4RUFBOEU7SUFDOUUsTUFBTSxjQUFjLEdBQUcsSUFBSSxHQUFHLENBQzVCLENBQUMsT0FBTyxDQUFDLEdBQUcsQ0FBQyxvQkFBb0IsSUFBSSxFQUFFLENBQUM7U0FDckMsS0FBSyxDQUFDLFFBQVEsQ0FBQztTQUNmLEdBQUcsQ0FBQyxDQUFDLENBQUMsRUFBRSxFQUFFLENBQUMsQ0FBQyxDQUFDLElBQUksRUFBRSxDQUFDO1NBQ3BCLE1BQU0sQ0FBQyxPQUFPLENBQUMsQ0FDbkIsQ0FBQztJQUVGLGtGQUFrRjtJQUNsRix1RUFBdUU7SUFDdkUsTUFBTSxlQUFlLEdBQUcsQ0FBQyxPQUFPLENBQUMsR0FBRyxDQUFDLGdCQUFnQixJQUFJLE9BQU8sQ0FBQyxDQUFDLElBQUksRUFBRSxDQUFDO0lBRXpFLE9BQU8sRUFBRSxJQUFJLEVBQUUsUUFBUSxFQUFFLFNBQVMsRUFBRSxZQUFZLENBQUMsUUFBUSxFQUFFLElBQUksQ0FBQyxFQUFFLGNBQWMsRUFBRSxlQUFlLEVBQUUsQ0FBQztBQUN0RyxDQUFDO0FBRUQsOEVBQThFO0FBQzlFLGNBQWM7QUFDZCw4RUFBOEU7QUFFOUUsTUFBTSxvQkFBb0IsR0FBRyxDQUFDLENBQUMsTUFBTSxDQUFDO0lBQ3BDLE1BQU0sRUFBRSxDQUFDLENBQUMsTUFBTSxFQUFFLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQyxDQUFDLFFBQVEsQ0FBQyx5REFBeUQsQ0FBQztJQUM3RixJQUFJLEVBQUUsQ0FBQztTQUNKLE1BQU0sRUFBRTtTQUNSLEdBQUcsQ0FBQyxDQUFDLENBQUM7U0FDTixRQUFRLENBQ1AsaUhBQWlILENBQ2xIO0lBQ0gsSUFBSSxFQUFFLENBQUM7U0FDSixNQUFNLENBQUMsQ0FBQyxDQUFDLEdBQUcsRUFBRSxDQUFDO1NBQ2YsUUFBUSxFQUFFO1NBQ1YsUUFBUSxDQUFDLDRHQUE0RyxDQUFDO0NBQzFILENBQUMsQ0FBQztBQUVILG9GQUFvRjtBQUNwRixrRkFBa0Y7QUFDbEYscUZBQXFGO0FBQ3JGLG1CQUFtQjtBQUNuQixNQUFNLDZCQUE2QixHQUFHLENBQUMsQ0FBQyxNQUFNLENBQUM7SUFDN0MsVUFBVSxFQUFFLENBQUMsQ0FBQyxNQUFNLEVBQUUsQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFDLENBQUMsUUFBUSxDQUFDLHdFQUF3RSxDQUFDO0lBQ2hILGFBQWEsRUFBRSxDQUFDLENBQUMsTUFBTSxFQUFFLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQyxDQUFDLFFBQVEsQ0FBQywrREFBK0QsQ0FBQztJQUMxRyxTQUFTLEVBQUUsQ0FBQyxDQUFDLE1BQU0sRUFBRSxDQUFDLFFBQVEsRUFBRSxDQUFDLFFBQVEsQ0FBQyxrREFBa0QsQ0FBQztJQUM3RixXQUFXLEVBQUUsQ0FBQyxDQUFDLE1BQU0sRUFBRSxDQUFDLFFBQVEsRUFBRSxDQUFDLFFBQVEsQ0FBQywrREFBK0QsQ0FBQztJQUM1RyxJQUFJLEVBQUUsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxDQUFDLENBQUMsR0FBRyxFQUFFLENBQUMsQ0FBQyxRQUFRLEVBQUUsQ0FBQyxRQUFRLENBQUMsc0RBQXNELENBQUM7Q0FDcEcsQ0FBQyxDQUFDO0FBRUgsOEVBQThFO0FBQzlFLFNBQVM7QUFDVCw4RUFBOEU7QUFFOUUsTUFBTSxtQkFBbUI7SUFDZixNQUFNLENBQVM7SUFDZixTQUFTLEdBQWdDLElBQUksQ0FBQztJQUM5QyxNQUFNLENBQWE7SUFDbkIsTUFBTSxHQUFHLENBQUMsQ0FBQztJQUNYLE1BQU0sR0FBRyxFQUFFLENBQUM7SUFDWixTQUFTLEdBQTBDLElBQUksQ0FBQztJQUVoRTtRQUNFLElBQUksQ0FBQyxNQUFNLEdBQUcsSUFBSSxNQUFNLENBQ3RCLEVBQUUsSUFBSSxFQUFFLG9CQUFvQixFQUFFLE9BQU8sRUFBRSxPQUFPLEVBQUUsRUFDaEQ7WUFDRSxZQUFZLEVBQUU7Z0JBQ1oscUVBQXFFO2dCQUNyRSx1RUFBdUU7Z0JBQ3ZFLHNCQUFzQjtnQkFDdEIsWUFBWSxFQUFFLEVBQUUsZ0JBQWdCLEVBQUUsRUFBRSxFQUFFO2dCQUN0QyxLQUFLLEVBQUUsRUFBRTthQUNWO1NBQ0YsQ0FDRixDQUFDO1FBQ0YsSUFBSSxDQUFDLE1BQU0sR0FBRyxpQkFBaUIsRUFBRSxDQUFDO1FBQ2xDLElBQUksQ0FBQyxhQUFhLEVBQUUsQ0FBQztJQUN2QixDQUFDO0lBRUQsK0VBQStFO0lBRS9FOzs7Ozs7T0FNRztJQUNLLEtBQUssQ0FBQyxhQUFhLENBQUMsT0FBZSxFQUFFLElBQTZCO1FBQ3hFLE1BQU0sTUFBTSxHQUFHLEVBQUUsR0FBRyxJQUFJLEVBQUUsTUFBTSxFQUFFLElBQUksQ0FBQyxNQUFNLENBQUMsSUFBSSxFQUFFLENBQUM7UUFDckQsTUFBTSxNQUFNLEdBQUcsRUFBRSxPQUFPLEVBQUUsSUFBSSxFQUFFLE1BQU0sRUFBRSxDQUFDO1FBQ3pDLElBQUksQ0FBQztZQUNILE1BQU0sSUFBSSxDQUFDLE1BQU0sQ0FBQyxZQUFZLENBQUM7Z0JBQzdCLE1BQU0sRUFBRSw4QkFBOEI7Z0JBQ3RDLE1BQU07YUFDQSxDQUFDLENBQUM7UUFDWixDQUFDO1FBQUMsTUFBTSxDQUFDO1lBQ1AsSUFBSSxJQUFJLENBQUMsU0FBUyxFQUFFLENBQUM7Z0JBQ25CLE1BQU0sSUFBSSxDQUFDLFNBQVMsQ0FBQyxJQUFJLENBQUM7b0JBQ3hCLE9BQU8sRUFBRSxLQUFLO29CQUNkLE1BQU0sRUFBRSw4QkFBOEI7b0JBQ3RDLE1BQU07aUJBQ0EsQ0FBQyxDQUFDO1lBQ1osQ0FBQztpQkFBTSxDQUFDO2dCQUNOLE9BQU8sQ0FBQyxLQUFLLENBQUMsb0RBQW9ELENBQUMsQ0FBQztZQUN0RSxDQUFDO1FBQ0gsQ0FBQztJQUNILENBQUM7SUFFRCw2RUFBNkU7SUFDckUsS0FBSyxDQUFDLGVBQWUsQ0FBQyxJQUFZO1FBQ3hDLE1BQU0sT0FBTyxHQUFHLElBQUksQ0FBQyxJQUFJLEVBQUUsQ0FBQztRQUM1QixJQUFJLENBQUMsT0FBTztZQUFFLE9BQU87UUFFckIsSUFBSSxPQUFlLENBQUM7UUFDcEIsSUFBSSxJQUE2QixDQUFDO1FBQ2xDLElBQUksQ0FBQztZQUNILE1BQU0sR0FBRyxHQUFHLElBQUksQ0FBQyxLQUFLLENBQUMsT0FBTyxDQUFDLENBQUM7WUFDaEMsT0FBTyxHQUFHLE9BQU8sR0FBRyxDQUFDLE9BQU8sS0FBSyxRQUFRLENBQUMsQ0FBQyxDQUFDLEdBQUcsQ0FBQyxPQUFPLENBQUMsQ0FBQyxDQUFDLE9BQU8sQ0FBQztZQUNsRSxJQUFJLEdBQUcsR0FBRyxDQUFDLElBQUksSUFBSSxPQUFPLEdBQUcsQ0FBQyxJQUFJLEtBQUssUUFBUSxDQUFDLENBQUMsQ0FBQyxHQUFHLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQyxFQUFFLENBQUM7UUFDbEUsQ0FBQztRQUFDLE1BQU0sQ0FBQztZQUNQLHFFQUFxRTtZQUNyRSxPQUFPLEdBQUcsT0FBTyxDQUFDO1lBQ2xCLElBQUksR0FBRyxFQUFFLENBQUM7UUFDWixDQUFDO1FBRUQscUVBQXFFO1FBQ3JFLE1BQU0sUUFBUSxHQUFHLE9BQU8sSUFBSSxDQUFDLFNBQVMsS0FBSyxRQUFRLENBQUMsQ0FBQyxDQUFFLElBQUksQ0FBQyxTQUFvQixDQUFDLENBQUMsQ0FBQyxFQUFFLENBQUM7UUFDdEYsSUFBSSxJQUFJLENBQUMsTUFBTSxDQUFDLGNBQWMsQ0FBQyxJQUFJLEdBQUcsQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLE1BQU0sQ0FBQyxjQUFjLENBQUMsR0FBRyxDQUFDLFFBQVEsQ0FBQyxFQUFFLENBQUM7WUFDckYsT0FBTyxDQUFDLEtBQUssQ0FDWCx5REFBeUQsUUFBUSxJQUFJLFNBQVMsZUFBZTtnQkFDM0YsR0FBRyxJQUFJLENBQUMsTUFBTSxDQUFDLGNBQWM7YUFDOUIsQ0FBQyxJQUFJLENBQUMsR0FBRyxDQUFDLEdBQUcsQ0FDZixDQUFDO1lBQ0YsT0FBTztRQUNULENBQUM7UUFFRCxNQUFNLElBQUksQ0FBQyxhQUFhLENBQUMsT0FBTyxFQUFFLElBQUksQ0FBQyxDQUFDO0lBQzFDLENBQUM7SUFFRCw4REFBOEQ7SUFDdEQsZ0JBQWdCO1FBQ3RCLE1BQU0sSUFBSSxHQUFHLElBQUksQ0FBQyxNQUFNLENBQUMsU0FBUyxDQUFDO1FBQ25DLDJFQUEyRTtRQUMzRSxJQUFJLENBQUMsVUFBVSxDQUFDLElBQUksQ0FBQztZQUFFLFNBQVMsQ0FBQyxRQUFRLENBQUMsSUFBSSxFQUFFLEdBQUcsQ0FBQyxDQUFDLENBQUM7UUFDdEQsSUFBSSxDQUFDO1lBQ0gsSUFBSSxDQUFDLE1BQU0sR0FBRyxRQUFRLENBQUMsSUFBSSxDQUFDLENBQUMsSUFBSSxDQUFDO1FBQ3BDLENBQUM7UUFBQyxNQUFNLENBQUM7WUFDUCxJQUFJLENBQUMsTUFBTSxHQUFHLENBQUMsQ0FBQztRQUNsQixDQUFDO1FBRUQsTUFBTSxJQUFJLEdBQUcsR0FBRyxFQUFFO1lBQ2hCLElBQUksSUFBWSxDQUFDO1lBQ2pCLElBQUksQ0FBQztnQkFDSCxJQUFJLEdBQUcsUUFBUSxDQUFDLElBQUksQ0FBQyxDQUFDLElBQUksQ0FBQztZQUM3QixDQUFDO1lBQUMsTUFBTSxDQUFDO2dCQUNQLE9BQU8sQ0FBQyxnREFBZ0Q7WUFDMUQsQ0FBQztZQUNELElBQUksSUFBSSxHQUFHLElBQUksQ0FBQyxNQUFNLEVBQUUsQ0FBQztnQkFDdkIsK0NBQStDO2dCQUMvQyxJQUFJLENBQUMsTUFBTSxHQUFHLENBQUMsQ0FBQztnQkFDaEIsSUFBSSxDQUFDLE1BQU0sR0FBRyxFQUFFLENBQUM7WUFDbkIsQ0FBQztZQUNELElBQUksSUFBSSxLQUFLLElBQUksQ0FBQyxNQUFNO2dCQUFFLE9BQU87WUFFakMsTUFBTSxFQUFFLEdBQUcsUUFBUSxDQUFDLElBQUksRUFBRSxHQUFHLENBQUMsQ0FBQztZQUMvQixJQUFJLENBQUM7Z0JBQ0gsTUFBTSxHQUFHLEdBQUcsSUFBSSxHQUFHLElBQUksQ0FBQyxNQUFNLENBQUM7Z0JBQy9CLE1BQU0sR0FBRyxHQUFHLE1BQU0sQ0FBQyxLQUFLLENBQUMsR0FBRyxDQUFDLENBQUM7Z0JBQzlCLE1BQU0sSUFBSSxHQUFHLFFBQVEsQ0FBQyxFQUFFLEVBQUUsR0FBRyxFQUFFLENBQUMsRUFBRSxHQUFHLEVBQUUsSUFBSSxDQUFDLE1BQU0sQ0FBQyxDQUFDO2dCQUNwRCxJQUFJLENBQUMsTUFBTSxJQUFJLElBQUksQ0FBQztnQkFDcEIsSUFBSSxDQUFDLE1BQU0sSUFBSSxHQUFHLENBQUMsUUFBUSxDQUFDLE1BQU0sRUFBRSxDQUFDLEVBQUUsSUFBSSxDQUFDLENBQUM7WUFDL0MsQ0FBQztvQkFBUyxDQUFDO2dCQUNULFNBQVMsQ0FBQyxFQUFFLENBQUMsQ0FBQztZQUNoQixDQUFDO1lBRUQsSUFBSSxFQUFVLENBQUM7WUFDZixPQUFPLENBQUMsRUFBRSxHQUFHLElBQUksQ0FBQyxNQUFNLENBQUMsT0FBTyxDQUFDLElBQUksQ0FBQyxDQUFDLEtBQUssQ0FBQyxDQUFDLEVBQUUsQ0FBQztnQkFDL0MsTUFBTSxJQUFJLEdBQUcsSUFBSSxDQUFDLE1BQU0sQ0FBQyxLQUFLLENBQUMsQ0FBQyxFQUFFLEVBQUUsQ0FBQyxDQUFDO2dCQUN0QyxJQUFJLENBQUMsTUFBTSxHQUFHLElBQUksQ0FBQyxNQUFNLENBQUMsS0FBSyxDQUFDLEVBQUUsR0FBRyxDQUFDLENBQUMsQ0FBQztnQkFDeEMsS0FBSyxJQUFJLENBQUMsZUFBZSxDQUFDLElBQUksQ0FBQyxDQUFDO1lBQ2xDLENBQUM7UUFDSCxDQUFDLENBQUM7UUFFRixJQUFJLENBQUMsU0FBUyxHQUFHLFdBQVcsQ0FBQyxJQUFJLEVBQUUsR0FBRyxDQUFDLENBQUM7UUFDeEMsT0FBTyxDQUFDLEtBQUssQ0FDWCx5QkFBeUIsSUFBSSxDQUFDLE1BQU0sQ0FBQyxJQUFJLG1CQUFtQixJQUFJLEVBQUU7WUFDaEUsQ0FBQyxJQUFJLENBQUMsTUFBTSxDQUFDLGNBQWMsQ0FBQyxJQUFJLEdBQUcsQ0FBQztnQkFDbEMsQ0FBQyxDQUFDLHVCQUF1QixDQUFDLEdBQUcsSUFBSSxDQUFDLE1BQU0sQ0FBQyxjQUFjLENBQUMsQ0FBQyxJQUFJLENBQUMsR0FBRyxDQUFDLEVBQUU7Z0JBQ3BFLENBQUMsQ0FBQywrREFBK0QsQ0FBQyxDQUN2RSxDQUFDO0lBQ0osQ0FBQztJQUVELDhFQUE4RTtJQUV0RSxLQUFLLENBQUMsVUFBVSxDQUFDLElBQWE7UUFDcEMsTUFBTSxTQUFTLEdBQUcsb0JBQW9CLENBQUMsS0FBSyxDQUFDLElBQUksQ0FBQyxDQUFDO1FBQ25ELE1BQU0sVUFBVSxHQUFHLFlBQVksQ0FBQyxJQUFJLENBQUMsTUFBTSxDQUFDLFFBQVEsRUFBRSxTQUFTLENBQUMsTUFBTSxDQUFDLENBQUM7UUFDeEUsOEVBQThFO1FBQzlFLCtFQUErRTtRQUMvRSxNQUFNLElBQUksR0FBRztZQUNYLE9BQU8sRUFBRSxTQUFTLENBQUMsTUFBTTtZQUN6QixHQUFHLENBQUMsU0FBUyxDQUFDLElBQUksSUFBSSxFQUFFLENBQUM7WUFDekIsU0FBUyxFQUFFLElBQUksQ0FBQyxNQUFNLENBQUMsSUFBSTtTQUM1QixDQUFDO1FBQ0YsTUFBTSxNQUFNLEdBQUcsSUFBSSxDQUFDLFNBQVMsQ0FBQyxFQUFFLE9BQU8sRUFBRSxTQUFTLENBQUMsSUFBSSxFQUFFLElBQUksRUFBRSxDQUFDLENBQUM7UUFDakUsSUFBSSxDQUFDO1lBQ0gsTUFBTSxFQUFFLEdBQUcsUUFBUSxDQUFDLFVBQVUsRUFBRSxHQUFHLENBQUMsQ0FBQztZQUNyQyxJQUFJLENBQUM7Z0JBQ0gsU0FBUyxDQUFDLEVBQUUsRUFBRSxNQUFNLEdBQUcsSUFBSSxDQUFDLENBQUM7WUFDL0IsQ0FBQztvQkFBUyxDQUFDO2dCQUNULFNBQVMsQ0FBQyxFQUFFLENBQUMsQ0FBQztZQUNoQixDQUFDO1lBQ0QsT0FBTztnQkFDTCxPQUFPLEVBQUU7b0JBQ1A7d0JBQ0UsSUFBSSxFQUFFLE1BQU07d0JBQ1osSUFBSSxFQUFFLElBQUksQ0FBQyxTQUFTLENBQ2xCLEVBQUUsRUFBRSxFQUFFLElBQUksRUFBRSxZQUFZLEVBQUUsU0FBUyxDQUFDLE1BQU0sRUFBRSxLQUFLLEVBQUUsVUFBVSxFQUFFLFNBQVMsRUFBRSxJQUFJLENBQUMsTUFBTSxDQUFDLElBQUksRUFBRSxFQUM1RixJQUFJLEVBQ0osQ0FBQyxDQUNGO3FCQUNGO2lCQUNGO2FBQ0YsQ0FBQztRQUNKLENBQUM7UUFBQyxPQUFPLEdBQVEsRUFBRSxDQUFDO1lBQ2xCLE9BQU87Z0JBQ0wsT0FBTyxFQUFFLENBQUMsRUFBRSxJQUFJLEVBQUUsTUFBTSxFQUFFLElBQUksRUFBRSw2QkFBNkIsU0FBUyxDQUFDLE1BQU0sTUFBTSxHQUFHLEVBQUUsT0FBTyxJQUFJLEdBQUcsRUFBRSxFQUFFLENBQUM7Z0JBQzNHLE9BQU8sRUFBRSxJQUFJO2FBQ2QsQ0FBQztRQUNKLENBQUM7SUFDSCxDQUFDO0lBRUQsK0VBQStFO0lBRXZFLEtBQUssQ0FBQyxtQkFBbUIsQ0FBQyxJQUFhO1FBQzdDLE1BQU0sQ0FBQyxHQUFHLDZCQUE2QixDQUFDLEtBQUssQ0FBQyxJQUFJLENBQUMsQ0FBQztRQUNwRCxNQUFNLFVBQVUsR0FBRyxZQUFZLENBQUMsSUFBSSxDQUFDLE1BQU0sQ0FBQyxRQUFRLEVBQUUsSUFBSSxDQUFDLE1BQU0sQ0FBQyxlQUFlLENBQUMsQ0FBQztRQUNuRixNQUFNLElBQUksR0FBRztZQUNYLE9BQU8sRUFBRSxJQUFJLENBQUMsTUFBTSxDQUFDLGVBQWU7WUFDcEMsTUFBTSxFQUFFLElBQUk7WUFDWixJQUFJLEVBQUUsZ0JBQWdCO1lBQ3RCLGFBQWEsRUFBRSxDQUFDLENBQUMsYUFBYTtZQUM5QixTQUFTLEVBQUUsQ0FBQyxDQUFDLFNBQVM7WUFDdEIsV0FBVyxFQUFFLENBQUMsQ0FBQyxXQUFXO1lBQzFCLEdBQUcsQ0FBQyxDQUFDLENBQUMsSUFBSSxJQUFJLEVBQUUsQ0FBQztZQUNqQixTQUFTLEVBQUUsSUFBSSxDQUFDLE1BQU0sQ0FBQyxJQUFJO1NBQzVCLENBQUM7UUFDRixNQUFNLE9BQU8sR0FDWCxNQUFNLElBQUksQ0FBQyxNQUFNLENBQUMsSUFBSSxlQUFlLENBQUMsQ0FBQyxTQUFTLElBQUksV0FBVyxPQUFPLENBQUMsQ0FBQyxhQUFhLElBQUk7WUFDekYsSUFBSSxDQUFDLENBQUMsVUFBVSxDQUFDLE9BQU8sQ0FBQyxNQUFNLEVBQUUsR0FBRyxDQUFDLENBQUMsSUFBSSxFQUFFLENBQUMsS0FBSyxDQUFDLENBQUMsRUFBRSxHQUFHLENBQUMsR0FBRyxDQUFDO1FBQ2hFLElBQUksQ0FBQztZQUNILFVBQVUsQ0FBQyxVQUFVLEVBQUUsRUFBRSxPQUFPLEVBQUUsSUFBSSxFQUFFLENBQUMsQ0FBQztZQUMxQyxPQUFPO2dCQUNMLE9BQU8sRUFBRTtvQkFDUDt3QkFDRSxJQUFJLEVBQUUsTUFBTTt3QkFDWixJQUFJLEVBQUUsSUFBSSxDQUFDLFNBQVMsQ0FDbEIsRUFBRSxFQUFFLEVBQUUsSUFBSSxFQUFFLFdBQVcsRUFBRSxJQUFJLENBQUMsTUFBTSxDQUFDLGVBQWUsRUFBRSxLQUFLLEVBQUUsVUFBVSxFQUFFLFdBQVcsRUFBRSxDQUFDLENBQUMsV0FBVyxFQUFFLEVBQ3JHLElBQUksRUFDSixDQUFDLENBQ0Y7cUJBQ0Y7aUJBQ0Y7YUFDRixDQUFDO1FBQ0osQ0FBQztRQUFDLE9BQU8sR0FBUSxFQUFFLENBQUM7WUFDbEIsT0FBTztnQkFDTCxPQUFPLEVBQUUsQ0FBQyxFQUFFLElBQUksRUFBRSxNQUFNLEVBQUUsSUFBSSxFQUFFLG1DQUFtQyxJQUFJLENBQUMsTUFBTSxDQUFDLGVBQWUsTUFBTSxHQUFHLEVBQUUsT0FBTyxJQUFJLEdBQUcsRUFBRSxFQUFFLENBQUM7Z0JBQzVILE9BQU8sRUFBRSxJQUFJO2FBQ2QsQ0FBQztRQUNKLENBQUM7SUFDSCxDQUFDO0lBRUQsK0VBQStFO0lBRXZFLGFBQWE7UUFDbkIsSUFBSSxDQUFDLE1BQU0sQ0FBQyxpQkFBaUIsQ0FBQyxzQkFBc0IsRUFBRSxLQUFLLElBQUksRUFBRTtZQUMvRCxNQUFNLEtBQUssR0FBVztnQkFDcEI7b0JBQ0UsSUFBSSxFQUFFLGNBQWM7b0JBQ3BCLFdBQVcsRUFDVCxtYUFBbWE7b0JBQ3JhLFdBQVcsRUFBRTt3QkFDWCxJQUFJLEVBQUUsUUFBUTt3QkFDZCxVQUFVLEVBQUU7NEJBQ1YsTUFBTSxFQUFFO2dDQUNOLElBQUksRUFBRSxRQUFRO2dDQUNkLFdBQVcsRUFBRSx5REFBeUQ7NkJBQ3ZFOzRCQUNELElBQUksRUFBRTtnQ0FDSixJQUFJLEVBQUUsUUFBUTtnQ0FDZCxXQUFXLEVBQUUsMkVBQTJFOzZCQUN6Rjs0QkFDRCxJQUFJLEVBQUU7Z0NBQ0osSUFBSSxFQUFFLFFBQVE7Z0NBQ2QsV0FBVyxFQUFFLDZGQUE2Rjs2QkFDM0c7eUJBQ0Y7d0JBQ0QsUUFBUSxFQUFFLENBQUMsUUFBUSxFQUFFLE1BQU0sQ0FBQztxQkFDN0I7aUJBQ0Y7Z0JBQ0Q7b0JBQ0UsSUFBSSxFQUFFLHVCQUF1QjtvQkFDN0IsV0FBVyxFQUNULG9ZQUFvWTtvQkFDdFksV0FBVyxFQUFFO3dCQUNYLElBQUksRUFBRSxRQUFRO3dCQUNkLFVBQVUsRUFBRTs0QkFDVixVQUFVLEVBQUUsRUFBRSxJQUFJLEVBQUUsUUFBUSxFQUFFLFdBQVcsRUFBRSxrREFBa0QsRUFBRTs0QkFDL0YsYUFBYSxFQUFFLEVBQUUsSUFBSSxFQUFFLFFBQVEsRUFBRSxXQUFXLEVBQUUscUNBQXFDLEVBQUU7NEJBQ3JGLFNBQVMsRUFBRSxFQUFFLElBQUksRUFBRSxRQUFRLEVBQUUsV0FBVyxFQUFFLGtEQUFrRCxFQUFFOzRCQUM5RixXQUFXLEVBQUUsRUFBRSxJQUFJLEVBQUUsUUFBUSxFQUFFLFdBQVcsRUFBRSwyREFBMkQsRUFBRTs0QkFDekcsSUFBSSxFQUFFLEVBQUUsSUFBSSxFQUFFLFFBQVEsRUFBRSxXQUFXLEVBQUUsa0RBQWtELEVBQUU7eUJBQzFGO3dCQUNELFFBQVEsRUFBRSxDQUFDLFlBQVksRUFBRSxlQUFlLENBQUM7cUJBQzFDO2lCQUNGO2FBQ0YsQ0FBQztZQUNGLE9BQU8sRUFBRSxLQUFLLEVBQUUsQ0FBQztRQUNuQixDQUFDLENBQUMsQ0FBQztRQUVILElBQUksQ0FBQyxNQUFNLENBQUMsaUJBQWlCLENBQUMscUJBQXFCLEVBQUUsS0FBSyxFQUFFLE9BQU8sRUFBRSxFQUFFO1lBQ3JFLElBQUksQ0FBQztnQkFDSCxNQUFNLEVBQUUsSUFBSSxFQUFFLFNBQVMsRUFBRSxJQUFJLEVBQUUsR0FBRyxPQUFPLENBQUMsTUFBTSxDQUFDO2dCQUNqRCxRQUFRLElBQUksRUFBRSxDQUFDO29CQUNiLEtBQUssY0FBYzt3QkFDakIsT0FBTyxNQUFNLElBQUksQ0FBQyxVQUFVLENBQUMsSUFBSSxDQUFDLENBQUM7b0JBQ3JDLEtBQUssdUJBQXVCO3dCQUMxQixPQUFPLE1BQU0sSUFBSSxDQUFDLG1CQUFtQixDQUFDLElBQUksQ0FBQyxDQUFDO29CQUM5Qzt3QkFDRSxNQUFNLElBQUksS0FBSyxDQUFDLGlCQUFpQixJQUFJLEVBQUUsQ0FBQyxDQUFDO2dCQUM3QyxDQUFDO1lBQ0gsQ0FBQztZQUFDLE9BQU8sS0FBSyxFQUFFLENBQUM7Z0JBQ2YsTUFBTSxHQUFHLEdBQUcsS0FBSyxZQUFZLEtBQUssQ0FBQyxDQUFDLENBQUMsS0FBSyxDQUFDLE9BQU8sQ0FBQyxDQUFDLENBQUMsTUFBTSxDQUFDLEtBQUssQ0FBQyxDQUFDO2dCQUNuRSxPQUFPLEVBQUUsT0FBTyxFQUFFLENBQUMsRUFBRSxJQUFJLEVBQUUsTUFBTSxFQUFFLElBQUksRUFBRSxVQUFVLEdBQUcsRUFBRSxFQUFFLENBQUMsRUFBRSxPQUFPLEVBQUUsSUFBSSxFQUFFLENBQUM7WUFDL0UsQ0FBQztRQUNILENBQUMsQ0FBQyxDQUFDO0lBQ0wsQ0FBQztJQUVELCtFQUErRTtJQUUvRSxLQUFLLENBQUMsR0FBRztRQUNQLElBQUksQ0FBQyxTQUFTLEdBQUcsSUFBSSxvQkFBb0IsRUFBRSxDQUFDO1FBQzVDLE1BQU0sSUFBSSxDQUFDLE1BQU0sQ0FBQyxPQUFPLENBQUMsSUFBSSxDQUFDLFNBQVMsQ0FBQyxDQUFDO1FBQzFDLE9BQU8sQ0FBQyxLQUFLLENBQ1gsdUNBQXVDLE9BQU8sNkJBQTZCLElBQUksQ0FBQyxNQUFNLENBQUMsSUFBSSxHQUFHLENBQy9GLENBQUM7UUFDRiw2RUFBNkU7UUFDN0UsSUFBSSxDQUFDLGdCQUFnQixFQUFFLENBQUM7UUFFeEIsTUFBTSxRQUFRLEdBQUcsR0FBRyxFQUFFO1lBQ3BCLElBQUksSUFBSSxDQUFDLFNBQVM7Z0JBQUUsYUFBYSxDQUFDLElBQUksQ0FBQyxTQUFTLENBQUMsQ0FBQztZQUNsRCxPQUFPLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQyxDQUFDO1FBQ2xCLENBQUMsQ0FBQztRQUNGLE9BQU8sQ0FBQyxJQUFJLENBQUMsUUFBUSxFQUFFLFFBQVEsQ0FBQyxDQUFDO1FBQ2pDLE9BQU8sQ0FBQyxJQUFJLENBQUMsU0FBUyxFQUFFLFFBQVEsQ0FBQyxDQUFDO0lBQ3BDLENBQUM7Q0FDRjtBQUVELDhFQUE4RTtBQUM5RSxtRkFBbUY7QUFDbkYsRUFBRTtBQUNGLDZFQUE2RTtBQUM3RSxvRUFBb0U7QUFDcEUsbUZBQW1GO0FBQ25GLG1GQUFtRjtBQUNuRixrRkFBa0Y7QUFDbEYsaUZBQWlGO0FBQ2pGLGdGQUFnRjtBQUNoRiw4RUFBOEU7QUFFOUUsS0FBSyxVQUFVLFNBQVM7SUFDdEIsTUFBTSxRQUFRLEdBQUcsT0FBTyxDQUFDLEdBQUcsQ0FBQyxjQUFjLElBQUksTUFBTSxDQUFDO0lBQ3RELE1BQU0sZUFBZSxHQUFHLENBQUMsT0FBTyxDQUFDLEdBQUcsQ0FBQyxnQkFBZ0IsSUFBSSxPQUFPLENBQUMsQ0FBQyxJQUFJLEVBQUUsQ0FBQztJQUN6RSxNQUFNLFVBQVUsR0FBRyxPQUFPLENBQUMsR0FBRyxDQUFDLG1CQUFtQixDQUFDO0lBQ25ELE1BQU0sS0FBSyxHQUFHLE9BQU8sQ0FBQyxHQUFHLENBQUMsZUFBZSxDQUFDO0lBQzFDLE1BQU0sVUFBVSxHQUFHLElBQUksQ0FBQyxRQUFRLEVBQUUsd0JBQXdCLENBQUMsQ0FBQztJQUM1RCxNQUFNLFdBQVcsR0FBRyxJQUFJLENBQUMsUUFBUSxFQUFFLHFCQUFxQixDQUFDLENBQUM7SUFFMUQsSUFBSSxDQUFDLFVBQVUsRUFBRSxDQUFDO1FBQ2hCLE9BQU8sQ0FBQyxLQUFLLENBQUMscUZBQXFGLENBQUMsQ0FBQztRQUNyRyxPQUFPLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQyxDQUFDO0lBQ2xCLENBQUM7SUFDRCxJQUFJLENBQUMsS0FBSyxFQUFFLENBQUM7UUFDWCxPQUFPLENBQUMsS0FBSyxDQUFDLHNFQUFzRSxDQUFDLENBQUM7UUFDdEYsT0FBTyxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsQ0FBQztJQUNsQixDQUFDO0lBRUQsTUFBTSxNQUFNLEdBQUcsSUFBSSxXQUFXLENBQUMsVUFBVSxDQUFDLENBQUM7SUFFM0MsK0VBQStFO0lBQy9FLDRFQUE0RTtJQUM1RSxtREFBbUQ7SUFDbkQsTUFBTSxRQUFRLEdBQXFCO1FBQ2pDLElBQUksWUFBWSxDQUFDLEVBQUUsS0FBSyxFQUFFLElBQUksV0FBVyxDQUFDLEtBQUssQ0FBQyxFQUFFLE1BQU0sRUFBRSxXQUFXLEVBQUUsQ0FBQztLQUN6RSxDQUFDO0lBQ0YsTUFBTSxRQUFRLEdBQUcsSUFBSSxRQUFRLENBQUMsRUFBRSxRQUFRLEVBQUUsVUFBVSxFQUFFLENBQUMsQ0FBQztJQUN4RCxNQUFNLE1BQU0sR0FBRyxJQUFJLE1BQU0sQ0FBQyxFQUFFLE1BQU0sRUFBRSxVQUFVLEVBQUUsUUFBUSxFQUFFLGVBQWUsRUFBRSxDQUFDLENBQUM7SUFFN0UsTUFBTSxVQUFVLEdBQUcsSUFBSSxDQUFDLEdBQUcsQ0FBQyxDQUFDLEVBQUUsTUFBTSxDQUFDLE9BQU8sRUFBRSxDQUFDLG1CQUFtQixDQUFDLEdBQUcsSUFBSSxDQUFDO0lBQzVFLE9BQU8sQ0FBQyxLQUFLLENBQ1gsZ0NBQWdDLE9BQU8sdUJBQXVCLE1BQU0sQ0FBQyxPQUFPLEVBQUUsQ0FBQyxtQkFBbUIsS0FBSztRQUNyRyxXQUFXLFVBQVUsb0JBQW9CLGVBQWUsR0FBRyxDQUM5RCxDQUFDO0lBRUYsSUFBSSxPQUFPLEdBQUcsS0FBSyxDQUFDO0lBQ3BCLE1BQU0sSUFBSSxHQUFHLEtBQUssSUFBSSxFQUFFO1FBQ3RCLElBQUksT0FBTztZQUFFLE9BQU8sQ0FBQyxrREFBa0Q7UUFDdkUsT0FBTyxHQUFHLElBQUksQ0FBQztRQUNmLElBQUksQ0FBQztZQUNILE1BQU0sTUFBTSxHQUFHLE1BQU0sUUFBUSxDQUFDLElBQUksRUFBRSxDQUFDO1lBQ3JDLE1BQU0sU0FBUyxHQUFHLE1BQU0sQ0FBQyxLQUFLLEVBQUUsQ0FBQztZQUNqQyxJQUFJLE1BQU0sSUFBSSxTQUFTO2dCQUFFLE9BQU8sQ0FBQyxLQUFLLENBQUMsbUJBQW1CLE1BQU0sWUFBWSxTQUFTLFlBQVksQ0FBQyxDQUFDO1FBQ3JHLENBQUM7UUFBQyxPQUFPLEdBQVEsRUFBRSxDQUFDO1lBQ2xCLE9BQU8sQ0FBQyxLQUFLLENBQUMsd0JBQXdCLEdBQUcsRUFBRSxPQUFPLElBQUksR0FBRyxFQUFFLENBQUMsQ0FBQztRQUMvRCxDQUFDO2dCQUFTLENBQUM7WUFDVCxPQUFPLEdBQUcsS0FBSyxDQUFDO1FBQ2xCLENBQUM7SUFDSCxDQUFDLENBQUM7SUFFRixNQUFNLEtBQUssR0FBRyxXQUFXLENBQUMsSUFBSSxFQUFFLFVBQVUsQ0FBQyxDQUFDO0lBQzVDLEtBQUssSUFBSSxFQUFFLENBQUM7SUFFWixNQUFNLFFBQVEsR0FBRyxHQUFHLEVBQUU7UUFDcEIsYUFBYSxDQUFDLEtBQUssQ0FBQyxDQUFDO1FBQ3JCLE9BQU8sQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLENBQUM7SUFDbEIsQ0FBQyxDQUFDO0lBQ0YsT0FBTyxDQUFDLElBQUksQ0FBQyxRQUFRLEVBQUUsUUFBUSxDQUFDLENBQUM7SUFDakMsT0FBTyxDQUFDLElBQUksQ0FBQyxTQUFTLEVBQUUsUUFBUSxDQUFDLENBQUM7QUFDcEMsQ0FBQztBQUVELG1FQUFtRTtBQUNuRSxNQUFNLE1BQU0sR0FBRyxDQUFDLEdBQUcsRUFBRTtJQUNuQixNQUFNLEtBQUssR0FBRyxPQUFPLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQyxDQUFDO0lBQzlCLElBQUksQ0FBQyxLQUFLO1FBQUUsT0FBTyxLQUFLLENBQUM7SUFDekIsTUFBTSxJQUFJLEdBQUcsYUFBYSxDQUFDLE1BQU0sQ0FBQyxJQUFJLENBQUMsR0FBRyxDQUFDLENBQUM7SUFDNUMsSUFBSSxDQUFDO1FBQ0gsT0FBTyxZQUFZLENBQUMsS0FBSyxDQUFDLEtBQUssWUFBWSxDQUFDLElBQUksQ0FBQyxDQUFDO0lBQ3BELENBQUM7SUFBQyxNQUFNLENBQUM7UUFDUCxPQUFPLEtBQUssS0FBSyxJQUFJLENBQUM7SUFDeEIsQ0FBQztBQUNILENBQUMsQ0FBQyxFQUFFLENBQUM7QUFFTCxJQUFJLE1BQU0sRUFBRSxDQUFDO0lBQ1gsSUFBSSxPQUFPLENBQUMsSUFBSSxDQUFDLFFBQVEsQ0FBQyxVQUFVLENBQUMsRUFBRSxDQUFDO1FBQ3RDLFNBQVMsRUFBRSxDQUFDLEtBQUssQ0FBQyxDQUFDLEdBQUcsRUFBRSxFQUFFO1lBQ3hCLE9BQU8sQ0FBQyxLQUFLLENBQUMsaUJBQWlCLEVBQUUsR0FBRyxDQUFDLENBQUM7WUFDdEMsT0FBTyxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsQ0FBQztRQUNsQixDQUFDLENBQUMsQ0FBQztJQUNMLENBQUM7U0FBTSxDQUFDO1FBQ04sTUFBTSxNQUFNLEdBQUcsSUFBSSxtQkFBbUIsRUFBRSxDQUFDO1FBQ3pDLE1BQU0sQ0FBQyxHQUFHLEVBQUUsQ0FBQyxLQUFLLENBQUMsT0FBTyxDQUFDLEtBQUssQ0FBQyxDQUFDO0lBQ3BDLENBQUM7QUFDSCxDQUFDO0FBRUQsT0FBTyxFQUFFLG1CQUFtQixFQUFFLENBQUMifQ==
@@ -0,0 +1,21 @@
1
+ /**
2
+ * ingest.ts — the source-AGNOSTIC staging writer.
3
+ *
4
+ * Knows nothing about Slack, GitHub, or any source. It drives a set of pluggable
5
+ * InboundAdapters (each produces normalized Envelopes) and appends every envelope
6
+ * to the staging log `chan-inbound-raw.jsonl` — the single queue the router tails.
7
+ * Adding a source (GitHub webhook = adapter #2) means registering another adapter
8
+ * here; this writer, the staging log, and the router stay untouched.
9
+ */
10
+ import type { InboundAdapter } from "./adapter.js";
11
+ export declare class Ingestor {
12
+ private adapters;
13
+ private rawLogPath;
14
+ constructor(opts: {
15
+ adapters: InboundAdapter[];
16
+ rawLogPath: string;
17
+ });
18
+ /** Collect from every adapter and stage their envelopes. Returns count staged. */
19
+ poll(): Promise<number>;
20
+ }
21
+ //# sourceMappingURL=ingest.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ingest.d.ts","sourceRoot":"","sources":["../src/ingest.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAEnD,qBAAa,QAAQ;IACnB,OAAO,CAAC,QAAQ,CAAmB;IACnC,OAAO,CAAC,UAAU,CAAS;gBAEf,IAAI,EAAE;QAAE,QAAQ,EAAE,cAAc,EAAE,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE;IAKpE,kFAAkF;IAC5E,IAAI,IAAI,OAAO,CAAC,MAAM,CAAC;CAiB9B"}
package/dist/ingest.js ADDED
@@ -0,0 +1,38 @@
1
+ /**
2
+ * ingest.ts — the source-AGNOSTIC staging writer.
3
+ *
4
+ * Knows nothing about Slack, GitHub, or any source. It drives a set of pluggable
5
+ * InboundAdapters (each produces normalized Envelopes) and appends every envelope
6
+ * to the staging log `chan-inbound-raw.jsonl` — the single queue the router tails.
7
+ * Adding a source (GitHub webhook = adapter #2) means registering another adapter
8
+ * here; this writer, the staging log, and the router stay untouched.
9
+ */
10
+ import { appendLine } from "./fileio.js";
11
+ export class Ingestor {
12
+ adapters;
13
+ rawLogPath;
14
+ constructor(opts) {
15
+ this.adapters = opts.adapters;
16
+ this.rawLogPath = opts.rawLogPath;
17
+ }
18
+ /** Collect from every adapter and stage their envelopes. Returns count staged. */
19
+ async poll() {
20
+ let staged = 0;
21
+ for (const adapter of this.adapters) {
22
+ let envelopes;
23
+ try {
24
+ envelopes = await adapter.collect();
25
+ }
26
+ catch (err) {
27
+ console.error(`[ingest] adapter '${adapter.source}' collect failed: ${err?.message || err}`);
28
+ continue;
29
+ }
30
+ for (const env of envelopes) {
31
+ appendLine(this.rawLogPath, env);
32
+ staged++;
33
+ }
34
+ }
35
+ return staged;
36
+ }
37
+ }
38
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5nZXN0LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vc3JjL2luZ2VzdC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTs7Ozs7Ozs7R0FRRztBQUVILE9BQU8sRUFBRSxVQUFVLEVBQUUsTUFBTSxhQUFhLENBQUM7QUFHekMsTUFBTSxPQUFPLFFBQVE7SUFDWCxRQUFRLENBQW1CO0lBQzNCLFVBQVUsQ0FBUztJQUUzQixZQUFZLElBQXdEO1FBQ2xFLElBQUksQ0FBQyxRQUFRLEdBQUcsSUFBSSxDQUFDLFFBQVEsQ0FBQztRQUM5QixJQUFJLENBQUMsVUFBVSxHQUFHLElBQUksQ0FBQyxVQUFVLENBQUM7SUFDcEMsQ0FBQztJQUVELGtGQUFrRjtJQUNsRixLQUFLLENBQUMsSUFBSTtRQUNSLElBQUksTUFBTSxHQUFHLENBQUMsQ0FBQztRQUNmLEtBQUssTUFBTSxPQUFPLElBQUksSUFBSSxDQUFDLFFBQVEsRUFBRSxDQUFDO1lBQ3BDLElBQUksU0FBUyxDQUFDO1lBQ2QsSUFBSSxDQUFDO2dCQUNILFNBQVMsR0FBRyxNQUFNLE9BQU8sQ0FBQyxPQUFPLEVBQUUsQ0FBQztZQUN0QyxDQUFDO1lBQUMsT0FBTyxHQUFRLEVBQUUsQ0FBQztnQkFDbEIsT0FBTyxDQUFDLEtBQUssQ0FBQyxxQkFBcUIsT0FBTyxDQUFDLE1BQU0scUJBQXFCLEdBQUcsRUFBRSxPQUFPLElBQUksR0FBRyxFQUFFLENBQUMsQ0FBQztnQkFDN0YsU0FBUztZQUNYLENBQUM7WUFDRCxLQUFLLE1BQU0sR0FBRyxJQUFJLFNBQVMsRUFBRSxDQUFDO2dCQUM1QixVQUFVLENBQUMsSUFBSSxDQUFDLFVBQVUsRUFBRSxHQUFHLENBQUMsQ0FBQztnQkFDakMsTUFBTSxFQUFFLENBQUM7WUFDWCxDQUFDO1FBQ0gsQ0FBQztRQUNELE9BQU8sTUFBTSxDQUFDO0lBQ2hCLENBQUM7Q0FDRiJ9
@@ -0,0 +1,52 @@
1
+ /**
2
+ * policy.ts — reads the Coordinator-owned routing policy (YAML), hot-reloaded.
3
+ *
4
+ * BUILD RULE: this module READS policy; it never bakes channel IDs / lane names
5
+ * into capability code. The file is authored + owned by the Coordinator. The
6
+ * router calls `resolve()` per message; the loader re-reads the file when its
7
+ * mtime changes, so policy edits take effect without a restart.
8
+ */
9
+ import type { Domain, Envelope } from "./envelope.js";
10
+ export interface WatchEntry {
11
+ id: string;
12
+ name: string;
13
+ suggestedDomain: Domain;
14
+ suggestedLane: string;
15
+ }
16
+ export interface PersonOverride {
17
+ match_display?: string;
18
+ match_id?: string;
19
+ suggestedLane: string;
20
+ suggestedDomain?: Domain;
21
+ }
22
+ export interface Policy {
23
+ pollIntervalSeconds: number;
24
+ watch: WatchEntry[];
25
+ personOverrides: PersonOverride[];
26
+ }
27
+ export interface RouteHint {
28
+ suggestedDomain: Domain;
29
+ suggestedLane: string;
30
+ reason: string;
31
+ }
32
+ /**
33
+ * Hot-reloading policy holder. Re-reads the file only when its mtime changes, so
34
+ * `current()` is cheap to call on every poll tick and every routed message.
35
+ */
36
+ export declare class PolicyStore {
37
+ private path;
38
+ private mtimeMs;
39
+ private policy;
40
+ private byChannel;
41
+ constructor(path: string);
42
+ reloadIfChanged(): boolean;
43
+ current(): Policy;
44
+ watchList(): WatchEntry[];
45
+ /**
46
+ * Compute the routing HINT for (channel, person). Person override beats channel
47
+ * default. This is advisory only — every message is delivered to omega, which
48
+ * makes the real dispatch.
49
+ */
50
+ resolve(env: Envelope): RouteHint;
51
+ }
52
+ //# sourceMappingURL=policy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"policy.d.ts","sourceRoot":"","sources":["../src/policy.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH,OAAO,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAEtD,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,eAAe,EAAE,MAAM,CAAC;IACxB,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,cAAc;IAC7B,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;IACtB,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,MAAM;IACrB,mBAAmB,EAAE,MAAM,CAAC;IAC5B,KAAK,EAAE,UAAU,EAAE,CAAC;IACpB,eAAe,EAAE,cAAc,EAAE,CAAC;CACnC;AAID,MAAM,WAAW,SAAS;IACxB,eAAe,EAAE,MAAM,CAAC;IACxB,aAAa,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;CAChB;AAgCD;;;GAGG;AACH,qBAAa,WAAW;IACtB,OAAO,CAAC,IAAI,CAAS;IACrB,OAAO,CAAC,OAAO,CAAM;IACrB,OAAO,CAAC,MAAM,CAAyF;IACvG,OAAO,CAAC,SAAS,CAAiC;gBAEtC,IAAI,EAAE,MAAM;IAKxB,eAAe,IAAI,OAAO;IAwB1B,OAAO,IAAI,MAAM;IAKjB,SAAS,IAAI,UAAU,EAAE;IAIzB;;;;OAIG;IACH,OAAO,CAAC,GAAG,EAAE,QAAQ,GAAG,SAAS;CA4BlC"}
package/dist/policy.js ADDED
@@ -0,0 +1,115 @@
1
+ /**
2
+ * policy.ts — reads the Coordinator-owned routing policy (YAML), hot-reloaded.
3
+ *
4
+ * BUILD RULE: this module READS policy; it never bakes channel IDs / lane names
5
+ * into capability code. The file is authored + owned by the Coordinator. The
6
+ * router calls `resolve()` per message; the loader re-reads the file when its
7
+ * mtime changes, so policy edits take effect without a restart.
8
+ */
9
+ import { readFileSync, statSync } from "fs";
10
+ import YAML from "yaml";
11
+ const DEFAULT_POLL_SECONDS = 4;
12
+ // Unknown channels / unmatched messages land with the Coordinator, who can see
13
+ // everything and reroute. Safe default — never silently drop.
14
+ const FALLBACK = {
15
+ suggestedDomain: "triage",
16
+ suggestedLane: "omega",
17
+ };
18
+ function parse(raw) {
19
+ const doc = (YAML.parse(raw) || {});
20
+ const watch = Array.isArray(doc.watch)
21
+ ? doc.watch.map((w) => ({
22
+ id: String(w.id),
23
+ name: String(w.name ?? ""),
24
+ suggestedDomain: (w.suggested_domain ?? null),
25
+ suggestedLane: String(w.suggested_lane),
26
+ }))
27
+ : [];
28
+ const personOverrides = Array.isArray(doc.person_overrides)
29
+ ? doc.person_overrides.map((p) => ({
30
+ match_display: p.match_display ? String(p.match_display).toLowerCase() : undefined,
31
+ match_id: p.match_id ? String(p.match_id) : undefined,
32
+ suggestedLane: String(p.suggested_lane),
33
+ suggestedDomain: (p.suggested_domain ?? undefined),
34
+ }))
35
+ : [];
36
+ const pollIntervalSeconds = Number(doc?.poll?.interval_seconds) || DEFAULT_POLL_SECONDS;
37
+ return { pollIntervalSeconds, watch, personOverrides };
38
+ }
39
+ /**
40
+ * Hot-reloading policy holder. Re-reads the file only when its mtime changes, so
41
+ * `current()` is cheap to call on every poll tick and every routed message.
42
+ */
43
+ export class PolicyStore {
44
+ path;
45
+ mtimeMs = -1;
46
+ policy = { pollIntervalSeconds: DEFAULT_POLL_SECONDS, watch: [], personOverrides: [] };
47
+ byChannel = new Map();
48
+ constructor(path) {
49
+ this.path = path;
50
+ this.reloadIfChanged();
51
+ }
52
+ reloadIfChanged() {
53
+ let mtimeMs;
54
+ try {
55
+ mtimeMs = statSync(this.path).mtimeMs;
56
+ }
57
+ catch {
58
+ return false; // file missing — keep whatever we last had
59
+ }
60
+ if (mtimeMs === this.mtimeMs)
61
+ return false;
62
+ try {
63
+ const raw = readFileSync(this.path, "utf8");
64
+ this.policy = parse(raw);
65
+ this.byChannel = new Map(this.policy.watch.map((w) => [w.id, w]));
66
+ this.mtimeMs = mtimeMs;
67
+ console.error(`[policy] loaded ${this.policy.watch.length} watched channels, ` +
68
+ `${this.policy.personOverrides.length} person overrides from ${this.path}`);
69
+ return true;
70
+ }
71
+ catch (err) {
72
+ console.error(`[policy] reload failed (keeping previous): ${err?.message || err}`);
73
+ return false;
74
+ }
75
+ }
76
+ current() {
77
+ this.reloadIfChanged();
78
+ return this.policy;
79
+ }
80
+ watchList() {
81
+ return this.current().watch;
82
+ }
83
+ /**
84
+ * Compute the routing HINT for (channel, person). Person override beats channel
85
+ * default. This is advisory only — every message is delivered to omega, which
86
+ * makes the real dispatch.
87
+ */
88
+ resolve(env) {
89
+ this.reloadIfChanged();
90
+ const channelEntry = this.byChannel.get(env.channel.id);
91
+ const display = (env.requester.display || env.requester.name || "").toLowerCase();
92
+ for (const ov of this.policy.personOverrides) {
93
+ const matchByDisplay = ov.match_display && display === ov.match_display;
94
+ const matchById = ov.match_id && env.requester.id === ov.match_id;
95
+ if (matchByDisplay || matchById) {
96
+ // Override sets the suggested lane; domain stays the channel default unless
97
+ // the override pins one (source channel ≠ handling domain stays visible).
98
+ return {
99
+ suggestedLane: ov.suggestedLane,
100
+ suggestedDomain: ov.suggestedDomain ?? channelEntry?.suggestedDomain ?? FALLBACK.suggestedDomain,
101
+ reason: `person_override(${ov.match_display || ov.match_id})`,
102
+ };
103
+ }
104
+ }
105
+ if (channelEntry) {
106
+ return {
107
+ suggestedLane: channelEntry.suggestedLane,
108
+ suggestedDomain: channelEntry.suggestedDomain,
109
+ reason: `channel(${channelEntry.name})`,
110
+ };
111
+ }
112
+ return { ...FALLBACK, reason: "fallback(unknown-channel)" };
113
+ }
114
+ }
115
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicG9saWN5LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vc3JjL3BvbGljeS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTs7Ozs7OztHQU9HO0FBRUgsT0FBTyxFQUFFLFlBQVksRUFBRSxRQUFRLEVBQUUsTUFBTSxJQUFJLENBQUM7QUFDNUMsT0FBTyxJQUFJLE1BQU0sTUFBTSxDQUFDO0FBK0J4QixNQUFNLG9CQUFvQixHQUFHLENBQUMsQ0FBQztBQUMvQiwrRUFBK0U7QUFDL0UsOERBQThEO0FBQzlELE1BQU0sUUFBUSxHQUF1RDtJQUNuRSxlQUFlLEVBQUUsUUFBUTtJQUN6QixhQUFhLEVBQUUsT0FBTztDQUN2QixDQUFDO0FBRUYsU0FBUyxLQUFLLENBQUMsR0FBVztJQUN4QixNQUFNLEdBQUcsR0FBRyxDQUFDLElBQUksQ0FBQyxLQUFLLENBQUMsR0FBRyxDQUFDLElBQUksRUFBRSxDQUFRLENBQUM7SUFDM0MsTUFBTSxLQUFLLEdBQWlCLEtBQUssQ0FBQyxPQUFPLENBQUMsR0FBRyxDQUFDLEtBQUssQ0FBQztRQUNsRCxDQUFDLENBQUMsR0FBRyxDQUFDLEtBQUssQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFNLEVBQUUsRUFBRSxDQUFDLENBQUM7WUFDekIsRUFBRSxFQUFFLE1BQU0sQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDO1lBQ2hCLElBQUksRUFBRSxNQUFNLENBQUMsQ0FBQyxDQUFDLElBQUksSUFBSSxFQUFFLENBQUM7WUFDMUIsZUFBZSxFQUFFLENBQUMsQ0FBQyxDQUFDLGdCQUFnQixJQUFJLElBQUksQ0FBVztZQUN2RCxhQUFhLEVBQUUsTUFBTSxDQUFDLENBQUMsQ0FBQyxjQUFjLENBQUM7U0FDeEMsQ0FBQyxDQUFDO1FBQ0wsQ0FBQyxDQUFDLEVBQUUsQ0FBQztJQUNQLE1BQU0sZUFBZSxHQUFxQixLQUFLLENBQUMsT0FBTyxDQUFDLEdBQUcsQ0FBQyxnQkFBZ0IsQ0FBQztRQUMzRSxDQUFDLENBQUMsR0FBRyxD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
@@ -0,0 +1,34 @@
1
+ /**
2
+ * router.ts — the classification + delivery leg.
3
+ *
4
+ * "Just another tailer": reads new envelopes from the staging log
5
+ * `chan-inbound-raw.jsonl` (resuming from a persisted offset), stamps the routing
6
+ * HINT (suggested_domain / suggested_lane) from Coordinator-owned policy, and
7
+ * delivers EVERY message to the Coordinator's inbox (chan-omega-in.jsonl).
8
+ *
9
+ * Delivery model (Abel, 2026-06-16): the Coordinator (omega) is the SOLE receiver.
10
+ * The router never auto-delivers to beon/alpha — it suggests; omega dispatches.
11
+ * Lane-liveness alerting is the Coordinator's job, deliberately NOT built here.
12
+ *
13
+ * The inbox line stays a LEAN doorbell (nudge-not-payload): a short summary plus a
14
+ * meta block carrying the reply-back return address + hints + envelope_id. The full
15
+ * envelope persists in the staging log, keyed by id.
16
+ */
17
+ import type { PolicyStore } from "./policy.js";
18
+ export declare class Router {
19
+ private policy;
20
+ private rawLogPath;
21
+ private inboxDir;
22
+ private coordinatorLane;
23
+ private tailer;
24
+ constructor(opts: {
25
+ policy: PolicyStore;
26
+ rawLogPath: string;
27
+ inboxDir: string;
28
+ coordinatorLane: string;
29
+ });
30
+ private deliver;
31
+ /** Drain newly-staged envelopes, classify + deliver each to the Coordinator. */
32
+ drain(): number;
33
+ }
34
+ //# sourceMappingURL=router.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"router.d.ts","sourceRoot":"","sources":["../src/router.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAKH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAO/C,qBAAa,MAAM;IACjB,OAAO,CAAC,MAAM,CAAc;IAC5B,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,eAAe,CAAS;IAChC,OAAO,CAAC,MAAM,CAAS;gBAEX,IAAI,EAAE;QAAE,MAAM,EAAE,WAAW,CAAC;QAAC,UAAU,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,eAAe,EAAE,MAAM,CAAA;KAAE;IAQxG,OAAO,CAAC,OAAO;IAmCf,gFAAgF;IAChF,KAAK,IAAI,MAAM;CAgBhB"}
package/dist/router.js ADDED
@@ -0,0 +1,86 @@
1
+ /**
2
+ * router.ts — the classification + delivery leg.
3
+ *
4
+ * "Just another tailer": reads new envelopes from the staging log
5
+ * `chan-inbound-raw.jsonl` (resuming from a persisted offset), stamps the routing
6
+ * HINT (suggested_domain / suggested_lane) from Coordinator-owned policy, and
7
+ * delivers EVERY message to the Coordinator's inbox (chan-omega-in.jsonl).
8
+ *
9
+ * Delivery model (Abel, 2026-06-16): the Coordinator (omega) is the SOLE receiver.
10
+ * The router never auto-delivers to beon/alpha — it suggests; omega dispatches.
11
+ * Lane-liveness alerting is the Coordinator's job, deliberately NOT built here.
12
+ *
13
+ * The inbox line stays a LEAN doorbell (nudge-not-payload): a short summary plus a
14
+ * meta block carrying the reply-back return address + hints + envelope_id. The full
15
+ * envelope persists in the staging log, keyed by id.
16
+ */
17
+ import { join } from "path";
18
+ import { appendLine, Tailer } from "./fileio.js";
19
+ function truncate(s, n = 140) {
20
+ const oneLine = s.replace(/\s+/g, " ").trim();
21
+ return oneLine.length > n ? oneLine.slice(0, n - 1) + "…" : oneLine;
22
+ }
23
+ export class Router {
24
+ policy;
25
+ rawLogPath;
26
+ inboxDir;
27
+ coordinatorLane;
28
+ tailer;
29
+ constructor(opts) {
30
+ this.policy = opts.policy;
31
+ this.rawLogPath = opts.rawLogPath;
32
+ this.inboxDir = opts.inboxDir;
33
+ this.coordinatorLane = opts.coordinatorLane;
34
+ this.tailer = new Tailer(this.rawLogPath);
35
+ }
36
+ deliver(env) {
37
+ const hint = this.policy.resolve(env);
38
+ env.suggested_domain = hint.suggestedDomain;
39
+ env.suggested_lane = hint.suggestedLane;
40
+ const inboxPath = join(this.inboxDir, `chan-${this.coordinatorLane}-in.jsonl`);
41
+ const doorbell = `📨 Slack inbound from ${env.requester.display} in #${env.channel.name} — ` +
42
+ `"${truncate(env.text)}" [hint: ${hint.suggestedDomain}/${hint.suggestedLane}]`;
43
+ const line = {
44
+ content: doorbell,
45
+ meta: {
46
+ from_lane: "router",
47
+ source: env.source,
48
+ envelope_id: env.id,
49
+ // reply-back return address (source channel ≠ handling domain stays routable)
50
+ reply_channel: env.channel.id,
51
+ reply_channel_name: env.channel.name,
52
+ reply_thread_ts: env.thread_ts,
53
+ requester: env.requester.display,
54
+ requester_id: env.requester.id,
55
+ // routing HINTS — omega makes the actual dispatch
56
+ suggested_domain: hint.suggestedDomain,
57
+ suggested_lane: hint.suggestedLane,
58
+ route_reason: hint.reason,
59
+ raw_log: this.rawLogPath,
60
+ },
61
+ };
62
+ appendLine(inboxPath, line);
63
+ console.error(`[router] ${env.id} from ${env.requester.display}@#${env.channel.name} ` +
64
+ `→ ${this.coordinatorLane} inbox (hint ${hint.suggestedDomain}/${hint.suggestedLane}, ${hint.reason})`);
65
+ }
66
+ /** Drain newly-staged envelopes, classify + deliver each to the Coordinator. */
67
+ drain() {
68
+ return this.tailer.drain((line) => {
69
+ let env;
70
+ try {
71
+ env = JSON.parse(line);
72
+ }
73
+ catch {
74
+ console.error(`[router] skipping malformed staging line: ${truncate(line, 80)}`);
75
+ return;
76
+ }
77
+ try {
78
+ this.deliver(env);
79
+ }
80
+ catch (err) {
81
+ console.error(`[router] deliver failed for ${env?.id}: ${err?.message || err}`);
82
+ }
83
+ });
84
+ }
85
+ }
86
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicm91dGVyLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vc3JjL3JvdXRlci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTs7Ozs7Ozs7Ozs7Ozs7O0dBZUc7QUFFSCxPQUFPLEVBQUUsSUFBSSxFQUFFLE1BQU0sTUFBTSxDQUFDO0FBQzVCLE9BQU8sRUFBRSxVQUFVLEVBQUUsTUFBTSxFQUFFLE1BQU0sYUFBYSxDQUFDO0FBSWpELFNBQVMsUUFBUSxDQUFDLENBQVMsRUFBRSxDQUFDLEdBQUcsR0FBRztJQUNsQyxNQUFNLE9BQU8sR0FBRyxDQUFDLENBQUMsT0FBTyxDQUFDLE1BQU0sRUFBRSxHQUFHLENBQUMsQ0FBQyxJQUFJLEVBQUUsQ0FBQztJQUM5QyxPQUFPLE9BQU8sQ0FBQyxNQUFNLEdBQUcsQ0FBQyxDQUFDLENBQUMsQ0FBQyxPQUFPLENBQUMsS0FBSyxDQUFDLENBQUMsRUFBRSxDQUFDLEdBQUcsQ0FBQyxDQUFDLEdBQUcsR0FBRyxDQUFDLENBQUMsQ0FBQyxPQUFPLENBQUM7QUFDdEUsQ0FBQztBQUVELE1BQU0sT0FBTyxNQUFNO0lBQ1QsTUFBTSxDQUFjO0lBQ3BCLFVBQVUsQ0FBUztJQUNuQixRQUFRLENBQVM7SUFDakIsZUFBZSxDQUFTO0lBQ3hCLE1BQU0sQ0FBUztJQUV2QixZQUFZLElBQTRGO1FBQ3RHLElBQUksQ0FBQyxNQUFNLEdBQUcsSUFBSSxDQUFDLE1BQU0sQ0FBQztRQUMxQixJQUFJLENBQUMsVUFBVSxHQUFHLElBQUksQ0FBQyxVQUFVLENBQUM7UUFDbEMsSUFBSSxDQUFDLFFBQVEsR0FBRyxJQUFJLENBQUMsUUFBUSxDQUFDO1FBQzlCLElBQUksQ0FBQyxlQUFlLEdBQUcsSUFBSSxDQUFDLGVBQWUsQ0FBQztRQUM1QyxJQUFJLENBQUMsTUFBTSxHQUFHLElBQUksTUFBTSxDQUFDLElBQUksQ0FBQyxVQUFVLENBQUMsQ0FBQztJQUM1QyxDQUFDO0lBRU8sT0FBTyxDQUFDLEdBQWE7UUFDM0IsTUFBTSxJ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
@@ -0,0 +1,29 @@
1
+ /**
2
+ * slack-adapter.ts — INBOUND adapter #1: Slack poll.
3
+ *
4
+ * Implements InboundAdapter by polling each watched channel (Coordinator-owned
5
+ * policy) via the Slack Web API with the existing bot token — NO public webhook.
6
+ * Per-channel cursor bookmarks make the poll no-loss / no-replay across restarts.
7
+ * Cursors are a Slack-specific concern and live HERE, not in the source-agnostic
8
+ * staging writer. Webhook adapters won't need them.
9
+ */
10
+ import type { Envelope } from "./envelope.js";
11
+ import type { InboundAdapter } from "./adapter.js";
12
+ import type { SlackClient } from "./slack.js";
13
+ import type { PolicyStore } from "./policy.js";
14
+ export declare class SlackAdapter implements InboundAdapter {
15
+ readonly source = "slack";
16
+ private slack;
17
+ private policy;
18
+ private cursorsPath;
19
+ private cursors;
20
+ private nowSeconds;
21
+ constructor(opts: {
22
+ slack: SlackClient;
23
+ policy: PolicyStore;
24
+ cursorsPath: string;
25
+ nowSeconds?: () => number;
26
+ });
27
+ collect(): Promise<Envelope[]>;
28
+ }
29
+ //# sourceMappingURL=slack-adapter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"slack-adapter.d.ts","sourceRoot":"","sources":["../src/slack-adapter.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAIH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAC9C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AACnD,OAAO,KAAK,EAAE,WAAW,EAAgB,MAAM,YAAY,CAAC;AAC5D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAa/C,qBAAa,YAAa,YAAW,cAAc;IACjD,QAAQ,CAAC,MAAM,WAAW;IAC1B,OAAO,CAAC,KAAK,CAAc;IAC3B,OAAO,CAAC,MAAM,CAAc;IAC5B,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,OAAO,CAAU;IACzB,OAAO,CAAC,UAAU,CAAe;gBAErB,IAAI,EAAE;QAChB,KAAK,EAAE,WAAW,CAAC;QACnB,MAAM,EAAE,WAAW,CAAC;QACpB,WAAW,EAAE,MAAM,CAAC;QACpB,UAAU,CAAC,EAAE,MAAM,MAAM,CAAC;KAC3B;IAQK,OAAO,IAAI,OAAO,CAAC,QAAQ,EAAE,CAAC;CAqCrC"}
@@ -0,0 +1,78 @@
1
+ /**
2
+ * slack-adapter.ts — INBOUND adapter #1: Slack poll.
3
+ *
4
+ * Implements InboundAdapter by polling each watched channel (Coordinator-owned
5
+ * policy) via the Slack Web API with the existing bot token — NO public webhook.
6
+ * Per-channel cursor bookmarks make the poll no-loss / no-replay across restarts.
7
+ * Cursors are a Slack-specific concern and live HERE, not in the source-agnostic
8
+ * staging writer. Webhook adapters won't need them.
9
+ */
10
+ import { readJsonFile, writeJsonFile } from "./fileio.js";
11
+ import { newEnvelope } from "./envelope.js";
12
+ /** Messages we never route: bot posts, joins/leaves, edits, and other subtypes. */
13
+ function isHumanMessage(m) {
14
+ if (m.bot_id)
15
+ return false;
16
+ if (!m.user)
17
+ return false;
18
+ if (m.subtype)
19
+ return false; // channel_join, message_changed, etc.
20
+ if (!m.text || !m.text.trim())
21
+ return false;
22
+ return true;
23
+ }
24
+ export class SlackAdapter {
25
+ source = "slack";
26
+ slack;
27
+ policy;
28
+ cursorsPath;
29
+ cursors;
30
+ nowSeconds;
31
+ constructor(opts) {
32
+ this.slack = opts.slack;
33
+ this.policy = opts.policy;
34
+ this.cursorsPath = opts.cursorsPath;
35
+ this.cursors = readJsonFile(this.cursorsPath, {});
36
+ this.nowSeconds = opts.nowSeconds ?? (() => Date.now() / 1000);
37
+ }
38
+ async collect() {
39
+ const watch = this.policy.watchList();
40
+ const out = [];
41
+ for (const ch of watch) {
42
+ // Cold start: bookmark "now" so we don't replay channel history on first boot.
43
+ if (!this.cursors[ch.id]) {
44
+ this.cursors[ch.id] = String(this.nowSeconds());
45
+ continue;
46
+ }
47
+ let msgs;
48
+ try {
49
+ msgs = await this.slack.fetchSince(ch.id, this.cursors[ch.id]);
50
+ }
51
+ catch (err) {
52
+ console.error(`[slack-adapter] ${ch.name} (${ch.id}) fetch failed: ${err?.message || err}`);
53
+ continue;
54
+ }
55
+ let maxTs = this.cursors[ch.id];
56
+ for (const m of msgs) {
57
+ if (Number(m.ts) > Number(maxTs))
58
+ maxTs = m.ts;
59
+ if (!isHumanMessage(m))
60
+ continue;
61
+ const profile = await this.slack.userProfile(m.user);
62
+ out.push(newEnvelope({
63
+ source: this.source,
64
+ channel: { id: ch.id, name: ch.name },
65
+ requester: profile,
66
+ text: m.text || "",
67
+ ts: m.ts,
68
+ thread_ts: m.thread_ts ?? null,
69
+ }));
70
+ }
71
+ this.cursors[ch.id] = maxTs;
72
+ }
73
+ if (watch.length > 0)
74
+ writeJsonFile(this.cursorsPath, this.cursors);
75
+ return out;
76
+ }
77
+ }
78
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic2xhY2stYWRhcHRlci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3NyYy9zbGFjay1hZGFwdGVyLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOzs7Ozs7OztHQVFHO0FBRUgsT0FBTyxFQUFFLFlBQVksRUFBRSxhQUFhLEVBQUUsTUFBTSxhQUFhLENBQUM7QUFDMUQsT0FBTyxFQUFFLFdBQVcsRUFBRSxNQUFNLGVBQWUsQ0FBQztBQVE1QyxtRkFBbUY7QUFDbkYsU0FBUyxjQUFjLENBQUMsQ0FBZTtJQUNyQyxJQUFJLENBQUMsQ0FBQyxNQUFNO1FBQUUsT0FBTyxLQUFLLENBQUM7SUFDM0IsSUFBSSxDQUFDLENBQUMsQ0FBQyxJQUFJO1FBQUUsT0FBTyxLQUFLLENBQUM7SUFDMUIsSUFBSSxDQUFDLENBQUMsT0FBTztRQUFFLE9BQU8sS0FBSyxDQUFDLENBQUMsc0NBQXNDO0lBQ25FLElBQUksQ0FBQyxDQUFDLENBQUMsSUFBSSxJQUFJLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQyxJQUFJLEVBQUU7UUFBRSxPQUFPLEtBQUssQ0FBQztJQUM1QyxPQUFPLElBQUksQ0FBQztBQUNkLENBQUM7QUFFRCxNQUFNLE9BQU8sWUFBWTtJQUNkLE1BQU0sR0FBRyxPQUFPLENBQUM7SUFDbEIsS0FBSyxDQUFjO0lBQ25CLE1BQU0sQ0FBYztJQUNwQixXQUFXLENBQVM7SUFDcEIsT0FBTyxDQUFVO0lBQ2pCLFVBQVUsQ0FBZTtJQUVqQyxZQUFZLElBS1g7UUFDQyxJQUFJLENBQUMsS0FBSyxHQUFHLElBQUksQ0FBQyxLQUFLLENBQUM7UUFDeEIsSUFBSSxDQUFDLE1BQU0sR0FBRyxJQUFJLENBQUMsTUFBTSxDQUFDO1FBQzFCLElBQUksQ0FBQyxXQUFXLEdBQUcsSUFBSSxDQUFDLFdBQVcsQ0FBQztRQUNwQyxJQUFJLENBQUMsT0FBTyxHQUFHLFlBQVksQ0FBVSxJQUFJLENBQUMsV0FBVyxFQUFFLEVBQUUsQ0FBQyxDQUFDO1FBQzNELElBQUks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
@@ -0,0 +1,38 @@
1
+ /**
2
+ * slack.ts — minimal Slack Web API client for the INGEST leg.
3
+ *
4
+ * Reuses the existing Slack bot token (SLACK_BOT_TOKEN), the same credential
5
+ * mcp-slack already loads — no new auth, no public webhook endpoint. Just enough
6
+ * surface to poll new messages and resolve display names for person-overrides.
7
+ */
8
+ export interface SlackMessage {
9
+ type?: string;
10
+ subtype?: string;
11
+ user?: string;
12
+ bot_id?: string;
13
+ text?: string;
14
+ ts: string;
15
+ thread_ts?: string;
16
+ }
17
+ interface UserProfile {
18
+ id: string;
19
+ name: string;
20
+ display: string;
21
+ }
22
+ export declare class SlackClient {
23
+ private token;
24
+ private base;
25
+ private userCache;
26
+ constructor(token: string);
27
+ private call;
28
+ /**
29
+ * Fetch messages newer than `oldestTs` (exclusive) for a channel, oldest-first.
30
+ * `oldestTs` is the ingest cursor bookmark; "0" means "from now-ish" on cold start
31
+ * (caller decides cold-start policy).
32
+ */
33
+ fetchSince(channelId: string, oldestTs: string, limit?: number): Promise<SlackMessage[]>;
34
+ /** Resolve a user id → profile (cached). Best-effort; never throws to the caller. */
35
+ userProfile(userId: string): Promise<UserProfile>;
36
+ }
37
+ export {};
38
+ //# sourceMappingURL=slack.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"slack.d.ts","sourceRoot":"","sources":["../src/slack.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,MAAM,WAAW,YAAY;IAC3B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,UAAU,WAAW;IACnB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,qBAAa,WAAW;IACtB,OAAO,CAAC,KAAK,CAAS;IACtB,OAAO,CAAC,IAAI,CAA2B;IACvC,OAAO,CAAC,SAAS,CAAkC;gBAEvC,KAAK,EAAE,MAAM;YAIX,IAAI;IAQlB;;;;OAIG;IACG,UAAU,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,SAAK,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC;IAY1F,qFAAqF;IAC/E,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC;CAgBxD"}
package/dist/slack.js ADDED
@@ -0,0 +1,59 @@
1
+ /**
2
+ * slack.ts — minimal Slack Web API client for the INGEST leg.
3
+ *
4
+ * Reuses the existing Slack bot token (SLACK_BOT_TOKEN), the same credential
5
+ * mcp-slack already loads — no new auth, no public webhook endpoint. Just enough
6
+ * surface to poll new messages and resolve display names for person-overrides.
7
+ */
8
+ export class SlackClient {
9
+ token;
10
+ base = "https://slack.com/api";
11
+ userCache = new Map();
12
+ constructor(token) {
13
+ this.token = token;
14
+ }
15
+ async call(method, params) {
16
+ const url = `${this.base}/${method}?${new URLSearchParams(params).toString()}`;
17
+ const res = await fetch(url, { headers: { Authorization: `Bearer ${this.token}` } });
18
+ const json = await res.json();
19
+ if (!json.ok)
20
+ throw new Error(`slack ${method} failed: ${json.error || "unknown"}`);
21
+ return json;
22
+ }
23
+ /**
24
+ * Fetch messages newer than `oldestTs` (exclusive) for a channel, oldest-first.
25
+ * `oldestTs` is the ingest cursor bookmark; "0" means "from now-ish" on cold start
26
+ * (caller decides cold-start policy).
27
+ */
28
+ async fetchSince(channelId, oldestTs, limit = 50) {
29
+ const json = await this.call("conversations.history", {
30
+ channel: channelId,
31
+ oldest: oldestTs,
32
+ inclusive: "false",
33
+ limit: String(limit),
34
+ });
35
+ const msgs = Array.isArray(json.messages) ? json.messages : [];
36
+ // Slack returns newest-first; we want oldest-first FIFO into the staging log.
37
+ return msgs.slice().sort((a, b) => Number(a.ts) - Number(b.ts));
38
+ }
39
+ /** Resolve a user id → profile (cached). Best-effort; never throws to the caller. */
40
+ async userProfile(userId) {
41
+ const cached = this.userCache.get(userId);
42
+ if (cached)
43
+ return cached;
44
+ let profile = { id: userId, name: userId, display: userId };
45
+ try {
46
+ const json = await this.call("users.info", { user: userId });
47
+ const u = json.user || {};
48
+ const p = u.profile || {};
49
+ const display = p.display_name || p.real_name || u.name || userId;
50
+ profile = { id: userId, name: u.name || userId, display };
51
+ }
52
+ catch (err) {
53
+ console.error(`[slack] users.info(${userId}) failed: ${err?.message || err}`);
54
+ }
55
+ this.userCache.set(userId, profile);
56
+ return profile;
57
+ }
58
+ }
59
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic2xhY2suanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvc2xhY2sudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7Ozs7OztHQU1HO0FBa0JILE1BQU0sT0FBTyxXQUFXO0lBQ2QsS0FBSyxDQUFTO0lBQ2QsSUFBSSxHQUFHLHVCQUF1QixDQUFDO0lBQy9CLFNBQVMsR0FBRyxJQUFJLEdBQUcsRUFBdUIsQ0FBQztJQUVuRCxZQUFZLEtBQWE7UUFDdkIsSUFBSSxDQUFDLEtBQUssR0FBRyxLQUFLLENBQUM7SUFDckIsQ0FBQztJQUVPLEtBQUssQ0FBQyxJQUFJLENBQUMsTUFBYyxFQUFFLE1BQThCO1FBQy9ELE1BQU0sR0FBRyxHQUFHLEdBQUcsSUFBSSxDQUFDLElBQUksSUFBSSxNQUFNLElBQUksSUFBSSxlQUFlLENBQUMsTUFBTSxDQUFDLENBQUMsUUFBUSxFQUFFLEVBQUUsQ0FBQztRQUMvRSxNQUFNLEdBQUcsR0FBRyxNQUFNLEtBQUssQ0FBQyxHQUFHLEVBQUUsRUFBRSxPQUFPLEVBQUUsRUFBRSxhQUFhLEVBQUUsVUFBVSxJQUFJLENBQUMsS0FBSyxFQUFFLEVBQUUsRUFBRSxDQUFDLENBQUM7UUFDckYsTUFBTSxJQUFJLEdBQVEsTUFBTSxHQUFHLENBQUMsSUFBSSxFQUFFLENBQUM7UUFDbkMsSUFBSSxDQUFDLElBQUksQ0FBQyxFQUFFO1lBQUUsTUFBTSxJQUFJLEtBQUssQ0FBQyxTQUFTLE1BQU0sWUFBWSxJQUFJLENBQUMsS0FBSyxJQUFJLFNBQVMsRUFBRSxDQUFDLENBQUM7UUFDcEYsT0FBTyxJQUFJLENBQUM7SUFDZCxDQUFDO0lBRUQ7Ozs7T0FJRztJQUNILEtBQUssQ0FBQyxVQUFVLENBQUMsU0FBaUIsRUFBRSxRQUFnQixFQUFFLEtBQUssR0FBRyxFQUFFO1FBQzlELE1BQU0sSUFBSSxHQUFHLE1BQU0sSUFBSSxDQUFDLElBQUksQ0FBQyx1QkFBdUIsRUFBRTtZQUNwRCxPQUFPLEVBQUUsU0FBUztZQUNsQixNQUFNLEVBQUUsUUFBUTtZQUNoQixTQUFTLEVBQUUsT0FBTztZQUNsQixLQUFLLEVBQUUsTUFBTSxDQUFDLEtBQUssQ0FBQztTQUNyQixDQUFDLENBQUM7UUFDSCxNQUFNLElBQUksR0FBbUIsS0FBSyxDQUFDLE9BQU8sQ0FBQyxJQUFJLENBQUMsUUFBUSxDQUFDLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQyxRQUFRLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQztRQUMvRSw4RUFBOEU7UUFDOUUsT0FBTyxJQUFJLENBQUMsS0FBSyxFQUFFLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQyxFQUFFLENBQUMsRUFBRSxFQUFFLENBQUMsTUFBTSxDQUFDLENBQUMsQ0FBQyxFQUFFLENBQUMsR0FBRyxNQUFNLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxDQUFDLENBQUM7SUFDbEUsQ0FBQztJQUVELHFGQUFxRjtJQUNyRixLQUFLLENBQUMsV0FBVyxDQUFDLE1BQWM7UUFDOUIsTUFBTSxNQUFNLEdBQUcsSUFBSSxDQUFDLFNBQVMsQ0FBQyxHQUFHLENBQUMsTUFBTSxDQUFDLENBQUM7UUFDMUMsSUFBSSxNQUFNO1lBQUUsT0FBTyxNQUFNLENBQUM7UUFDMUIsSUFBSSxPQUFPLEdBQWdCLEVBQUUsRUFBRSxFQUFFLE1BQU0sRUFBRSxJQUFJLEVBQUUsTUFBTSxFQUFFLE9BQU8sRUFBRSxNQUFNLEVBQUUsQ0FBQztRQUN6RSxJQUFJLENBQUM7WUFDSCxNQUFNLElBQUksR0FBRyxNQUFNLElBQUksQ0FBQyxJQUFJLENBQUMsWUFBWSxFQUFFLEVBQUUsSUFBSSxFQUFFLE1BQU0sRUFBRSxDQUFDLENBQUM7WUFDN0QsTUFBTSxDQUFDLEdBQUcsSUFBSSxDQUFDLElBQUksSUFBSSxFQUFFLENBQUM7WUFDMUIsTUFBTSxDQUFDLEdBQUcsQ0FBQyxDQUFDLE9BQU8sSUFBSSxFQUFFLENBQUM7WUFDMUIsTUFBTSxPQUFPLEdBQUcsQ0FBQyxDQUFDLFlBQVksSUFBSSxDQUFDLENBQUMsU0FBUyxJQUFJLENBQUMsQ0FBQyxJQUFJLElBQUksTUFBTSxDQUFDO1lBQ2xFLE9BQU8sR0FBRyxFQUFFLEVBQUUsRUFBRSxNQUFNLEVBQUUsSUFBSSxFQUFFLENBQUMsQ0FBQyxJQUFJLElBQUksTUFBTSxFQUFFLE9BQU8sRUFBRSxDQUFDO1FBQzVELENBQUM7UUFBQyxPQUFPLEdBQVEsRUFBRSxDQUFDO1lBQ2xCLE9BQU8sQ0FBQyxLQUFLLENBQUMsc0JBQXNCLE1BQU0sYUFBYSxHQUFHLEVBQUUsT0FBTyxJQUFJLEdBQUcsRUFBRSxDQUFDLENBQUM7UUFDaEYsQ0FBQztRQUNELElBQUksQ0FBQyxTQUFTLENBQUMsR0FBRyxDQUFDLE1BQU0sRUFBRSxPQUFPLENBQUMsQ0FBQztRQUNwQyxPQUFPLE9BQU8sQ0FBQztJQUNqQixDQUFDO0NBQ0YifQ==
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@chinchillaenterprises/mcp-claude-channel",
3
- "version": "0.1.0",
4
- "description": "Lane-to-lane Claude Code side-door channel server. Pushes a NUDGE from a peer lane INTO a live Claude Code session via the official Channels notification (notifications/claude/channel) and exposes a send_to_lane tool to nudge the next lane. v1: session↔session only, no Slack. The SQLite Lane Board stays the source of truth; the push is a doorbell, not the payload.",
3
+ "version": "0.2.0",
4
+ "description": "Claude Code side-door channel server + inbound Slack→lane router. Lane↔lane: pushes a NUDGE into a live session via the official Channels notification (notifications/claude/channel) and a send_to_lane tool. v0.2 adds an --ingest bridge that polls Slack, normalizes to an envelope, classifies against Coordinator-owned policy, and delivers to the Coordinator inbox (sole receiver), plus a mirror_to_coordinator tool. Transport stays policy-free; the routing table is read, never hardcoded.",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",
7
7
  "bin": {
@@ -31,6 +31,7 @@
31
31
  },
32
32
  "dependencies": {
33
33
  "@modelcontextprotocol/sdk": "^1.12.3",
34
+ "yaml": "^2.9.0",
34
35
  "zod": "^3.25.64"
35
36
  },
36
37
  "devDependencies": {