@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.
Files changed (40) hide show
  1. package/dist/connectors/github.d.ts +1 -1
  2. package/dist/connectors/index.d.ts +30 -4
  3. package/dist/connectors/index.js +3 -3
  4. package/dist/delivery/openclaw.d.ts +2 -2
  5. package/dist/delivery/openclaw.js +1 -1
  6. package/dist/delivery/resume.d.ts +2 -2
  7. package/dist/delivery/resume.js +1 -1
  8. package/dist/delivery/router.d.ts +1 -1
  9. package/dist/delivery/router.js +1 -1
  10. package/dist/delivery/webhook.d.ts +1 -1
  11. package/dist/do.d.ts +10 -4
  12. package/dist/do.js +22 -6
  13. package/dist/{hub-I67BuX9S.js → hub-Ds7W5n89.js} +2 -2
  14. package/dist/hub-server.d.ts +2 -2
  15. package/dist/hub-server.js +1 -1
  16. package/dist/hub.d.ts +30 -2
  17. package/dist/hub.js +1 -1
  18. package/dist/markdown.d.ts +1 -1
  19. package/dist/markdown.js +5 -0
  20. package/dist/payload-CmKx5yL2.d.ts +6 -0
  21. package/dist/{poll-BrcAuaAz.js → poll-ZSv3wN9C.js} +1 -1
  22. package/dist/{proc-BMp_pbPS.js → proc-CBLeAM7c.js} +1 -1
  23. package/dist/{router-BH74psWn.js → router-DrYLHCF4.js} +1 -1
  24. package/dist/{router-DOwWVak2.d.ts → router-NoiuUFb-.d.ts} +3 -3
  25. package/dist/{schema-BlM4lTLW.d.ts → schema-BLZ3MqMD.d.ts} +101 -1
  26. package/dist/schema-DDpii7iQ.js +192 -0
  27. package/dist/schema.d.ts +2 -2
  28. package/dist/schema.js +2 -163
  29. package/dist/schema.json +246 -0
  30. package/dist/{sessions-BOrd8Yvv.d.ts → sessions-Bd7_G5jW.d.ts} +1 -1
  31. package/dist/sessions.d.ts +1 -1
  32. package/dist/{slack-Dnq9bnXd.js → slack-C4P0AbuZ.js} +211 -61
  33. package/dist/{store-IR3o5YwY.js → store-Daziyk0_.js} +1 -1
  34. package/dist/{store-DAB5CEA1.d.ts → store-QLtACKhW.d.ts} +2 -2
  35. package/dist/store.d.ts +1 -1
  36. package/dist/store.js +1 -1
  37. package/dist/{types-BmfT_m1p.d.ts → types-BIm_s4nu.d.ts} +1 -1
  38. package/dist/ws-protocol.d.ts +1 -1
  39. package/package.json +1 -1
  40. 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 res = await fetchImpl(`${api}/app/installations/${opts.installationId}/access_tokens`, {
53
- method: "POST",
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 { async request(op, params) {
88
- const number = Number(params["number"]);
89
- if (NUMBERED_OPS.has(op) && (!Number.isInteger(number) || number <= 0)) throw new GithubAppError(`github ${op} needs a positive issue number`, 0);
90
- switch (op) {
91
- case "issue.create": {
92
- const issue = await call("POST", `/repos/${repo}/issues`, {
93
- title: params["title"],
94
- body: params["body"]
95
- });
96
- return {
97
- number: issue.number,
98
- url: issue.html_url
99
- };
100
- }
101
- case "issue.comment":
102
- await call("POST", `/repos/${repo}/issues/${number}/comments`, { body: params["body"] });
103
- return;
104
- case "issue.view": {
105
- const issue = await call("GET", `/repos/${repo}/issues/${number}`);
106
- const comments = await allComments(number);
107
- return {
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 { pkcs1ToPkcs8 as a, createGithubAppTransport as i, createSlackTransport as n, toPkcs8Pem as o, GithubAppError as r, createSlackConnector as t };
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 { LinkSchema, PinInputSchema, PinSchema, SessionRefSchema, ThreadMessageSchema } from "./schema.js";
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-BlM4lTLW.js";
2
- import { r as SessionStore } from "./sessions-BOrd8Yvv.js";
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-DAB5CEA1.js";
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-IR3o5YwY.js";
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-BlM4lTLW.js";
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 {
@@ -1,4 +1,4 @@
1
- import { l as StoredEvent } from "./store-DAB5CEA1.js";
1
+ import { l as StoredEvent } from "./store-QLtACKhW.js";
2
2
  import { z } from "zod";
3
3
  //#region src/ws-protocol.d.ts
4
4
  declare const WS_PATH = "/ws";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@autono/pinbox-core",
3
- "version": "0.18.0",
3
+ "version": "0.20.0",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -1,6 +0,0 @@
1
- import { s as Pin } from "./schema-BlM4lTLW.js";
2
- import "./store-DAB5CEA1.js";
3
- //#region src/delivery/payload.d.ts
4
- type GetPin = (id: string) => Pin | null;
5
- //#endregion
6
- export { GetPin as t };