@snagset/server 0.0.4 → 0.1.1

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/README.md +4 -3
  2. package/dist/app.js +82 -9
  3. package/dist/app.js.map +1 -1
  4. package/dist/bin.js +85 -11
  5. package/dist/bin.js.map +1 -1
  6. package/dist/boot.js +67 -51
  7. package/dist/boot.js.map +1 -1
  8. package/dist/config.generated.js +5 -2
  9. package/dist/config.generated.js.map +1 -1
  10. package/dist/db/connect.js +7 -0
  11. package/dist/db/connect.js.map +1 -1
  12. package/dist/db/migrations/0005_studio_reviewers.sql +45 -0
  13. package/dist/db/migrations/0006_instance_settings.sql +69 -0
  14. package/dist/db/migrations/0007_jira_provider.no-txn.sql +13 -0
  15. package/dist/domain.js +129 -0
  16. package/dist/domain.js.map +1 -0
  17. package/dist/instance/settings.js +54 -0
  18. package/dist/instance/settings.js.map +1 -0
  19. package/dist/integrations/github.js +112 -0
  20. package/dist/integrations/github.js.map +1 -0
  21. package/dist/integrations/inbound.js +88 -0
  22. package/dist/integrations/inbound.js.map +1 -0
  23. package/dist/integrations/jira.js +129 -0
  24. package/dist/integrations/jira.js.map +1 -0
  25. package/dist/integrations/linear.js +111 -0
  26. package/dist/integrations/linear.js.map +1 -0
  27. package/dist/integrations/outbox.js +305 -0
  28. package/dist/integrations/outbox.js.map +1 -0
  29. package/dist/integrations/slack.js +135 -0
  30. package/dist/integrations/slack.js.map +1 -0
  31. package/dist/integrations/store.js +101 -0
  32. package/dist/integrations/store.js.map +1 -0
  33. package/dist/listen.js +58 -0
  34. package/dist/listen.js.map +1 -0
  35. package/dist/mail/index.js +21 -11
  36. package/dist/mail/index.js.map +1 -1
  37. package/dist/mail/resolve.js +80 -0
  38. package/dist/mail/resolve.js.map +1 -0
  39. package/dist/platform.js +27 -0
  40. package/dist/platform.js.map +1 -0
  41. package/dist/projectCache.js +3 -2
  42. package/dist/projectCache.js.map +1 -1
  43. package/dist/projects.js +8 -2
  44. package/dist/projects.js.map +1 -1
  45. package/dist/routes/account.js +59 -0
  46. package/dist/routes/account.js.map +1 -0
  47. package/dist/routes/activity.js +111 -0
  48. package/dist/routes/activity.js.map +1 -0
  49. package/dist/routes/assignees.js +139 -0
  50. package/dist/routes/assignees.js.map +1 -0
  51. package/dist/routes/attachments.js +2 -2
  52. package/dist/routes/attachments.js.map +1 -1
  53. package/dist/routes/comments.js +27 -17
  54. package/dist/routes/comments.js.map +1 -1
  55. package/dist/routes/cors.js +10 -1
  56. package/dist/routes/cors.js.map +1 -1
  57. package/dist/routes/instance.js +164 -0
  58. package/dist/routes/instance.js.map +1 -0
  59. package/dist/routes/integrations.js +610 -0
  60. package/dist/routes/integrations.js.map +1 -0
  61. package/dist/routes/jobs.js +15 -3
  62. package/dist/routes/jobs.js.map +1 -1
  63. package/dist/routes/mcpTokens.js +119 -0
  64. package/dist/routes/mcpTokens.js.map +1 -0
  65. package/dist/routes/members.js +177 -0
  66. package/dist/routes/members.js.map +1 -0
  67. package/dist/routes/owner.js +64 -7
  68. package/dist/routes/owner.js.map +1 -1
  69. package/dist/routes/sites.js +181 -8
  70. package/dist/routes/sites.js.map +1 -1
  71. package/dist/routes/threads.js +276 -9
  72. package/dist/routes/threads.js.map +1 -1
  73. package/dist/serverless.js +41 -6
  74. package/dist/serverless.js.map +1 -1
  75. package/dist/studioReviewers.js +67 -0
  76. package/dist/studioReviewers.js.map +1 -0
  77. package/dist/vendor/adapters/outbound/sanitise.js +18 -3
  78. package/dist/vendor/types/brief.js +121 -0
  79. package/package.json +1 -1
  80. package/static/dashboard/assets/CompositeItem-DnLNS57f.js +1 -0
  81. package/static/dashboard/assets/ConfirmDialog-CCAJDc_P.js +1 -0
  82. package/static/dashboard/assets/CopyButton-exuJDdXj.js +1 -0
  83. package/static/dashboard/assets/FieldItemContext-jf0Is3j7.js +1 -0
  84. package/static/dashboard/assets/InstallTag-CkeGLk1a.js +1 -0
  85. package/static/dashboard/assets/SaveState-BsrfwjoH.js +6 -0
  86. package/static/dashboard/assets/_app-CiDKmy_a.js +1 -0
  87. package/static/dashboard/assets/_app-xxJ7UG34.js +56 -0
  88. package/static/dashboard/assets/activity-mwHb7ir7.js +1 -0
  89. package/static/dashboard/assets/arrow-left-D08BSlJU.js +6 -0
  90. package/static/dashboard/assets/avatar-BITl2vTh.js +1 -0
  91. package/static/dashboard/assets/badge-CGEswzew.js +1 -0
  92. package/static/dashboard/assets/card-8T0wnIiU.js +1 -0
  93. package/static/dashboard/assets/check-CiLHTeII.js +6 -0
  94. package/static/dashboard/assets/chevron-right-BokKbPOn.js +6 -0
  95. package/static/dashboard/assets/copy-CKgsZbj1.js +6 -0
  96. package/static/dashboard/assets/createBaseUIEventDetails-CEmaecLi.js +1 -0
  97. package/static/dashboard/assets/createLucideIcon-llnWWh0E.js +36 -0
  98. package/static/dashboard/assets/dialog-DrrnCoT5.js +6 -0
  99. package/static/dashboard/assets/dropdown-menu-DIE2xmL5.js +11 -0
  100. package/static/dashboard/assets/image-off-CgUC8krg.js +6 -0
  101. package/static/dashboard/assets/index-BRTBtV91.css +1 -0
  102. package/static/dashboard/assets/index-C87aqO18.js +129 -0
  103. package/static/dashboard/assets/label-BfMHjFA7.js +1 -0
  104. package/static/dashboard/assets/labels--UUyt9uB.js +1 -0
  105. package/static/dashboard/assets/layout-grid-B7mn7n3K.js +6 -0
  106. package/static/dashboard/assets/me-D1prnPpY.js +1 -0
  107. package/static/dashboard/assets/parts-B-HQayFH.js +1 -0
  108. package/static/dashboard/assets/plus-CuMhvZUZ.js +6 -0
  109. package/static/dashboard/assets/s._site-Ccc7pzT-.js +1 -0
  110. package/static/dashboard/assets/s._site.comments-nJZLYDQh.js +39 -0
  111. package/static/dashboard/assets/s._site.reviewers-BmRPiC0a.js +1 -0
  112. package/static/dashboard/assets/s._site.settings-DNv5ovrJ.js +1 -0
  113. package/static/dashboard/assets/setup-B_VsIyK2.js +1 -0
  114. package/static/dashboard/assets/sign-in-sWUNd3Lp.js +1 -0
  115. package/static/dashboard/assets/sites-CSzUAHV6.js +1 -0
  116. package/static/dashboard/assets/studio-DVFn025O.js +1 -0
  117. package/static/dashboard/assets/studio-DzBapfQK.js +1 -0
  118. package/static/dashboard/assets/studio.index-Cp7lBb8S.js +1 -0
  119. package/static/dashboard/assets/studio.integrations-DD2EWaL-.js +1 -0
  120. package/static/dashboard/assets/studio.members-CVu-eP6p.js +1 -0
  121. package/static/dashboard/assets/sun-DTI4O-GM.js +16 -0
  122. package/static/dashboard/assets/table-DkqScFZb.js +1 -0
  123. package/static/dashboard/assets/update-duXzIzFS.js +1 -0
  124. package/static/dashboard/assets/useBaseQuery-0OqUJMfc.js +1 -0
  125. package/static/dashboard/assets/useQuery-COJ09EkU.js +1 -0
  126. package/static/dashboard/assets/useRender-dtgBGlgC.js +1 -0
  127. package/static/dashboard/assets/users-O11swHBQ.js +6 -0
  128. package/static/dashboard/assets/welcome-CT6oH0I8.js +11 -0
  129. package/static/dashboard/assets/you-_yIPqa_9.js +1 -0
  130. package/static/dashboard/assets/zod-iC-UhR5u.js +1 -0
  131. package/static/dashboard/index.html +17 -2
  132. package/static/widget/snag.capture.js +1 -1
  133. package/static/widget/snag.dock.js +1 -1
  134. package/static/widget/snag.panels.js +1 -1
  135. package/static/widget/snag.runtime.js +2 -2
  136. package/static/widget/snag.text.js +1 -1
  137. package/vercel.json +4 -0
  138. package/static/dashboard/assets/index-CATItBzT.js +0 -1
  139. package/static/dashboard/assets/index-DsUJ96yc.css +0 -1
@@ -0,0 +1,305 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { sanitise } from "../vendor/adapters/index.js";
3
+ import { briefMarkdown } from "../vendor/types/brief.js";
4
+ import { ID_PREFIX, param } from "../vendor/types/names.js";
5
+ import { githubClient, GitHubError, issueTitle } from "./github.js";
6
+ import { linearClient, LinearError, linearStateFor } from "./linear.js";
7
+ import { escapeForJiraWiki, jiraCategoryFor, jiraClient, JiraError } from "./jira.js";
8
+ import { slackClient, SlackError, slackMessage, slackPermalink } from "./slack.js";
9
+ import { getIntegration, markIntegration, trackerSecrets, REF_KIND, } from "./store.js";
10
+ /** Queue one row; a duplicate key is a no-op (the request was retried). */
11
+ export async function queueIntegrationEvent(tx, q) {
12
+ const provider = q.provider ?? "github";
13
+ await tx `
14
+ INSERT INTO outbox (id, org_id, project_id, integration_id, thread_id, provider, topic, dedupe_key, event)
15
+ VALUES (${randomUUID()}, ${q.orgId}::uuid, ${q.projectId}::uuid, ${q.integrationId}::uuid, ${q.threadId}::uuid,
16
+ ${provider}::snag_provider, ${q.topic}, ${`${q.topic}:${ID_PREFIX.thread}${q.threadId}:${q.version}`},
17
+ ${tx.json((q.event ?? {}))})
18
+ ON CONFLICT (provider, dedupe_key) DO NOTHING
19
+ `;
20
+ }
21
+ const BACKOFF_MIN = [1, 5, 15, 30, 60];
22
+ /** The thread as the brief wants it, under the org's claim. */
23
+ async function loadThread(sql, orgId, threadId) {
24
+ return sql.begin(async (tx) => {
25
+ await tx `SELECT set_config('app.org_id', ${orgId}, true)`;
26
+ const [t] = await tx `
27
+ SELECT t.id, t.key, t.status, t.version, t.anchor, t.anchor_state, t.context, t.route, t.created_at,
28
+ p.uid AS project_uid, p.name AS project_name, e.key AS environment_key,
29
+ r.name AS created_by_name,
30
+ ar.name AS assignee_name,
31
+ COALESCE(
32
+ (SELECT json_agg(json_build_object('body', c.body, 'createdAt', c.created_at,
33
+ 'author', json_build_object('name', cr.name, 'kind', cr.kind))
34
+ ORDER BY c.seq)
35
+ FROM comments c JOIN reviewers cr ON cr.id = c.author_id
36
+ WHERE c.thread_id = t.id AND c.deleted_at IS NULL AND c.private = false),
37
+ '[]'::json) AS comments
38
+ FROM threads t
39
+ JOIN projects p ON p.id = t.project_id
40
+ JOIN reviewers r ON r.id = t.created_by
41
+ JOIN environments e ON e.id = t.environment_id
42
+ LEFT JOIN reviewers ar ON ar.id = t.assignee_id
43
+ WHERE t.id = ${threadId}::uuid AND t.deleted_at IS NULL
44
+ `;
45
+ return t ?? null;
46
+ });
47
+ }
48
+ /** Every reviewer-typed string goes through the outbound sanitiser for the target before the brief sees it. */
49
+ const fenceFor = (target) => (kind, value) => {
50
+ const opts = kind === "body" ? { max: 10_000 } : { singleLine: true, max: kind === "name" ? 80 : 400 };
51
+ // Jira's v2 description is wiki markup: the plain target, then the few
52
+ // wiki specials escaped so a reviewer's text cannot steer the page.
53
+ if (target === "jira")
54
+ return escapeForJiraWiki(String(sanitise(value, "plain", opts)));
55
+ return String(sanitise(value, target, opts));
56
+ };
57
+ export function issueBody(t, baseUrl, target = "github") {
58
+ const brief = {
59
+ key: t.key,
60
+ status: t.status,
61
+ anchor: t.anchor,
62
+ anchorState: t.anchor_state,
63
+ // The capture context stays out of a tracker: it is the personal-data-bearing half (07 §2.6.3).
64
+ context: null,
65
+ comments: t.comments ?? [],
66
+ attachments: [],
67
+ createdBy: { name: t.created_by_name },
68
+ assignee: t.assignee_name ? { name: t.assignee_name } : null,
69
+ createdAt: new Date(t.created_at).toISOString(),
70
+ ...(t.route ? { route: t.route } : {}),
71
+ };
72
+ const md = briefMarkdown(brief, { name: t.project_name }, { fence: fenceFor(target) });
73
+ const open = `${baseUrl.replace(/\/+$/, "")}/s/${t.project_uid}/comments?thread=${encodeURIComponent(t.key)}`;
74
+ // Plain strings, not a template: the SQL-comment linter reads the marker's
75
+ // "-->" as a comment start on a line that also closes a template literal.
76
+ const marker = "<!-- snagset:thread=" + ID_PREFIX.thread + t.id + " key=" + t.key + " project=" + t.project_uid + " -->";
77
+ const end = "<!-- snag:end -->";
78
+ return [marker, md, "**Open in Snagset:** " + open, end].join("\n") + "\n";
79
+ }
80
+ const titleOf = (thread, target) => {
81
+ const first = thread.comments[0];
82
+ return issueTitle({
83
+ key: thread.key,
84
+ route: thread.route ?? "/",
85
+ firstBody: first?.body ?? null,
86
+ author: String(sanitise(String(thread.created_by_name ?? ""), target === "jira" ? "plain" : target, { singleLine: true, max: 80 })),
87
+ });
88
+ };
89
+ const OPS = {
90
+ github: {
91
+ async create({ integration, token, thread, deps }) {
92
+ const cfg = integration.config;
93
+ const gh = githubClient({ token, ...(deps.githubApiBase ? { apiBase: deps.githubApiBase } : {}), ...(deps.fetch ? { fetch: deps.fetch } : {}) });
94
+ const repo = { owner: cfg.owner, repo: cfg.repo };
95
+ const made = await gh.createIssue(repo, { title: titleOf(thread, "github"), body: issueBody(thread, deps.baseUrl, "github"), labels: cfg.labels });
96
+ // Born resolved, or moved while the row waited: the issue starts in the right state.
97
+ if (thread.status === "resolved")
98
+ await gh.setIssueState(repo, made.number, "closed", "completed");
99
+ return { externalId: String(made.number), url: made.url };
100
+ },
101
+ async setState({ integration, token, thread, deps }, externalId) {
102
+ const cfg = integration.config;
103
+ const gh = githubClient({ token, ...(deps.githubApiBase ? { apiBase: deps.githubApiBase } : {}), ...(deps.fetch ? { fetch: deps.fetch } : {}) });
104
+ const closed = thread.status === "resolved";
105
+ await gh.setIssueState({ owner: cfg.owner, repo: cfg.repo }, Number(externalId), closed ? "closed" : "open", closed ? "completed" : undefined);
106
+ },
107
+ authError: (e) => e instanceof GitHubError && (e.status === 401 || e.status === 404),
108
+ retryable: (e) => (e instanceof GitHubError ? e.retryable : true),
109
+ },
110
+ linear: {
111
+ async create({ integration, token, thread, deps }) {
112
+ const cfg = integration.config;
113
+ const lc = linearClient({ apiKey: token, ...(deps.linearApiBase ? { apiBase: deps.linearApiBase } : {}), ...(deps.fetch ? { fetch: deps.fetch } : {}) });
114
+ const made = await lc.createIssue({
115
+ teamId: cfg.teamId,
116
+ title: titleOf(thread, "linear"),
117
+ description: issueBody(thread, deps.baseUrl, "linear"),
118
+ stateId: linearStateFor(thread.status, cfg.states),
119
+ });
120
+ return { externalId: made.id, url: made.url };
121
+ },
122
+ async setState({ integration, token, thread, deps }, externalId) {
123
+ const cfg = integration.config;
124
+ const lc = linearClient({ apiKey: token, ...(deps.linearApiBase ? { apiBase: deps.linearApiBase } : {}), ...(deps.fetch ? { fetch: deps.fetch } : {}) });
125
+ await lc.setState(externalId, linearStateFor(thread.status, cfg.states));
126
+ },
127
+ authError: (e) => e instanceof LinearError && (e.status === 401 || e.status === 403),
128
+ retryable: (e) => (e instanceof LinearError ? e.retryable : true),
129
+ },
130
+ jira: {
131
+ async create({ integration, token, thread, deps }) {
132
+ const cfg = integration.config;
133
+ const jc = jiraClient({ siteUrl: cfg.siteUrl, email: cfg.email, apiToken: token, ...(deps.fetch ? { fetch: deps.fetch } : {}) });
134
+ const made = await jc.createIssue({
135
+ projectId: cfg.projectId,
136
+ issueTypeId: cfg.issueTypeId,
137
+ summary: titleOf(thread, "jira"),
138
+ description: issueBody(thread, deps.baseUrl, "jira"),
139
+ });
140
+ // Born in the project's initial status; moved at once if ours says otherwise.
141
+ if (thread.status !== "open" && thread.status !== "review")
142
+ await jc.transitionTo(made.key, jiraCategoryFor(thread.status));
143
+ return { externalId: made.key, url: made.url };
144
+ },
145
+ async setState({ integration, token, thread, deps }, externalId) {
146
+ const cfg = integration.config;
147
+ const jc = jiraClient({ siteUrl: cfg.siteUrl, email: cfg.email, apiToken: token, ...(deps.fetch ? { fetch: deps.fetch } : {}) });
148
+ await jc.transitionTo(externalId, jiraCategoryFor(thread.status));
149
+ },
150
+ authError: (e) => e instanceof JiraError && (e.status === 401 || e.status === 403 || e.status === 404),
151
+ retryable: (e) => (e instanceof JiraError ? e.retryable : true),
152
+ },
153
+ slack: {
154
+ async create({ integration, token, thread, deps }) {
155
+ const cfg = integration.config;
156
+ const sc = slackClient({ token, ...(deps.slackApiBase ? { apiBase: deps.slackApiBase } : {}), ...(deps.fetch ? { fetch: deps.fetch } : {}) });
157
+ const posted = await sc.post(cfg.channelId, slackMessage(slackInput(thread, deps.baseUrl)));
158
+ return { externalId: `${posted.channel}:${posted.ts}`, url: slackPermalink(cfg.teamUrl, posted.channel, posted.ts) };
159
+ },
160
+ async setState({ integration, token, thread, deps }, externalId) {
161
+ const cfg = integration.config;
162
+ const sc = slackClient({ token, ...(deps.slackApiBase ? { apiBase: deps.slackApiBase } : {}), ...(deps.fetch ? { fetch: deps.fetch } : {}) });
163
+ const at = externalId.indexOf(":");
164
+ const channel = at === -1 ? cfg.channelId : externalId.slice(0, at);
165
+ const ts = at === -1 ? externalId : externalId.slice(at + 1);
166
+ await sc.update(channel, ts, slackMessage(slackInput(thread, deps.baseUrl)));
167
+ },
168
+ authError: (e) => e instanceof SlackError && ["invalid_auth", "not_authed", "account_inactive", "token_revoked", "token_expired", "missing_scope"].includes(e.code),
169
+ retryable: (e) => (e instanceof SlackError ? e.retryable : true),
170
+ },
171
+ };
172
+ const STATUS_WORD = { open: "Open", progress: "In progress", review: "In review", resolved: "Resolved" };
173
+ /** The thread as the Slack message wants it — strings only; the message builder does the escaping. */
174
+ function slackInput(thread, baseUrl) {
175
+ const anchor = (thread.anchor ?? {});
176
+ const page = anchor.page;
177
+ const route = page?.route ?? thread.route ?? "/";
178
+ const first = thread.comments[0];
179
+ const ctx = (thread.context ?? {});
180
+ const d = ctx.derived;
181
+ return {
182
+ key: String(thread.key),
183
+ status: String(thread.status),
184
+ statusWord: STATUS_WORD[String(thread.status)] ?? String(thread.status),
185
+ siteName: String(thread.project_name ?? ""),
186
+ route,
187
+ label: typeof anchor.label === "string" ? anchor.label : null,
188
+ author: String(thread.created_by_name ?? ""),
189
+ body: first?.body ?? null,
190
+ openUrl: `${baseUrl.replace(/\/+$/, "")}/s/${thread.project_uid}/comments?thread=${encodeURIComponent(String(thread.key))}`,
191
+ pageUrl: page?.origin ? `${page.origin}${page.pathname ?? route}?${param("t")}=${encodeURIComponent(String(thread.key))}` : null,
192
+ environment: typeof thread.environment_key === "string" ? thread.environment_key : null,
193
+ browser: d?.browser || d?.os ? [d.browser, d.os, d.device].filter(Boolean).join(" · ") : null,
194
+ };
195
+ }
196
+ export function pushToTrackers(sql0, deps) {
197
+ return {
198
+ name: "push-to-trackers",
199
+ everyMs: 15_000,
200
+ async run({ sql, now, log }) {
201
+ const [row] = await sql `
202
+ UPDATE outbox SET status = 'inflight', attempts = attempts + 1
203
+ WHERE id = (
204
+ SELECT id FROM outbox
205
+ -- The DATABASE's clock, not the tick's: a row stamped by now() a
206
+ -- moment ago must be claimable by a drain that starts at once,
207
+ -- and a database whose clock runs a second ahead of the process
208
+ -- (a container's does) would otherwise hold it for a whole tick.
209
+ WHERE provider IN ('github', 'linear', 'jira', 'slack') AND status = 'pending' AND next_attempt_at <= GREATEST(${now}, now())
210
+ ORDER BY next_attempt_at
211
+ FOR UPDATE SKIP LOCKED
212
+ LIMIT 1
213
+ )
214
+ RETURNING id, org_id, project_id, integration_id, thread_id, provider, topic, attempts, event
215
+ `;
216
+ if (!row)
217
+ return { did: 0 };
218
+ const done = async (patch) => {
219
+ await sql `
220
+ UPDATE outbox SET status = ${patch.status}, last_error = ${patch.error ?? null},
221
+ next_attempt_at = ${patch.next ?? now},
222
+ completed_at = ${patch.status === "done" || patch.status === "dropped" ? now : null}
223
+ WHERE id = ${row.id}`;
224
+ };
225
+ if (!row.integration_id || !row.thread_id) {
226
+ await done({ status: "dropped", error: "no integration or thread on the row" });
227
+ return { did: 0, more: true };
228
+ }
229
+ const ops = OPS[row.provider];
230
+ // The helpers open their own transactions: the pool, not the tick's connection.
231
+ const integration = await getIntegration(sql0, deps.keys, row.org_id, row.project_id, row.provider);
232
+ if (!ops || !integration || !integration.enabled || integration.id !== row.integration_id) {
233
+ await done({ status: "dropped", error: "the integration is gone" });
234
+ return { did: 0, more: true };
235
+ }
236
+ const token = trackerSecrets(integration.secret).token;
237
+ if (!token) {
238
+ await markIntegration(sql0, row.org_id, integration.id, "needs_reauth", "the token will not decrypt — connect it again");
239
+ await done({ status: "dropped", error: "the token will not decrypt" });
240
+ return { did: 0, more: true };
241
+ }
242
+ const thread = await loadThread(sql0, row.org_id, row.thread_id);
243
+ if (!thread) {
244
+ await done({ status: "dropped", error: "the thread is gone" });
245
+ return { did: 0, more: true };
246
+ }
247
+ const ctx = { integration, token, thread, deps };
248
+ try {
249
+ const kind = REF_KIND[row.provider];
250
+ const [ref] = await sql `
251
+ SELECT external_id FROM external_refs
252
+ WHERE thread_id = ${row.thread_id}::uuid AND provider = ${row.provider}::snag_provider AND kind = ${kind}::snag_ref_kind AND status <> 'detached'
253
+ `;
254
+ if (row.topic === "issue.create") {
255
+ if (!ref) {
256
+ const made = await ops.create(ctx);
257
+ // ONE canonical ref per thread per tracker (05 §3.6's index): a
258
+ // detached ref from an issue the tracker deleted is reused for
259
+ // the new issue rather than kept beside it.
260
+ const reused = await sql `
261
+ UPDATE external_refs
262
+ SET external_id = ${made.externalId}, url = ${made.url}, status = 'synced', synced_at = now(), last_error = NULL
263
+ WHERE thread_id = ${row.thread_id}::uuid AND provider = ${row.provider}::snag_provider AND kind = ${kind}::snag_ref_kind
264
+ `;
265
+ if (reused.count === 0) {
266
+ await sql `
267
+ INSERT INTO external_refs (id, org_id, project_id, thread_id, provider, kind, status, external_id, url)
268
+ VALUES (${randomUUID()}, ${row.org_id}::uuid, ${row.project_id}::uuid, ${row.thread_id}::uuid, ${row.provider}::snag_provider, ${kind}::snag_ref_kind, 'synced',
269
+ ${made.externalId}, ${made.url})
270
+ `;
271
+ }
272
+ }
273
+ }
274
+ else if (row.topic === "issue.state" && ref) {
275
+ await ops.setState(ctx, ref.external_id);
276
+ await sql `UPDATE external_refs SET synced_at = now(), status = 'synced', last_error = NULL
277
+ WHERE thread_id = ${row.thread_id}::uuid AND provider = ${row.provider}::snag_provider AND kind = ${kind}::snag_ref_kind AND external_id = ${ref.external_id}`;
278
+ }
279
+ await markIntegration(sql0, row.org_id, integration.id, "ok", null);
280
+ await done({ status: "done" });
281
+ return { did: 1, more: true };
282
+ }
283
+ catch (e) {
284
+ const message = e instanceof Error ? e.message : String(e);
285
+ const wait = BACKOFF_MIN[Math.min(row.attempts - 1, BACKOFF_MIN.length - 1)];
286
+ const exhausted = row.attempts >= BACKOFF_MIN.length || !ops.retryable(e);
287
+ await markIntegration(sql0, row.org_id, integration.id, ops.authError(e) ? "needs_reauth" : "error", message);
288
+ await done({ status: exhausted ? "failed" : "pending", error: message, next: new Date(now.getTime() + wait * 60_000) });
289
+ log(`jobs: push-to-trackers: ${row.provider} attempt ${row.attempts} failed: ${message}`);
290
+ return { did: 0, more: true };
291
+ }
292
+ },
293
+ };
294
+ }
295
+ /** Drain the queue now — the dashboard's "Create issue" wants the link back in the same click. */
296
+ export async function drainNow(sql, deps, limit = 5) {
297
+ const job = pushToTrackers(sql, deps);
298
+ const ctx = { sql, now: new Date(), log: () => { } };
299
+ for (let i = 0; i < limit; i++) {
300
+ const out = await job.run(ctx);
301
+ if (!out.more)
302
+ break;
303
+ }
304
+ }
305
+ //# sourceMappingURL=outbox.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"outbox.js","sourceRoot":"","sources":["../../src/integrations/outbox.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,135 @@
1
+ import { sanitise } from "../vendor/adapters/index.js";
2
+ export class SlackError extends Error {
3
+ code;
4
+ retryable;
5
+ constructor(code, message, retryable = false) {
6
+ super(message);
7
+ this.code = code;
8
+ this.retryable = retryable;
9
+ }
10
+ }
11
+ const AUTH_ERRORS = new Set(["invalid_auth", "not_authed", "account_inactive", "token_revoked", "token_expired", "missing_scope"]);
12
+ export function slackClient(opts) {
13
+ const base = (opts.apiBase ?? "https://slack.com/api").replace(/\/+$/, "");
14
+ const f = opts.fetch ?? globalThis.fetch;
15
+ async function call(method, body = {}) {
16
+ let res;
17
+ try {
18
+ res = await f(`${base}/${method}`, {
19
+ method: "POST",
20
+ headers: { authorization: `Bearer ${opts.token}`, "content-type": "application/json; charset=utf-8", "user-agent": "snagset" },
21
+ body: JSON.stringify(body),
22
+ });
23
+ }
24
+ catch (e) {
25
+ throw new SlackError("unreachable", `could not reach Slack: ${e instanceof Error ? e.message : String(e)}`, true);
26
+ }
27
+ if (res.status === 429)
28
+ throw new SlackError("ratelimited", "Slack is rate limiting this app", true);
29
+ let parsed = null;
30
+ try {
31
+ parsed = await res.json();
32
+ }
33
+ catch {
34
+ /* no body */
35
+ }
36
+ if (!res.ok)
37
+ throw new SlackError("http", `Slack answered ${res.status}`, res.status >= 500);
38
+ if (!parsed?.ok) {
39
+ const code = String(parsed?.error ?? "unknown");
40
+ const what = code === "invalid_auth" || code === "not_authed" || code === "token_revoked"
41
+ ? "the bot token was refused"
42
+ : code === "missing_scope"
43
+ ? `the app lacks a scope it needs (${String(parsed?.needed ?? "chat:write")})`
44
+ : code === "channel_not_found"
45
+ ? "the channel was not found — a private channel needs the app invited into it"
46
+ : code === "not_in_channel"
47
+ ? "the app is not in that channel — invite it, or give it chat:write.public for public channels"
48
+ : `Slack answered ${code}`;
49
+ throw new SlackError(code, what, code === "internal_error" || code === "service_unavailable");
50
+ }
51
+ return parsed;
52
+ }
53
+ return {
54
+ /** Who the token is, and where the workspace lives (for permalinks). */
55
+ async whoami() {
56
+ const r = await call("auth.test");
57
+ return { team: r.team, url: r.url.replace(/\/+$/, ""), bot: r.user };
58
+ },
59
+ /** `#name` or a channel id → the channel; public channels by name, any channel by id. */
60
+ async resolveChannel(input) {
61
+ const wanted = input.trim().replace(/^#/, "");
62
+ if (/^[CG][A-Z0-9]{2,}$/.test(wanted)) {
63
+ const r = await call("conversations.info", { channel: wanted });
64
+ return { id: r.channel.id, name: r.channel.name, isMember: !!r.channel.is_member };
65
+ }
66
+ let cursor = "";
67
+ for (let page = 0; page < 10; page++) {
68
+ const r = await call("conversations.list", { types: "public_channel,private_channel", limit: 1000, exclude_archived: true, ...(cursor ? { cursor } : {}) });
69
+ const hit = r.channels.find((c) => c.name.toLowerCase() === wanted.toLowerCase());
70
+ if (hit)
71
+ return { id: hit.id, name: hit.name, isMember: !!hit.is_member };
72
+ cursor = r.response_metadata?.next_cursor ?? "";
73
+ if (!cursor)
74
+ break;
75
+ }
76
+ throw new SlackError("channel_not_found", `no channel called #${wanted} is visible to the app — a private channel needs the app invited into it`);
77
+ },
78
+ async post(channel, message) {
79
+ const r = await call("chat.postMessage", { channel, ...message, parse: "none", unfurl_links: false, unfurl_media: false });
80
+ return { ts: r.ts, channel: r.channel };
81
+ },
82
+ async update(channel, ts, message) {
83
+ // chat.update defaults to parse=client, which would expand a typed @channel; say none.
84
+ await call("chat.update", { channel, ts, ...message, parse: "none" });
85
+ },
86
+ };
87
+ }
88
+ const STATUS_MARK = { open: "🔵", progress: "🟠", review: "🟣", resolved: "✅" };
89
+ /** The message, 07 §6.1's shape: what, where, who, the words, and two buttons — edited in place as the status moves. */
90
+ export function slackMessage(i) {
91
+ const mark = STATUS_MARK[i.status] ?? "⚪";
92
+ const md = (s, max) => String(sanitise(s, "slack-mrkdwn", { singleLine: true, max }));
93
+ const plain = (s, max) => String(sanitise(s, "slack-plain", { singleLine: true, max }));
94
+ const route = md(i.route, 150);
95
+ const author = md(i.author, 80);
96
+ const label = i.label ? md(i.label, 150) : "";
97
+ const body = i.body ? String(sanitise(i.body, "slack-mrkdwn", { max: 500 })) : "";
98
+ const context = [i.environment ? `\`${md(i.environment, 40)}\`` : "", i.browser ? md(i.browser, 150) : ""].filter(Boolean).join(" · ");
99
+ return {
100
+ // The fallback is rendered and notifies: plain, and never a name or a body raw.
101
+ text: `${i.key} · ${i.statusWord} — ${plain(i.route, 150)} by ${plain(i.author, 80)}${i.body ? `: ${plain(i.body, 200)}` : ""}`,
102
+ blocks: [
103
+ {
104
+ type: "section",
105
+ text: {
106
+ type: "mrkdwn",
107
+ text: `${mark} *${i.key} · ${i.statusWord}* on ${md(i.siteName, 80)}\n*<${i.openUrl}|${route}>*${label ? ` — ${label}` : ""} · ${author}`,
108
+ },
109
+ },
110
+ ...(body ? [{ type: "section", text: { type: "mrkdwn", text: body.split("\n").map((l) => `> ${l}`).join("\n") } }] : []),
111
+ ...(context ? [{ type: "context", elements: [{ type: "mrkdwn", text: context }] }] : []),
112
+ {
113
+ type: "actions",
114
+ elements: [
115
+ { type: "button", text: { type: "plain_text", text: "Open in Snagset" }, url: i.openUrl },
116
+ ...(i.pageUrl ? [{ type: "button", text: { type: "plain_text", text: "Open the page" }, url: i.pageUrl }] : []),
117
+ ],
118
+ },
119
+ ],
120
+ };
121
+ }
122
+ /** The message's permalink from the workspace URL, the channel and the ts — no extra call. */
123
+ export function slackPermalink(teamUrl, channel, ts) {
124
+ return `${teamUrl.replace(/\/+$/, "")}/archives/${channel}/p${ts.replace(".", "")}`;
125
+ }
126
+ /** 07 §6.2 — the manifest the operator creates the app from, one click. */
127
+ export function slackManifest(instanceName) {
128
+ return {
129
+ display_information: { name: `Snagset · ${instanceName}`.slice(0, 35), description: "Review comments from Snagset, one message each, kept up to date." },
130
+ features: { bot_user: { display_name: "Snagset", always_online: false } },
131
+ oauth_config: { scopes: { bot: ["chat:write", "chat:write.public", "channels:read", "groups:read"] } },
132
+ settings: { org_deploy_enabled: false, socket_mode_enabled: false, token_rotation_enabled: false },
133
+ };
134
+ }
135
+ //# sourceMappingURL=slack.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"slack.js","sourceRoot":"","sources":["../../src/integrations/slack.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,101 @@
1
+ import { randomBytes, randomUUID } from "node:crypto";
2
+ import { open, seal } from "../crypto.js";
3
+ /** The providers a site can be connected to today; the enum has more. */
4
+ export const TRACKERS = ["github", "linear", "jira", "slack"];
5
+ export const isTracker = (s) => TRACKERS.includes(s);
6
+ /** What a thread's mirror is called in each tracker — `external_refs.kind`. */
7
+ export const REF_KIND = { github: "issue", linear: "issue", jira: "issue", slack: "message" };
8
+ const SELECT = (tx) => tx `
9
+ id, org_id, project_id, provider, config, credentials, credentials_iv, enabled, install_state, last_error, updated_at`;
10
+ function shapeRow(keys, r) {
11
+ const sealed = r.credentials && r.credentials_iv ? { ciphertext: r.credentials, iv: r.credentials_iv } : null;
12
+ const secret = sealed ? open(keys, sealed) : null;
13
+ return {
14
+ id: r.id,
15
+ orgId: r.org_id,
16
+ projectId: r.project_id,
17
+ provider: r.provider,
18
+ config: r.config,
19
+ secret,
20
+ unreadable: !!sealed && secret === null,
21
+ enabled: r.enabled,
22
+ installState: r.install_state,
23
+ lastError: r.last_error,
24
+ updatedAt: r.updated_at,
25
+ };
26
+ }
27
+ export async function getIntegration(sql, keys, orgId, projectId, provider) {
28
+ const [row] = await sql.begin(async (tx) => {
29
+ await tx `SELECT set_config('app.org_id', ${orgId}, true)`;
30
+ return tx `
31
+ SELECT ${SELECT(tx)} FROM integrations
32
+ WHERE project_id = ${projectId}::uuid AND provider = ${provider}::snag_provider AND deleted_at IS NULL
33
+ `;
34
+ });
35
+ return row ? shapeRow(keys, row) : null;
36
+ }
37
+ /** Every live connection in the studio, for the Integrations screen. */
38
+ export async function listIntegrations(sql, keys, orgId) {
39
+ const rows = await sql.begin(async (tx) => {
40
+ await tx `SELECT set_config('app.org_id', ${orgId}, true)`;
41
+ return tx `
42
+ SELECT ${SELECT(tx)} FROM integrations
43
+ WHERE org_id = ${orgId}::uuid AND deleted_at IS NULL ORDER BY updated_at DESC
44
+ `;
45
+ });
46
+ return rows.map((r) => shapeRow(keys, r));
47
+ }
48
+ export async function putIntegration(sql, keys, input) {
49
+ const sealed = input.secret === null ? null : seal(keys, input.secret);
50
+ await sql.begin(async (tx) => {
51
+ await tx `SELECT set_config('app.org_id', ${input.orgId}, true)`;
52
+ // A soft-deleted row for the same pair is revived rather than duplicated:
53
+ // `UNIQUE (project_id, provider)` counts the tombstone too.
54
+ await tx `
55
+ INSERT INTO integrations (id, org_id, project_id, provider, config, credentials, credentials_iv, enabled, install_state, last_error)
56
+ VALUES (${randomUUID()}, ${input.orgId}::uuid, ${input.projectId}::uuid, ${input.provider}::snag_provider,
57
+ ${tx.json(input.config)}, ${sealed?.ciphertext ?? null}, ${sealed?.iv ?? null}, true, 'ok', NULL)
58
+ ON CONFLICT (project_id, provider) DO UPDATE
59
+ SET config = EXCLUDED.config,
60
+ credentials = COALESCE(EXCLUDED.credentials, integrations.credentials),
61
+ credentials_iv = COALESCE(EXCLUDED.credentials_iv, integrations.credentials_iv),
62
+ enabled = true, install_state = 'ok', last_error = NULL, deleted_at = NULL, updated_at = now()
63
+ `;
64
+ });
65
+ }
66
+ export async function removeIntegration(sql, orgId, projectId, provider) {
67
+ await sql.begin(async (tx) => {
68
+ await tx `SELECT set_config('app.org_id', ${orgId}, true)`;
69
+ await tx `
70
+ UPDATE integrations SET deleted_at = now(), enabled = false, credentials = NULL, credentials_iv = NULL, updated_at = now()
71
+ WHERE project_id = ${projectId}::uuid AND provider = ${provider}::snag_provider AND deleted_at IS NULL
72
+ `;
73
+ });
74
+ }
75
+ /** Health is a column: the worker writes what it saw, the card shows it. */
76
+ export async function markIntegration(sql, orgId, id, state, error) {
77
+ await sql.begin(async (tx) => {
78
+ await tx `SELECT set_config('app.org_id', ${orgId}, true)`;
79
+ await tx `UPDATE integrations SET install_state = ${state}, last_error = ${error}, updated_at = now() WHERE id = ${id}::uuid`;
80
+ });
81
+ }
82
+ export const trackerSecrets = (secret) => githubSecrets(secret);
83
+ export function githubSecrets(secret) {
84
+ if (!secret)
85
+ return { token: null, webhookSecret: null };
86
+ if (secret.startsWith("{")) {
87
+ try {
88
+ const j = JSON.parse(secret);
89
+ return {
90
+ token: typeof j.token === "string" && j.token ? j.token : null,
91
+ webhookSecret: typeof j.webhookSecret === "string" && j.webhookSecret ? j.webhookSecret : null,
92
+ };
93
+ }
94
+ catch {
95
+ /* a token that happens to start with a brace — fall through */
96
+ }
97
+ }
98
+ return { token: secret, webhookSecret: null };
99
+ }
100
+ export const mintWebhookSecret = () => randomBytes(24).toString("base64url");
101
+ //# sourceMappingURL=store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.js","sourceRoot":"","sources":["../../src/integrations/store.ts"],"names":[],"mappings":"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"}