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.
- package/README.md +1 -1
- package/dist/client/assets/{CodeMirrorEditor-DXmxwE2Z.js → CodeMirrorEditor-BuLFJjB1.js} +13 -13
- package/dist/client/assets/CodeMirrorEditor-BuLFJjB1.js.map +1 -0
- package/dist/client/assets/index-CEqSkIuy.css +1 -0
- package/dist/client/assets/index-GubcPYw6.js +375 -0
- package/dist/client/assets/index-GubcPYw6.js.map +1 -0
- package/dist/client/assets/{workbox-window.prod.es5-Cch4wiA5.js → workbox-window.prod.es5-Bd17z0YL.js} +2 -2
- package/dist/client/assets/{workbox-window.prod.es5-Cch4wiA5.js.map → workbox-window.prod.es5-Bd17z0YL.js.map} +1 -1
- package/dist/client/index.html +2 -2
- package/dist/client/sw.js +1 -1
- package/dist/client/sw.js.map +1 -1
- package/dist/server/git-clone.js +364 -0
- package/dist/server/git-clone.js.map +1 -0
- package/dist/server/index.js +22 -0
- package/dist/server/index.js.map +1 -1
- package/dist/server/orchestration/config.js +61 -0
- package/dist/server/orchestration/config.js.map +1 -0
- package/dist/server/orchestration/event-bridge.js +93 -0
- package/dist/server/orchestration/event-bridge.js.map +1 -0
- package/dist/server/orchestration/inbox.js +199 -0
- package/dist/server/orchestration/inbox.js.map +1 -0
- package/dist/server/orchestration/init.js +39 -0
- package/dist/server/orchestration/init.js.map +1 -0
- package/dist/server/orchestration/store.js +352 -0
- package/dist/server/orchestration/store.js.map +1 -0
- package/dist/server/orchestration/tools.js +769 -0
- package/dist/server/orchestration/tools.js.map +1 -0
- package/dist/server/orchestration/types.js +57 -0
- package/dist/server/orchestration/types.js.map +1 -0
- package/dist/server/processes/manager.js +23 -1
- package/dist/server/processes/manager.js.map +1 -1
- package/dist/server/project-manager.js +46 -32
- package/dist/server/project-manager.js.map +1 -1
- package/dist/server/routes/control.js +9 -0
- package/dist/server/routes/control.js.map +1 -1
- package/dist/server/routes/health.js +14 -1
- package/dist/server/routes/health.js.map +1 -1
- package/dist/server/routes/orchestration.js +464 -0
- package/dist/server/routes/orchestration.js.map +1 -0
- package/dist/server/routes/projects.js +239 -14
- package/dist/server/routes/projects.js.map +1 -1
- package/dist/server/routes/sessions.js +53 -34
- package/dist/server/routes/sessions.js.map +1 -1
- package/dist/server/routes/webhooks.js +362 -0
- package/dist/server/routes/webhooks.js.map +1 -0
- package/dist/server/session-registry.js +226 -3
- package/dist/server/session-registry.js.map +1 -1
- package/dist/server/sse-bridge.js +85 -14
- package/dist/server/sse-bridge.js.map +1 -1
- package/dist/server/webhooks/dispatcher.js +254 -0
- package/dist/server/webhooks/dispatcher.js.map +1 -0
- package/dist/server/webhooks/event-bridge.js +185 -0
- package/dist/server/webhooks/event-bridge.js.map +1 -0
- package/dist/server/webhooks/init.js +55 -0
- package/dist/server/webhooks/init.js.map +1 -0
- package/dist/server/webhooks/store.js +394 -0
- package/dist/server/webhooks/store.js.map +1 -0
- package/dist/server/webhooks/types.js +32 -0
- package/dist/server/webhooks/types.js.map +1 -0
- package/package.json +4 -4
- package/dist/client/assets/CodeMirrorEditor-DXmxwE2Z.js.map +0 -1
- package/dist/client/assets/index-CMSjnWtF.js +0 -365
- package/dist/client/assets/index-CMSjnWtF.js.map +0 -1
- package/dist/client/assets/index-Cp8qEy7Q.css +0 -1
|
@@ -0,0 +1,394 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Disk-backed store for webhook configs + delivery history.
|
|
3
|
+
*
|
|
4
|
+
* Two files in `${FORGE_DATA_DIR}/`:
|
|
5
|
+
* - `webhooks.json` — config (one entry per webhook)
|
|
6
|
+
* - `webhook-deliveries.json` — rolling history (FIFO capped per webhook)
|
|
7
|
+
*
|
|
8
|
+
* Separated so config writes (rare, single-tenant operator) don't
|
|
9
|
+
* fight delivery writes (frequent, fired by the agent loop). Same
|
|
10
|
+
* atomic-write + per-file lock pattern as `project-manager.ts`.
|
|
11
|
+
*
|
|
12
|
+
* Single-process / single-tenant: locks are in-process promises,
|
|
13
|
+
* not file locks. Cross-process safety would need a real flock.
|
|
14
|
+
*/
|
|
15
|
+
import { randomUUID } from "node:crypto";
|
|
16
|
+
import { chmod, mkdir, readFile, rename, unlink, writeFile } from "node:fs/promises";
|
|
17
|
+
import { join } from "node:path";
|
|
18
|
+
import { config } from "../config.js";
|
|
19
|
+
import { isWebhookEvent, MAX_DELIVERIES_PER_WEBHOOK, } from "./types.js";
|
|
20
|
+
const WEBHOOKS_FILE = () => join(config.forgeDataDir, "webhooks.json");
|
|
21
|
+
const DELIVERIES_FILE = () => join(config.forgeDataDir, "webhook-deliveries.json");
|
|
22
|
+
async function ensureDir() {
|
|
23
|
+
await mkdir(config.forgeDataDir, { recursive: true });
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Atomic JSON write: tmp file → fsync → rename. The temp filename
|
|
27
|
+
* embeds a uuid so concurrent calls (which shouldn't happen under
|
|
28
|
+
* the lock, but defense in depth) don't collide. The `chmod 0600`
|
|
29
|
+
* after creation matters for `webhooks.json` specifically because
|
|
30
|
+
* it can contain HMAC secrets — same posture as `jwt-secret` /
|
|
31
|
+
* `password-hash`.
|
|
32
|
+
*/
|
|
33
|
+
async function atomicWriteJson(target, data, mode) {
|
|
34
|
+
await ensureDir();
|
|
35
|
+
const tmp = `${target}.${randomUUID()}.tmp`;
|
|
36
|
+
await writeFile(tmp, JSON.stringify(data, null, 2), "utf8");
|
|
37
|
+
try {
|
|
38
|
+
await chmod(tmp, mode);
|
|
39
|
+
await rename(tmp, target);
|
|
40
|
+
}
|
|
41
|
+
catch (err) {
|
|
42
|
+
await unlink(tmp).catch(() => undefined);
|
|
43
|
+
throw err;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
// ---- webhooks.json (config) ----
|
|
47
|
+
let configLock = Promise.resolve();
|
|
48
|
+
function withConfigLock(fn) {
|
|
49
|
+
const next = configLock.then(fn, fn);
|
|
50
|
+
configLock = next.catch(() => undefined);
|
|
51
|
+
return next;
|
|
52
|
+
}
|
|
53
|
+
function isScope(v) {
|
|
54
|
+
if (typeof v !== "object" || v === null)
|
|
55
|
+
return false;
|
|
56
|
+
const r = v;
|
|
57
|
+
if (r.kind === "global")
|
|
58
|
+
return true;
|
|
59
|
+
if (r.kind === "project" && typeof r.projectId === "string")
|
|
60
|
+
return true;
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
function isWebhookConfig(v) {
|
|
64
|
+
if (typeof v !== "object" || v === null)
|
|
65
|
+
return false;
|
|
66
|
+
const r = v;
|
|
67
|
+
return (typeof r.id === "string" &&
|
|
68
|
+
typeof r.name === "string" &&
|
|
69
|
+
typeof r.url === "string" &&
|
|
70
|
+
Array.isArray(r.events) &&
|
|
71
|
+
r.events.every((e) => isWebhookEvent(e)) &&
|
|
72
|
+
isScope(r.scope) &&
|
|
73
|
+
typeof r.enabled === "boolean" &&
|
|
74
|
+
typeof r.createdAt === "string");
|
|
75
|
+
}
|
|
76
|
+
export async function readWebhooks() {
|
|
77
|
+
await ensureDir();
|
|
78
|
+
try {
|
|
79
|
+
const raw = await readFile(WEBHOOKS_FILE(), "utf8");
|
|
80
|
+
const parsed = JSON.parse(raw);
|
|
81
|
+
if (!Array.isArray(parsed))
|
|
82
|
+
return [];
|
|
83
|
+
return parsed.filter(isWebhookConfig);
|
|
84
|
+
}
|
|
85
|
+
catch (err) {
|
|
86
|
+
if (err.code === "ENOENT")
|
|
87
|
+
return [];
|
|
88
|
+
throw err;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
async function writeWebhooks(webhooks) {
|
|
92
|
+
// mode 0o600 — webhooks.json holds HMAC secrets. Same posture as
|
|
93
|
+
// ~/.pi-forge/jwt-secret and ~/.pi-forge/password-hash.
|
|
94
|
+
await atomicWriteJson(WEBHOOKS_FILE(), webhooks, 0o600);
|
|
95
|
+
}
|
|
96
|
+
export class WebhookNotFoundError extends Error {
|
|
97
|
+
code = "webhook_not_found";
|
|
98
|
+
constructor(id) {
|
|
99
|
+
super(`Webhook not found: ${id}`);
|
|
100
|
+
this.name = "WebhookNotFoundError";
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
export class InvalidWebhookError extends Error {
|
|
104
|
+
code;
|
|
105
|
+
constructor(code, message) {
|
|
106
|
+
super(message);
|
|
107
|
+
this.code = code;
|
|
108
|
+
this.name = "InvalidWebhookError";
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Same `***REDACTED***` sentinel `config-manager.ts` uses for inline
|
|
113
|
+
* `apiKey` in models.json. The webhook headers map can hold things
|
|
114
|
+
* like `Authorization: Bearer …` — we round-trip header VALUES
|
|
115
|
+
* through this sentinel so the wire never carries the real value
|
|
116
|
+
* and editing in the UI doesn't overwrite the stored secret with
|
|
117
|
+
* the sentinel string. Header NAMES stay visible (knowing a webhook
|
|
118
|
+
* has an `Authorization` header isn't sensitive; the value is).
|
|
119
|
+
*/
|
|
120
|
+
export const SECRET_PLACEHOLDER = "***REDACTED***";
|
|
121
|
+
/**
|
|
122
|
+
* Build the wire-safe headers object: same keys, every value
|
|
123
|
+
* replaced with the sentinel. The persisted file stays unchanged;
|
|
124
|
+
* this redaction is purely on the read path. The companion
|
|
125
|
+
* `mergeHeadersOnWrite` below restores the real values when the
|
|
126
|
+
* client PATCHes back unchanged headers.
|
|
127
|
+
*/
|
|
128
|
+
export function redactHeaders(headers) {
|
|
129
|
+
if (headers === undefined)
|
|
130
|
+
return undefined;
|
|
131
|
+
const out = {};
|
|
132
|
+
for (const name of Object.keys(headers)) {
|
|
133
|
+
out[name] = SECRET_PLACEHOLDER;
|
|
134
|
+
}
|
|
135
|
+
return out;
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* On PATCH, treat any header VALUE that's literally the sentinel
|
|
139
|
+
* as "keep the existing value." Same semantics as the apiKey
|
|
140
|
+
* round-trip in writeModelsJson. New headers (typed-in values, not
|
|
141
|
+
* sentinel) replace; header NAMES that were dropped from the
|
|
142
|
+
* incoming map are dropped from storage.
|
|
143
|
+
*
|
|
144
|
+
* Returns `undefined` for the empty case so the caller can drop
|
|
145
|
+
* the field entirely from the stored config (rather than persist
|
|
146
|
+
* an empty object).
|
|
147
|
+
*/
|
|
148
|
+
export function mergeHeadersOnWrite(incoming, existing) {
|
|
149
|
+
const out = {};
|
|
150
|
+
for (const [name, value] of Object.entries(incoming)) {
|
|
151
|
+
if (value === SECRET_PLACEHOLDER) {
|
|
152
|
+
const prior = existing?.[name];
|
|
153
|
+
if (prior !== undefined)
|
|
154
|
+
out[name] = prior;
|
|
155
|
+
// No prior value → silently drop. The sentinel was sent
|
|
156
|
+
// because the UI showed it, but if there's nothing to keep
|
|
157
|
+
// (e.g. the existing config was just deleted), skip.
|
|
158
|
+
}
|
|
159
|
+
else {
|
|
160
|
+
out[name] = value;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return Object.keys(out).length === 0 ? undefined : out;
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Validate a webhook URL: HTTPS only (same rationale as the clone
|
|
167
|
+
* route — embedding secrets / tokens over cleartext is asking for
|
|
168
|
+
* trouble). HTTP is rejected even on the explicit insecureTls
|
|
169
|
+
* path; `insecureTls` only relaxes cert validation, not the
|
|
170
|
+
* scheme.
|
|
171
|
+
*/
|
|
172
|
+
export function validateWebhookUrl(raw) {
|
|
173
|
+
let parsed;
|
|
174
|
+
try {
|
|
175
|
+
parsed = new URL(raw);
|
|
176
|
+
}
|
|
177
|
+
catch {
|
|
178
|
+
throw new InvalidWebhookError("invalid_url", `Not a valid URL: ${raw}`);
|
|
179
|
+
}
|
|
180
|
+
if (parsed.protocol !== "https:") {
|
|
181
|
+
throw new InvalidWebhookError("unsupported_protocol", `Only HTTPS webhook URLs are supported (got ${parsed.protocol}).`);
|
|
182
|
+
}
|
|
183
|
+
if (parsed.hostname.length === 0) {
|
|
184
|
+
throw new InvalidWebhookError("invalid_url", "URL is missing a host.");
|
|
185
|
+
}
|
|
186
|
+
return parsed;
|
|
187
|
+
}
|
|
188
|
+
export async function createWebhook(input) {
|
|
189
|
+
const name = input.name.trim();
|
|
190
|
+
if (name.length === 0) {
|
|
191
|
+
throw new InvalidWebhookError("invalid_name", "Webhook name cannot be empty.");
|
|
192
|
+
}
|
|
193
|
+
validateWebhookUrl(input.url);
|
|
194
|
+
if (input.events.length === 0) {
|
|
195
|
+
throw new InvalidWebhookError("no_events", "Webhook must subscribe to at least one event.");
|
|
196
|
+
}
|
|
197
|
+
if (input.secret === SECRET_PLACEHOLDER) {
|
|
198
|
+
throw new InvalidWebhookError("invalid_secret", "Secret cannot be the redaction sentinel.");
|
|
199
|
+
}
|
|
200
|
+
if (input.headers !== undefined) {
|
|
201
|
+
for (const value of Object.values(input.headers)) {
|
|
202
|
+
if (value === SECRET_PLACEHOLDER) {
|
|
203
|
+
// No prior config exists on create — a sentinel value here
|
|
204
|
+
// would persist as a literal "***REDACTED***" header value
|
|
205
|
+
// and confuse subsequent edits. Reject explicitly.
|
|
206
|
+
throw new InvalidWebhookError("invalid_header", "Header values cannot be the redaction sentinel on create.");
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
return withConfigLock(async () => {
|
|
211
|
+
const list = await readWebhooks();
|
|
212
|
+
const webhook = {
|
|
213
|
+
id: randomUUID(),
|
|
214
|
+
name,
|
|
215
|
+
url: input.url,
|
|
216
|
+
events: [...input.events],
|
|
217
|
+
scope: input.scope,
|
|
218
|
+
enabled: input.enabled ?? true,
|
|
219
|
+
createdAt: new Date().toISOString(),
|
|
220
|
+
};
|
|
221
|
+
if (input.secret !== undefined && input.secret.length > 0)
|
|
222
|
+
webhook.secret = input.secret;
|
|
223
|
+
if (input.headers !== undefined && Object.keys(input.headers).length > 0)
|
|
224
|
+
webhook.headers = { ...input.headers };
|
|
225
|
+
if (input.insecureTls === true)
|
|
226
|
+
webhook.insecureTls = true;
|
|
227
|
+
list.push(webhook);
|
|
228
|
+
await writeWebhooks(list);
|
|
229
|
+
return webhook;
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
export async function updateWebhook(id, patch) {
|
|
233
|
+
if (patch.url !== undefined)
|
|
234
|
+
validateWebhookUrl(patch.url);
|
|
235
|
+
if (patch.events !== undefined && patch.events.length === 0) {
|
|
236
|
+
throw new InvalidWebhookError("no_events", "Webhook must subscribe to at least one event.");
|
|
237
|
+
}
|
|
238
|
+
if (patch.name !== undefined && patch.name.trim().length === 0) {
|
|
239
|
+
throw new InvalidWebhookError("invalid_name", "Webhook name cannot be empty.");
|
|
240
|
+
}
|
|
241
|
+
return withConfigLock(async () => {
|
|
242
|
+
const list = await readWebhooks();
|
|
243
|
+
const idx = list.findIndex((w) => w.id === id);
|
|
244
|
+
if (idx === -1)
|
|
245
|
+
throw new WebhookNotFoundError(id);
|
|
246
|
+
const existing = list[idx];
|
|
247
|
+
const updated = {
|
|
248
|
+
...existing,
|
|
249
|
+
...(patch.name !== undefined ? { name: patch.name.trim() } : {}),
|
|
250
|
+
...(patch.url !== undefined ? { url: patch.url } : {}),
|
|
251
|
+
...(patch.events !== undefined ? { events: [...patch.events] } : {}),
|
|
252
|
+
...(patch.scope !== undefined ? { scope: patch.scope } : {}),
|
|
253
|
+
...(patch.enabled !== undefined ? { enabled: patch.enabled } : {}),
|
|
254
|
+
};
|
|
255
|
+
// Optional fields: explicit-undefined-in-patch means "clear it".
|
|
256
|
+
// We can't tell `undefined` from "not provided" in a JSON body,
|
|
257
|
+
// so opt for "absent in body = leave alone, empty-string = clear"
|
|
258
|
+
// for secret/headers; insecureTls is a boolean toggle so any
|
|
259
|
+
// explicit value wins.
|
|
260
|
+
//
|
|
261
|
+
// Secret: empty-string clears; the SECRET_PLACEHOLDER sentinel
|
|
262
|
+
// means "keep the existing secret" (the wire never exposes the
|
|
263
|
+
// real value, so a UI round-trip would otherwise overwrite the
|
|
264
|
+
// stored secret with the literal "***REDACTED***" string).
|
|
265
|
+
if (patch.secret !== undefined) {
|
|
266
|
+
if (patch.secret.length === 0)
|
|
267
|
+
delete updated.secret;
|
|
268
|
+
else if (patch.secret === SECRET_PLACEHOLDER) {
|
|
269
|
+
// Leave `updated.secret` whatever the spread of `existing`
|
|
270
|
+
// carried — i.e., no change.
|
|
271
|
+
}
|
|
272
|
+
else
|
|
273
|
+
updated.secret = patch.secret;
|
|
274
|
+
}
|
|
275
|
+
if (patch.headers !== undefined) {
|
|
276
|
+
// Round-trip protection for header VALUES (Bearer tokens
|
|
277
|
+
// etc.). Per-name: sentinel → keep prior; new value → use;
|
|
278
|
+
// missing-from-incoming → drop. `mergeHeadersOnWrite`
|
|
279
|
+
// returns undefined when the merged map is empty, which
|
|
280
|
+
// collapses to "no headers configured."
|
|
281
|
+
const merged = mergeHeadersOnWrite(patch.headers, existing.headers);
|
|
282
|
+
if (merged === undefined)
|
|
283
|
+
delete updated.headers;
|
|
284
|
+
else
|
|
285
|
+
updated.headers = merged;
|
|
286
|
+
}
|
|
287
|
+
if (patch.insecureTls !== undefined) {
|
|
288
|
+
if (patch.insecureTls)
|
|
289
|
+
updated.insecureTls = true;
|
|
290
|
+
else
|
|
291
|
+
delete updated.insecureTls;
|
|
292
|
+
}
|
|
293
|
+
list[idx] = updated;
|
|
294
|
+
await writeWebhooks(list);
|
|
295
|
+
return updated;
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
export async function deleteWebhook(id) {
|
|
299
|
+
await withConfigLock(async () => {
|
|
300
|
+
const list = await readWebhooks();
|
|
301
|
+
const next = list.filter((w) => w.id !== id);
|
|
302
|
+
if (next.length === list.length)
|
|
303
|
+
throw new WebhookNotFoundError(id);
|
|
304
|
+
await writeWebhooks(next);
|
|
305
|
+
});
|
|
306
|
+
// Best-effort: prune this webhook's delivery records too. A
|
|
307
|
+
// failure here just leaves orphan records that nothing reads;
|
|
308
|
+
// the deletion of the config itself is already done.
|
|
309
|
+
await withDeliveriesLock(async () => {
|
|
310
|
+
const records = await readDeliveriesRaw();
|
|
311
|
+
const pruned = records.filter((r) => r.webhookId !== id);
|
|
312
|
+
if (pruned.length !== records.length) {
|
|
313
|
+
await writeDeliveries(pruned);
|
|
314
|
+
}
|
|
315
|
+
}).catch(() => undefined);
|
|
316
|
+
}
|
|
317
|
+
export async function getWebhook(id) {
|
|
318
|
+
const list = await readWebhooks();
|
|
319
|
+
return list.find((w) => w.id === id);
|
|
320
|
+
}
|
|
321
|
+
// ---- webhook-deliveries.json (history) ----
|
|
322
|
+
let deliveriesLock = Promise.resolve();
|
|
323
|
+
function withDeliveriesLock(fn) {
|
|
324
|
+
const next = deliveriesLock.then(fn, fn);
|
|
325
|
+
deliveriesLock = next.catch(() => undefined);
|
|
326
|
+
return next;
|
|
327
|
+
}
|
|
328
|
+
function isDeliveryRecord(v) {
|
|
329
|
+
if (typeof v !== "object" || v === null)
|
|
330
|
+
return false;
|
|
331
|
+
const r = v;
|
|
332
|
+
return (typeof r.id === "string" &&
|
|
333
|
+
typeof r.webhookId === "string" &&
|
|
334
|
+
typeof r.deliveryId === "string" &&
|
|
335
|
+
typeof r.event === "string" &&
|
|
336
|
+
typeof r.attempt === "number" &&
|
|
337
|
+
(r.status === "delivered" || r.status === "failed" || r.status === "error") &&
|
|
338
|
+
typeof r.durationMs === "number" &&
|
|
339
|
+
typeof r.requestedAt === "string");
|
|
340
|
+
}
|
|
341
|
+
async function readDeliveriesRaw() {
|
|
342
|
+
await ensureDir();
|
|
343
|
+
try {
|
|
344
|
+
const raw = await readFile(DELIVERIES_FILE(), "utf8");
|
|
345
|
+
const parsed = JSON.parse(raw);
|
|
346
|
+
if (!Array.isArray(parsed))
|
|
347
|
+
return [];
|
|
348
|
+
return parsed.filter(isDeliveryRecord);
|
|
349
|
+
}
|
|
350
|
+
catch (err) {
|
|
351
|
+
if (err.code === "ENOENT")
|
|
352
|
+
return [];
|
|
353
|
+
throw err;
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
async function writeDeliveries(records) {
|
|
357
|
+
// Deliveries don't contain secrets but mode 0600 anyway: the
|
|
358
|
+
// file lives alongside webhooks.json and there's no reason for
|
|
359
|
+
// anything outside the pi-forge process to read it.
|
|
360
|
+
await atomicWriteJson(DELIVERIES_FILE(), records, 0o600);
|
|
361
|
+
}
|
|
362
|
+
/**
|
|
363
|
+
* Append a delivery record and trim the per-webhook history to the
|
|
364
|
+
* latest `MAX_DELIVERIES_PER_WEBHOOK` entries. FIFO eviction so
|
|
365
|
+
* the most recent N stays visible in the UI.
|
|
366
|
+
*/
|
|
367
|
+
export async function recordDelivery(record) {
|
|
368
|
+
await withDeliveriesLock(async () => {
|
|
369
|
+
const list = await readDeliveriesRaw();
|
|
370
|
+
list.push(record);
|
|
371
|
+
// Trim per-webhook. Walk newest-to-oldest, keep first N per id.
|
|
372
|
+
// Cheaper than groupBy on small N (cap=100, total ~few-hundred).
|
|
373
|
+
const kept = [];
|
|
374
|
+
const counts = new Map();
|
|
375
|
+
for (let i = list.length - 1; i >= 0; i--) {
|
|
376
|
+
const r = list[i];
|
|
377
|
+
const n = (counts.get(r.webhookId) ?? 0) + 1;
|
|
378
|
+
counts.set(r.webhookId, n);
|
|
379
|
+
if (n <= MAX_DELIVERIES_PER_WEBHOOK)
|
|
380
|
+
kept.push(r);
|
|
381
|
+
}
|
|
382
|
+
kept.reverse();
|
|
383
|
+
await writeDeliveries(kept);
|
|
384
|
+
});
|
|
385
|
+
}
|
|
386
|
+
/**
|
|
387
|
+
* Return delivery records for one webhook, newest first. Bounded
|
|
388
|
+
* by the per-webhook cap so this is cheap to call from the UI.
|
|
389
|
+
*/
|
|
390
|
+
export async function readDeliveriesForWebhook(webhookId) {
|
|
391
|
+
const all = await readDeliveriesRaw();
|
|
392
|
+
return all.filter((r) => r.webhookId === webhookId).reverse();
|
|
393
|
+
}
|
|
394
|
+
//# sourceMappingURL=store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.js","sourceRoot":"","sources":["../../src/webhooks/store.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wire-shape definitions for the webhook feature. The config file
|
|
3
|
+
* (`${FORGE_DATA_DIR}/webhooks.json`) and the rolling delivery log
|
|
4
|
+
* (`${FORGE_DATA_DIR}/webhook-deliveries.json`) both serialize using
|
|
5
|
+
* these types directly. Adding fields is safe (older
|
|
6
|
+
* configs/deliveries deserialize with the new fields undefined);
|
|
7
|
+
* renaming or removing requires a migration step.
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Canonical event list. Iteration order matches the UI's checklist
|
|
11
|
+
* order; the order is part of the user-visible surface (the
|
|
12
|
+
* Settings tab renders checkboxes in this order).
|
|
13
|
+
*/
|
|
14
|
+
export const WEBHOOK_EVENTS = [
|
|
15
|
+
"agent_end",
|
|
16
|
+
"ask_user_question",
|
|
17
|
+
"process_alert",
|
|
18
|
+
"auto_retry_end",
|
|
19
|
+
"compaction_end",
|
|
20
|
+
"session_created",
|
|
21
|
+
"session_deleted",
|
|
22
|
+
];
|
|
23
|
+
export function isWebhookEvent(v) {
|
|
24
|
+
return typeof v === "string" && WEBHOOK_EVENTS.includes(v);
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Cap on persisted delivery records per webhook. Older deliveries
|
|
28
|
+
* roll off FIFO. 100 is enough for "did my last few fires work?"
|
|
29
|
+
* debugging without letting the file grow unbounded.
|
|
30
|
+
*/
|
|
31
|
+
export const MAX_DELIVERIES_PER_WEBHOOK = 100;
|
|
32
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/webhooks/types.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAgBH;;;;GAIG;AACH,MAAM,CAAC,MAAM,cAAc,GAA4B;IACrD,WAAW;IACX,mBAAmB;IACnB,eAAe;IACf,gBAAgB;IAChB,gBAAgB;IAChB,iBAAiB;IACjB,iBAAiB;CACT,CAAC;AAEX,MAAM,UAAU,cAAc,CAAC,CAAU;IACvC,OAAO,OAAO,CAAC,KAAK,QAAQ,IAAK,cAAoC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;AACpF,CAAC;AAmFD;;;;GAIG;AACH,MAAM,CAAC,MAAM,0BAA0B,GAAG,GAAG,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-forge",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"description": "Browser UI for the pi coding agent — embedded HTTP server with a React workbench (chat, file browser, terminal, git, MCP).",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi",
|
|
@@ -45,9 +45,9 @@
|
|
|
45
45
|
"@fastify/swagger": "^9.5.0",
|
|
46
46
|
"@fastify/swagger-ui": "^5.2.3",
|
|
47
47
|
"@fastify/websocket": "^11.0.2",
|
|
48
|
-
"@earendil-works/pi-agent-core": "0.
|
|
49
|
-
"@earendil-works/pi-ai": "0.
|
|
50
|
-
"@earendil-works/pi-coding-agent": "0.
|
|
48
|
+
"@earendil-works/pi-agent-core": "0.75.5",
|
|
49
|
+
"@earendil-works/pi-ai": "0.75.5",
|
|
50
|
+
"@earendil-works/pi-coding-agent": "0.75.5",
|
|
51
51
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
52
52
|
"exceljs": "^4.4.0",
|
|
53
53
|
"fastify": "^5.3.2",
|