@vercel/board 0.28.0 → 0.30.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 (139) hide show
  1. package/dist/{a2-B0_VlbtC.mjs → a2-D_28Pdip.mjs} +197 -259
  2. package/dist/access-CQ39gqEV.mjs +226 -0
  3. package/dist/access.d.mts +1 -1
  4. package/dist/access.mjs +3 -3
  5. package/dist/{actors-BLXVp4oZ.mjs → actors-xPiamLWk.mjs} +3 -3
  6. package/dist/{auth-lTkOgvcE.mjs → auth-BPinwfN0.mjs} +3 -226
  7. package/dist/backend-a2/blob-vercel.d.mts +1 -1
  8. package/dist/backend-a2/contract.d.mts +2 -2
  9. package/dist/backend-a2/contract.mjs +3 -3
  10. package/dist/backend-a2/scheduler-local.d.mts +1 -1
  11. package/dist/backend-a2/scheduler-local.mjs +1 -1
  12. package/dist/backend-a2/scheduler-qstash.d.mts +3 -3
  13. package/dist/backend-a2/scheduler-qstash.mjs +8 -8
  14. package/dist/backend-a2/scheduler-vercel.d.mts +3 -3
  15. package/dist/backend-a2/scheduler-vercel.mjs +6 -6
  16. package/dist/backend-a2/store-memory.d.mts +2 -2
  17. package/dist/backend-a2/store-memory.mjs +3 -3
  18. package/dist/backend-a2/store-postgres.d.mts +1 -1
  19. package/dist/backend-a2/store-postgres.mjs +34 -15
  20. package/dist/backend-a2/store-redis-http.d.mts +1 -1
  21. package/dist/backend-a2/store-redis-http.mjs +6 -6
  22. package/dist/backend-a2/store-redis.d.mts +2 -2
  23. package/dist/backend-a2/store-redis.mjs +5 -5
  24. package/dist/backend-a2/store-sqlite.d.mts +1 -1
  25. package/dist/backend-a2/store-sqlite.mjs +3 -3
  26. package/dist/backend-a2.d.mts +20 -7
  27. package/dist/backend-a2.mjs +2 -1
  28. package/dist/{backend-http-BvNFrvcY.mjs → backend-http-16SbvnAK.mjs} +2 -2
  29. package/dist/{backend-http-E7YU0qhn.d.mts → backend-http-3PoBYQoo.d.mts} +15 -3
  30. package/dist/{backend-http-CfLVYqmj.mjs → backend-http-BPaNYLWj.mjs} +4 -5
  31. package/dist/backend-http.d.mts +1 -1
  32. package/dist/backend-http.mjs +1 -1
  33. package/dist/backend-http.node.d.mts +1 -1
  34. package/dist/backend-http.node.mjs +1 -1
  35. package/dist/{backend-ZKYTe3xa.d.mts → backend-n3hajfrD.d.mts} +12 -6
  36. package/dist/{backend-vercel-D87pIp2k.mjs → backend-vercel-C4Kee2-K.mjs} +260 -30
  37. package/dist/{backend-vercel-DTrDiSk5.mjs → backend-vercel-Kd8nvW5A.mjs} +3 -3
  38. package/dist/{backend-vercel-CIRObA0t.d.mts → backend-vercel-qznuDxnN.d.mts} +20 -5
  39. package/dist/backend-vercel.d.mts +1 -1
  40. package/dist/backend-vercel.mjs +1 -1
  41. package/dist/backend-vercel.node.d.mts +1 -1
  42. package/dist/backend-vercel.node.mjs +1 -1
  43. package/dist/{blob-B-9AvYis.d.mts → blob-CqILvdmr.d.mts} +2 -2
  44. package/dist/blob-DQK5prXT.mjs +182 -0
  45. package/dist/{chat-provider-DnHibqnM.mjs → chat-provider-COtsU5jB.mjs} +2 -2
  46. package/dist/{chat-provider-DsNEBQiA.d.mts → chat-provider-D6jVT4wS.d.mts} +5 -3
  47. package/dist/cli-commands.d.mts +1 -1
  48. package/dist/cli-commands.mjs +1 -1
  49. package/dist/cli-embedded.d.mts +2 -2
  50. package/dist/cli-embedded.mjs +1 -1
  51. package/dist/cli.mjs +5 -5
  52. package/dist/{client-xLxeDz0f.d.mts → client-CjAatpV4.d.mts} +67 -10
  53. package/dist/{commands-BFookFtn.mjs → commands-B5DHjZKK.mjs} +54 -10
  54. package/dist/{commands-D6gvrOh3.d.mts → commands-BtH69yqt.d.mts} +17 -0
  55. package/dist/{conformance-DlnfuzEB.mjs → conformance-C7fwIvmN.mjs} +258 -18
  56. package/dist/conformance.d.mts +1 -1
  57. package/dist/conformance.mjs +1 -1
  58. package/dist/{engine-Bfe6pcXb.mjs → engine-DkdkXQEy.mjs} +21 -13
  59. package/dist/{errors-CB4tnZGz.d.mts → errors-87TVtXeb.d.mts} +1 -1
  60. package/dist/{client-D0Px7KzF.mjs → errors-C_5rB8Iz.mjs} +104 -4
  61. package/dist/{events-CewD4kw7.mjs → events-qf5XG5VQ.mjs} +2 -2
  62. package/dist/{exports-CCzw2Vdw.mjs → exports-CG0P8Qi4.mjs} +59 -33
  63. package/dist/exports-CrGA5_yN.d.mts +5 -0
  64. package/dist/{external-BV_cUncs.mjs → external-D3p5T57R.mjs} +4 -2
  65. package/dist/{folds-jS9wlh9_.d.mts → folds-8wRRXjAt.d.mts} +4 -0
  66. package/dist/{folds-Dyjput8M.mjs → folds-DGYkMW19.mjs} +3 -2
  67. package/dist/{index-C05it0mC.d.mts → index-BygAXP7t.d.mts} +2 -2
  68. package/dist/index.d.mts +6 -6
  69. package/dist/{index.default-DmKxH_E6.mjs → index.default-DhkgpfR2.mjs} +1 -1
  70. package/dist/index.mjs +5 -6
  71. package/dist/index.node.d.mts +6 -6
  72. package/dist/index.node.mjs +3 -4
  73. package/dist/inspect/app.js +29 -29
  74. package/dist/inspect/engine.js +5 -5
  75. package/dist/{internal-CvsrprnU-CRY89I-b.mjs → internal-CvsrprnU-zZMpDiRY.mjs} +1 -1
  76. package/dist/{local-DhkrAqPP.mjs → local-C76Fy5ad.mjs} +1 -1
  77. package/dist/{main-eci7w-aP.mjs → main-D2s-QBOK.mjs} +76 -26
  78. package/dist/{middleware-ChKM1zuu.mjs → middleware-DlJQYiwV.mjs} +2 -2
  79. package/dist/{node-Zd_C-gxP.mjs → node-D8Rtg1su.mjs} +1 -1
  80. package/dist/{platform-B4TnJtWu-CwcTyKg2.mjs → platform-B4TnJtWu-Do3agKr5.mjs} +1 -1
  81. package/dist/{project-DMDST9kl.mjs → project-C_2xs57L.mjs} +1 -1
  82. package/dist/{provider-poIkXapd.mjs → provider-BaVmi4Hi.mjs} +3 -1
  83. package/dist/{provider-Cdg1qadh.d.mts → provider-Bm46LAdp.d.mts} +9 -2
  84. package/dist/{redis-tables-DN9PfIDi.mjs → redis-tables-BWJZHUGg.mjs} +41 -25
  85. package/dist/{reducer-CjmvZSAb-D_OSISmj.mjs → reducer-Bswr11ar-P324o9vn.mjs} +2 -2
  86. package/dist/{reducer-CYPHsSmd-BQIfozGi.d.mts → reducer-CYPHsSmd-BXjGiptm.d.mts} +1 -1
  87. package/dist/{reducers-BcmSs4qf.mjs → reducers-8jqLewgw.mjs} +5 -5
  88. package/dist/{retryable-lazy-DZWmHpii-Bye-W8q7.mjs → retryable-lazy-DZWmHpii-DpTc5PIP.mjs} +1 -1
  89. package/dist/{scheduler-task-X1SToDjP-BVoWuInI.mjs → scheduler-task-X1SToDjP-D2Il2rln.mjs} +1 -1
  90. package/dist/{schemas-Be08nyfW.mjs → schemas-DBmUO6pr.mjs} +2 -1
  91. package/dist/{serve-B09an9DQ.mjs → serve-lPoBDEds.mjs} +7 -6
  92. package/dist/{server-qgez0Xny-D5mLfqFQ.d.mts → server-B9gu0qi1-DI1WvcVK.d.mts} +10 -4
  93. package/dist/{server-DTX0prGr.mjs → server-D82a4uMe.mjs} +4 -4
  94. package/dist/{server-DtFrWxi3-Boa6gdWO.mjs → server-DX7mM6nm-D7N_7bug.mjs} +32 -23
  95. package/dist/{serving-CvOWEym2.mjs → serving-DKny38LK.mjs} +1 -1
  96. package/dist/{session-clear-wHd5GCpZ-BnKSxReK.mjs → session-clear-XJYpmUpg-2q8l_FJR.mjs} +14 -2
  97. package/dist/{sql-U9WTxrwJ.mjs → sql-Bj0fk6dn.mjs} +2 -2
  98. package/dist/{sqlite-db-Ds4majtT.mjs → sqlite-db-Dc8G78XV.mjs} +1 -1
  99. package/dist/{store-D0MhBvg6-4Z2L0Kqd.d.mts → store-DEzcHZOA-B-5vhxef.d.mts} +12 -2
  100. package/dist/store-N8PXxDAS-DWWgv-Vz.mjs +5 -0
  101. package/dist/{store-codec-DwrWlgQx-CYxGW0aD.mjs → store-codec-pAG2oPgQ-SqDXlPBR.mjs} +4 -4
  102. package/dist/store-memory-4IBl0Ztu.mjs +2 -0
  103. package/dist/{store-memory-Bas9Uq5C.mjs → store-memory-B0PkVSuQ.mjs} +14 -7
  104. package/dist/{store-polling-BZt2Rf3D-BiEM6_HB.mjs → store-polling-BZt2Rf3D-Div-bJ_D.mjs} +2 -2
  105. package/dist/{store-presence-polling-JuAy2Ck2-CYpuK75X.mjs → store-presence-polling-JuAy2Ck2-Dh-g_KYM.mjs} +3 -3
  106. package/dist/{store-sqlite-CxxPCcCT.mjs → store-sqlite-D85t3tE7.mjs} +14 -7
  107. package/dist/store-sqlite-iBTYYKBj.mjs +2 -0
  108. package/dist/{stream-buffer-DrTyiIuo-BKy-vI14.mjs → stream-buffer-DrTyiIuo-Be10yOBR.mjs} +2 -2
  109. package/dist/sync-github.d.mts +4 -2
  110. package/dist/sync-github.mjs +4 -14
  111. package/dist/sync-kapso.d.mts +3 -2
  112. package/dist/sync-kapso.mjs +3 -3
  113. package/dist/sync-linear.d.mts +12 -2
  114. package/dist/sync-linear.mjs +191 -9
  115. package/dist/sync-slack.d.mts +41 -5
  116. package/dist/sync-slack.mjs +70 -11
  117. package/dist/sync.chat.d.mts +1 -1
  118. package/dist/sync.chat.mjs +1 -1
  119. package/dist/sync.d.mts +2 -2
  120. package/dist/sync.mjs +3 -3
  121. package/dist/tags-BpFo6q0_.mjs +14 -0
  122. package/dist/{targets-C-wdR5wP.mjs → targets-Cgjs29T0.mjs} +1 -1
  123. package/dist/{tokens-BuCU-61R.mjs → tokens-C5_yGRlL.mjs} +2 -2
  124. package/dist/{types-aCAf6lGU.d.mts → types-shZmGZ-q.d.mts} +2 -2
  125. package/dist/{watchers-hash-7DJU9b-n.mjs → watchers-hash-B_5b0-ZG.mjs} +50 -19
  126. package/docs/01-board/04-messages.md +93 -0
  127. package/docs/01-board/08-middleware.md +5 -2
  128. package/docs/02-sync/03-slack.md +6 -2
  129. package/docs/02-sync/07-linear.md +28 -6
  130. package/docs/04-reference/01-cli.md +27 -12
  131. package/docs/04-reference/02-board.md +20 -5
  132. package/docs/04-reference/03-http.md +17 -6
  133. package/docs/04-reference/05-conformance.md +14 -3
  134. package/package.json +2 -2
  135. package/dist/errors-CusN4x2l.mjs +0 -88
  136. package/dist/exports-D22M68rk.d.mts +0 -5
  137. package/dist/store-N8PXxDAS-BWBgzBHp.mjs +0 -5
  138. package/dist/store-memory-ZLiZbH3R.mjs +0 -2
  139. package/dist/store-sqlite-BmwL3pUR.mjs +0 -2
@@ -1,5 +1,5 @@
1
- import { S as SyncUnit, f as SyncContext, y as SyncFn } from "./provider-Cdg1qadh.mjs";
2
- import { n as ChatSyncProvider, t as ChatInput } from "./chat-provider-DsNEBQiA.mjs";
1
+ import { S as SyncUnit, f as SyncContext, y as SyncFn } from "./provider-Bm46LAdp.mjs";
2
+ import { n as ChatSyncProvider, t as ChatInput } from "./chat-provider-D6jVT4wS.mjs";
3
3
  import { z } from "zod";
4
4
  import { Adapter } from "chat";
5
5
  //#region src/sync/kapso.d.ts
@@ -127,6 +127,7 @@ export declare const WhatsAppEvent: z.ZodDiscriminatedUnion<[z.ZodObject<{
127
127
  type: z.ZodString;
128
128
  title: z.ZodString;
129
129
  url: z.ZodNullable<z.ZodString>;
130
+ body: z.ZodOptional<z.ZodNullable<z.ZodString>>;
130
131
  labels: z.ZodRecord<z.ZodString, z.ZodString>;
131
132
  unset: z.ZodArray<z.ZodString>;
132
133
  owns: z.ZodOptional<z.ZodArray<z.ZodString>>;
@@ -1,6 +1,6 @@
1
- import { s as channels } from "./provider-poIkXapd.mjs";
2
- import { i as eventSchema, r as envelope } from "./events-CewD4kw7.mjs";
3
- import { r as markdownOf, t as ChatSyncProvider } from "./chat-provider-DnHibqnM.mjs";
1
+ import { s as channels } from "./provider-BaVmi4Hi.mjs";
2
+ import { i as eventSchema, r as envelope } from "./events-qf5XG5VQ.mjs";
3
+ import { r as markdownOf, t as ChatSyncProvider } from "./chat-provider-COtsU5jB.mjs";
4
4
  import { z } from "zod";
5
5
  //#region src/sync/kapso.ts
6
6
  const decode = (id) => id.split(":").map((part) => Buffer.from(part, "base64url").toString("utf8")).join(":");
@@ -1,5 +1,6 @@
1
- import { S as SyncUnit, c as ReceivedEvent, d as ResolvedTarget, f as SyncContext, o as ProviderPost, r as ProviderDelivery, y as SyncFn } from "./provider-Cdg1qadh.mjs";
2
- import { n as ChatSyncProvider, t as ChatInput } from "./chat-provider-DsNEBQiA.mjs";
1
+ import { ht as SourceRead } from "./client-CjAatpV4.mjs";
2
+ import { S as SyncUnit, c as ReceivedEvent, d as ResolvedTarget, f as SyncContext, o as ProviderPost, r as ProviderDelivery, y as SyncFn } from "./provider-Bm46LAdp.mjs";
3
+ import { n as ChatSyncProvider, t as ChatInput } from "./chat-provider-D6jVT4wS.mjs";
3
4
  import { z } from "zod";
4
5
  import { Adapter } from "chat";
5
6
  //#region src/sync/linear.d.ts
@@ -127,6 +128,7 @@ export declare const LinearEvent: z.ZodDiscriminatedUnion<[z.ZodObject<{
127
128
  type: z.ZodString;
128
129
  title: z.ZodString;
129
130
  url: z.ZodNullable<z.ZodString>;
131
+ body: z.ZodOptional<z.ZodNullable<z.ZodString>>;
130
132
  labels: z.ZodRecord<z.ZodString, z.ZodString>;
131
133
  unset: z.ZodArray<z.ZodString>;
132
134
  owns: z.ZodOptional<z.ZodArray<z.ZodString>>;
@@ -174,6 +176,14 @@ export declare class LinearProvider extends ChatSyncProvider<LinearEvent> {
174
176
  * webhook sets up: anything else (a delivery, a root fetched on a later pull) asks for one here.
175
177
  */
176
178
  private api;
179
+ /**
180
+ * A project's or team's issues, newest first, or one issue and its comments, read on demand:
181
+ * nothing is synced or stored. `next` pages past a read's `limit`.
182
+ */
183
+ read(target: string, { limit, after }: {
184
+ limit: number;
185
+ after: string | null;
186
+ }): Promise<SourceRead>;
177
187
  resolveChannel(key: string): Promise<string>;
178
188
  /**
179
189
  * `ENG` is a team; `ENG:<uuid>`, a bare UUID, or `ENG-12` is an issue, by its team and UUID:
@@ -1,6 +1,7 @@
1
- import { i as UnreadableError, s as channels } from "./provider-poIkXapd.mjs";
2
- import { i as eventSchema, r as envelope } from "./events-CewD4kw7.mjs";
3
- import { r as markdownOf, t as ChatSyncProvider } from "./chat-provider-DnHibqnM.mjs";
1
+ import { i as UnreadableError, s as channels } from "./provider-BaVmi4Hi.mjs";
2
+ import { i as eventSchema, r as envelope } from "./events-qf5XG5VQ.mjs";
3
+ import { r as markdownOf, t as ChatSyncProvider } from "./chat-provider-COtsU5jB.mjs";
4
+ import { n as tagOf, t as slug } from "./tags-BpFo6q0_.mjs";
4
5
  import "zod";
5
6
  import { connectLinearAdapter } from "@vercel/connect/chat";
6
7
  import { createLinearAdapter } from "@chat-adapter/linear";
@@ -30,6 +31,43 @@ const author = (user, isBot) => ({
30
31
  });
31
32
  /** `ENG` from `ENG-123`, or from a Linear URL that names an issue (`…/issue/ENG-123/…`). */
32
33
  const teamOf = (identifierOrUrl) => /(?:^|\/issue\/)([A-Z0-9_]+)-\d+/i.exec(identifierOrUrl ?? "")?.[1]?.toUpperCase() ?? null;
34
+ /** An issue's description, or null when it has none. */
35
+ const bodyOf = (description) => description?.trim() || null;
36
+ /**
37
+ * An issue's properties as board labels: `priority=high`, `project=v0-accessibility`, a label per
38
+ * Linear label (`tag.bug`), and its assignee (`assignee.ada`). Only what the payload says: a
39
+ * property it carries empty is unset, and one it names without its details is left as it was.
40
+ * Linear leaves an empty assignee or project out of the payload rather than sending `null`, so
41
+ * either one missing, with no ID either, is empty.
42
+ * `priority` and `project` are single keys, so they're unset by name; `tag.` and `assignee.` are
43
+ * prefixes the mirror owns, so a key under them that Linear doesn't have goes.
44
+ */
45
+ function propertiesOf(data) {
46
+ const labels = {};
47
+ const unset = [];
48
+ const owns = [];
49
+ if (data.priority === 0 || data.priorityLabel === "No priority") unset.push("priority");
50
+ else if (data.priorityLabel && slug(data.priorityLabel)) labels.priority = slug(data.priorityLabel).slice(0, 256);
51
+ if (data.project?.name && slug(data.project.name)) labels.project = slug(data.project.name).slice(0, 256);
52
+ else if (!data.project && !data.projectId) unset.push("project");
53
+ if (Array.isArray(data.labels)) {
54
+ owns.push("tag.");
55
+ for (const label of data.labels) {
56
+ const tag = tagOf("tag", label.name);
57
+ if (tag) labels[tag[0]] = tag[1];
58
+ }
59
+ }
60
+ if (data.assignee) {
61
+ owns.push("assignee.");
62
+ const tag = tagOf("assignee", handleOf(data.assignee));
63
+ if (tag) labels[tag[0]] = tag[1];
64
+ } else if (!data.assigneeId) owns.push("assignee.");
65
+ return {
66
+ labels,
67
+ unset,
68
+ owns
69
+ };
70
+ }
33
71
  /** The copy of an issue the board opened: the issue itself, not a comment. */
34
72
  const OPENED = "issue";
35
73
  /** A root's copy: one the board opened, or imported (`lifecycle:<event>`, `root:<event>`). Comment IDs are UUIDs. */
@@ -52,6 +90,51 @@ function titleOf(markdown) {
52
90
  function stateOf(state) {
53
91
  return state?.type ?? state?.name?.toLowerCase() ?? null;
54
92
  }
93
+ const ISSUE_FIELDS = `id identifier title description url createdAt priority priorityLabel
94
+ state { name } assignee { name displayName url } creator { name displayName url } project { name }
95
+ labels(first: 10) { nodes { name } }`;
96
+ const ISSUES_PER_PAGE = 100;
97
+ const DESCRIPTION_IN_LIST = 1500;
98
+ const MAX_COMMENTS = 1e3;
99
+ /** What a read names: a project (by its link), a team (by its key), or an issue (by its ID or link). */
100
+ function readTargetOf(target) {
101
+ const project = /\/project\/(?:[^/?#]*-)?([0-9a-f]{8,})(?:[/?#]|$)/i.exec(target);
102
+ if (project) return {
103
+ kind: "project",
104
+ slugId: project[1]
105
+ };
106
+ const issue = /(?:^|\/issue\/)([A-Z0-9_]+-\d+)(?:[/?#]|$)/i.exec(target.trim());
107
+ if (issue) return {
108
+ kind: "issue",
109
+ id: issue[1].toUpperCase()
110
+ };
111
+ if (/^[A-Z0-9_]+$/i.test(target.trim())) return {
112
+ kind: "team",
113
+ key: target.trim().toUpperCase()
114
+ };
115
+ throw new UnreadableError("invalid_target", `can't read ${target} from Linear: name a project by its link, a team by its key (ENG), or an issue (ENG-123)`);
116
+ }
117
+ /** An issue as a read shows it: its title, its properties on one line, its link, and what it says. */
118
+ function issueMessage(issue, descriptionMax) {
119
+ const properties = [
120
+ issue.state?.name,
121
+ issue.priority ? issue.priorityLabel : void 0,
122
+ issue.assignee ? `assigned to ${handleOf(issue.assignee)}` : "unassigned",
123
+ issue.project?.name,
124
+ ...(issue.labels?.nodes ?? []).map((label) => label.name)
125
+ ].filter(Boolean);
126
+ const description = bodyOf(issue.description);
127
+ const shown = description && descriptionMax !== null && description.length > descriptionMax ? `${description.slice(0, descriptionMax)}…` : description;
128
+ return {
129
+ id: issue.identifier,
130
+ thread: issue.identifier,
131
+ author: handleOf(issue.creator ?? void 0) ?? "unknown",
132
+ text: [`${issue.identifier}: ${issue.title}\n${properties.join(" · ")}\n${issue.url}`, shown].filter(Boolean).join("\n\n"),
133
+ createdAt: issue.createdAt,
134
+ replies: issue.comments?.nodes.length ?? 0,
135
+ files: []
136
+ };
137
+ }
55
138
  var LinearProvider = class extends ChatSyncProvider {
56
139
  linear;
57
140
  teamIds = /* @__PURE__ */ new Map();
@@ -133,13 +216,14 @@ var LinearProvider = class extends ChatSyncProvider {
133
216
  const { data } = payload;
134
217
  const created = payload.action === "create";
135
218
  if (!created && payload.action !== "update") return [];
136
- const retitled = payload.action === "update" && payload.updatedFrom?.title !== void 0;
219
+ const retitled = payload.action === "update" && (payload.updatedFrom?.title !== void 0 || payload.updatedFrom?.description !== void 0);
137
220
  const team = data.team?.key ?? teamOf(data.identifier);
138
221
  const state = stateOf(data.state);
139
222
  const bot = adapter.defaultBotUserId;
140
223
  const own = payload.actor?.id !== void 0 && payload.actor.id === bot;
141
224
  const opens = created ? !own : !own && (bot === null || data.creatorId !== bot) && payload.actor?.type === "user" && !data.archivedAt;
142
225
  const delivery = headers.get("linear-delivery") ?? `${payload.webhookId}:${payload.webhookTimestamp}`;
226
+ const properties = propertiesOf(data);
143
227
  return [{
144
228
  event: {
145
229
  kind: "lifecycle",
@@ -148,11 +232,14 @@ var LinearProvider = class extends ChatSyncProvider {
148
232
  type: created ? "issue.created" : "issue.updated",
149
233
  title: `${data.identifier}: ${data.title}`,
150
234
  url: data.url ?? payload.url ?? null,
235
+ body: bodyOf(data.description),
151
236
  labels: {
152
237
  issue: data.identifier,
153
- ...state ? { state } : {}
238
+ ...state ? { state } : {},
239
+ ...properties.labels
154
240
  },
155
- unset: [],
241
+ unset: properties.unset,
242
+ owns: properties.owns,
156
243
  reply: null,
157
244
  createRoot: opens,
158
245
  ...retitled ? { retitled: true } : {},
@@ -173,6 +260,84 @@ var LinearProvider = class extends ChatSyncProvider {
173
260
  await this.ready();
174
261
  return this.linear.isConnectMode() ? this.linear.withConnectClient(void 0, fn) : fn();
175
262
  }
263
+ /**
264
+ * A project's or team's issues, newest first, or one issue and its comments, read on demand:
265
+ * nothing is synced or stored. `next` pages past a read's `limit`.
266
+ */
267
+ async read(target, { limit, after }) {
268
+ const wanted = readTargetOf(target);
269
+ const query = (text, variables) => this.api(async () => (await this.client().client.rawRequest(text, variables)).data);
270
+ if (wanted.kind === "issue") {
271
+ let issue = null;
272
+ const comments = [];
273
+ let cursor = null;
274
+ do {
275
+ const data = await query(`query BoardReadIssue($id: String!, $first: Int!, $after: String) { issue(id: $id) { ${ISSUE_FIELDS}
276
+ comments(first: $first, after: $after) { nodes { id body createdAt user { name displayName url } } pageInfo { hasNextPage endCursor } } } }`, {
277
+ id: wanted.id,
278
+ first: ISSUES_PER_PAGE,
279
+ after: cursor
280
+ });
281
+ if (!data?.issue) throw new UnreadableError("channel_not_found", `no Linear issue ${wanted.id}`);
282
+ issue = data.issue;
283
+ comments.push(...issue.comments?.nodes ?? []);
284
+ const page = issue.comments?.pageInfo;
285
+ cursor = page?.hasNextPage ? page.endCursor ?? null : null;
286
+ } while (cursor !== null && comments.length < MAX_COMMENTS);
287
+ comments.sort((a, b) => a.createdAt.localeCompare(b.createdAt));
288
+ const from = after === null ? 0 : Math.max(0, Number.parseInt(after, 10) || 0);
289
+ const shown = comments.slice(from, from + Math.min(limit, ISSUES_PER_PAGE));
290
+ const to = from + shown.length;
291
+ return {
292
+ channel: teamOf(issue.identifier) ?? wanted.id,
293
+ name: null,
294
+ thread: issue.identifier,
295
+ messages: [...from === 0 ? [{
296
+ ...issueMessage(issue, null),
297
+ replies: comments.length
298
+ }] : [], ...shown.map((comment) => ({
299
+ id: comment.id,
300
+ thread: issue.identifier,
301
+ author: handleOf(comment.user ?? void 0) ?? "unknown",
302
+ text: comment.body ?? "",
303
+ createdAt: comment.createdAt,
304
+ replies: 0,
305
+ files: []
306
+ }))],
307
+ more: to < comments.length,
308
+ next: to < comments.length ? String(to) : null
309
+ };
310
+ }
311
+ let filter;
312
+ let channel;
313
+ let name;
314
+ if (wanted.kind === "project") {
315
+ const project = (await query("query BoardReadProject($slugId: String!) { projects(filter: { slugId: { eq: $slugId } }, first: 1) { nodes { id name url } } }", { slugId: wanted.slugId }))?.projects.nodes[0];
316
+ if (!project) throw new UnreadableError("channel_not_found", `no Linear project ${target}`);
317
+ filter = { project: { id: { eq: project.id } } };
318
+ channel = project.url;
319
+ name = project.name;
320
+ } else {
321
+ await this.team(wanted.key);
322
+ filter = { team: { key: { eq: wanted.key } } };
323
+ channel = wanted.key;
324
+ name = null;
325
+ }
326
+ const issues = (await query(`query BoardReadIssues($filter: IssueFilter, $first: Int!, $after: String) {
327
+ issues(filter: $filter, first: $first, after: $after) { nodes { ${ISSUE_FIELDS} comments(first: 20) { nodes { id createdAt } } } pageInfo { hasNextPage endCursor } } }`, {
328
+ filter,
329
+ first: Math.min(limit, ISSUES_PER_PAGE),
330
+ after
331
+ }))?.issues;
332
+ return {
333
+ channel,
334
+ name,
335
+ thread: null,
336
+ messages: (issues?.nodes ?? []).map((issue) => issueMessage(issue, DESCRIPTION_IN_LIST)),
337
+ more: issues?.pageInfo.hasNextPage ?? false,
338
+ next: issues?.pageInfo.hasNextPage ? issues.pageInfo.endCursor ?? null : null
339
+ };
340
+ }
176
341
  async resolveChannel(key) {
177
342
  await this.team(key);
178
343
  return key.toUpperCase();
@@ -207,10 +372,26 @@ var LinearProvider = class extends ChatSyncProvider {
207
372
  async fetchRoot(thread) {
208
373
  return this.api(async () => {
209
374
  const issue = await this.client().issue(parseThread(thread).issue);
210
- const [state, creator] = await Promise.all([issue.state, issue.creator]);
375
+ const [state, creator, project, assignee, tags] = await Promise.all([
376
+ issue.state,
377
+ issue.creator,
378
+ issue.project,
379
+ issue.assignee,
380
+ issue.labels?.()
381
+ ]);
211
382
  const found = stateOf(state);
383
+ const properties = propertiesOf({
384
+ id: issue.id,
385
+ identifier: issue.identifier,
386
+ title: issue.title,
387
+ priority: issue.priority,
388
+ priorityLabel: issue.priorityLabel,
389
+ project,
390
+ assignee,
391
+ labels: tags?.nodes
392
+ });
212
393
  return {
213
- text: `${issue.identifier}: ${issue.title}\n${issue.url}`,
394
+ text: [`${issue.identifier}: ${issue.title}\n${issue.url}`, bodyOf(issue.description)].filter(Boolean).join("\n\n"),
214
395
  raw: { issue: {
215
396
  id: issue.id,
216
397
  identifier: issue.identifier,
@@ -220,7 +401,8 @@ var LinearProvider = class extends ChatSyncProvider {
220
401
  author: author(creator, false),
221
402
  labels: {
222
403
  issue: issue.identifier,
223
- ...found ? { state: found } : {}
404
+ ...found ? { state: found } : {},
405
+ ...properties.labels
224
406
  }
225
407
  };
226
408
  });
@@ -1,6 +1,6 @@
1
- import { f as ExternalLink } from "./folds-jS9wlh9_.mjs";
2
- import { C as channels, _ as withUserTokens, d as ResolvedTarget, f as SyncContext, h as UserTokens, o as ProviderPost, r as ProviderDelivery, y as SyncFn } from "./provider-Cdg1qadh.mjs";
3
- import { n as ChatSyncProvider, t as ChatInput } from "./chat-provider-DsNEBQiA.mjs";
1
+ import { f as ExternalLink } from "./folds-8wRRXjAt.mjs";
2
+ import { C as channels, _ as withUserTokens, d as ResolvedTarget, f as SyncContext, h as UserTokens, o as ProviderPost, r as ProviderDelivery, y as SyncFn } from "./provider-Bm46LAdp.mjs";
3
+ import { n as ChatSyncProvider, t as ChatInput } from "./chat-provider-D6jVT4wS.mjs";
4
4
  import { z } from "zod";
5
5
  import { Adapter } from "chat";
6
6
  //#region src/sync/slack.d.ts
@@ -67,6 +67,31 @@ export type SlackClient = {
67
67
  delete(args: {
68
68
  file: string;
69
69
  }): Promise<unknown>;
70
+ /** Uploads files and shares them in one message, with `initial_comment` as its text; needs `files:write`. */
71
+ uploadV2?(args: {
72
+ channel_id: string;
73
+ initial_comment: string;
74
+ file_uploads: {
75
+ file: Buffer;
76
+ filename: string;
77
+ }[];
78
+ }): Promise<{
79
+ files?: {
80
+ files?: {
81
+ id?: string;
82
+ }[];
83
+ }[];
84
+ }>;
85
+ /** Where a file was shared (`shares`), to find the message an upload made; needs `files:read`. */
86
+ info?(args: {
87
+ file: string;
88
+ }): Promise<{
89
+ file?: {
90
+ shares?: Partial<Record<'public' | 'private', Record<string, {
91
+ ts?: string;
92
+ }[]>>>;
93
+ };
94
+ }>;
70
95
  };
71
96
  /** Posts an agent's message under its own name; needs `chat:write.customize` for `username` and `icon_url`. */
72
97
  chat?: {
@@ -296,6 +321,7 @@ export declare const SlackEvent: z.ZodDiscriminatedUnion<[z.ZodObject<{
296
321
  type: z.ZodString;
297
322
  title: z.ZodString;
298
323
  url: z.ZodNullable<z.ZodString>;
324
+ body: z.ZodOptional<z.ZodNullable<z.ZodString>>;
299
325
  labels: z.ZodRecord<z.ZodString, z.ZodString>;
300
326
  unset: z.ZodArray<z.ZodString>;
301
327
  owns: z.ZodOptional<z.ZodArray<z.ZodString>>;
@@ -339,7 +365,7 @@ export type SlackOptions = {
339
365
  export declare function slack(app: string | SlackOptions, sync: SyncFn<SlackEvent>): SlackProvider;
340
366
  export declare class SlackProvider extends ChatSyncProvider<SlackEvent> {
341
367
  private readonly web;
342
- private customize;
368
+ private scopes;
343
369
  constructor(given: SlackOptions, sync: SyncFn<SlackEvent>);
344
370
  /**
345
371
  * An agent's message goes out under its own name (`clever-lemur via vboard-factory`), and its
@@ -347,11 +373,21 @@ export declare class SlackProvider extends ChatSyncProvider<SlackEvent> {
347
373
  * `**name**: ` label dropped. Without `chat:write.customize` it posts as the app, label and all.
348
374
  */
349
375
  post(target: string, kind: ResolvedTarget['kind'], body: ProviderPost): Promise<ProviderDelivery>;
376
+ /**
377
+ * A new thread whose root shows its files under its text, as one message: Slack makes
378
+ * that message from an upload's `initial_comment`, and names it only on the file, a
379
+ * moment later. A file upload can't set a username, so it goes out as the app, label and all.
380
+ * Null, before anything is sent, where the client can't, or the app can't read files
381
+ * (`files:read`) to find that message.
382
+ */
383
+ private postWithFiles;
350
384
  /** An edit keeps the label out only where the message went out under the agent's name. */
351
385
  edit(thread: string, message: string, body: ProviderPost): Promise<void>;
352
386
  private postNamed;
353
- /** Whether the app may set a message's username and icon (`chat:write.customize`), asked once. */
387
+ /** Whether the app may set a message's username and icon (`chat:write.customize`). */
354
388
  private canCustomize;
389
+ /** Whether the app has a scope, from its scopes, asked once. */
390
+ private hasScope;
355
391
  /** Whether a message Slack has went out under `name`. */
356
392
  private sentAs;
357
393
  /**
@@ -1,9 +1,9 @@
1
- import { m as MESSAGE_TEXT_MAX } from "./schemas-Be08nyfW.mjs";
2
- import "./external-BV_cUncs.mjs";
3
- import { i as UnreadableError, s as channels } from "./provider-poIkXapd.mjs";
4
- import { i as eventSchema, r as envelope } from "./events-CewD4kw7.mjs";
1
+ import { m as MESSAGE_TEXT_MAX } from "./schemas-DBmUO6pr.mjs";
2
+ import "./external-D3p5T57R.mjs";
3
+ import { i as UnreadableError, s as channels } from "./provider-BaVmi4Hi.mjs";
4
+ import { i as eventSchema, r as envelope } from "./events-qf5XG5VQ.mjs";
5
5
  import { r as withUserTokens, t as connectUserTokens } from "./users-yryHKFMo.mjs";
6
- import { r as markdownOf, t as ChatSyncProvider } from "./chat-provider-DnHibqnM.mjs";
6
+ import { r as markdownOf, t as ChatSyncProvider } from "./chat-provider-COtsU5jB.mjs";
7
7
  import { z } from "zod";
8
8
  import { stringifyMarkdown } from "chat";
9
9
  import { createSlackAdapter } from "@chat-adapter/slack";
@@ -220,6 +220,8 @@ const SlackEvent = eventSchema("slack", "message");
220
220
  function slack(app, sync) {
221
221
  return new SlackProvider(typeof app === "string" ? { adapter: app } : app, sync);
222
222
  }
223
+ /** How many times to look for the message an upload made, about 15 seconds in all. */
224
+ const SHARE_POLLS = 10;
223
225
  /**
224
226
  * Slack's client retries 10 times over about 30 minutes by default, which outlives any function:
225
227
  * here a call gets 3 more tries. Each still waits out Slack's `Retry-After` (about a minute at
@@ -250,7 +252,7 @@ function fromConnector(connector) {
250
252
  }
251
253
  var SlackProvider = class extends ChatSyncProvider {
252
254
  web;
253
- customize;
255
+ scopes;
254
256
  constructor(given, sync) {
255
257
  const connected = typeof given.adapter === "string" ? fromConnector(given.adapter) : null;
256
258
  const options = connected ? {
@@ -386,6 +388,7 @@ var SlackProvider = class extends ChatSyncProvider {
386
388
  platform: "slack",
387
389
  adapter: options.adapter,
388
390
  canStartThreads: true,
391
+ startsThreadsWithFiles: true,
389
392
  async uploadFiles(thread, files) {
390
393
  const sent = await options.adapter.postMessage(thread, {
391
394
  markdown: "",
@@ -681,8 +684,60 @@ var SlackProvider = class extends ChatSyncProvider {
681
684
  ...given,
682
685
  markdown: withoutHtml(given.markdown)
683
686
  };
687
+ if (kind === "channel" && post.files?.length && post.markdown.trim()) {
688
+ const sent = await this.postWithFiles(target, post.markdown, post.files);
689
+ if (sent) return sent;
690
+ const { files: _files, ...text } = post;
691
+ return (as ? await this.postNamed(target, as, text.markdown) : null) ?? super.post(target, kind, text);
692
+ }
684
693
  return (as && !post.files?.length ? await this.postNamed(target, as, post.markdown) : null) ?? super.post(target, kind, post);
685
694
  }
695
+ /**
696
+ * A new thread whose root shows its files under its text, as one message: Slack makes
697
+ * that message from an upload's `initial_comment`, and names it only on the file, a
698
+ * moment later. A file upload can't set a username, so it goes out as the app, label and all.
699
+ * Null, before anything is sent, where the client can't, or the app can't read files
700
+ * (`files:read`) to find that message.
701
+ */
702
+ async postWithFiles(target, markdown, files) {
703
+ const adapter = await this.ready();
704
+ const web = await this.web();
705
+ const toMrkdwn = adapter.formatConverter?.toResponseUrlText;
706
+ if (!web?.files?.uploadV2 || !web.files.info || !toMrkdwn || !await this.hasScope(web, "files:read")) return null;
707
+ const threadId = this.toChat(target);
708
+ const { channel } = adapter.decodeThreadId(threadId);
709
+ const message = { markdown };
710
+ const resolved = await adapter.resolveMessageMentions?.(message, threadId) ?? message;
711
+ const ids = ((await web.files.uploadV2({
712
+ channel_id: channel,
713
+ initial_comment: toMrkdwn.call(adapter.formatConverter, resolved),
714
+ file_uploads: files.map((file) => ({
715
+ file: Buffer.from(file.data),
716
+ filename: slackFileName(file.filename, file.mimeType)
717
+ }))
718
+ })).files?.[0]?.files ?? []).map((file) => file.id).filter((id) => !!id);
719
+ const found = async () => {
720
+ for (let attempt = 0; attempt < SHARE_POLLS; attempt++) {
721
+ const shares = (await web.files.info({ file: ids[0] })).file?.shares;
722
+ const ts = shares?.public?.[channel]?.[0]?.ts ?? shares?.private?.[channel]?.[0]?.ts;
723
+ if (ts) return ts;
724
+ await new Promise((resolve) => setTimeout(resolve, Math.min(250 * 2 ** attempt, 2e3)));
725
+ }
726
+ throw new Error("never shared");
727
+ };
728
+ if (!ids[0]) throw new Error("slack: files.uploadV2 returned no file IDs");
729
+ let ts;
730
+ try {
731
+ ts = await found();
732
+ } catch (error) {
733
+ throw new Error(`slack: couldn't find the message uploading ${ids[0]} made in ${channel} (${slackError(error)})`);
734
+ }
735
+ return {
736
+ thread: this.toNative(`${adapter.name}:${channel}:${ts}`),
737
+ message: ts,
738
+ files: ids
739
+ };
740
+ }
686
741
  /** An edit keeps the label out only where the message went out under the agent's name. */
687
742
  async edit(thread, message, body) {
688
743
  const { as, ...given } = body;
@@ -720,14 +775,18 @@ var SlackProvider = class extends ChatSyncProvider {
720
775
  message: sent.ts
721
776
  };
722
777
  }
723
- /** Whether the app may set a message's username and icon (`chat:write.customize`), asked once. */
778
+ /** Whether the app may set a message's username and icon (`chat:write.customize`). */
724
779
  canCustomize(web) {
725
- this.customize ??= (async () => (await web.auth?.test())?.response_metadata?.scopes?.includes("chat:write.customize") ?? false)().catch((error) => {
726
- this.customize = void 0;
780
+ return this.hasScope(web, "chat:write.customize");
781
+ }
782
+ /** Whether the app has a scope, from its scopes, asked once. */
783
+ async hasScope(web, scope) {
784
+ this.scopes ??= (async () => (await web.auth?.test())?.response_metadata?.scopes ?? [])().catch((error) => {
785
+ this.scopes = void 0;
727
786
  console.warn(`[board] slack: couldn't check the app's scopes (${slackError(error)}); posting as the app`);
728
- return false;
787
+ return [];
729
788
  });
730
- return this.customize;
789
+ return (await this.scopes).includes(scope);
731
790
  }
732
791
  /** Whether a message Slack has went out under `name`. */
733
792
  async sentAs(thread, message, name) {
@@ -1,2 +1,2 @@
1
- import { i as externalAuthor, n as ChatSyncProvider, r as ChatTransport, t as ChatInput } from "./chat-provider-DsNEBQiA.mjs";
1
+ import { i as externalAuthor, n as ChatSyncProvider, r as ChatTransport, t as ChatInput } from "./chat-provider-D6jVT4wS.mjs";
2
2
  export { ChatInput, ChatSyncProvider, ChatTransport, externalAuthor };
@@ -1,2 +1,2 @@
1
- import { n as externalAuthor, t as ChatSyncProvider } from "./chat-provider-DnHibqnM.mjs";
1
+ import { n as externalAuthor, t as ChatSyncProvider } from "./chat-provider-COtsU5jB.mjs";
2
2
  export { ChatSyncProvider, externalAuthor };
package/dist/sync.d.mts CHANGED
@@ -1,3 +1,3 @@
1
- import { d as ExternalFile, l as ExternalAuthor, u as ExternalEvent } from "./folds-jS9wlh9_.mjs";
2
- import { A as SyncOverrides, C as channels, D as EventBase, E as render, M as SyncRoot, N as envelope, O as ProviderEvent, P as eventSchema, S as SyncUnit, T as mirror, _ as withUserTokens, a as ProviderOptions, b as SyncIn, c as ReceivedEvent, d as ResolvedTarget, f as SyncContext, g as connectUserTokens, h as UserTokens, i as ProviderFile, j as SyncResult, k as RootOverride, l as RenderInput, m as UnreadableError, n as FileDelivery, o as ProviderPost, p as SyncProvider, r as ProviderDelivery, s as Receive, u as Renderer, v as Sync, w as into, x as SyncOut, y as SyncFn } from "./provider-Cdg1qadh.mjs";
1
+ import { d as ExternalFile, l as ExternalAuthor, u as ExternalEvent } from "./folds-8wRRXjAt.mjs";
2
+ import { A as SyncOverrides, C as channels, D as EventBase, E as render, M as SyncRoot, N as envelope, O as ProviderEvent, P as eventSchema, S as SyncUnit, T as mirror, _ as withUserTokens, a as ProviderOptions, b as SyncIn, c as ReceivedEvent, d as ResolvedTarget, f as SyncContext, g as connectUserTokens, h as UserTokens, i as ProviderFile, j as SyncResult, k as RootOverride, l as RenderInput, m as UnreadableError, n as FileDelivery, o as ProviderPost, p as SyncProvider, r as ProviderDelivery, s as Receive, u as Renderer, v as Sync, w as into, x as SyncOut, y as SyncFn } from "./provider-Bm46LAdp.mjs";
3
3
  export { EventBase, type ExternalAuthor, ExternalEvent, type ExternalFile, type FileDelivery, type ProviderDelivery, type ProviderEvent, type ProviderFile, type ProviderOptions, type ProviderPost, type Receive, ReceivedEvent, type RenderInput, type Renderer, ResolvedTarget, type RootOverride, type Sync, type SyncContext, type SyncFn, type SyncIn, type SyncOut, SyncOverrides, SyncProvider, type SyncResult, type SyncRoot, type SyncUnit, UnreadableError, type UserTokens, channels, connectUserTokens, envelope, eventSchema, into, mirror, render, withUserTokens };
package/dist/sync.mjs CHANGED
@@ -1,5 +1,5 @@
1
- import { n as ExternalEvent } from "./external-BV_cUncs.mjs";
2
- import { c as into, i as UnreadableError, l as mirror, n as ResolvedTarget, r as SyncProvider, s as channels, t as ReceivedEvent, u as render } from "./provider-poIkXapd.mjs";
3
- import { i as eventSchema, n as SyncOverrides, r as envelope, t as EventBase } from "./events-CewD4kw7.mjs";
1
+ import { n as ExternalEvent } from "./external-D3p5T57R.mjs";
2
+ import { c as into, i as UnreadableError, l as mirror, n as ResolvedTarget, r as SyncProvider, s as channels, t as ReceivedEvent, u as render } from "./provider-BaVmi4Hi.mjs";
3
+ import { i as eventSchema, n as SyncOverrides, r as envelope, t as EventBase } from "./events-qf5XG5VQ.mjs";
4
4
  import { r as withUserTokens, t as connectUserTokens } from "./users-yryHKFMo.mjs";
5
5
  export { EventBase, ExternalEvent, ReceivedEvent, ResolvedTarget, SyncOverrides, SyncProvider, UnreadableError, channels, connectUserTokens, envelope, eventSchema, into, mirror, render, withUserTokens };
@@ -0,0 +1,14 @@
1
+ //#region src/sync/tags.ts
2
+ /** `good first issue` → `good-first-issue`: the part of a label key a name gives. Empty when nothing's left. */
3
+ const slug = (name) => name.normalize("NFKD").replace(/[\u0300-\u036f]/g, "").toLowerCase().replace(/[^a-z0-9_.-]+/g, "-").replace(/^[-_.]+|-+$/g, "");
4
+ /**
5
+ * A platform's label or assignee as a board label: `bug` → `tag.bug=bug`,
6
+ * `julianbenegas` → `assignee.julianbenegas=julianbenegas`. The keys don't
7
+ * name the platform, so a filter like `--label tag.bug` holds for any of them.
8
+ */
9
+ function tagOf(prefix, name) {
10
+ const key = name ? slug(name) : "";
11
+ return key ? [`${prefix}.${key}`.slice(0, 64), name.slice(0, 256)] : null;
12
+ }
13
+ //#endregion
14
+ export { tagOf as n, slug as t };
@@ -1,4 +1,4 @@
1
- import { l as isThreadTarget } from "./external-BV_cUncs.mjs";
1
+ import { l as isThreadTarget } from "./external-D3p5T57R.mjs";
2
2
  import { n as replaceMentions, t as extractMentions } from "./mentions-CB6ShY4R.mjs";
3
3
  //#region src/server/targets.ts
4
4
  /** A target as a post or a receipt names it, resolved: a plain one's kind goes by its shape. */
@@ -1,5 +1,5 @@
1
- import { l as unauthorized } from "./errors-CusN4x2l.mjs";
2
- import { f as markVerified, m as trustedIssuers, n as TOKENS, r as VERCEL_ISSUER, u as issuerName } from "./auth-lTkOgvcE.mjs";
1
+ import { l as unauthorized } from "./errors-C_5rB8Iz.mjs";
2
+ import { f as markVerified, m as trustedIssuers, n as TOKENS, r as VERCEL_ISSUER, u as issuerName } from "./auth-BPinwfN0.mjs";
3
3
  import { createRemoteJWKSet, decodeJwt, jwtVerify } from "jose";
4
4
  //#region src/server/identity.ts
5
5
  let fromDisk;
@@ -1,5 +1,5 @@
1
- import { a as ExternalCopy, c as SyncedThread, g as BoardEvent, o as Message } from "./folds-jS9wlh9_.mjs";
2
- import { t as A2Store } from "./store-D0MhBvg6-4Z2L0Kqd.mjs";
1
+ import { a as ExternalCopy, c as SyncedThread, g as BoardEvent, o as Message } from "./folds-8wRRXjAt.mjs";
2
+ import { t as A2Store } from "./store-DEzcHZOA-B-5vhxef.mjs";
3
3
  //#region src/store/types.d.ts
4
4
  /**
5
5
  * What a query matches, resolved by the board: handles are already actor and