@h-rig/server 0.0.6-alpha.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.
Files changed (60) hide show
  1. package/README.md +14 -0
  2. package/dist/src/bootstrap.js +161 -0
  3. package/dist/src/index.js +13153 -0
  4. package/dist/src/inspector/agent-runtime.js +1077 -0
  5. package/dist/src/inspector/analysis.js +41 -0
  6. package/dist/src/inspector/discovery.js +137 -0
  7. package/dist/src/inspector/journal.js +518 -0
  8. package/dist/src/inspector/mission.js +562 -0
  9. package/dist/src/inspector/prompt.js +97 -0
  10. package/dist/src/inspector/provider-session.js +65 -0
  11. package/dist/src/inspector/reconcile.js +118 -0
  12. package/dist/src/inspector/review.js +13 -0
  13. package/dist/src/inspector/service.js +1759 -0
  14. package/dist/src/inspector/skills.js +155 -0
  15. package/dist/src/inspector/tools.js +1592 -0
  16. package/dist/src/inspector/types.js +1 -0
  17. package/dist/src/inspector/upstream-sync.js +479 -0
  18. package/dist/src/orchestration.js +402 -0
  19. package/dist/src/remote.js +123 -0
  20. package/dist/src/scheduler.js +84 -0
  21. package/dist/src/server-helpers/broadcasters.js +161 -0
  22. package/dist/src/server-helpers/conversation-snapshot.js +382 -0
  23. package/dist/src/server-helpers/event-emitter.js +41 -0
  24. package/dist/src/server-helpers/github-auth-store.js +155 -0
  25. package/dist/src/server-helpers/github-credentials.js +38 -0
  26. package/dist/src/server-helpers/github-project-status-sync.js +196 -0
  27. package/dist/src/server-helpers/github-projects.js +147 -0
  28. package/dist/src/server-helpers/github-reconciler.js +89 -0
  29. package/dist/src/server-helpers/http-router.js +3781 -0
  30. package/dist/src/server-helpers/http-utils.js +135 -0
  31. package/dist/src/server-helpers/inspector-agent-lifecycle.js +104 -0
  32. package/dist/src/server-helpers/inspector-jobs.js +4145 -0
  33. package/dist/src/server-helpers/issue-analysis.js +362 -0
  34. package/dist/src/server-helpers/normalizers.js +31 -0
  35. package/dist/src/server-helpers/notifications.js +96 -0
  36. package/dist/src/server-helpers/orchestration-ops.js +287 -0
  37. package/dist/src/server-helpers/orchestration.js +39 -0
  38. package/dist/src/server-helpers/plugin-host-cache.js +86 -0
  39. package/dist/src/server-helpers/project-fs-ops.js +194 -0
  40. package/dist/src/server-helpers/project-registry.js +124 -0
  41. package/dist/src/server-helpers/queue-state.js +78 -0
  42. package/dist/src/server-helpers/remote-checkout.js +140 -0
  43. package/dist/src/server-helpers/remote-snapshots.js +119 -0
  44. package/dist/src/server-helpers/run-io.js +262 -0
  45. package/dist/src/server-helpers/run-mutations.js +1784 -0
  46. package/dist/src/server-helpers/run-steering.js +176 -0
  47. package/dist/src/server-helpers/run-writers.js +75 -0
  48. package/dist/src/server-helpers/server-paths.js +27 -0
  49. package/dist/src/server-helpers/snapshot-orchestrator.js +832 -0
  50. package/dist/src/server-helpers/snapshot-service.js +1143 -0
  51. package/dist/src/server-helpers/summaries.js +126 -0
  52. package/dist/src/server-helpers/task-config.js +50 -0
  53. package/dist/src/server-helpers/task-projection.js +98 -0
  54. package/dist/src/server-helpers/terminal-runtime.js +156 -0
  55. package/dist/src/server-helpers/terminal-sessions.js +22 -0
  56. package/dist/src/server-helpers/validation-failure.js +31 -0
  57. package/dist/src/server-helpers/ws-router.js +1308 -0
  58. package/dist/src/server.js +12628 -0
  59. package/dist/src/websocket.js +63 -0
  60. package/package.json +33 -0
@@ -0,0 +1,362 @@
1
+ // @bun
2
+ // packages/server/src/server-helpers/issue-analysis.ts
3
+ import { execFile } from "child_process";
4
+ import { createHash } from "crypto";
5
+ function stableIssueHash(issue) {
6
+ const labels = Array.isArray(issue.labels) ? [...issue.labels].map(String).sort() : [];
7
+ const body = typeof issue.body === "string" ? issue.body : "";
8
+ const title = typeof issue.title === "string" ? issue.title : "";
9
+ return createHash("sha256").update(JSON.stringify({ id: issue.id, title, body, labels, deps: issue.deps, status: issue.status })).digest("hex");
10
+ }
11
+ function renderIssueAnalysisPrompt(input) {
12
+ const issue = input.issue;
13
+ const neighbors = input.neighbors ?? [];
14
+ return [
15
+ "You are Rig issue analysis running inside Pi.",
16
+ "Return JSON only with optional metadataPatch, labelsToAdd, labelsToRemove, and generatedIssues.",
17
+ "Preserve all human-authored issue body content. Only propose edits for Rig-owned metadata/status sections, labels, and generated issues.",
18
+ "Generated issues must be concrete, minimal follow-up tasks and will be labeled rig:generated by Rig.",
19
+ "",
20
+ "Issue:",
21
+ JSON.stringify({
22
+ id: issue.id,
23
+ title: issue.title,
24
+ body: issue.body,
25
+ labels: issue.labels,
26
+ deps: issue.deps,
27
+ status: issue.status
28
+ }, null, 2),
29
+ "",
30
+ "Neighbor tasks:",
31
+ JSON.stringify(neighbors.map((task) => ({ id: task.id, title: task.title, status: task.status, deps: task.deps })), null, 2)
32
+ ].join(`
33
+ `);
34
+ }
35
+ function isRecord(value) {
36
+ return Boolean(value && typeof value === "object" && !Array.isArray(value));
37
+ }
38
+ function stringArray(value) {
39
+ if (!Array.isArray(value))
40
+ return;
41
+ return value.map(String).filter((entry) => entry.trim().length > 0);
42
+ }
43
+ function generatedIssues(value) {
44
+ if (!Array.isArray(value))
45
+ return;
46
+ return value.flatMap((entry) => {
47
+ if (!isRecord(entry) || typeof entry.title !== "string")
48
+ return [];
49
+ return [{
50
+ title: entry.title,
51
+ body: typeof entry.body === "string" ? entry.body : "",
52
+ labels: stringArray(entry.labels) ?? [],
53
+ ...Array.isArray(entry.dependsOn) ? { dependsOn: entry.dependsOn.map(String) } : {}
54
+ }];
55
+ });
56
+ }
57
+ function findJsonLikeText(value) {
58
+ if (typeof value === "string") {
59
+ const trimmed = value.trim();
60
+ if (trimmed.startsWith("{") || trimmed.startsWith("```"))
61
+ return trimmed;
62
+ return null;
63
+ }
64
+ if (Array.isArray(value)) {
65
+ for (const entry of value) {
66
+ const found = findJsonLikeText(entry);
67
+ if (found)
68
+ return found;
69
+ }
70
+ return null;
71
+ }
72
+ if (!isRecord(value))
73
+ return null;
74
+ for (const key of ["text", "content", "message", "output_text", "response", "stdout"]) {
75
+ const found = findJsonLikeText(value[key]);
76
+ if (found)
77
+ return found;
78
+ }
79
+ for (const entry of Object.values(value)) {
80
+ const found = findJsonLikeText(entry);
81
+ if (found)
82
+ return found;
83
+ }
84
+ return null;
85
+ }
86
+ function candidateAnalysisObject(value) {
87
+ if (!isRecord(value))
88
+ return null;
89
+ if (isRecord(value.result))
90
+ return candidateAnalysisObject(value.result) ?? value.result;
91
+ if (isRecord(value.analysis))
92
+ return candidateAnalysisObject(value.analysis) ?? value.analysis;
93
+ if (isRecord(value.metadataPatch) || Array.isArray(value.labelsToAdd) || Array.isArray(value.labelsToRemove) || Array.isArray(value.generatedIssues)) {
94
+ return value;
95
+ }
96
+ const nested = findJsonLikeText(value);
97
+ if (nested && nested !== JSON.stringify(value)) {
98
+ try {
99
+ const parsedNested = JSON.parse(nested.match(/```(?:json)?\s*([\s\S]*?)\s*```/i)?.[1]?.trim() ?? nested);
100
+ return candidateAnalysisObject(parsedNested);
101
+ } catch {
102
+ return null;
103
+ }
104
+ }
105
+ return null;
106
+ }
107
+ function parseIssueAnalysisResult(raw) {
108
+ let parsed = raw;
109
+ if (typeof raw === "string") {
110
+ const trimmed = raw.trim();
111
+ const fenced = trimmed.match(/```(?:json)?\s*([\s\S]*?)\s*```/i)?.[1]?.trim();
112
+ try {
113
+ parsed = JSON.parse(fenced ?? trimmed);
114
+ } catch {
115
+ const lastJsonLine = trimmed.split(/\r?\n/).reverse().find((line) => line.trim().startsWith("{"));
116
+ parsed = lastJsonLine ? JSON.parse(lastJsonLine) : {};
117
+ }
118
+ }
119
+ const candidate = candidateAnalysisObject(parsed);
120
+ if (!candidate)
121
+ return {};
122
+ const result = {};
123
+ if (isRecord(candidate.metadataPatch))
124
+ result.metadataPatch = candidate.metadataPatch;
125
+ const add = stringArray(candidate.labelsToAdd);
126
+ if (add?.length)
127
+ result.labelsToAdd = add;
128
+ const remove = stringArray(candidate.labelsToRemove);
129
+ if (remove?.length)
130
+ result.labelsToRemove = remove;
131
+ const generated = generatedIssues(candidate.generatedIssues);
132
+ if (generated?.length)
133
+ result.generatedIssues = generated;
134
+ return result;
135
+ }
136
+ function createDefaultPiIssueAnalysisCommandRunner() {
137
+ return (command, args, options) => new Promise((resolve) => {
138
+ execFile(command, [...args], {
139
+ timeout: options.timeoutMs,
140
+ maxBuffer: 10 * 1024 * 1024,
141
+ env: options.env ? { ...process.env, ...options.env } : process.env
142
+ }, (error, stdout, stderr) => {
143
+ const exitCode = typeof error?.code === "number" ? error.code : error ? 1 : 0;
144
+ resolve({ exitCode, stdout: String(stdout ?? ""), stderr: String(stderr ?? "") });
145
+ });
146
+ });
147
+ }
148
+ function createPiIssueAnalyzer(input = {}) {
149
+ const piBinary = input.piBinary ?? process.env.RIG_ISSUE_ANALYSIS_PI_BINARY ?? "pi";
150
+ const timeoutMs = Math.max(1000, Math.trunc(input.timeoutMs ?? Number(process.env.RIG_ISSUE_ANALYSIS_TIMEOUT_MS ?? 120000)));
151
+ const runCommand = input.runCommand ?? createDefaultPiIssueAnalysisCommandRunner();
152
+ return async ({ prompt }) => {
153
+ const args = ["--print", "--mode", "json", "--no-session"];
154
+ const model = input.model?.trim() || process.env.RIG_ISSUE_ANALYSIS_MODEL?.trim() || "openai-codex/gpt-5.5";
155
+ if (model)
156
+ args.push("--model", model);
157
+ args.push(prompt);
158
+ const result = await runCommand(piBinary, args, { timeoutMs, ...input.env ? { env: input.env } : {} });
159
+ if (result.exitCode !== 0) {
160
+ throw new Error(`Pi issue analysis failed (exit ${result.exitCode}): ${result.stderr ?? result.stdout}`);
161
+ }
162
+ return parseIssueAnalysisResult(result.stdout);
163
+ };
164
+ }
165
+ function defaultStatusComment(input) {
166
+ const changes = [
167
+ input.result.metadataPatch ? "metadata" : null,
168
+ input.result.labelsToAdd?.length ? `labels added: ${input.result.labelsToAdd.join(", ")}` : null,
169
+ input.result.labelsToRemove?.length ? `labels removed: ${input.result.labelsToRemove.join(", ")}` : null,
170
+ input.result.generatedIssues?.length ? `generated issues: ${input.result.generatedIssues.length}` : null
171
+ ].filter((entry) => Boolean(entry));
172
+ if (changes.length === 0)
173
+ return null;
174
+ return [
175
+ "<!-- rig:status-comment -->",
176
+ "### Rig issue analysis",
177
+ "",
178
+ `Analyzed issue ${input.issue.id}${input.reason ? ` (${input.reason})` : ""}.`,
179
+ "",
180
+ ...changes.map((change) => `- ${change}`)
181
+ ].join(`
182
+ `);
183
+ }
184
+ function uniqueLabels(labels, required = []) {
185
+ return [...new Set([...labels ?? [], ...required].map((label) => label.trim()).filter(Boolean))];
186
+ }
187
+ function createIssueAnalysisWriteBack(input) {
188
+ return async ({ issue, result, reason }) => {
189
+ if (result.metadataPatch && Object.keys(result.metadataPatch).length > 0) {
190
+ if (!input.target.updateTask)
191
+ throw new Error("Issue analysis writeback requires updateTask for metadata patches.");
192
+ await input.target.updateTask(issue.id, { metadata: result.metadataPatch });
193
+ }
194
+ if (result.labelsToAdd?.length) {
195
+ if (!input.target.addLabels)
196
+ throw new Error("Issue analysis writeback requires addLabels for labelsToAdd.");
197
+ await input.target.addLabels(issue.id, uniqueLabels(result.labelsToAdd));
198
+ }
199
+ if (result.labelsToRemove?.length) {
200
+ if (!input.target.removeLabels)
201
+ throw new Error("Issue analysis writeback requires removeLabels for labelsToRemove.");
202
+ await input.target.removeLabels(issue.id, uniqueLabels(result.labelsToRemove));
203
+ }
204
+ const comment = (input.buildStatusComment ?? defaultStatusComment)({ issue, result, reason });
205
+ if (comment?.trim()) {
206
+ if (!input.target.updateTask)
207
+ throw new Error("Issue analysis writeback requires updateTask for sticky status comments.");
208
+ await input.target.updateTask(issue.id, { comment });
209
+ }
210
+ for (const generated of result.generatedIssues ?? []) {
211
+ if (!input.target.createIssue)
212
+ throw new Error("Issue analysis writeback requires createIssue for generated issues.");
213
+ await input.target.createIssue({
214
+ title: generated.title,
215
+ body: generated.dependsOn?.length ? `${generated.body.trimEnd()}
216
+
217
+ depends-on: ${generated.dependsOn.map((dep) => dep.startsWith("#") ? dep : `#${dep}`).join(", ")}
218
+ ` : generated.body,
219
+ labels: uniqueLabels(generated.labels, ["rig:generated"])
220
+ });
221
+ }
222
+ };
223
+ }
224
+ function sourceWithWriteBackCapabilities(source) {
225
+ const candidate = source;
226
+ if (typeof candidate.updateTask !== "function")
227
+ return null;
228
+ return {
229
+ get: candidate.get?.bind(candidate),
230
+ updateTask: candidate.updateTask.bind(candidate),
231
+ ...typeof candidate.addLabels === "function" ? { addLabels: candidate.addLabels.bind(candidate) } : {},
232
+ ...typeof candidate.removeLabels === "function" ? { removeLabels: candidate.removeLabels.bind(candidate) } : {},
233
+ ...typeof candidate.createIssue === "function" ? { createIssue: candidate.createIssue.bind(candidate) } : {}
234
+ };
235
+ }
236
+ function issueAnalysisEnabled(config) {
237
+ const issueAnalysis = config.issueAnalysis;
238
+ if (!issueAnalysis)
239
+ return false;
240
+ if (issueAnalysis.enabled !== true)
241
+ return false;
242
+ if (issueAnalysis.mode === "off")
243
+ return false;
244
+ if (issueAnalysis.harness && issueAnalysis.harness !== "pi")
245
+ return false;
246
+ return true;
247
+ }
248
+ function createConfiguredIssueAnalysisRunner(input) {
249
+ if (!issueAnalysisEnabled(input.context.config))
250
+ return null;
251
+ const source = input.context.taskSourceRegistry.list()[0];
252
+ if (!source)
253
+ return null;
254
+ const target = sourceWithWriteBackCapabilities(source);
255
+ if (!target)
256
+ return null;
257
+ const analyzer = input.analyzer ?? createPiIssueAnalyzer({
258
+ runCommand: input.runCommand,
259
+ model: input.context.config.issueAnalysis?.model
260
+ });
261
+ const baseWriteBack = createIssueAnalysisWriteBack({ target });
262
+ const service = createIssueAnalysisService({
263
+ analyzer,
264
+ writeBack: async (writeBackInput) => {
265
+ await baseWriteBack(writeBackInput);
266
+ await input.onWriteBack?.();
267
+ }
268
+ });
269
+ return createContinuousIssueAnalysisRunner({
270
+ loadIssues: async () => [...await source.list()],
271
+ service,
272
+ intervalMs: input.intervalMs,
273
+ reason: "continuous-issue-analysis",
274
+ ...input.setIntervalFn ? { setIntervalFn: input.setIntervalFn } : {},
275
+ ...input.clearIntervalFn ? { clearIntervalFn: input.clearIntervalFn } : {},
276
+ ...input.onError ? { onError: input.onError } : {}
277
+ });
278
+ }
279
+ function createIssueAnalysisService(input) {
280
+ const analyzedHashes = new Map;
281
+ return {
282
+ async analyze(issues, options = {}) {
283
+ const results = [];
284
+ const neighbors = options.neighbors ?? issues;
285
+ for (const issue of issues) {
286
+ const hash = stableIssueHash(issue);
287
+ if (analyzedHashes.get(issue.id) === hash)
288
+ continue;
289
+ const prompt = renderIssueAnalysisPrompt({ issue, neighbors: neighbors.filter((candidate) => candidate.id !== issue.id) });
290
+ const result = await input.analyzer({ issue, neighbors, prompt });
291
+ analyzedHashes.set(issue.id, hash);
292
+ if (result.metadataPatch || result.labelsToAdd?.length || result.labelsToRemove?.length || result.generatedIssues?.length) {
293
+ await input.writeBack?.({ issue, result, reason: options.reason });
294
+ }
295
+ results.push({ issue, result });
296
+ }
297
+ return results;
298
+ },
299
+ clearCache() {
300
+ analyzedHashes.clear();
301
+ }
302
+ };
303
+ }
304
+ function createContinuousIssueAnalysisRunner(input) {
305
+ const intervalMs = Math.max(1000, Math.trunc(input.intervalMs ?? 60000));
306
+ const setIntervalFn = input.setIntervalFn ?? ((callback, ms) => setInterval(() => {
307
+ callback();
308
+ }, ms));
309
+ const clearIntervalFn = input.clearIntervalFn ?? ((timer2) => clearInterval(timer2));
310
+ let timer;
311
+ let running = false;
312
+ let inFlight = null;
313
+ const tick = async (reason = input.reason ?? "continuous") => {
314
+ if (inFlight)
315
+ return inFlight;
316
+ inFlight = (async () => {
317
+ const issues = await input.loadIssues();
318
+ return input.service.analyze(issues, { reason });
319
+ })();
320
+ try {
321
+ return await inFlight;
322
+ } finally {
323
+ inFlight = null;
324
+ }
325
+ };
326
+ return {
327
+ start() {
328
+ if (running)
329
+ return;
330
+ running = true;
331
+ timer = setIntervalFn(async () => {
332
+ try {
333
+ await tick();
334
+ } catch (error) {
335
+ input.onError?.(error);
336
+ }
337
+ }, intervalMs);
338
+ },
339
+ stop() {
340
+ if (!running)
341
+ return;
342
+ running = false;
343
+ if (timer !== undefined)
344
+ clearIntervalFn(timer);
345
+ timer = undefined;
346
+ },
347
+ tick,
348
+ isRunning() {
349
+ return running;
350
+ }
351
+ };
352
+ }
353
+ export {
354
+ renderIssueAnalysisPrompt,
355
+ parseIssueAnalysisResult,
356
+ createPiIssueAnalyzer,
357
+ createIssueAnalysisWriteBack,
358
+ createIssueAnalysisService,
359
+ createDefaultPiIssueAnalysisCommandRunner,
360
+ createContinuousIssueAnalysisRunner,
361
+ createConfiguredIssueAnalysisRunner
362
+ };
@@ -0,0 +1,31 @@
1
+ // @bun
2
+ // packages/server/src/server-helpers/normalizers.ts
3
+ function normalizeString(value) {
4
+ return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
5
+ }
6
+ function normalizeSequence(value) {
7
+ const parsed = typeof value === "number" ? value : typeof value === "string" ? Number.parseInt(value, 10) : NaN;
8
+ return Number.isFinite(parsed) ? parsed : null;
9
+ }
10
+ function normalizeStringArray(value) {
11
+ return Array.isArray(value) ? value.map((entry) => normalizeString(entry)).filter((entry) => entry !== null) : [];
12
+ }
13
+ function normalizeRuntimeAdapter(value) {
14
+ const normalized = normalizeString(value)?.toLowerCase();
15
+ if (!normalized) {
16
+ return "pi";
17
+ }
18
+ if (normalized === "codex" || normalized === "codex-cli" || normalized === "codex-app-server" || normalized === "gpt-codex") {
19
+ return "codex";
20
+ }
21
+ if (normalized === "pi" || normalized === "rig-pi" || normalized === "@rig/pi") {
22
+ return "pi";
23
+ }
24
+ return "claude-code";
25
+ }
26
+ export {
27
+ normalizeStringArray,
28
+ normalizeString,
29
+ normalizeSequence,
30
+ normalizeRuntimeAdapter
31
+ };
@@ -0,0 +1,96 @@
1
+ // @bun
2
+ // packages/server/src/server-helpers/notifications.ts
3
+ import { existsSync, mkdirSync, readFileSync } from "fs";
4
+ import { dirname } from "path";
5
+ async function loadNotificationConfig(path) {
6
+ if (!existsSync(path)) {
7
+ const defaultConfig = { targets: [] };
8
+ mkdirSync(dirname(path), { recursive: true });
9
+ await Bun.write(path, `${JSON.stringify(defaultConfig, null, 2)}
10
+ `);
11
+ return defaultConfig;
12
+ }
13
+ try {
14
+ const parsed = await Bun.file(path).json();
15
+ return {
16
+ targets: (parsed.targets || []).filter((target) => !!target && !!target.id && !!target.url)
17
+ };
18
+ } catch {
19
+ return { targets: [] };
20
+ }
21
+ }
22
+ function readRecentEvents(file, limit) {
23
+ if (!existsSync(file)) {
24
+ return [];
25
+ }
26
+ const lines = readFileSync(file, "utf-8").split(/\r?\n/).map((line) => line.trim()).filter(Boolean).slice(-limit);
27
+ const events = [];
28
+ for (const line of lines) {
29
+ try {
30
+ events.push(JSON.parse(line));
31
+ } catch {}
32
+ }
33
+ return events;
34
+ }
35
+ async function dispatchEventToTargets(event, targets) {
36
+ const timeoutMs = resolveNotifyTimeoutMs();
37
+ let sent = 0;
38
+ for (const target of targets) {
39
+ if (target.enabled === false) {
40
+ continue;
41
+ }
42
+ if (target.events && target.events.length > 0 && !target.events.includes(event.type)) {
43
+ continue;
44
+ }
45
+ const payload = buildTargetPayload(target, event);
46
+ const controller = new AbortController;
47
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
48
+ try {
49
+ const response = await fetch(target.url, {
50
+ method: "POST",
51
+ headers: {
52
+ "Content-Type": "application/json",
53
+ ...target.headers || {}
54
+ },
55
+ body: JSON.stringify(payload),
56
+ signal: controller.signal
57
+ });
58
+ if (response.ok) {
59
+ sent += 1;
60
+ } else {
61
+ console.error(`[server] failed to dispatch event ${event.type} to ${target.id}: ${response.status} ${response.statusText}`);
62
+ }
63
+ } catch (error) {
64
+ console.error(`[server] failed to dispatch event ${event.type} to ${target.id}`, error);
65
+ } finally {
66
+ clearTimeout(timer);
67
+ }
68
+ }
69
+ return sent;
70
+ }
71
+ function buildTargetPayload(target, event) {
72
+ if (target.type === "slack-webhook") {
73
+ return {
74
+ text: `[Rig] ${event.type} ${event.runId ? `(run ${event.runId})` : ""}`.trim(),
75
+ event
76
+ };
77
+ }
78
+ return {
79
+ source: "project-rig",
80
+ target: target.id,
81
+ summary: `${event.type}${event.runId ? `:${event.runId}` : ""}`,
82
+ event
83
+ };
84
+ }
85
+ function resolveNotifyTimeoutMs() {
86
+ const raw = process.env.RIG_NOTIFY_TIMEOUT_MS;
87
+ const parsed = raw ? Number.parseInt(raw, 10) : NaN;
88
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : 1e4;
89
+ }
90
+ export {
91
+ resolveNotifyTimeoutMs,
92
+ readRecentEvents,
93
+ loadNotificationConfig,
94
+ dispatchEventToTargets,
95
+ buildTargetPayload
96
+ };