pi-forge 1.2.5 → 1.3.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 (64) hide show
  1. package/README.md +1 -1
  2. package/dist/client/assets/{CodeMirrorEditor-DXmxwE2Z.js → CodeMirrorEditor-BuLFJjB1.js} +13 -13
  3. package/dist/client/assets/CodeMirrorEditor-BuLFJjB1.js.map +1 -0
  4. package/dist/client/assets/index-CEqSkIuy.css +1 -0
  5. package/dist/client/assets/index-GubcPYw6.js +375 -0
  6. package/dist/client/assets/index-GubcPYw6.js.map +1 -0
  7. package/dist/client/assets/{workbox-window.prod.es5-Cch4wiA5.js → workbox-window.prod.es5-Bd17z0YL.js} +2 -2
  8. package/dist/client/assets/{workbox-window.prod.es5-Cch4wiA5.js.map → workbox-window.prod.es5-Bd17z0YL.js.map} +1 -1
  9. package/dist/client/index.html +2 -2
  10. package/dist/client/sw.js +1 -1
  11. package/dist/client/sw.js.map +1 -1
  12. package/dist/server/git-clone.js +364 -0
  13. package/dist/server/git-clone.js.map +1 -0
  14. package/dist/server/index.js +22 -0
  15. package/dist/server/index.js.map +1 -1
  16. package/dist/server/orchestration/config.js +61 -0
  17. package/dist/server/orchestration/config.js.map +1 -0
  18. package/dist/server/orchestration/event-bridge.js +93 -0
  19. package/dist/server/orchestration/event-bridge.js.map +1 -0
  20. package/dist/server/orchestration/inbox.js +199 -0
  21. package/dist/server/orchestration/inbox.js.map +1 -0
  22. package/dist/server/orchestration/init.js +39 -0
  23. package/dist/server/orchestration/init.js.map +1 -0
  24. package/dist/server/orchestration/store.js +352 -0
  25. package/dist/server/orchestration/store.js.map +1 -0
  26. package/dist/server/orchestration/tools.js +769 -0
  27. package/dist/server/orchestration/tools.js.map +1 -0
  28. package/dist/server/orchestration/types.js +57 -0
  29. package/dist/server/orchestration/types.js.map +1 -0
  30. package/dist/server/processes/manager.js +23 -1
  31. package/dist/server/processes/manager.js.map +1 -1
  32. package/dist/server/project-manager.js +46 -32
  33. package/dist/server/project-manager.js.map +1 -1
  34. package/dist/server/routes/control.js +9 -0
  35. package/dist/server/routes/control.js.map +1 -1
  36. package/dist/server/routes/health.js +14 -1
  37. package/dist/server/routes/health.js.map +1 -1
  38. package/dist/server/routes/orchestration.js +464 -0
  39. package/dist/server/routes/orchestration.js.map +1 -0
  40. package/dist/server/routes/projects.js +239 -14
  41. package/dist/server/routes/projects.js.map +1 -1
  42. package/dist/server/routes/sessions.js +53 -34
  43. package/dist/server/routes/sessions.js.map +1 -1
  44. package/dist/server/routes/webhooks.js +362 -0
  45. package/dist/server/routes/webhooks.js.map +1 -0
  46. package/dist/server/session-registry.js +226 -3
  47. package/dist/server/session-registry.js.map +1 -1
  48. package/dist/server/sse-bridge.js +85 -14
  49. package/dist/server/sse-bridge.js.map +1 -1
  50. package/dist/server/webhooks/dispatcher.js +254 -0
  51. package/dist/server/webhooks/dispatcher.js.map +1 -0
  52. package/dist/server/webhooks/event-bridge.js +185 -0
  53. package/dist/server/webhooks/event-bridge.js.map +1 -0
  54. package/dist/server/webhooks/init.js +55 -0
  55. package/dist/server/webhooks/init.js.map +1 -0
  56. package/dist/server/webhooks/store.js +394 -0
  57. package/dist/server/webhooks/store.js.map +1 -0
  58. package/dist/server/webhooks/types.js +32 -0
  59. package/dist/server/webhooks/types.js.map +1 -0
  60. package/package.json +4 -4
  61. package/dist/client/assets/CodeMirrorEditor-DXmxwE2Z.js.map +0 -1
  62. package/dist/client/assets/index-CMSjnWtF.js +0 -365
  63. package/dist/client/assets/index-CMSjnWtF.js.map +0 -1
  64. package/dist/client/assets/index-Cp8qEy7Q.css +0 -1
@@ -0,0 +1,254 @@
1
+ /**
2
+ * Webhook delivery: takes a candidate event, finds every webhook
3
+ * that subscribes to it (matching events + scope + enabled), and
4
+ * fires an HTTP POST per match with retry + delivery recording.
5
+ *
6
+ * Per-webhook fire-and-forget — the function returns as soon as
7
+ * the deliveries are kicked off. Retries run in the background.
8
+ * The event source (event-bridge.ts) doesn't block on webhook
9
+ * outcomes.
10
+ *
11
+ * Retry policy:
12
+ * - 2xx → "delivered", no retry.
13
+ * - 4xx → "failed", no retry (consumer's
14
+ * problem; retrying won't help).
15
+ * - 5xx / network error → "error", retry with exponential
16
+ * backoff (1s, 5s, 30s — 3 attempts
17
+ * total counting the initial fire).
18
+ *
19
+ * HMAC: when `webhook.secret` is set, every request includes
20
+ * `X-Pi-Forge-Signature: sha256=<hex of HMAC-SHA256(secret, body)>`.
21
+ * The hex digest is the same convention GitHub webhooks use.
22
+ *
23
+ * `insecureTls: true` swaps the `https.Agent` for one with
24
+ * `rejectUnauthorized: false`. Logged to stderr on every fire
25
+ * so the relaxed security is visible in `docker logs`.
26
+ */
27
+ import { createHmac, randomUUID } from "node:crypto";
28
+ import { Agent as HttpsAgent } from "node:https";
29
+ import { recordDelivery, readWebhooks, SECRET_PLACEHOLDER } from "./store.js";
30
+ /**
31
+ * Reusable agents. The insecure one is allocated lazily — most
32
+ * deployments never set `insecureTls` on any webhook, and we'd
33
+ * rather not have a permissive agent sitting around unless asked.
34
+ */
35
+ const SECURE_AGENT = new HttpsAgent({ keepAlive: true });
36
+ let insecureAgent;
37
+ function getInsecureAgent() {
38
+ if (insecureAgent === undefined) {
39
+ insecureAgent = new HttpsAgent({ keepAlive: true, rejectUnauthorized: false });
40
+ }
41
+ return insecureAgent;
42
+ }
43
+ const BACKOFF_MS = [1_000, 5_000, 30_000];
44
+ const MAX_ATTEMPTS = BACKOFF_MS.length;
45
+ const REQUEST_TIMEOUT_MS = 30_000;
46
+ const ERROR_PREVIEW_LIMIT = 200;
47
+ /**
48
+ * Reserved headers we always set ourselves. User-supplied custom
49
+ * headers for these names are silently overridden — letting a
50
+ * webhook config rewrite `X-Pi-Forge-Signature` would defeat the
51
+ * point of HMAC.
52
+ */
53
+ const RESERVED_HEADERS = new Set([
54
+ "content-type",
55
+ "x-pi-forge-event",
56
+ "x-pi-forge-delivery",
57
+ "x-pi-forge-signature",
58
+ "user-agent",
59
+ ]);
60
+ /**
61
+ * Public entry point. Resolves once all matching webhooks have
62
+ * been queued (kicks off the first attempt synchronously per
63
+ * webhook so the operator can observe failures immediately, then
64
+ * retries continue in the background).
65
+ *
66
+ * Returns the number of webhooks targeted — useful for the test
67
+ * route to surface "0 webhooks matched" feedback.
68
+ */
69
+ export async function dispatch(opts, targeting) {
70
+ const webhooks = await readWebhooks();
71
+ const matches = webhooks.filter((w) => isMatch(w, opts, targeting));
72
+ if (matches.length === 0)
73
+ return 0;
74
+ // Build the payload ONCE — same body across all matching
75
+ // webhooks. Each webhook gets its own deliveryId since each is
76
+ // independently retryable.
77
+ const timestamp = new Date().toISOString();
78
+ for (const webhook of matches) {
79
+ const payload = {
80
+ deliveryId: randomUUID(),
81
+ event: opts.event,
82
+ timestamp,
83
+ ...(opts.sessionId !== undefined ? { sessionId: opts.sessionId } : {}),
84
+ ...(opts.projectId !== undefined ? { projectId: opts.projectId } : {}),
85
+ data: opts.data,
86
+ };
87
+ // Fire-and-forget. Retries handled inside; we don't await the
88
+ // full chain.
89
+ void deliverWithRetries(webhook, payload);
90
+ }
91
+ return matches.length;
92
+ }
93
+ function isMatch(webhook, opts, targeting) {
94
+ if (targeting?.onlyWebhookId !== undefined) {
95
+ return webhook.id === targeting.onlyWebhookId;
96
+ }
97
+ if (!webhook.enabled)
98
+ return false;
99
+ // Event subscription. `webhook.test` always matches if the test
100
+ // route invoked us (handled by targeting above); the real-event
101
+ // dispatch path goes through here and filters strictly.
102
+ if (opts.event === "webhook.test")
103
+ return false;
104
+ if (!webhook.events.includes(opts.event))
105
+ return false;
106
+ // Scope. Global webhooks match every event with a projectId or
107
+ // none. Per-project webhooks only fire when the event carries a
108
+ // matching projectId.
109
+ if (webhook.scope.kind === "project") {
110
+ return opts.projectId !== undefined && opts.projectId === webhook.scope.projectId;
111
+ }
112
+ return true;
113
+ }
114
+ async function deliverWithRetries(webhook, payload) {
115
+ for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {
116
+ const outcome = await deliverOnce(webhook, payload, attempt);
117
+ const record = {
118
+ id: randomUUID(),
119
+ webhookId: webhook.id,
120
+ deliveryId: payload.deliveryId,
121
+ event: payload.event,
122
+ attempt,
123
+ status: outcome.status,
124
+ durationMs: outcome.durationMs,
125
+ requestedAt: outcome.requestedAt,
126
+ ...(payload.sessionId !== undefined ? { sessionId: payload.sessionId } : {}),
127
+ ...(payload.projectId !== undefined ? { projectId: payload.projectId } : {}),
128
+ ...(outcome.statusCode !== undefined ? { statusCode: outcome.statusCode } : {}),
129
+ ...(outcome.errorPreview !== undefined ? { errorPreview: outcome.errorPreview } : {}),
130
+ };
131
+ await recordDelivery(record).catch(() => undefined);
132
+ // Stop on success or terminal-4xx; retry on error.
133
+ if (outcome.status === "delivered" || outcome.status === "failed")
134
+ return;
135
+ if (attempt < MAX_ATTEMPTS) {
136
+ const backoff = BACKOFF_MS[attempt - 1] ?? BACKOFF_MS[BACKOFF_MS.length - 1] ?? 1000;
137
+ await new Promise((resolve) => setTimeout(resolve, backoff));
138
+ }
139
+ }
140
+ }
141
+ async function deliverOnce(webhook, payload, attempt) {
142
+ const body = JSON.stringify(payload);
143
+ const headers = {
144
+ "Content-Type": "application/json; charset=utf-8",
145
+ "User-Agent": "pi-forge-webhook/1.0",
146
+ "X-Pi-Forge-Event": payload.event,
147
+ "X-Pi-Forge-Delivery": payload.deliveryId,
148
+ };
149
+ if (webhook.secret !== undefined && webhook.secret.length > 0) {
150
+ const sig = createHmac("sha256", webhook.secret).update(body).digest("hex");
151
+ headers["X-Pi-Forge-Signature"] = `sha256=${sig}`;
152
+ }
153
+ if (webhook.headers !== undefined) {
154
+ for (const [name, value] of Object.entries(webhook.headers)) {
155
+ if (RESERVED_HEADERS.has(name.toLowerCase()))
156
+ continue;
157
+ // Defense in depth: the CRUD path round-trips header values
158
+ // through the SECRET_PLACEHOLDER sentinel so the wire never
159
+ // exposes them. A hand-edited webhooks.json (or a future
160
+ // bug) could leak the sentinel into stored config — skip
161
+ // here so we never POST literally `Authorization: ***REDACTED***`
162
+ // to the consumer.
163
+ if (value === SECRET_PLACEHOLDER)
164
+ continue;
165
+ headers[name] = value;
166
+ }
167
+ }
168
+ const requestedAt = new Date().toISOString();
169
+ const t0 = Date.now();
170
+ const controller = new AbortController();
171
+ const timeout = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
172
+ timeout.unref();
173
+ if (webhook.insecureTls === true) {
174
+ process.stderr.write(JSON.stringify({
175
+ level: "warn",
176
+ time: requestedAt,
177
+ msg: "webhook-insecure-tls",
178
+ webhookId: webhook.id,
179
+ url: webhook.url,
180
+ event: payload.event,
181
+ attempt,
182
+ }) + "\n");
183
+ }
184
+ try {
185
+ // Node's global fetch routes through undici. We pass the
186
+ // agent via the `dispatcher` undici-extension field — TS's
187
+ // standard RequestInit doesn't know about it, hence the cast.
188
+ // Falls back gracefully on runtimes that ignore the field.
189
+ const agent = webhook.insecureTls === true ? getInsecureAgent() : SECURE_AGENT;
190
+ const init = {
191
+ method: "POST",
192
+ headers,
193
+ body,
194
+ signal: controller.signal,
195
+ agent,
196
+ };
197
+ const res = await fetch(webhook.url, init);
198
+ const durationMs = Date.now() - t0;
199
+ const statusCode = res.status;
200
+ if (statusCode >= 200 && statusCode < 300) {
201
+ return { status: "delivered", statusCode, durationMs, requestedAt };
202
+ }
203
+ if (statusCode >= 400 && statusCode < 500) {
204
+ const text = await safeReadErrorBody(res);
205
+ return {
206
+ status: "failed",
207
+ statusCode,
208
+ durationMs,
209
+ requestedAt,
210
+ ...(text !== undefined ? { errorPreview: text } : {}),
211
+ };
212
+ }
213
+ // 5xx or other non-2xx — retryable.
214
+ const text = await safeReadErrorBody(res);
215
+ return {
216
+ status: "error",
217
+ statusCode,
218
+ durationMs,
219
+ requestedAt,
220
+ ...(text !== undefined ? { errorPreview: text } : {}),
221
+ };
222
+ }
223
+ catch (err) {
224
+ const durationMs = Date.now() - t0;
225
+ const message = err instanceof Error ? err.message : String(err);
226
+ return {
227
+ status: "error",
228
+ durationMs,
229
+ requestedAt,
230
+ errorPreview: message.slice(0, ERROR_PREVIEW_LIMIT),
231
+ };
232
+ }
233
+ finally {
234
+ clearTimeout(timeout);
235
+ }
236
+ }
237
+ /**
238
+ * Read a small slice of the response body for diagnostics.
239
+ * Bounded so a runaway server returning megabytes doesn't blow
240
+ * memory. Body bytes themselves are not persisted in
241
+ * DeliveryRecord — only this preview makes it through.
242
+ */
243
+ async function safeReadErrorBody(res) {
244
+ try {
245
+ const text = await res.text();
246
+ if (text.length === 0)
247
+ return undefined;
248
+ return text.slice(0, ERROR_PREVIEW_LIMIT);
249
+ }
250
+ catch {
251
+ return undefined;
252
+ }
253
+ }
254
+ //# sourceMappingURL=dispatcher.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dispatcher.js","sourceRoot":"","sources":["../../src/webhooks/dispatcher.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,185 @@
1
+ import { dispatch } from "./dispatcher.js";
2
+ /**
3
+ * Called from `session-registry.makeSubscribeHandler` for every
4
+ * AgentSessionEvent that fires on a live session. We filter inside
5
+ * for the 3 SDK events we care about — the rest are ignored.
6
+ */
7
+ export function bridgeAgentSessionEvent(meta, event) {
8
+ // `event` is the SDK's union; some fields are typed loose enough
9
+ // that we cast through unknown to read them. That matches the
10
+ // pattern session-registry.ts itself uses for the same union.
11
+ const e = event;
12
+ if (e.type === "agent_end") {
13
+ // session.messages is post-turn; pull the final assistant
14
+ // message for the payload's primary content. The SDK guarantees
15
+ // session.errorMessage is the authoritative error field on
16
+ // agent_end.
17
+ const messages = meta.session.messages;
18
+ const lastAssistant = findLastAssistant(messages);
19
+ const errorMessage = meta.session.errorMessage ??
20
+ lastAssistant?.errorMessage;
21
+ void dispatch({
22
+ event: "agent_end",
23
+ sessionId: meta.sessionId,
24
+ projectId: meta.projectId,
25
+ data: {
26
+ stopReason: lastAssistant?.stopReason ?? null,
27
+ errorMessage: errorMessage ?? null,
28
+ assistantText: lastAssistant?.text ?? null,
29
+ usage: lastAssistant?.usage ?? null,
30
+ provider: lastAssistant?.provider ?? null,
31
+ model: lastAssistant?.model ?? null,
32
+ },
33
+ });
34
+ return;
35
+ }
36
+ if (e.type === "auto_retry_end" && e.success === false) {
37
+ void dispatch({
38
+ event: "auto_retry_end",
39
+ sessionId: meta.sessionId,
40
+ projectId: meta.projectId,
41
+ data: {
42
+ attempt: e.attempt ?? null,
43
+ maxAttempts: e.maxAttempts ?? null,
44
+ finalError: e.finalError ?? null,
45
+ },
46
+ });
47
+ return;
48
+ }
49
+ if (e.type === "compaction_end") {
50
+ // Only fire on completed compactions (aborted ones aren't
51
+ // useful for "your context just shrank" integrations).
52
+ if (e.aborted === true)
53
+ return;
54
+ void dispatch({
55
+ event: "compaction_end",
56
+ sessionId: meta.sessionId,
57
+ projectId: meta.projectId,
58
+ data: {
59
+ reason: e.reason ?? null,
60
+ tokensBefore: e.tokensBefore ?? null,
61
+ },
62
+ });
63
+ return;
64
+ }
65
+ }
66
+ function findLastAssistant(messages) {
67
+ for (let i = messages.length - 1; i >= 0; i--) {
68
+ const m = messages[i];
69
+ if (m?.role !== "assistant")
70
+ continue;
71
+ const a = m;
72
+ const text = extractAssistantText(a.content);
73
+ const result = {};
74
+ if (a.stopReason !== undefined)
75
+ result.stopReason = a.stopReason;
76
+ if (a.errorMessage !== undefined)
77
+ result.errorMessage = a.errorMessage;
78
+ if (text !== undefined)
79
+ result.text = text;
80
+ if (a.usage !== undefined)
81
+ result.usage = a.usage;
82
+ if (a.provider !== undefined)
83
+ result.provider = a.provider;
84
+ if (a.model !== undefined)
85
+ result.model = a.model;
86
+ return result;
87
+ }
88
+ return undefined;
89
+ }
90
+ function extractAssistantText(content) {
91
+ if (!Array.isArray(content))
92
+ return undefined;
93
+ const parts = [];
94
+ for (const c of content) {
95
+ const o = c;
96
+ if (o.type === "text" && typeof o.text === "string")
97
+ parts.push(o.text);
98
+ }
99
+ if (parts.length === 0)
100
+ return undefined;
101
+ return parts.join("\n");
102
+ }
103
+ /**
104
+ * Called from a subscriber on the ask-user-question registry.
105
+ * Fires the `ask_user_question` webhook when a NEW request lands;
106
+ * cancellations aren't a webhook event (no obvious consumer use
107
+ * case yet).
108
+ */
109
+ export function bridgeAskUserQuestion(meta, questions, requestId) {
110
+ void dispatch({
111
+ event: "ask_user_question",
112
+ sessionId: meta.sessionId,
113
+ projectId: meta.projectId,
114
+ data: {
115
+ requestId,
116
+ questions: questions.map((q) => ({
117
+ header: q.header,
118
+ question: q.question,
119
+ multiSelect: q.multiSelect,
120
+ options: q.options.map((o) => ({
121
+ label: o.label,
122
+ description: o.description,
123
+ })),
124
+ })),
125
+ },
126
+ });
127
+ }
128
+ // ---- processes ----
129
+ /**
130
+ * Called from a subscriber on processManager. Fires the
131
+ * `process_alert` webhook with the same trigger conditions the
132
+ * agent-side alert uses (manager filters alertOn* flags before
133
+ * emitting the manager event).
134
+ */
135
+ export function bridgeProcessAlert(meta, info, reason) {
136
+ void dispatch({
137
+ event: "process_alert",
138
+ sessionId: meta.sessionId,
139
+ projectId: meta.projectId,
140
+ data: {
141
+ reason,
142
+ processId: info.id,
143
+ name: info.name,
144
+ pid: info.pid,
145
+ command: info.command,
146
+ cwd: info.cwd,
147
+ startTime: info.startTime,
148
+ endTime: info.endTime,
149
+ exitCode: info.exitCode,
150
+ success: info.success,
151
+ },
152
+ });
153
+ }
154
+ // ---- session lifecycle ----
155
+ /**
156
+ * Called from session-registry on session creation. Fires
157
+ * `session_created` with enough metadata for an audit-log
158
+ * consumer to record "session X was created in project Y at T."
159
+ */
160
+ export function bridgeSessionCreated(meta) {
161
+ void dispatch({
162
+ event: "session_created",
163
+ sessionId: meta.sessionId,
164
+ projectId: meta.projectId,
165
+ data: {
166
+ workspacePath: meta.workspacePath,
167
+ },
168
+ });
169
+ }
170
+ /**
171
+ * Called from session-registry on session deletion (cold delete
172
+ * via the DELETE route). The wasLive flag distinguishes "we
173
+ * disposed an active session" from "we removed a cold JSONL."
174
+ */
175
+ export function bridgeSessionDeleted(meta) {
176
+ void dispatch({
177
+ event: "session_deleted",
178
+ sessionId: meta.sessionId,
179
+ ...(meta.projectId !== undefined ? { projectId: meta.projectId } : {}),
180
+ data: {
181
+ wasLive: meta.wasLive,
182
+ },
183
+ });
184
+ }
185
+ //# sourceMappingURL=event-bridge.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"event-bridge.js","sourceRoot":"","sources":["../../src/webhooks/event-bridge.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Boot-time hookup of the webhook event-bridge to the forge-native
3
+ * event sources (ask-user-question registry, processManager). The
4
+ * AgentSession-side bridge is wired directly inside
5
+ * `session-registry.makeSubscribeHandler` because that's where the
6
+ * per-session subscription is created and torn down; this module
7
+ * handles the singleton-channel sources.
8
+ *
9
+ * Mirrors the `initAskUserQuestionFanout` / `initProcessesFanout`
10
+ * pattern in `sse-bridge.ts`. Called once from `index.ts` at
11
+ * server boot; the returned unsubscribe is exposed for tests.
12
+ */
13
+ import { subscribe as subscribeAskQuestions } from "../ask-user-question/registry.js";
14
+ import { processManager } from "../processes/manager.js";
15
+ import { getSession } from "../session-registry.js";
16
+ import { bridgeAskUserQuestion, bridgeProcessAlert } from "./event-bridge.js";
17
+ /**
18
+ * Wire `ask_user_question` registry events to the webhook dispatcher.
19
+ * We only fire for fresh requests (not cancellations) since "agent
20
+ * needs you" is the actionable signal; cancellations are bookkeeping.
21
+ *
22
+ * The projectId comes from looking up the live session — the
23
+ * registry event doesn't carry it directly. If the session is
24
+ * tombstoned/disposed in the brief window between event emission
25
+ * and webhook dispatch, we skip rather than fire with a missing
26
+ * project (per-project webhooks would never match anyway).
27
+ */
28
+ export function initAskUserQuestionWebhookBridge() {
29
+ return subscribeAskQuestions((event) => {
30
+ if (event.type !== "ask_user_question")
31
+ return;
32
+ const live = getSession(event.sessionId);
33
+ if (live === undefined)
34
+ return;
35
+ bridgeAskUserQuestion({ sessionId: event.sessionId, projectId: live.projectId }, event.questions, event.requestId);
36
+ });
37
+ }
38
+ /**
39
+ * Wire `processManager` events to the webhook dispatcher. Only
40
+ * the `process_alert` variant gets forwarded — that's the curated
41
+ * "completion with intent to notify" signal the manager already
42
+ * filters on the alertOn* flags. The chatty per-line output and
43
+ * watch-match events stay in the SSE fanout where they belong.
44
+ */
45
+ export function initProcessesWebhookBridge() {
46
+ return processManager.subscribe((event) => {
47
+ if (event.type !== "process_alert")
48
+ return;
49
+ const live = getSession(event.sessionId);
50
+ if (live === undefined)
51
+ return;
52
+ bridgeProcessAlert({ sessionId: event.sessionId, projectId: live.projectId }, event.info, event.reason);
53
+ });
54
+ }
55
+ //# sourceMappingURL=init.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"init.js","sourceRoot":"","sources":["../../src/webhooks/init.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,EAAE,SAAS,IAAI,qBAAqB,EAAE,MAAM,kCAAkC,CAAC;AACtF,OAAO,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AACzD,OAAO,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AACpD,OAAO,EAAE,qBAAqB,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAE9E;;;;;;;;;;GAUG;AACH,MAAM,UAAU,gCAAgC;IAC9C,OAAO,qBAAqB,CAAC,CAAC,KAAK,EAAE,EAAE;QACrC,IAAI,KAAK,CAAC,IAAI,KAAK,mBAAmB;YAAE,OAAO;QAC/C,MAAM,IAAI,GAAG,UAAU,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;QACzC,IAAI,IAAI,KAAK,SAAS;YAAE,OAAO;QAC/B,qBAAqB,CACnB,EAAE,SAAS,EAAE,KAAK,CAAC,SAAS,EAAE,SAAS,EAAE,IAAI,CAAC,SAAS,EAAE,EACzD,KAAK,CAAC,SAAS,EACf,KAAK,CAAC,SAAS,CAChB,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,0BAA0B;IACxC,OAAO,cAAc,CAAC,SAAS,CAAC,CAAC,KAAK,EAAE,EAAE;QACxC,IAAI,KAAK,CAAC,IAAI,KAAK,eAAe;YAAE,OAAO;QAC3C,MAAM,IAAI,GAAG,UAAU,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;QACzC,IAAI,IAAI,KAAK,SAAS;YAAE,OAAO;QAC/B,kBAAkB,CAChB,EAAE,SAAS,EAAE,KAAK,CAAC,SAAS,EAAE,SAAS,EAAE,IAAI,CAAC,SAAS,EAAE,EACzD,KAAK,CAAC,IAAI,EACV,KAAK,CAAC,MAAM,CACb,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC"}