@damian87/omp 0.7.0 → 0.9.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. package/.github/skills/slack/SKILL.md +82 -0
  2. package/README.md +61 -34
  3. package/catalog/capabilities.json +46 -0
  4. package/catalog/skills-general.json +26 -0
  5. package/dist/src/cli.js +365 -3
  6. package/dist/src/cli.js.map +1 -1
  7. package/dist/src/copilot/version.js +10 -0
  8. package/dist/src/copilot/version.js.map +1 -1
  9. package/dist/src/env/dotenv.d.ts +41 -0
  10. package/dist/src/env/dotenv.js +112 -0
  11. package/dist/src/env/dotenv.js.map +1 -0
  12. package/dist/src/env/init.d.ts +56 -0
  13. package/dist/src/env/init.js +294 -0
  14. package/dist/src/env/init.js.map +1 -0
  15. package/dist/src/gateway/connector.d.ts +37 -0
  16. package/dist/src/gateway/connector.js +12 -0
  17. package/dist/src/gateway/connector.js.map +1 -0
  18. package/dist/src/gateway/connectors/slack.d.ts +69 -0
  19. package/dist/src/gateway/connectors/slack.js +159 -0
  20. package/dist/src/gateway/connectors/slack.js.map +1 -0
  21. package/dist/src/gateway/notify.d.ts +35 -0
  22. package/dist/src/gateway/notify.js +261 -0
  23. package/dist/src/gateway/notify.js.map +1 -0
  24. package/dist/src/gateway/registry.d.ts +29 -0
  25. package/dist/src/gateway/registry.js +37 -0
  26. package/dist/src/gateway/registry.js.map +1 -0
  27. package/dist/src/gateway/runtime.d.ts +58 -0
  28. package/dist/src/gateway/runtime.js +105 -0
  29. package/dist/src/gateway/runtime.js.map +1 -0
  30. package/dist/src/gateway/target-parser.d.ts +76 -0
  31. package/dist/src/gateway/target-parser.js +105 -0
  32. package/dist/src/gateway/target-parser.js.map +1 -0
  33. package/dist/src/jira.js +2 -14
  34. package/dist/src/jira.js.map +1 -1
  35. package/dist/src/schedule/commands.js +1 -0
  36. package/dist/src/schedule/commands.js.map +1 -1
  37. package/dist/src/schedule/runner.d.ts +9 -0
  38. package/dist/src/schedule/runner.js +31 -1
  39. package/dist/src/schedule/runner.js.map +1 -1
  40. package/dist/src/schedule/types.d.ts +9 -0
  41. package/dist/src/slack/config.d.ts +32 -0
  42. package/dist/src/slack/config.js +52 -0
  43. package/dist/src/slack/config.js.map +1 -0
  44. package/dist/src/slack/handler.d.ts +48 -0
  45. package/dist/src/slack/handler.js +68 -0
  46. package/dist/src/slack/handler.js.map +1 -0
  47. package/dist/src/slack/serve.d.ts +8 -0
  48. package/dist/src/slack/serve.js +7 -0
  49. package/dist/src/slack/serve.js.map +1 -0
  50. package/dist/src/team/tmux.d.ts +1 -0
  51. package/dist/src/team/tmux.js +9 -0
  52. package/dist/src/team/tmux.js.map +1 -1
  53. package/docs/slack-setup.md +177 -0
  54. package/package.json +13 -4
  55. package/plugin.json +12 -4
  56. package/scripts/lib/version-check.mjs +3 -0
  57. package/dist/src/mcp/server.d.ts +0 -10
  58. package/dist/src/mcp/server.js +0 -44
  59. package/dist/src/mcp/server.js.map +0 -1
  60. package/dist/src/mcp/tools/daily-log.d.ts +0 -2
  61. package/dist/src/mcp/tools/daily-log.js +0 -148
  62. package/dist/src/mcp/tools/daily-log.js.map +0 -1
  63. package/dist/src/mcp/tools/index.d.ts +0 -9
  64. package/dist/src/mcp/tools/index.js +0 -15
  65. package/dist/src/mcp/tools/index.js.map +0 -1
  66. package/dist/src/mcp/tools/notepad.d.ts +0 -2
  67. package/dist/src/mcp/tools/notepad.js +0 -135
  68. package/dist/src/mcp/tools/notepad.js.map +0 -1
  69. package/dist/src/mcp/tools/project-memory.d.ts +0 -2
  70. package/dist/src/mcp/tools/project-memory.js +0 -91
  71. package/dist/src/mcp/tools/project-memory.js.map +0 -1
  72. package/dist/src/mcp/tools/shared-memory.d.ts +0 -2
  73. package/dist/src/mcp/tools/shared-memory.js +0 -148
  74. package/dist/src/mcp/tools/shared-memory.js.map +0 -1
  75. package/dist/src/mcp/tools/state.d.ts +0 -2
  76. package/dist/src/mcp/tools/state.js +0 -107
  77. package/dist/src/mcp/tools/state.js.map +0 -1
  78. package/dist/src/mcp/tools/trace.d.ts +0 -10
  79. package/dist/src/mcp/tools/trace.js +0 -102
  80. package/dist/src/mcp/tools/trace.js.map +0 -1
  81. package/dist/src/mcp/types.d.ts +0 -29
  82. package/dist/src/mcp/types.js +0 -7
  83. package/dist/src/mcp/types.js.map +0 -1
  84. package/dist/test/catalog.test.d.ts +0 -1
  85. package/dist/test/catalog.test.js +0 -21
  86. package/dist/test/catalog.test.js.map +0 -1
  87. package/dist/test/jira.test.d.ts +0 -1
  88. package/dist/test/jira.test.js +0 -26
  89. package/dist/test/jira.test.js.map +0 -1
  90. package/dist/test/lint.test.d.ts +0 -1
  91. package/dist/test/lint.test.js +0 -9
  92. package/dist/test/lint.test.js.map +0 -1
  93. package/dist/test/sync.test.d.ts +0 -1
  94. package/dist/test/sync.test.js +0 -15
  95. package/dist/test/sync.test.js.map +0 -1
@@ -0,0 +1,261 @@
1
+ /**
2
+ * Outbound Slack notifier — Hermes-style. No daemon. No socket. Each call is
3
+ * a stateless POST to Slack's REST API.
4
+ *
5
+ * Resolution model (mirrors apps/hermes-agent/tools/send_message_tool.py):
6
+ * 1. Caller passes `target` OR we read `SLACK_HOME_CHANNEL` from env.
7
+ * 2. If the resolved Slack ID is a user-id (U…), we call
8
+ * `conversations.open` once to get the corresponding D… IM channel.
9
+ * 3. POST to `chat.postMessage` with the (D…/C…/G…) channel id.
10
+ *
11
+ * Retry: bounded exponential backoff on 429 (rate limit) and 5xx. Other Slack
12
+ * errors return immediately with the typed reason code so callers (cron) can
13
+ * record dropped payloads to a side log without hammering Slack.
14
+ */
15
+ import { parseTarget, parseSlackRef } from "./target-parser.js";
16
+ const SLACK_POST_URL = "https://slack.com/api/chat.postMessage";
17
+ const SLACK_OPEN_URL = "https://slack.com/api/conversations.open";
18
+ const DEFAULT_TIMEOUT_MS = 10_000;
19
+ const MAX_ATTEMPTS = 3;
20
+ /**
21
+ * Send a Slack message. Library entry point — never throws. Returns a
22
+ * structured result so callers (cron / `/slack send` skill / CLI) decide
23
+ * how to surface or persist failures.
24
+ */
25
+ export async function notify(opts, deps = {}) {
26
+ const env = deps.env ?? process.env;
27
+ const doFetch = deps.fetch ?? fetch;
28
+ const sleep = deps.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
29
+ const text = (opts.text ?? "").toString();
30
+ if (!text.trim()) {
31
+ return { ok: false, code: "POST_FAILED", reason: "text is empty" };
32
+ }
33
+ const token = (env.SLACK_BOT_TOKEN ?? "").trim();
34
+ if (!token) {
35
+ return { ok: false, code: "MISSING_TOKEN", reason: "SLACK_BOT_TOKEN is not set" };
36
+ }
37
+ // 1. Resolve target.
38
+ let target;
39
+ if (opts.target && opts.target.trim()) {
40
+ const parsed = parseTarget(opts.target);
41
+ if (!parsed.ok)
42
+ return { ok: false, code: "BAD_TARGET", reason: parsed.error };
43
+ target = parsed.target;
44
+ }
45
+ else {
46
+ const home = (env.SLACK_HOME_CHANNEL ?? "").trim();
47
+ if (!home) {
48
+ return {
49
+ ok: false,
50
+ code: "MISSING_TARGET",
51
+ reason: "no --target and SLACK_HOME_CHANNEL is unset; set one via `omp env init` or pass --target slack:C0…",
52
+ };
53
+ }
54
+ const parsed = parseSlackRef(home);
55
+ if (!parsed.ok) {
56
+ return {
57
+ ok: false,
58
+ code: "BAD_HOME_CHANNEL",
59
+ reason: `SLACK_HOME_CHANNEL=${home}: ${parsed.error}`,
60
+ };
61
+ }
62
+ target = parsed.target;
63
+ }
64
+ // Validate an explicit --thread-ts the same way we validate :thread_ts
65
+ // suffixes in the target string. Without this, a typo via the flag path
66
+ // bypasses parser validation and Slack reports an opaque error at runtime.
67
+ if (opts.threadTs && !/^[0-9]+\.[0-9]+$/.test(opts.threadTs)) {
68
+ return {
69
+ ok: false,
70
+ code: "BAD_TARGET",
71
+ reason: `threadTs "${opts.threadTs}" is not a Slack timestamp (expected digits.digits)`,
72
+ };
73
+ }
74
+ // One absolute deadline for the whole notify call — shared across
75
+ // conversations.open + chat.postMessage so a U-target send can't quietly
76
+ // take 2× the documented budget.
77
+ const deadline = Date.now() + (opts.timeoutMs ?? DEFAULT_TIMEOUT_MS);
78
+ // 2. If user-id, resolve to a DM channel via conversations.open.
79
+ let channelId = target.id;
80
+ let openedIm = false;
81
+ if (target.kind === "user") {
82
+ const opened = await openIm(target.id, { token, doFetch, sleep, deadline });
83
+ if (!opened.ok)
84
+ return opened;
85
+ channelId = opened.channel;
86
+ openedIm = true;
87
+ }
88
+ // 3. Post.
89
+ const threadTs = opts.threadTs ?? target.threadTs;
90
+ return await postMessage({
91
+ token,
92
+ channel: channelId,
93
+ text,
94
+ threadTs,
95
+ doFetch,
96
+ sleep,
97
+ openedIm,
98
+ deadline,
99
+ });
100
+ }
101
+ async function openIm(userId, args) {
102
+ const r = await callSlack({
103
+ url: SLACK_OPEN_URL,
104
+ body: { users: userId },
105
+ ...args,
106
+ });
107
+ if (!r.ok) {
108
+ if (r.code === "RATE_LIMITED" || r.code === "TIMEOUT" || r.code === "NETWORK_ERROR") {
109
+ return r;
110
+ }
111
+ return { ok: false, code: "OPEN_FAILED", reason: r.reason };
112
+ }
113
+ const channel = r.payload?.channel?.id;
114
+ if (!channel) {
115
+ return { ok: false, code: "OPEN_FAILED", reason: "conversations.open returned no channel.id" };
116
+ }
117
+ return { ok: true, channel };
118
+ }
119
+ async function postMessage(args) {
120
+ const body = {
121
+ channel: args.channel,
122
+ text: args.text,
123
+ };
124
+ if (args.threadTs)
125
+ body.thread_ts = args.threadTs;
126
+ const r = await callSlack({ url: SLACK_POST_URL, body, ...args });
127
+ if (!r.ok)
128
+ return r;
129
+ const ts = r.payload?.ts ?? "";
130
+ return { ok: true, channel: args.channel, ts, openedIm: args.openedIm };
131
+ }
132
+ /**
133
+ * Single Slack API call with bounded retry. Treats 429 + 5xx as retryable
134
+ * (exponential backoff bounded by `timeoutMs`). Slack-level `ok:false`
135
+ * payloads return immediately as POST_FAILED — those are deterministic
136
+ * (invalid token / channel_not_found / not_in_channel etc).
137
+ */
138
+ async function callSlack(args) {
139
+ for (let attempt = 0; attempt < MAX_ATTEMPTS; attempt++) {
140
+ const remaining = args.deadline - Date.now();
141
+ if (remaining <= 0) {
142
+ return { ok: false, code: "TIMEOUT", reason: `gave up after deadline` };
143
+ }
144
+ let res;
145
+ try {
146
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
147
+ res = await args.doFetch(args.url, {
148
+ method: "POST",
149
+ headers: {
150
+ "Content-Type": "application/json; charset=utf-8",
151
+ Authorization: `Bearer ${args.token}`,
152
+ },
153
+ body: JSON.stringify(args.body),
154
+ signal: AbortSignal.timeout(Math.min(remaining, 5_000)),
155
+ });
156
+ }
157
+ catch (err) {
158
+ const msg = redact(err instanceof Error ? err.message : String(err), args.token);
159
+ if (/abort|timeout/i.test(msg)) {
160
+ return { ok: false, code: "TIMEOUT", reason: msg };
161
+ }
162
+ // Network blip — try again if we have budget.
163
+ const sleepMs = Math.min(backoffMs(attempt), Math.max(args.deadline - Date.now() - 50, 0));
164
+ if (attempt < MAX_ATTEMPTS - 1 && sleepMs > 0) {
165
+ await args.sleep(sleepMs);
166
+ continue;
167
+ }
168
+ return { ok: false, code: "NETWORK_ERROR", reason: msg };
169
+ }
170
+ // 429 → wait per Retry-After header if present; otherwise exponential.
171
+ if (res.status === 429) {
172
+ const retryAfter = Number(res.headers.get("retry-after") ?? 1);
173
+ const sleepBudget = Math.max(args.deadline - Date.now() - 100, 0);
174
+ const sleepMs = Math.min(retryAfter * 1000, sleepBudget);
175
+ if (attempt < MAX_ATTEMPTS - 1 && sleepMs >= 0 && sleepBudget > 0) {
176
+ await args.sleep(sleepMs);
177
+ continue;
178
+ }
179
+ return { ok: false, code: "RATE_LIMITED", reason: `429 after ${attempt + 1} attempt(s)` };
180
+ }
181
+ // 5xx → retry.
182
+ if (res.status >= 500 && res.status < 600) {
183
+ const sleepMs = Math.min(backoffMs(attempt), Math.max(args.deadline - Date.now() - 50, 0));
184
+ if (attempt < MAX_ATTEMPTS - 1 && sleepMs > 0) {
185
+ await args.sleep(sleepMs);
186
+ continue;
187
+ }
188
+ return { ok: false, code: "POST_FAILED", reason: `slack ${res.status} after ${attempt + 1} attempt(s)` };
189
+ }
190
+ // Anything else: parse the body and dispatch on Slack's own ok flag.
191
+ let payload;
192
+ try {
193
+ payload = (await res.json());
194
+ }
195
+ catch {
196
+ return { ok: false, code: "POST_FAILED", reason: `slack ${res.status}: non-JSON response` };
197
+ }
198
+ if (payload.ok === true)
199
+ return { ok: true, payload };
200
+ // Slack's documented schema has `error` as a string, but proxies / WAFs /
201
+ // wrong endpoints can return object / array / null. Coerce defensively
202
+ // before redacting — the public contract is never-throws.
203
+ const rawError = payload.error;
204
+ const reasonRaw = coerceErrorReason(rawError);
205
+ const reason = redact(reasonRaw, args.token);
206
+ // Slack uses `ratelimited` (no underscore) for rate-limit errors when not
207
+ // surfaced as HTTP 429. Treat as RATE_LIMITED so callers can back off.
208
+ if (reasonRaw === "ratelimited" || reasonRaw === "rate_limited") {
209
+ return { ok: false, code: "RATE_LIMITED", reason };
210
+ }
211
+ return { ok: false, code: "POST_FAILED", reason };
212
+ }
213
+ return { ok: false, code: "POST_FAILED", reason: "exhausted retries" };
214
+ }
215
+ /**
216
+ * Coerce a Slack error field of unknown shape to a string. Slack documents
217
+ * `error: string`, but proxies/WAFs/mis-pointed endpoints can return objects,
218
+ * arrays, or null. We must NOT throw — the caller's never-throws guarantee
219
+ * is part of the public contract.
220
+ */
221
+ function coerceErrorReason(raw) {
222
+ if (raw == null)
223
+ return "slack returned ok:false";
224
+ if (typeof raw === "string")
225
+ return raw;
226
+ if (typeof raw === "number" || typeof raw === "boolean")
227
+ return String(raw);
228
+ try {
229
+ return JSON.stringify(raw);
230
+ }
231
+ catch {
232
+ return "slack returned ok:false (unserializable error field)";
233
+ }
234
+ }
235
+ /**
236
+ * Strip the bot token (and Bearer-prefix patterns) from any string that's
237
+ * about to be returned in `reason` or logged. Cheap belt-and-braces guard:
238
+ * Slack itself never echoes our token, but a fetch / abort / DNS error could
239
+ * contain the request URL or headers depending on the runtime, and any such
240
+ * leak would land in the schedule runner's stderr log.
241
+ */
242
+ function redact(s, token) {
243
+ let out = s;
244
+ if (token) {
245
+ // Replace literal token wherever it appears.
246
+ out = out.split(token).join("[REDACTED]");
247
+ }
248
+ // Strip generic Bearer-token patterns that some fetch errors include.
249
+ out = out.replace(/Bearer\s+[A-Za-z0-9-._~+/]+=*/g, "Bearer [REDACTED]");
250
+ // Strip Slack-format tokens that might appear in payloads. Covers:
251
+ // xox[a/b/e/p/r/s]- (legacy access / bot / refresh / user / config / signing)
252
+ // xapp- (app-level tokens — what `omp gateway serve` uses)
253
+ out = out.replace(/xox[a-z]-[A-Za-z0-9-]+/gi, "[REDACTED]");
254
+ out = out.replace(/xapp-[A-Za-z0-9-]+/gi, "[REDACTED]");
255
+ return out;
256
+ }
257
+ function backoffMs(attempt) {
258
+ // 250ms, 500ms, 1000ms — bounded so a misbehaving Slack doesn't stall cron forever.
259
+ return Math.min(250 * 2 ** attempt, 1_000);
260
+ }
261
+ //# sourceMappingURL=notify.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"notify.js","sourceRoot":"","sources":["../../../src/gateway/notify.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Connector registry — the seam where new connectors plug in. Today: slack
3
+ * only. To add Telegram/Discord/etc, register a factory here, drop a file
4
+ * under src/gateway/connectors/, and the CLI `--only <name>` picks it up.
5
+ */
6
+ import type { Connector, ConnectorDoctor } from "./connector.js";
7
+ export interface RegistryDeps {
8
+ /** Env overrides for tests; default reads process.env. */
9
+ env?: NodeJS.ProcessEnv;
10
+ }
11
+ export interface BuiltConnectors {
12
+ /** Live connectors to be started by the gateway runtime. */
13
+ connectors: Connector[];
14
+ /** Doctor probes — one per *potential* connector, started or not. */
15
+ doctors: ConnectorDoctor[];
16
+ /** Warnings encountered while building the registry (e.g. missing tokens). */
17
+ warnings: string[];
18
+ }
19
+ /**
20
+ * Build the set of connectors that should run given the current env. A
21
+ * connector that lacks its required env vars is reported as a doctor probe
22
+ * (so `gateway status` can show the gap) but is NOT added to `connectors`.
23
+ *
24
+ * `enabledNames` (from `--only`) further filters the set; passing undefined
25
+ * means "all auto-detected".
26
+ */
27
+ export declare function buildConnectors(enabledNames: string[] | undefined, deps?: RegistryDeps): BuiltConnectors;
28
+ /** Known connector names — used for `--only` validation messages. */
29
+ export declare const KNOWN_CONNECTORS: readonly string[];
@@ -0,0 +1,37 @@
1
+ import { createSlackConnector, slackDoctor, SLACK_CONNECTOR_NAME } from "./connectors/slack.js";
2
+ import { loadSlackConfig } from "../slack/config.js";
3
+ /**
4
+ * Build the set of connectors that should run given the current env. A
5
+ * connector that lacks its required env vars is reported as a doctor probe
6
+ * (so `gateway status` can show the gap) but is NOT added to `connectors`.
7
+ *
8
+ * `enabledNames` (from `--only`) further filters the set; passing undefined
9
+ * means "all auto-detected".
10
+ */
11
+ export function buildConnectors(enabledNames, deps = {}) {
12
+ const env = deps.env ?? process.env;
13
+ const warnings = [];
14
+ const connectors = [];
15
+ const doctors = [];
16
+ // --- slack ---
17
+ const slackEnabled = enabledNames ? enabledNames.includes(SLACK_CONNECTOR_NAME) : true;
18
+ if (slackEnabled) {
19
+ let slackCfg = null;
20
+ let slackErr;
21
+ try {
22
+ slackCfg = loadSlackConfig(undefined, env);
23
+ }
24
+ catch (err) {
25
+ slackErr = err instanceof Error ? err.message : String(err);
26
+ warnings.push(`slack: ${slackErr}`);
27
+ }
28
+ if (slackCfg) {
29
+ connectors.push(createSlackConnector({ config: slackCfg }));
30
+ }
31
+ doctors.push(slackDoctor(slackCfg, slackErr));
32
+ }
33
+ return { connectors, doctors, warnings };
34
+ }
35
+ /** Known connector names — used for `--only` validation messages. */
36
+ export const KNOWN_CONNECTORS = [SLACK_CONNECTOR_NAME];
37
+ //# sourceMappingURL=registry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.js","sourceRoot":"","sources":["../../../src/gateway/registry.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,oBAAoB,EAAE,WAAW,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAC;AAChG,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAgBrD;;;;;;;GAOG;AACH,MAAM,UAAU,eAAe,CAC7B,YAAkC,EAClC,OAAqB,EAAE;IAEvB,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG,CAAC;IACpC,MAAM,QAAQ,GAAa,EAAE,CAAC;IAC9B,MAAM,UAAU,GAAgB,EAAE,CAAC;IACnC,MAAM,OAAO,GAAsB,EAAE,CAAC;IAEtC,gBAAgB;IAChB,MAAM,YAAY,GAAG,YAAY,CAAC,CAAC,CAAC,YAAY,CAAC,QAAQ,CAAC,oBAAoB,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IACvF,IAAI,YAAY,EAAE,CAAC;QACjB,IAAI,QAAQ,GAAG,IAAI,CAAC;QACpB,IAAI,QAA4B,CAAC;QACjC,IAAI,CAAC;YACH,QAAQ,GAAG,eAAe,CAAC,SAAS,EAAE,GAAG,CAAC,CAAC;QAC7C,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,QAAQ,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YAC5D,QAAQ,CAAC,IAAI,CAAC,UAAU,QAAQ,EAAE,CAAC,CAAC;QACtC,CAAC;QACD,IAAI,QAAQ,EAAE,CAAC;YACb,UAAU,CAAC,IAAI,CAAC,oBAAoB,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC;QAC9D,CAAC;QACD,OAAO,CAAC,IAAI,CAAC,WAAW,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC,CAAC;IAChD,CAAC;IAED,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC;AAC3C,CAAC;AAED,qEAAqE;AACrE,MAAM,CAAC,MAAM,gBAAgB,GAAsB,CAAC,oBAAoB,CAAC,CAAC"}
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Gateway runtime. Starts a set of {@link Connector}s in parallel, blocks on
3
+ * SIGINT/SIGTERM, then stops them via Promise.allSettled so one failing stop
4
+ * cannot strand the rest.
5
+ *
6
+ * Signal handling is injectable so tests can drive shutdown without sending
7
+ * real signals to the test runner.
8
+ */
9
+ import type { Connector, ConnectorStatus } from "./connector.js";
10
+ export interface RunGatewayOptions {
11
+ /** Connectors to run. Order is preserved in status output. */
12
+ connectors: Connector[];
13
+ /**
14
+ * Returns a promise that resolves when the runtime should shut down.
15
+ * Default: resolves on SIGINT or SIGTERM.
16
+ */
17
+ waitForShutdown?: () => Promise<void>;
18
+ /** Logger; defaults to console.error so info lines never pollute stdout JSON. */
19
+ log?: (msg: string) => void;
20
+ }
21
+ export interface GatewayStatusReport {
22
+ ready: boolean;
23
+ connectors: Array<{
24
+ name: string;
25
+ } & ConnectorStatus>;
26
+ }
27
+ /**
28
+ * Compute a status snapshot for a set of connectors. Pure — no I/O. The
29
+ * aggregate `ready` is true only when every connector reports ready.
30
+ */
31
+ export declare function getGatewayStatus(connectors: Connector[]): GatewayStatusReport;
32
+ /**
33
+ * Resolve on SIGINT/SIGTERM. Exported so it can be tested without sending
34
+ * real signals to the test runner: tests inject a fake `proc` with the same
35
+ * shape as `process` and assert listener attach/detach.
36
+ */
37
+ export declare function waitForSignals(proc?: Pick<NodeJS.Process, "off" | "once">, log?: (msg: string) => void): Promise<void>;
38
+ /**
39
+ * Run the gateway until shutdown. Returns when all connectors have stopped.
40
+ * Throws if no connectors are provided — callers should fail loudly when their
41
+ * `--only` filter or env config produced an empty set.
42
+ */
43
+ export declare function runGateway(opts: RunGatewayOptions): Promise<void>;
44
+ /**
45
+ * Build a filtered list of connectors from a registry by name. Names not in
46
+ * the registry are reported back so the caller can decide whether to error.
47
+ */
48
+ export declare function selectConnectors<T extends {
49
+ name: string;
50
+ }>(registry: Record<string, () => T>, names: string[]): {
51
+ connectors: T[];
52
+ unknown: string[];
53
+ };
54
+ /**
55
+ * Parse a comma-separated `--only` value into a list of trimmed, non-empty
56
+ * connector names.
57
+ */
58
+ export declare function parseOnlyFlag(value: string | undefined): string[] | undefined;
@@ -0,0 +1,105 @@
1
+ /**
2
+ * Compute a status snapshot for a set of connectors. Pure — no I/O. The
3
+ * aggregate `ready` is true only when every connector reports ready.
4
+ */
5
+ export function getGatewayStatus(connectors) {
6
+ const rows = connectors.map((c) => {
7
+ const s = c.status();
8
+ return { name: c.name, ready: s.ready, detail: s.detail };
9
+ });
10
+ return { ready: rows.length > 0 && rows.every((r) => r.ready), connectors: rows };
11
+ }
12
+ /**
13
+ * Resolve on SIGINT/SIGTERM. Exported so it can be tested without sending
14
+ * real signals to the test runner: tests inject a fake `proc` with the same
15
+ * shape as `process` and assert listener attach/detach.
16
+ */
17
+ export function waitForSignals(proc = process, log = (m) => console.error(m)) {
18
+ return new Promise((resolve) => {
19
+ const onSig = (sig) => {
20
+ // Detach both listeners so a second signal doesn't double-resolve.
21
+ proc.off("SIGINT", onSig);
22
+ proc.off("SIGTERM", onSig);
23
+ // Log to stderr so JSON consumers on stdout aren't disturbed.
24
+ log(`omp gateway: received ${sig}, shutting down…`);
25
+ resolve();
26
+ };
27
+ proc.once("SIGINT", onSig);
28
+ proc.once("SIGTERM", onSig);
29
+ });
30
+ }
31
+ /**
32
+ * Run the gateway until shutdown. Returns when all connectors have stopped.
33
+ * Throws if no connectors are provided — callers should fail loudly when their
34
+ * `--only` filter or env config produced an empty set.
35
+ */
36
+ export async function runGateway(opts) {
37
+ const log = opts.log ?? ((m) => console.error(m));
38
+ const connectors = opts.connectors;
39
+ if (connectors.length === 0) {
40
+ throw new Error("no connectors configured — set tokens or pass --only <name>");
41
+ }
42
+ // Start all in parallel; surface per-connector failure but let the rest run.
43
+ // A reasonable default is "best-effort start": if one connector cannot come
44
+ // up, the others continue and `gateway status` reports the failure.
45
+ const startResults = await Promise.allSettled(connectors.map((c) => c.start()));
46
+ startResults.forEach((r, i) => {
47
+ if (r.status === "rejected") {
48
+ const msg = r.reason instanceof Error ? r.reason.message : String(r.reason);
49
+ log(`omp gateway: connector "${connectors[i].name}" failed to start: ${msg}`);
50
+ }
51
+ else {
52
+ log(`omp gateway: connector "${connectors[i].name}" ready`);
53
+ }
54
+ });
55
+ const ready = connectors.filter((_, i) => startResults[i].status === "fulfilled");
56
+ if (ready.length === 0) {
57
+ // Nothing came up. Even failed start()s may have allocated resources, and
58
+ // stop() is required to be idempotent — call it on every connector before
59
+ // surfacing the error, so the runtime upholds its own lifecycle contract.
60
+ await Promise.allSettled(connectors.map((c) => c.stop()));
61
+ throw new Error("all gateway connectors failed to start");
62
+ }
63
+ const wait = opts.waitForShutdown ?? (() => waitForSignals());
64
+ // try/finally guarantees stop() runs even if waitForShutdown rejects (e.g.
65
+ // an injected wait throws, or a signal handler propagates an error).
66
+ try {
67
+ await wait();
68
+ }
69
+ finally {
70
+ // Stop everything — including connectors that never started, since stop()
71
+ // is required to be idempotent.
72
+ await Promise.allSettled(connectors.map((c) => c.stop()));
73
+ }
74
+ }
75
+ /**
76
+ * Build a filtered list of connectors from a registry by name. Names not in
77
+ * the registry are reported back so the caller can decide whether to error.
78
+ */
79
+ export function selectConnectors(registry, names) {
80
+ const unknown = [];
81
+ const connectors = [];
82
+ for (const name of names) {
83
+ const factory = registry[name];
84
+ if (!factory) {
85
+ unknown.push(name);
86
+ continue;
87
+ }
88
+ connectors.push(factory());
89
+ }
90
+ return { connectors, unknown };
91
+ }
92
+ /**
93
+ * Parse a comma-separated `--only` value into a list of trimmed, non-empty
94
+ * connector names.
95
+ */
96
+ export function parseOnlyFlag(value) {
97
+ if (value === undefined)
98
+ return undefined;
99
+ const parts = value
100
+ .split(",")
101
+ .map((s) => s.trim())
102
+ .filter((s) => s.length > 0);
103
+ return parts.length === 0 ? undefined : parts;
104
+ }
105
+ //# sourceMappingURL=runtime.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runtime.js","sourceRoot":"","sources":["../../../src/gateway/runtime.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Parse outbound notification targets.
3
+ *
4
+ * Target grammar (Hermes-mirror — see apps/hermes-agent/tools/send_message_tool.py:21-46):
5
+ *
6
+ * "<platform>:<ref>" → explicit per-call target
7
+ *
8
+ * Bare-platform fallback (`<platform>` with no `:<ref>`) is NOT implemented in
9
+ * this parser. The notify() caller layer handles the "no target → env default"
10
+ * decision by passing undefined/empty as the target and reading
11
+ * `SLACK_HOME_CHANNEL` directly — it never calls `parseTarget("slack")`.
12
+ *
13
+ * For slice 1 the only platform is "slack". <ref> for slack is a 9+ char
14
+ * uppercase alphanumeric ID prefixed with one of:
15
+ * C — public channel
16
+ * G — private channel ("group")
17
+ * D — direct message conversation
18
+ * U — user (must be conversations.open'd to a D… before chat.postMessage;
19
+ * the caller is responsible for that resolution)
20
+ *
21
+ * Optional thread suffix: "<ref>:<thread_ts>" pins the post to an existing
22
+ * thread in that conversation. Not all kinds support threading sensibly, but
23
+ * the parser accepts it for any non-user kind.
24
+ *
25
+ * NOT supported in this pass: user IDs with embedded thread suffixes (no
26
+ * coherent meaning until U→D resolution); Hermes-style multi-platform
27
+ * regexes for telegram/discord/feishu (those land in slice 2+).
28
+ */
29
+ /** Slack ID after the `slack:` prefix. */
30
+ export declare const SLACK_ID_RE: RegExp;
31
+ /**
32
+ * Slack ID + optional thread suffix `:<thread_ts>`.
33
+ *
34
+ * `thread_ts` must look like Slack's canonical message timestamp shape
35
+ * (`<seconds>.<microseconds>` — digits-only). Anything else is rejected here
36
+ * so the CLI / schedule add validators catch typos before they reach Slack.
37
+ */
38
+ export declare const SLACK_TARGET_RE: RegExp;
39
+ export type SlackTargetKind = "channel" | "group" | "dm" | "user";
40
+ export interface SlackTarget {
41
+ platform: "slack";
42
+ kind: SlackTargetKind;
43
+ /**
44
+ * The raw Slack ID (`C…`/`G…`/`D…`/`U…`). For user targets, the caller
45
+ * must resolve to a D-ID via conversations.open before posting.
46
+ */
47
+ id: string;
48
+ /** Optional `thread_ts` for threaded posts. */
49
+ threadTs?: string;
50
+ }
51
+ export type ParsedTarget = SlackTarget;
52
+ export interface ParseError {
53
+ ok: false;
54
+ error: string;
55
+ }
56
+ export type ParseResult = {
57
+ ok: true;
58
+ target: ParsedTarget;
59
+ } | ParseError;
60
+ /**
61
+ * Parse a target string of the form `<platform>:<ref>`. Returns either a
62
+ * structured target or a friendly error. Pure — no I/O.
63
+ */
64
+ export declare function parseTarget(raw: string): ParseResult;
65
+ /**
66
+ * Parse just the slack-side `<ref>` (the part after `slack:`). Useful when a
67
+ * caller has already established platform context (e.g. `SLACK_HOME_CHANNEL`
68
+ * is set without a platform prefix).
69
+ */
70
+ export declare function parseSlackRef(ref: string): ParseResult;
71
+ /**
72
+ * Best-effort check for "looks like a Slack ID" without committing to a
73
+ * specific kind — used by config validators to nudge users about typos
74
+ * before any network call.
75
+ */
76
+ export declare function looksLikeSlackId(value: string): boolean;