@autono/pinbox-core 0.18.0 → 0.20.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/dist/connectors/github.d.ts +1 -1
- package/dist/connectors/index.d.ts +30 -4
- package/dist/connectors/index.js +3 -3
- package/dist/delivery/openclaw.d.ts +2 -2
- package/dist/delivery/openclaw.js +1 -1
- package/dist/delivery/resume.d.ts +2 -2
- package/dist/delivery/resume.js +1 -1
- package/dist/delivery/router.d.ts +1 -1
- package/dist/delivery/router.js +1 -1
- package/dist/delivery/webhook.d.ts +1 -1
- package/dist/do.d.ts +10 -4
- package/dist/do.js +22 -6
- package/dist/{hub-I67BuX9S.js → hub-Ds7W5n89.js} +2 -2
- package/dist/hub-server.d.ts +2 -2
- package/dist/hub-server.js +1 -1
- package/dist/hub.d.ts +30 -2
- package/dist/hub.js +1 -1
- package/dist/markdown.d.ts +1 -1
- package/dist/markdown.js +5 -0
- package/dist/payload-CmKx5yL2.d.ts +6 -0
- package/dist/{poll-BrcAuaAz.js → poll-ZSv3wN9C.js} +1 -1
- package/dist/{proc-BMp_pbPS.js → proc-CBLeAM7c.js} +1 -1
- package/dist/{router-BH74psWn.js → router-DrYLHCF4.js} +1 -1
- package/dist/{router-DOwWVak2.d.ts → router-NoiuUFb-.d.ts} +3 -3
- package/dist/{schema-BlM4lTLW.d.ts → schema-BLZ3MqMD.d.ts} +101 -1
- package/dist/schema-DDpii7iQ.js +192 -0
- package/dist/schema.d.ts +2 -2
- package/dist/schema.js +2 -163
- package/dist/schema.json +246 -0
- package/dist/{sessions-BOrd8Yvv.d.ts → sessions-Bd7_G5jW.d.ts} +1 -1
- package/dist/sessions.d.ts +1 -1
- package/dist/{slack-Dnq9bnXd.js → slack-C4P0AbuZ.js} +211 -61
- package/dist/{store-IR3o5YwY.js → store-Daziyk0_.js} +1 -1
- package/dist/{store-DAB5CEA1.d.ts → store-QLtACKhW.d.ts} +2 -2
- package/dist/store.d.ts +1 -1
- package/dist/store.js +1 -1
- package/dist/{types-BmfT_m1p.d.ts → types-BIm_s4nu.d.ts} +1 -1
- package/dist/ws-protocol.d.ts +1 -1
- package/package.json +1 -1
- package/dist/payload-CYbjb8ZB.d.ts +0 -6
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { i as inboundEvents } from "./poll-ZSv3wN9C.js";
|
|
1
2
|
import { pinsToMarkdown } from "./markdown.js";
|
|
2
3
|
import { z } from "zod";
|
|
3
4
|
import { SignJWT, importPKCS8 } from "jose";
|
|
@@ -28,38 +29,44 @@ var GithubAppError = class extends Error {
|
|
|
28
29
|
this.hint = hint;
|
|
29
30
|
}
|
|
30
31
|
};
|
|
32
|
+
/** The App's own credential: a short-lived RS256 JWT with `iss` = app id. */
|
|
33
|
+
async function signAppJwt(appId, privateKeyPem, now = () => Date.now()) {
|
|
34
|
+
const key = await importPKCS8(toPkcs8Pem(privateKeyPem), "RS256");
|
|
35
|
+
const iat = Math.floor(now() / 1e3) - 60;
|
|
36
|
+
return new SignJWT({}).setProtectedHeader({
|
|
37
|
+
alg: "RS256",
|
|
38
|
+
typ: "JWT"
|
|
39
|
+
}).setIssuer(appId).setIssuedAt(iat).setExpirationTime(iat + 60 + APP_JWT_TTL_S).sign(key);
|
|
40
|
+
}
|
|
41
|
+
/** GitHub's standard request headers for the REST API. */
|
|
42
|
+
function githubHeaders(bearer, json = false) {
|
|
43
|
+
return headers(bearer, json);
|
|
44
|
+
}
|
|
45
|
+
/** Exchange an App JWT for a one-hour installation token. */
|
|
46
|
+
async function mintInstallationToken(api, installationId, appJwt, fetchImpl) {
|
|
47
|
+
const res = await fetchImpl(`${api}/app/installations/${installationId}/access_tokens`, {
|
|
48
|
+
method: "POST",
|
|
49
|
+
headers: headers(appJwt)
|
|
50
|
+
});
|
|
51
|
+
if (!res.ok) throw new GithubAppError(`GitHub App token request failed: HTTP ${res.status}`, res.status, res.status === 401 ? "check GITHUB_APP_ID and GITHUB_APP_PRIVATE_KEY belong to the same App" : res.status === 404 ? "check GITHUB_INSTALLATION_ID — the App may not be installed on this repo's owner" : void 0);
|
|
52
|
+
const body = await res.json();
|
|
53
|
+
if (typeof body.token !== "string" || typeof body.expires_at !== "string") throw new GithubAppError("GitHub App token response was not { token, expires_at }", 502);
|
|
54
|
+
return {
|
|
55
|
+
token: body.token,
|
|
56
|
+
expiresAt: Date.parse(body.expires_at)
|
|
57
|
+
};
|
|
58
|
+
}
|
|
31
59
|
function createGithubAppTransport(opts) {
|
|
32
60
|
const fetchImpl = opts.fetchImpl ?? ((input, init) => fetch(input, init));
|
|
33
61
|
const now = opts.now ?? (() => Date.now());
|
|
34
62
|
const api = (opts.apiBase ?? "https://api.github.com").replace(/\/+$/, "");
|
|
35
63
|
const repo = opts.repo.replace(/^\/+|\/+$/g, "");
|
|
36
64
|
if (!/^[^/\s]+\/[^/\s]+$/.test(repo)) throw new GithubAppError(`GITHUB_REPO must be "owner/name", got "${opts.repo}"`, 0);
|
|
37
|
-
let keyPromise = null;
|
|
38
65
|
let cached = null;
|
|
39
|
-
function key() {
|
|
40
|
-
keyPromise ??= importPKCS8(toPkcs8Pem(opts.privateKeyPem), "RS256");
|
|
41
|
-
return keyPromise;
|
|
42
|
-
}
|
|
43
|
-
async function appJwt() {
|
|
44
|
-
const iat = Math.floor(now() / 1e3) - 60;
|
|
45
|
-
return new SignJWT({}).setProtectedHeader({
|
|
46
|
-
alg: "RS256",
|
|
47
|
-
typ: "JWT"
|
|
48
|
-
}).setIssuer(opts.appId).setIssuedAt(iat).setExpirationTime(iat + 60 + APP_JWT_TTL_S).sign(await key());
|
|
49
|
-
}
|
|
50
66
|
async function installationToken() {
|
|
51
67
|
if (cached !== null && cached.expiresAt - now() > REFRESH_MARGIN_MS) return cached.token;
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
headers: headers(await appJwt())
|
|
55
|
-
});
|
|
56
|
-
if (!res.ok) throw new GithubAppError(`GitHub App token request failed: HTTP ${res.status}`, res.status, res.status === 401 ? "check GITHUB_APP_ID and GITHUB_APP_PRIVATE_KEY belong to the same App" : res.status === 404 ? "check GITHUB_INSTALLATION_ID — the App may not be installed on this repo's owner" : void 0);
|
|
57
|
-
const body = await res.json();
|
|
58
|
-
if (typeof body.token !== "string" || typeof body.expires_at !== "string") throw new GithubAppError("GitHub App token response was not { token, expires_at }", 502);
|
|
59
|
-
cached = {
|
|
60
|
-
token: body.token,
|
|
61
|
-
expiresAt: Date.parse(body.expires_at)
|
|
62
|
-
};
|
|
68
|
+
const jwt = await signAppJwt(opts.appId, opts.privateKeyPem, now);
|
|
69
|
+
cached = await mintInstallationToken(api, opts.installationId, jwt, fetchImpl);
|
|
63
70
|
return cached.token;
|
|
64
71
|
}
|
|
65
72
|
async function call(method, path, body) {
|
|
@@ -84,44 +91,51 @@ function createGithubAppTransport(opts) {
|
|
|
84
91
|
}
|
|
85
92
|
return out;
|
|
86
93
|
}
|
|
87
|
-
return {
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
state: issue.state === "closed" ? "closed" : "open",
|
|
109
|
-
comments: comments.map((c) => ({
|
|
110
|
-
author: c.user?.login ?? "ghost",
|
|
111
|
-
body: c.body ?? "",
|
|
112
|
-
createdAt: c.created_at
|
|
113
|
-
}))
|
|
114
|
-
};
|
|
115
|
-
}
|
|
116
|
-
case "issue.close":
|
|
117
|
-
await call("PATCH", `/repos/${repo}/issues/${number}`, { state: "closed" });
|
|
118
|
-
return;
|
|
119
|
-
case "issue.reopen":
|
|
120
|
-
await call("PATCH", `/repos/${repo}/issues/${number}`, { state: "open" });
|
|
121
|
-
return;
|
|
122
|
-
default: throw new GithubAppError(`unknown github op: ${op}`, 0);
|
|
94
|
+
return { request: (op, params) => dispatch(op, params, {
|
|
95
|
+
repo,
|
|
96
|
+
call,
|
|
97
|
+
allComments
|
|
98
|
+
}) };
|
|
99
|
+
}
|
|
100
|
+
/** The pinned op vocabulary (github.ts) over GitHub's REST paths. */
|
|
101
|
+
async function dispatch(op, params, api) {
|
|
102
|
+
const { repo, call } = api;
|
|
103
|
+
const number = Number(params["number"]);
|
|
104
|
+
if (NUMBERED_OPS.has(op) && (!Number.isInteger(number) || number <= 0)) throw new GithubAppError(`github ${op} needs a positive issue number`, 0);
|
|
105
|
+
switch (op) {
|
|
106
|
+
case "issue.create": {
|
|
107
|
+
const issue = await call("POST", `/repos/${repo}/issues`, {
|
|
108
|
+
title: params["title"],
|
|
109
|
+
body: params["body"]
|
|
110
|
+
});
|
|
111
|
+
return {
|
|
112
|
+
number: issue.number,
|
|
113
|
+
url: issue.html_url
|
|
114
|
+
};
|
|
123
115
|
}
|
|
124
|
-
|
|
116
|
+
case "issue.comment":
|
|
117
|
+
await call("POST", `/repos/${repo}/issues/${number}/comments`, { body: params["body"] });
|
|
118
|
+
return;
|
|
119
|
+
case "issue.view": {
|
|
120
|
+
const issue = await call("GET", `/repos/${repo}/issues/${number}`);
|
|
121
|
+
const comments = await api.allComments(number);
|
|
122
|
+
return {
|
|
123
|
+
state: issue.state === "closed" ? "closed" : "open",
|
|
124
|
+
comments: comments.map((c) => ({
|
|
125
|
+
author: c.user?.login ?? "ghost",
|
|
126
|
+
body: c.body ?? "",
|
|
127
|
+
createdAt: c.created_at
|
|
128
|
+
}))
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
case "issue.close":
|
|
132
|
+
await call("PATCH", `/repos/${repo}/issues/${number}`, { state: "closed" });
|
|
133
|
+
return;
|
|
134
|
+
case "issue.reopen":
|
|
135
|
+
await call("PATCH", `/repos/${repo}/issues/${number}`, { state: "open" });
|
|
136
|
+
return;
|
|
137
|
+
default: throw new GithubAppError(`unknown github op: ${op}`, 0);
|
|
138
|
+
}
|
|
125
139
|
}
|
|
126
140
|
function headers(bearer, json = false) {
|
|
127
141
|
return {
|
|
@@ -203,6 +217,142 @@ function concat(...parts) {
|
|
|
203
217
|
return out;
|
|
204
218
|
}
|
|
205
219
|
//#endregion
|
|
220
|
+
//#region src/connectors/github-webhook.ts
|
|
221
|
+
/** Trailer marking bodies pinbox itself wrote (github.ts); never mirror those back in. */
|
|
222
|
+
const PINBOX_TRAILER = "— pinbox";
|
|
223
|
+
/**
|
|
224
|
+
* Handle one webhook delivery. Returns the hub's machine envelope: 200 with
|
|
225
|
+
* `{ applied, ignored }`, 401 `E_AUTH` on a signature failure, 400 `E_INVALID_INPUT` on a
|
|
226
|
+
* body GitHub would never send.
|
|
227
|
+
*/
|
|
228
|
+
async function handleGithubWebhook(req, store, opts) {
|
|
229
|
+
const raw = await req.text();
|
|
230
|
+
const signature = req.headers.get("x-hub-signature-256");
|
|
231
|
+
if (!await signatureValid(opts.secret, raw, signature)) return envelope(401, {
|
|
232
|
+
code: "E_AUTH",
|
|
233
|
+
message: "webhook signature missing or invalid",
|
|
234
|
+
hint: "the App's webhook secret must equal GITHUB_WEBHOOK_SECRET on the hub"
|
|
235
|
+
});
|
|
236
|
+
let payload;
|
|
237
|
+
try {
|
|
238
|
+
payload = JSON.parse(raw);
|
|
239
|
+
} catch {
|
|
240
|
+
return envelope(400, {
|
|
241
|
+
code: "E_INVALID_INPUT",
|
|
242
|
+
message: "webhook body is not JSON"
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
const event = req.headers.get("x-github-event") ?? "";
|
|
246
|
+
return envelope(200, void 0, await apply(store, opts.repo, event, payload));
|
|
247
|
+
}
|
|
248
|
+
async function apply(store, repo, event, payload) {
|
|
249
|
+
if (event === "ping") return {
|
|
250
|
+
applied: 0,
|
|
251
|
+
ignored: "ping"
|
|
252
|
+
};
|
|
253
|
+
if (event !== "issues" && event !== "issue_comment") return {
|
|
254
|
+
applied: 0,
|
|
255
|
+
ignored: `event ${event}`
|
|
256
|
+
};
|
|
257
|
+
const body = payload;
|
|
258
|
+
const fullName = body.repository?.full_name;
|
|
259
|
+
if (typeof fullName !== "string" || fullName.toLowerCase() !== repo.toLowerCase()) return {
|
|
260
|
+
applied: 0,
|
|
261
|
+
ignored: `repository ${fullName ?? "?"}`
|
|
262
|
+
};
|
|
263
|
+
const number = body.issue?.number;
|
|
264
|
+
if (typeof number !== "number") return {
|
|
265
|
+
applied: 0,
|
|
266
|
+
ignored: "no issue"
|
|
267
|
+
};
|
|
268
|
+
const row = store.links.all().find((r) => r.link.connector === "github" && r.link.ref === String(number));
|
|
269
|
+
if (row === void 0) return {
|
|
270
|
+
applied: 0,
|
|
271
|
+
ignored: `issue #${number} is not linked`
|
|
272
|
+
};
|
|
273
|
+
const { events } = inboundEvents(store, row.pinId, "github", false);
|
|
274
|
+
return event === "issue_comment" ? applyComment(body, row.link, events) : applyStatus(body, row.link, events);
|
|
275
|
+
}
|
|
276
|
+
async function applyComment(body, link, events) {
|
|
277
|
+
if (body.action !== "created") return {
|
|
278
|
+
applied: 0,
|
|
279
|
+
ignored: `issue_comment ${body.action}`
|
|
280
|
+
};
|
|
281
|
+
const comment = body.comment;
|
|
282
|
+
const text = comment?.body ?? "";
|
|
283
|
+
if (isOwnMirror(text) || comment?.user?.type === "Bot") return {
|
|
284
|
+
applied: 0,
|
|
285
|
+
ignored: "own mirror"
|
|
286
|
+
};
|
|
287
|
+
await events.onRemoteComment(link, {
|
|
288
|
+
origin: `github:${comment?.user?.login ?? "ghost"}`,
|
|
289
|
+
text,
|
|
290
|
+
at: comment?.created_at ?? (/* @__PURE__ */ new Date()).toISOString()
|
|
291
|
+
});
|
|
292
|
+
return {
|
|
293
|
+
applied: 1,
|
|
294
|
+
ignored: null
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
async function applyStatus(body, link, events) {
|
|
298
|
+
if (body.action === "closed") {
|
|
299
|
+
await events.onRemoteStatus(link, "closed");
|
|
300
|
+
return {
|
|
301
|
+
applied: 1,
|
|
302
|
+
ignored: null
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
if (body.action === "reopened") {
|
|
306
|
+
await events.onRemoteStatus(link, "open");
|
|
307
|
+
return {
|
|
308
|
+
applied: 1,
|
|
309
|
+
ignored: null
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
return {
|
|
313
|
+
applied: 0,
|
|
314
|
+
ignored: `issues ${body.action}`
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
function isOwnMirror(text) {
|
|
318
|
+
return (text.trimEnd().split("\n").at(-1) ?? "").startsWith(PINBOX_TRAILER);
|
|
319
|
+
}
|
|
320
|
+
/** `sha256=<hex hmac>` over the raw body, compared in constant time. */
|
|
321
|
+
async function signatureValid(secret, rawBody, header) {
|
|
322
|
+
if (header === null || !header.startsWith("sha256=")) return false;
|
|
323
|
+
const expected = await hmacHex(secret, rawBody);
|
|
324
|
+
return timingSafeEqual(header.slice(7), expected);
|
|
325
|
+
}
|
|
326
|
+
async function hmacHex(secret, body) {
|
|
327
|
+
const enc = new TextEncoder();
|
|
328
|
+
const key = await crypto.subtle.importKey("raw", enc.encode(secret), {
|
|
329
|
+
name: "HMAC",
|
|
330
|
+
hash: "SHA-256"
|
|
331
|
+
}, false, ["sign"]);
|
|
332
|
+
const sig = new Uint8Array(await crypto.subtle.sign("HMAC", key, enc.encode(body)));
|
|
333
|
+
let hex = "";
|
|
334
|
+
for (const b of sig) hex += b.toString(16).padStart(2, "0");
|
|
335
|
+
return hex;
|
|
336
|
+
}
|
|
337
|
+
function timingSafeEqual(a, b) {
|
|
338
|
+
if (a.length !== b.length) return false;
|
|
339
|
+
let diff = 0;
|
|
340
|
+
for (let i = 0; i < a.length; i += 1) diff |= a.charCodeAt(i) ^ b.charCodeAt(i);
|
|
341
|
+
return diff === 0;
|
|
342
|
+
}
|
|
343
|
+
function envelope(status, error, data) {
|
|
344
|
+
return new Response(JSON.stringify(error === void 0 ? {
|
|
345
|
+
ok: true,
|
|
346
|
+
data
|
|
347
|
+
} : {
|
|
348
|
+
ok: false,
|
|
349
|
+
error
|
|
350
|
+
}), {
|
|
351
|
+
status,
|
|
352
|
+
headers: { "content-type": "application/json; charset=utf-8" }
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
//#endregion
|
|
206
356
|
//#region src/connectors/slack.ts
|
|
207
357
|
/**
|
|
208
358
|
* request(op, params) → POST https://slack.com/api/<op> (JSON, bearer botToken).
|
|
@@ -302,4 +452,4 @@ function slackTsToMs(ts) {
|
|
|
302
452
|
return Number(ts) * 1e3;
|
|
303
453
|
}
|
|
304
454
|
//#endregion
|
|
305
|
-
export {
|
|
455
|
+
export { GithubAppError as a, mintInstallationToken as c, toPkcs8Pem as d, signatureValid as i, pkcs1ToPkcs8 as l, createSlackTransport as n, createGithubAppTransport as o, handleGithubWebhook as r, githubHeaders as s, createSlackConnector as t, signAppJwt as u };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { a as PinSchema, c as SessionRefSchema, i as PinInputSchema, l as ThreadMessageSchema, r as LinkSchema } from "./schema-DDpii7iQ.js";
|
|
2
2
|
import { a as NotFoundError, i as ConflictError, n as SqliteSessionStore, o as newId } from "./sessions-DrCVTMfI.js";
|
|
3
3
|
import { Database } from "bun:sqlite";
|
|
4
4
|
//#region src/store-deliveries.ts
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { a as Link, c as PinInput, g as ThreadMessage, m as SessionRef, r as Attachment, s as Pin, t as AppliedEdit } from "./schema-
|
|
2
|
-
import { r as SessionStore } from "./sessions-
|
|
1
|
+
import { a as Link, c as PinInput, g as ThreadMessage, m as SessionRef, r as Attachment, s as Pin, t as AppliedEdit } from "./schema-BLZ3MqMD.js";
|
|
2
|
+
import { r as SessionStore } from "./sessions-Bd7_G5jW.js";
|
|
3
3
|
//#region src/store-errors.d.ts
|
|
4
4
|
/** The addressed row does not exist. Mapped to 404 / `E_NOT_FOUND` by the hub. */
|
|
5
5
|
declare class NotFoundError extends Error {}
|
package/dist/store.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as LinkStore, c as PinStore, d as ConflictError, f as NotFoundError, i as DeliveryStore, l as StoredEvent, n as DeliveryRow, o as MIGRATIONS, r as DeliveryStatus, s as Migration, t as CursorStore, u as openStore } from "./store-
|
|
1
|
+
import { a as LinkStore, c as PinStore, d as ConflictError, f as NotFoundError, i as DeliveryStore, l as StoredEvent, n as DeliveryRow, o as MIGRATIONS, r as DeliveryStatus, s as Migration, t as CursorStore, u as openStore } from "./store-QLtACKhW.js";
|
|
2
2
|
export { ConflictError, CursorStore, DeliveryRow, DeliveryStatus, DeliveryStore, LinkStore, MIGRATIONS, Migration, NotFoundError, PinStore, StoredEvent, openStore };
|
package/dist/store.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import { a as NotFoundError, i as ConflictError } from "./sessions-DrCVTMfI.js";
|
|
2
|
-
import { n as openStore, t as MIGRATIONS } from "./store-
|
|
2
|
+
import { n as openStore, t as MIGRATIONS } from "./store-Daziyk0_.js";
|
|
3
3
|
export { ConflictError, MIGRATIONS, NotFoundError, openStore };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as Link, g as ThreadMessage, s as Pin } from "./schema-
|
|
1
|
+
import { a as Link, g as ThreadMessage, s as Pin } from "./schema-BLZ3MqMD.js";
|
|
2
2
|
//#region src/connectors/types.d.ts
|
|
3
3
|
/** Host-injected transport: local = `gh` CLI shell-out (impl in packages/cli, Bun.$); Worker = App token fetch. */
|
|
4
4
|
interface ConnectorTransport {
|
package/dist/ws-protocol.d.ts
CHANGED
package/package.json
CHANGED