@noodleseed/one 0.97.0 → 0.98.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 (84) hide show
  1. package/dist/bin.js +0 -0
  2. package/node_modules/@noodle-borg/service/dist/billing/stripe-provider.d.ts +4 -2
  3. package/node_modules/@noodle-borg/service/dist/billing/stripe-provider.d.ts.map +1 -1
  4. package/node_modules/@noodle-borg/service/dist/billing/stripe-provider.js +4 -26
  5. package/node_modules/@noodle-borg/service/dist/billing/stripe-provider.js.map +1 -1
  6. package/node_modules/@noodle-borg/service/dist/billing/stripe-service.d.ts +1 -0
  7. package/node_modules/@noodle-borg/service/dist/billing/stripe-service.d.ts.map +1 -1
  8. package/node_modules/@noodle-borg/service/dist/billing/stripe-service.js +7 -3
  9. package/node_modules/@noodle-borg/service/dist/billing/stripe-service.js.map +1 -1
  10. package/node_modules/@noodle-borg/service/dist/billing/stripe-webhook.d.ts +10 -0
  11. package/node_modules/@noodle-borg/service/dist/billing/stripe-webhook.d.ts.map +1 -0
  12. package/node_modules/@noodle-borg/service/dist/billing/stripe-webhook.js +133 -0
  13. package/node_modules/@noodle-borg/service/dist/billing/stripe-webhook.js.map +1 -0
  14. package/node_modules/@noodle-borg/service/dist/feedback-operations/bootstrap.d.ts +7 -0
  15. package/node_modules/@noodle-borg/service/dist/feedback-operations/bootstrap.d.ts.map +1 -0
  16. package/node_modules/@noodle-borg/service/dist/feedback-operations/bootstrap.js +23 -0
  17. package/node_modules/@noodle-borg/service/dist/feedback-operations/bootstrap.js.map +1 -0
  18. package/node_modules/@noodle-borg/service/dist/feedback-operations/config.d.ts +12 -0
  19. package/node_modules/@noodle-borg/service/dist/feedback-operations/config.d.ts.map +1 -0
  20. package/node_modules/@noodle-borg/service/dist/feedback-operations/config.js +47 -0
  21. package/node_modules/@noodle-borg/service/dist/feedback-operations/config.js.map +1 -0
  22. package/node_modules/@noodle-borg/service/dist/feedback-operations/contracts.d.ts +24 -0
  23. package/node_modules/@noodle-borg/service/dist/feedback-operations/contracts.d.ts.map +1 -0
  24. package/node_modules/@noodle-borg/service/dist/feedback-operations/contracts.js +2 -0
  25. package/node_modules/@noodle-borg/service/dist/feedback-operations/contracts.js.map +1 -0
  26. package/node_modules/@noodle-borg/service/dist/feedback-operations/delivery-worker.d.ts +29 -0
  27. package/node_modules/@noodle-borg/service/dist/feedback-operations/delivery-worker.d.ts.map +1 -0
  28. package/node_modules/@noodle-borg/service/dist/feedback-operations/delivery-worker.js +256 -0
  29. package/node_modules/@noodle-borg/service/dist/feedback-operations/delivery-worker.js.map +1 -0
  30. package/node_modules/@noodle-borg/service/dist/feedback-operations/linear-client.d.ts +39 -0
  31. package/node_modules/@noodle-borg/service/dist/feedback-operations/linear-client.d.ts.map +1 -0
  32. package/node_modules/@noodle-borg/service/dist/feedback-operations/linear-client.js +142 -0
  33. package/node_modules/@noodle-borg/service/dist/feedback-operations/linear-client.js.map +1 -0
  34. package/node_modules/@noodle-borg/service/dist/feedback-operations/reporter-email.d.ts +33 -0
  35. package/node_modules/@noodle-borg/service/dist/feedback-operations/reporter-email.d.ts.map +1 -0
  36. package/node_modules/@noodle-borg/service/dist/feedback-operations/reporter-email.js +142 -0
  37. package/node_modules/@noodle-borg/service/dist/feedback-operations/reporter-email.js.map +1 -0
  38. package/node_modules/@noodle-borg/service/dist/feedback-operations/slack-card.d.ts +138 -0
  39. package/node_modules/@noodle-borg/service/dist/feedback-operations/slack-card.d.ts.map +1 -0
  40. package/node_modules/@noodle-borg/service/dist/feedback-operations/slack-card.js +199 -0
  41. package/node_modules/@noodle-borg/service/dist/feedback-operations/slack-card.js.map +1 -0
  42. package/node_modules/@noodle-borg/service/dist/feedback-operations/slack-client.d.ts +44 -0
  43. package/node_modules/@noodle-borg/service/dist/feedback-operations/slack-client.d.ts.map +1 -0
  44. package/node_modules/@noodle-borg/service/dist/feedback-operations/slack-client.js +95 -0
  45. package/node_modules/@noodle-borg/service/dist/feedback-operations/slack-client.js.map +1 -0
  46. package/node_modules/@noodle-borg/service/dist/main.js +38 -3
  47. package/node_modules/@noodle-borg/service/dist/main.js.map +1 -1
  48. package/node_modules/@noodle-borg/service/dist/options.d.ts +14 -0
  49. package/node_modules/@noodle-borg/service/dist/options.d.ts.map +1 -1
  50. package/node_modules/@noodle-borg/service/dist/routes/feedback-operations-dispatch.d.ts +9 -0
  51. package/node_modules/@noodle-borg/service/dist/routes/feedback-operations-dispatch.d.ts.map +1 -0
  52. package/node_modules/@noodle-borg/service/dist/routes/feedback-operations-dispatch.js +53 -0
  53. package/node_modules/@noodle-borg/service/dist/routes/feedback-operations-dispatch.js.map +1 -0
  54. package/node_modules/@noodle-borg/service/dist/routes/feedback.d.ts +2 -0
  55. package/node_modules/@noodle-borg/service/dist/routes/feedback.d.ts.map +1 -1
  56. package/node_modules/@noodle-borg/service/dist/routes/feedback.js +22 -0
  57. package/node_modules/@noodle-borg/service/dist/routes/feedback.js.map +1 -1
  58. package/node_modules/@noodle-borg/service/dist/routes/linear-feedback-webhook.d.ts +17 -0
  59. package/node_modules/@noodle-borg/service/dist/routes/linear-feedback-webhook.d.ts.map +1 -0
  60. package/node_modules/@noodle-borg/service/dist/routes/linear-feedback-webhook.js +141 -0
  61. package/node_modules/@noodle-borg/service/dist/routes/linear-feedback-webhook.js.map +1 -0
  62. package/node_modules/@noodle-borg/service/dist/routes/slack-feedback-actions.d.ts +13 -0
  63. package/node_modules/@noodle-borg/service/dist/routes/slack-feedback-actions.d.ts.map +1 -0
  64. package/node_modules/@noodle-borg/service/dist/routes/slack-feedback-actions.js +99 -0
  65. package/node_modules/@noodle-borg/service/dist/routes/slack-feedback-actions.js.map +1 -0
  66. package/node_modules/@noodle-borg/service/dist/serve.d.ts.map +1 -1
  67. package/node_modules/@noodle-borg/service/dist/serve.js +23 -0
  68. package/node_modules/@noodle-borg/service/dist/serve.js.map +1 -1
  69. package/node_modules/@noodle-borg/service/dist/service-resource-cleanup.d.ts +1 -0
  70. package/node_modules/@noodle-borg/service/dist/service-resource-cleanup.d.ts.map +1 -1
  71. package/node_modules/@noodle-borg/service/dist/service-resource-cleanup.js +2 -0
  72. package/node_modules/@noodle-borg/service/dist/service-resource-cleanup.js.map +1 -1
  73. package/node_modules/@noodle-borg/service/dist/service.d.ts.map +1 -1
  74. package/node_modules/@noodle-borg/service/dist/service.js +6 -0
  75. package/node_modules/@noodle-borg/service/dist/service.js.map +1 -1
  76. package/node_modules/@noodle-borg/service/dist/store/feedback-operations.d.ts +186 -0
  77. package/node_modules/@noodle-borg/service/dist/store/feedback-operations.d.ts.map +1 -0
  78. package/node_modules/@noodle-borg/service/dist/store/feedback-operations.js +483 -0
  79. package/node_modules/@noodle-borg/service/dist/store/feedback-operations.js.map +1 -0
  80. package/node_modules/@noodle-borg/service/dist/store/postgres-feedback-operations.d.ts +35 -0
  81. package/node_modules/@noodle-borg/service/dist/store/postgres-feedback-operations.d.ts.map +1 -0
  82. package/node_modules/@noodle-borg/service/dist/store/postgres-feedback-operations.js +172 -0
  83. package/node_modules/@noodle-borg/service/dist/store/postgres-feedback-operations.js.map +1 -0
  84. package/package.json +1 -1
@@ -0,0 +1,142 @@
1
+ import { z } from 'zod';
2
+ import { sanitizedFeedbackText } from './slack-card.js';
3
+ const SUBJECT = "We're looking into your Noodle Seed feedback";
4
+ const RESEND_ENDPOINT = 'https://api.resend.com/emails';
5
+ const DELIVERY_TIMEOUT_MS = 10_000;
6
+ const MAX_PROVIDER_RESPONSE_BYTES = 64_000;
7
+ const ProviderResponseSchema = z.object({ id: z.string().trim().min(1) });
8
+ export class ResendFeedbackReporterEmailSender {
9
+ #apiKey;
10
+ #from;
11
+ #replyTo;
12
+ #fetch;
13
+ constructor(options) {
14
+ this.#apiKey = options.apiKey;
15
+ this.#from = options.from;
16
+ this.#replyTo = options.replyTo;
17
+ this.#fetch = options.fetch ?? globalThis.fetch;
18
+ }
19
+ async send(input, options = {}) {
20
+ const response = await this.#fetch(RESEND_ENDPOINT, {
21
+ method: 'POST',
22
+ redirect: 'error',
23
+ signal: boundedSignal(options.signal),
24
+ headers: {
25
+ authorization: `Bearer ${this.#apiKey}`,
26
+ 'content-type': 'application/json',
27
+ 'idempotency-key': `feedback-accepted/${input.githubIssueNumber}`,
28
+ },
29
+ body: JSON.stringify({
30
+ from: this.#from,
31
+ to: [input.recipient],
32
+ reply_to: this.#replyTo,
33
+ ...renderFeedbackAcceptedEmail(input),
34
+ }),
35
+ });
36
+ if (!response.ok) {
37
+ throw new Error(`feedback email provider rejected request with status ${response.status}`);
38
+ }
39
+ let providerResponse;
40
+ try {
41
+ providerResponse = JSON.parse(await readBoundedText(response, MAX_PROVIDER_RESPONSE_BYTES));
42
+ }
43
+ catch {
44
+ throw new Error('feedback email provider returned an invalid response');
45
+ }
46
+ const parsed = zProviderResponse(providerResponse);
47
+ return { providerMessageId: parsed };
48
+ }
49
+ }
50
+ function boundedSignal(signal) {
51
+ const timeout = AbortSignal.timeout(DELIVERY_TIMEOUT_MS);
52
+ return signal === undefined ? timeout : AbortSignal.any([signal, timeout]);
53
+ }
54
+ export function renderFeedbackAcceptedEmail(input) {
55
+ const title = clean(sanitizedFeedbackText(input.title, 200), 200);
56
+ const message = sanitizedFeedbackText(input.message, 4_000);
57
+ const text = `Hi,
58
+
59
+ Thanks for sharing this feedback with Noodle Seed. I wanted to let you know that our engineering team is now looking into:
60
+
61
+ ${title}
62
+
63
+ ${message}
64
+
65
+ If you'd like to discuss it directly, just reply to this email.
66
+
67
+ — Asad`;
68
+ const html = `<!doctype html>
69
+ <html lang="en">
70
+ <head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>${escapeHtml(SUBJECT)}</title></head>
71
+ <body style="margin:0;background:#f4f2ed;color:#171714;font-family:Inter,-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;">
72
+ <table role="presentation" width="100%" cellspacing="0" cellpadding="0" style="background:#f4f2ed;padding:32px 16px;"><tr><td align="center">
73
+ <table role="presentation" width="100%" cellspacing="0" cellpadding="0" style="max-width:600px;background:#fff;border:1px solid #dedbd3;border-radius:16px;">
74
+ <tr><td style="padding:40px;">
75
+ <h1 style="margin:0 0 24px;font-size:28px;line-height:1.2;">We're looking into your feedback</h1>
76
+ <p style="margin:0 0 18px;font-size:16px;line-height:1.65;">Hi,</p>
77
+ <p style="margin:0 0 18px;font-size:16px;line-height:1.65;">Thanks for sharing this feedback with Noodle Seed. I wanted to let you know that our engineering team is now looking into:</p>
78
+ <p style="margin:0 0 10px;font-size:18px;line-height:1.5;"><strong>${escapeHtml(title)}</strong></p>
79
+ <p style="margin:0 0 24px;font-size:16px;line-height:1.65;white-space:pre-wrap;">${escapeHtml(message)}</p>
80
+ <p style="margin:0 0 18px;font-size:16px;line-height:1.65;">If you'd like to discuss it directly, just reply to this email.</p>
81
+ <p style="margin:0;font-size:16px;line-height:1.65;">— Asad</p>
82
+ </td></tr>
83
+ </table>
84
+ </td></tr></table>
85
+ </body>
86
+ </html>`;
87
+ return { subject: SUBJECT, text, html };
88
+ }
89
+ function clean(value, length) {
90
+ return value
91
+ .replace(/[\r\n]+/g, ' ')
92
+ .trim()
93
+ .slice(0, length);
94
+ }
95
+ function escapeHtml(value) {
96
+ return value
97
+ .replaceAll('&', '&amp;')
98
+ .replaceAll('<', '&lt;')
99
+ .replaceAll('>', '&gt;')
100
+ .replaceAll('"', '&quot;')
101
+ .replaceAll("'", '&#039;');
102
+ }
103
+ function zProviderResponse(value) {
104
+ const parsed = ProviderResponseSchema.safeParse(value);
105
+ if (!parsed.success) {
106
+ throw new Error('feedback email provider returned an invalid response');
107
+ }
108
+ return parsed.data.id;
109
+ }
110
+ async function readBoundedText(response, maxBytes) {
111
+ const declaredLength = Number(response.headers.get('content-length'));
112
+ if (Number.isFinite(declaredLength) && declaredLength > maxBytes) {
113
+ throw new Error('response exceeds byte limit');
114
+ }
115
+ if (response.body === null)
116
+ return '';
117
+ const reader = response.body.getReader();
118
+ const chunks = [];
119
+ let total = 0;
120
+ try {
121
+ while (true) {
122
+ const { done, value } = await reader.read();
123
+ if (done)
124
+ break;
125
+ total += value.byteLength;
126
+ if (total > maxBytes)
127
+ throw new Error('response exceeds byte limit');
128
+ chunks.push(value);
129
+ }
130
+ }
131
+ finally {
132
+ await reader.cancel().catch(() => undefined);
133
+ }
134
+ const bytes = new Uint8Array(total);
135
+ let offset = 0;
136
+ for (const chunk of chunks) {
137
+ bytes.set(chunk, offset);
138
+ offset += chunk.byteLength;
139
+ }
140
+ return new TextDecoder().decode(bytes);
141
+ }
142
+ //# sourceMappingURL=reporter-email.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reporter-email.js","sourceRoot":"","sources":["../../src/feedback-operations/reporter-email.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,138 @@
1
+ import { z } from 'zod';
2
+ import type { SlackFeedbackCardInput } from './contracts.js';
3
+ export declare const SlackFeedbackCardSchema: z.ZodObject<{
4
+ text: z.ZodString;
5
+ blocks: z.ZodArray<z.ZodUnion<readonly [z.ZodObject<{
6
+ type: z.ZodLiteral<"header">;
7
+ text: z.ZodObject<{
8
+ type: z.ZodLiteral<"plain_text">;
9
+ text: z.ZodString;
10
+ emoji: z.ZodOptional<z.ZodBoolean>;
11
+ }, z.core.$strict>;
12
+ }, z.core.$strict>, z.ZodObject<{
13
+ type: z.ZodLiteral<"section">;
14
+ text: z.ZodObject<{
15
+ type: z.ZodLiteral<"mrkdwn">;
16
+ text: z.ZodString;
17
+ }, z.core.$strict>;
18
+ }, z.core.$strict>, z.ZodObject<{
19
+ type: z.ZodLiteral<"context">;
20
+ elements: z.ZodArray<z.ZodObject<{
21
+ type: z.ZodLiteral<"mrkdwn">;
22
+ text: z.ZodString;
23
+ }, z.core.$strict>>;
24
+ }, z.core.$strict>, z.ZodObject<{
25
+ type: z.ZodLiteral<"actions">;
26
+ elements: z.ZodArray<z.ZodUnion<readonly [z.ZodObject<{
27
+ type: z.ZodLiteral<"button">;
28
+ text: z.ZodObject<{
29
+ type: z.ZodLiteral<"plain_text">;
30
+ text: z.ZodString;
31
+ emoji: z.ZodOptional<z.ZodBoolean>;
32
+ }, z.core.$strict>;
33
+ url: z.ZodURL;
34
+ action_id: z.ZodLiteral<"feedback_open_linear">;
35
+ }, z.core.$strict>, z.ZodObject<{
36
+ type: z.ZodLiteral<"button">;
37
+ text: z.ZodObject<{
38
+ type: z.ZodLiteral<"plain_text">;
39
+ text: z.ZodString;
40
+ emoji: z.ZodOptional<z.ZodBoolean>;
41
+ }, z.core.$strict>;
42
+ style: z.ZodLiteral<"primary">;
43
+ action_id: z.ZodLiteral<"feedback_start_work">;
44
+ value: z.ZodString;
45
+ confirm: z.ZodObject<{
46
+ title: z.ZodObject<{
47
+ type: z.ZodLiteral<"plain_text">;
48
+ text: z.ZodString;
49
+ emoji: z.ZodOptional<z.ZodBoolean>;
50
+ }, z.core.$strict>;
51
+ text: z.ZodObject<{
52
+ type: z.ZodLiteral<"mrkdwn">;
53
+ text: z.ZodString;
54
+ }, z.core.$strict>;
55
+ confirm: z.ZodObject<{
56
+ type: z.ZodLiteral<"plain_text">;
57
+ text: z.ZodString;
58
+ emoji: z.ZodOptional<z.ZodBoolean>;
59
+ }, z.core.$strict>;
60
+ deny: z.ZodObject<{
61
+ type: z.ZodLiteral<"plain_text">;
62
+ text: z.ZodString;
63
+ emoji: z.ZodOptional<z.ZodBoolean>;
64
+ }, z.core.$strict>;
65
+ }, z.core.$strict>;
66
+ }, z.core.$strict>]>>;
67
+ }, z.core.$strict>]>>;
68
+ }, z.core.$strict>;
69
+ export type SlackFeedbackCard = z.infer<typeof SlackFeedbackCardSchema>;
70
+ export declare const SlackFeedbackCardJsonSchema: z.core.ZodStandardJSONSchemaPayload<z.ZodObject<{
71
+ text: z.ZodString;
72
+ blocks: z.ZodArray<z.ZodUnion<readonly [z.ZodObject<{
73
+ type: z.ZodLiteral<"header">;
74
+ text: z.ZodObject<{
75
+ type: z.ZodLiteral<"plain_text">;
76
+ text: z.ZodString;
77
+ emoji: z.ZodOptional<z.ZodBoolean>;
78
+ }, z.core.$strict>;
79
+ }, z.core.$strict>, z.ZodObject<{
80
+ type: z.ZodLiteral<"section">;
81
+ text: z.ZodObject<{
82
+ type: z.ZodLiteral<"mrkdwn">;
83
+ text: z.ZodString;
84
+ }, z.core.$strict>;
85
+ }, z.core.$strict>, z.ZodObject<{
86
+ type: z.ZodLiteral<"context">;
87
+ elements: z.ZodArray<z.ZodObject<{
88
+ type: z.ZodLiteral<"mrkdwn">;
89
+ text: z.ZodString;
90
+ }, z.core.$strict>>;
91
+ }, z.core.$strict>, z.ZodObject<{
92
+ type: z.ZodLiteral<"actions">;
93
+ elements: z.ZodArray<z.ZodUnion<readonly [z.ZodObject<{
94
+ type: z.ZodLiteral<"button">;
95
+ text: z.ZodObject<{
96
+ type: z.ZodLiteral<"plain_text">;
97
+ text: z.ZodString;
98
+ emoji: z.ZodOptional<z.ZodBoolean>;
99
+ }, z.core.$strict>;
100
+ url: z.ZodURL;
101
+ action_id: z.ZodLiteral<"feedback_open_linear">;
102
+ }, z.core.$strict>, z.ZodObject<{
103
+ type: z.ZodLiteral<"button">;
104
+ text: z.ZodObject<{
105
+ type: z.ZodLiteral<"plain_text">;
106
+ text: z.ZodString;
107
+ emoji: z.ZodOptional<z.ZodBoolean>;
108
+ }, z.core.$strict>;
109
+ style: z.ZodLiteral<"primary">;
110
+ action_id: z.ZodLiteral<"feedback_start_work">;
111
+ value: z.ZodString;
112
+ confirm: z.ZodObject<{
113
+ title: z.ZodObject<{
114
+ type: z.ZodLiteral<"plain_text">;
115
+ text: z.ZodString;
116
+ emoji: z.ZodOptional<z.ZodBoolean>;
117
+ }, z.core.$strict>;
118
+ text: z.ZodObject<{
119
+ type: z.ZodLiteral<"mrkdwn">;
120
+ text: z.ZodString;
121
+ }, z.core.$strict>;
122
+ confirm: z.ZodObject<{
123
+ type: z.ZodLiteral<"plain_text">;
124
+ text: z.ZodString;
125
+ emoji: z.ZodOptional<z.ZodBoolean>;
126
+ }, z.core.$strict>;
127
+ deny: z.ZodObject<{
128
+ type: z.ZodLiteral<"plain_text">;
129
+ text: z.ZodString;
130
+ emoji: z.ZodOptional<z.ZodBoolean>;
131
+ }, z.core.$strict>;
132
+ }, z.core.$strict>;
133
+ }, z.core.$strict>]>>;
134
+ }, z.core.$strict>]>>;
135
+ }, z.core.$strict>>;
136
+ export declare function renderSlackFeedbackCard(input: SlackFeedbackCardInput): SlackFeedbackCard;
137
+ export declare function sanitizedFeedbackText(value: string, maxLength?: number): string;
138
+ //# sourceMappingURL=slack-card.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"slack-card.d.ts","sourceRoot":"","sources":["../../src/feedback-operations/slack-card.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,gBAAgB,CAAC;AAyD7D,eAAO,MAAM,uBAAuB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBAUzB,CAAC;AAEZ,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,uBAAuB,CAAC,CAAC;AAExE,eAAO,MAAM,2BAA2B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;mBAEtC,CAAC;AAEH,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,sBAAsB,GAAG,iBAAiB,CA8DxF;AAoBD,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,SAAqB,GAAG,MAAM,CAE3F"}
@@ -0,0 +1,199 @@
1
+ import { z } from 'zod';
2
+ const START_WORK_ACTION_ID = 'feedback_start_work';
3
+ const MAX_EXCERPT_LENGTH = 900;
4
+ const PlainTextSchema = z
5
+ .object({
6
+ type: z.literal('plain_text'),
7
+ text: z.string().min(1).max(300),
8
+ emoji: z.boolean().optional(),
9
+ })
10
+ .strict();
11
+ const MrkdwnTextSchema = z
12
+ .object({ type: z.literal('mrkdwn'), text: z.string().min(1).max(3_000) })
13
+ .strict();
14
+ const HeaderBlockSchema = z.object({ type: z.literal('header'), text: PlainTextSchema }).strict();
15
+ const SectionBlockSchema = z
16
+ .object({ type: z.literal('section'), text: MrkdwnTextSchema })
17
+ .strict();
18
+ const ContextBlockSchema = z
19
+ .object({ type: z.literal('context'), elements: z.array(MrkdwnTextSchema).min(1).max(10) })
20
+ .strict();
21
+ const LinearButtonSchema = z
22
+ .object({
23
+ type: z.literal('button'),
24
+ text: PlainTextSchema,
25
+ url: z.url(),
26
+ action_id: z.literal('feedback_open_linear'),
27
+ })
28
+ .strict();
29
+ const StartWorkButtonSchema = z
30
+ .object({
31
+ type: z.literal('button'),
32
+ text: PlainTextSchema,
33
+ style: z.literal('primary'),
34
+ action_id: z.literal(START_WORK_ACTION_ID),
35
+ value: z.string().min(1).max(2_000),
36
+ confirm: z
37
+ .object({
38
+ title: PlainTextSchema,
39
+ text: MrkdwnTextSchema,
40
+ confirm: PlainTextSchema,
41
+ deny: PlainTextSchema,
42
+ })
43
+ .strict(),
44
+ })
45
+ .strict();
46
+ const ActionsBlockSchema = z
47
+ .object({
48
+ type: z.literal('actions'),
49
+ elements: z
50
+ .array(z.union([LinearButtonSchema, StartWorkButtonSchema]))
51
+ .min(1)
52
+ .max(2),
53
+ })
54
+ .strict();
55
+ export const SlackFeedbackCardSchema = z
56
+ .object({
57
+ text: z.string().min(1).max(300),
58
+ blocks: z
59
+ .array(z.union([HeaderBlockSchema, SectionBlockSchema, ContextBlockSchema, ActionsBlockSchema]))
60
+ .min(3)
61
+ .max(4),
62
+ })
63
+ .strict();
64
+ export const SlackFeedbackCardJsonSchema = z.toJSONSchema(SlackFeedbackCardSchema, {
65
+ target: 'draft-2020-12',
66
+ });
67
+ export function renderSlackFeedbackCard(input) {
68
+ const title = projectionSafeText(input.title, 180) || 'Feedback';
69
+ const metadata = [
70
+ priority(input.labels),
71
+ area(input.labels),
72
+ kind(input.labels),
73
+ input.status.name,
74
+ input.assigneeName,
75
+ reporterNotificationLabel(input.reporterNotification),
76
+ ]
77
+ .filter((value) => value !== undefined)
78
+ .map((value) => projectionSafeText(value, 150))
79
+ .filter((value) => value.length > 0);
80
+ const blocks = [
81
+ {
82
+ type: 'header',
83
+ text: { type: 'plain_text', text: clamp(title, 150), emoji: true },
84
+ },
85
+ {
86
+ type: 'section',
87
+ text: { type: 'mrkdwn', text: sanitizedExcerpt(input.message) },
88
+ },
89
+ {
90
+ type: 'context',
91
+ elements: [{ type: 'mrkdwn', text: metadata.map(escapeMrkdwn).join(' • ') }],
92
+ },
93
+ ];
94
+ const actions = [];
95
+ const linearUrl = approvedLinearUrl(input.linearUrl);
96
+ if (linearUrl !== undefined) {
97
+ actions.push({
98
+ type: 'button',
99
+ text: { type: 'plain_text', text: 'Open in Linear', emoji: true },
100
+ url: linearUrl,
101
+ action_id: 'feedback_open_linear',
102
+ });
103
+ }
104
+ if (mayStartWork(input)) {
105
+ actions.push({
106
+ type: 'button',
107
+ text: { type: 'plain_text', text: 'Start work & notify reporter', emoji: true },
108
+ style: 'primary',
109
+ action_id: START_WORK_ACTION_ID,
110
+ value: input.linearIssueId,
111
+ confirm: {
112
+ title: { type: 'plain_text', text: 'Start work?' },
113
+ text: {
114
+ type: 'mrkdwn',
115
+ text: 'Move this issue to In Progress and email the reporter?\n\nThis will send an external email.',
116
+ },
117
+ confirm: { type: 'plain_text', text: 'Start work' },
118
+ deny: { type: 'plain_text', text: 'Cancel' },
119
+ },
120
+ });
121
+ }
122
+ if (actions.length > 0)
123
+ blocks.push({ type: 'actions', elements: actions });
124
+ return SlackFeedbackCardSchema.parse({
125
+ text: `${projectionSafeText(input.linearIdentifier, 100) || 'Feedback'}: ${title}`,
126
+ blocks,
127
+ });
128
+ }
129
+ function reporterNotificationLabel(state) {
130
+ if (state === 'sent')
131
+ return 'Reporter notified';
132
+ if (state === 'delayed')
133
+ return 'Reporter notification delayed';
134
+ if (state === 'queued')
135
+ return 'Reporter notification queued';
136
+ if (state === 'unknown')
137
+ return 'Reporter notification outcome unknown';
138
+ return undefined;
139
+ }
140
+ function mayStartWork(input) {
141
+ return (input.trustedReporter &&
142
+ input.reporterNotification === 'pending' &&
143
+ (input.status.type === 'backlog' || input.status.type === 'unstarted'));
144
+ }
145
+ export function sanitizedFeedbackText(value, maxLength = MAX_EXCERPT_LENGTH) {
146
+ return projectionSafeText(value, maxLength);
147
+ }
148
+ function projectionSafeText(value, maxLength) {
149
+ const withoutPrivateSections = value.split(/^#{1,6}\s+(?:Reporter|Diagnostics)\b/im)[0] ?? '';
150
+ const redacted = withoutPrivateSections
151
+ .replace(/https?:\/\/\S+/gi, '[redacted URL]')
152
+ .replace(/[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}/gi, '[redacted]')
153
+ .replace(/\bBearer\s+[A-Za-z0-9._~+/-]+=*/gi, 'Bearer [redacted]')
154
+ .replace(/\b(?:xox[baprs]-|sk-|gh[pousr]_)[A-Za-z0-9_-]+\b/gi, '[redacted]')
155
+ .replace(/\b(?:api[_ -]?key|access[_ -]?token|refresh[_ -]?token|token|password|secret|authorization)\s*[:=]\s*(?:"[^"]*"|'[^']*'|[^\s,;]+)/gi, '[redacted]')
156
+ .replace(/(?:\/(?:Users|home|private|var|tmp)\/|[A-Z]:\\Users\\)\S+/gi, '[redacted path]');
157
+ return clamp(redacted.trim(), maxLength);
158
+ }
159
+ function sanitizedExcerpt(value) {
160
+ const sanitized = sanitizedFeedbackText(value);
161
+ return sanitized.length > 0 ? escapeMrkdwn(sanitized) : '_No feedback excerpt available._';
162
+ }
163
+ function priority(labels) {
164
+ return labels.find((label) => /^P[0-3]$/i.test(label))?.toUpperCase();
165
+ }
166
+ function area(labels) {
167
+ return labelValue(labels, 'area:');
168
+ }
169
+ function kind(labels) {
170
+ return labelValue(labels, 'type:');
171
+ }
172
+ function labelValue(labels, prefix) {
173
+ const label = labels.find((candidate) => candidate.toLowerCase().startsWith(prefix));
174
+ const value = label?.slice(prefix.length);
175
+ return value ? value.toUpperCase() : undefined;
176
+ }
177
+ function clamp(value, length) {
178
+ const trimmed = value.trim();
179
+ return trimmed.length <= length ? trimmed : `${trimmed.slice(0, Math.max(0, length - 1))}…`;
180
+ }
181
+ function escapeMrkdwn(value) {
182
+ return value.replaceAll('&', '&amp;').replaceAll('<', '&lt;').replaceAll('>', '&gt;');
183
+ }
184
+ function approvedLinearUrl(value) {
185
+ try {
186
+ const url = new URL(value);
187
+ if (url.origin !== 'https://linear.app' ||
188
+ url.username !== '' ||
189
+ url.password !== '' ||
190
+ url.hash !== '') {
191
+ return undefined;
192
+ }
193
+ return url.href;
194
+ }
195
+ catch {
196
+ return undefined;
197
+ }
198
+ }
199
+ //# sourceMappingURL=slack-card.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"slack-card.js","sourceRoot":"","sources":["../../src/feedback-operations/slack-card.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,44 @@
1
+ import type { SlackFeedbackCard } from './slack-card.js';
2
+ export interface SlackFeedbackClient {
3
+ publishCard(input: {
4
+ channelId: string;
5
+ messageTs: string | undefined;
6
+ clientMessageId: string;
7
+ card: SlackFeedbackCard;
8
+ }, options?: {
9
+ readonly signal?: AbortSignal;
10
+ }): Promise<{
11
+ readonly messageTs: string;
12
+ }>;
13
+ sendPrivateResponse(input: {
14
+ responseUrl: string;
15
+ text: string;
16
+ }, options?: {
17
+ readonly signal?: AbortSignal;
18
+ }): Promise<void>;
19
+ }
20
+ export declare class SlackWebApiFeedbackClient implements SlackFeedbackClient {
21
+ #private;
22
+ constructor(options: {
23
+ botToken: string;
24
+ fetch?: typeof fetch;
25
+ });
26
+ publishCard(input: {
27
+ channelId: string;
28
+ messageTs: string | undefined;
29
+ clientMessageId: string;
30
+ card: SlackFeedbackCard;
31
+ }, options?: {
32
+ readonly signal?: AbortSignal;
33
+ }): Promise<{
34
+ readonly messageTs: string;
35
+ }>;
36
+ sendPrivateResponse(input: {
37
+ responseUrl: string;
38
+ text: string;
39
+ }, options?: {
40
+ readonly signal?: AbortSignal;
41
+ }): Promise<void>;
42
+ }
43
+ export declare function assertSlackResponseUrl(value: string): void;
44
+ //# sourceMappingURL=slack-client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"slack-client.d.ts","sourceRoot":"","sources":["../../src/feedback-operations/slack-client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAMzD,MAAM,WAAW,mBAAmB;IAClC,WAAW,CACT,KAAK,EAAE;QACL,SAAS,EAAE,MAAM,CAAC;QAClB,SAAS,EAAE,MAAM,GAAG,SAAS,CAAC;QAC9B,eAAe,EAAE,MAAM,CAAC;QACxB,IAAI,EAAE,iBAAiB,CAAC;KACzB,EACD,OAAO,CAAC,EAAE;QAAE,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAA;KAAE,GAC1C,OAAO,CAAC;QAAE,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC3C,mBAAmB,CACjB,KAAK,EAAE;QAAE,WAAW,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,EAC5C,OAAO,CAAC,EAAE;QAAE,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAA;KAAE,GAC1C,OAAO,CAAC,IAAI,CAAC,CAAC;CAClB;AAED,qBAAa,yBAA0B,YAAW,mBAAmB;;gBAIvD,OAAO,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,OAAO,KAAK,CAAA;KAAE;IAMzD,WAAW,CACf,KAAK,EAAE;QACL,SAAS,EAAE,MAAM,CAAC;QAClB,SAAS,EAAE,MAAM,GAAG,SAAS,CAAC;QAC9B,eAAe,EAAE,MAAM,CAAC;QACxB,IAAI,EAAE,iBAAiB,CAAC;KACzB,EACD,OAAO,GAAE;QAAE,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAA;KAAO,GAC9C,OAAO,CAAC;QAAE,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC;IAyBpC,mBAAmB,CACvB,KAAK,EAAE;QAAE,WAAW,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,EAC5C,OAAO,GAAE;QAAE,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAA;KAAO,GAC9C,OAAO,CAAC,IAAI,CAAC;CAoDjB;AAOD,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAS1D"}
@@ -0,0 +1,95 @@
1
+ const SLACK_API_BASE = 'https://slack.com/api';
2
+ const REQUEST_TIMEOUT_MS = 10_000;
3
+ const MAX_RESPONSE_BYTES = 256_000;
4
+ export class SlackWebApiFeedbackClient {
5
+ #botToken;
6
+ #fetch;
7
+ constructor(options) {
8
+ if (options.botToken.trim() === '')
9
+ throw new Error('Slack bot token is required');
10
+ this.#botToken = options.botToken;
11
+ this.#fetch = options.fetch ?? globalThis.fetch;
12
+ }
13
+ async publishCard(input, options = {}) {
14
+ const method = input.messageTs === undefined ? 'chat.postMessage' : 'chat.update';
15
+ const result = await this.#request(`${SLACK_API_BASE}/${method}`, {
16
+ channel: input.channelId,
17
+ ...(input.messageTs !== undefined
18
+ ? { ts: input.messageTs }
19
+ : { client_msg_id: input.clientMessageId }),
20
+ ...input.card,
21
+ unfurl_links: false,
22
+ unfurl_media: false,
23
+ }, options);
24
+ const ts = typeof result === 'object' &&
25
+ result !== null &&
26
+ typeof result.ts === 'string'
27
+ ? result.ts
28
+ : input.messageTs;
29
+ if (ts === undefined)
30
+ throw new Error('Slack returned a malformed message response');
31
+ return { messageTs: ts };
32
+ }
33
+ async sendPrivateResponse(input, options = {}) {
34
+ assertSlackResponseUrl(input.responseUrl);
35
+ const response = await this.#fetch(input.responseUrl, {
36
+ method: 'POST',
37
+ redirect: 'error',
38
+ signal: boundedSignal(options.signal),
39
+ headers: { 'content-type': 'application/json' },
40
+ body: JSON.stringify({
41
+ response_type: 'ephemeral',
42
+ replace_original: false,
43
+ text: input.text,
44
+ }),
45
+ });
46
+ if (!response.ok)
47
+ throw new Error(`Slack private response failed with status ${response.status}`);
48
+ }
49
+ async #request(url, body, options = {}) {
50
+ const response = await this.#fetch(url, {
51
+ method: 'POST',
52
+ redirect: 'error',
53
+ signal: boundedSignal(options.signal),
54
+ headers: {
55
+ authorization: `Bearer ${this.#botToken}`,
56
+ 'content-type': 'application/json; charset=utf-8',
57
+ },
58
+ body: JSON.stringify(body),
59
+ });
60
+ if (!response.ok)
61
+ throw new Error(`Slack request failed with status ${response.status}`);
62
+ const length = Number(response.headers.get('content-length') ?? '0');
63
+ if (Number.isFinite(length) && length > MAX_RESPONSE_BYTES) {
64
+ throw new Error('Slack response exceeded the size limit');
65
+ }
66
+ const text = await response.text();
67
+ if (Buffer.byteLength(text) > MAX_RESPONSE_BYTES) {
68
+ throw new Error('Slack response exceeded the size limit');
69
+ }
70
+ let parsed;
71
+ try {
72
+ parsed = JSON.parse(text);
73
+ }
74
+ catch {
75
+ throw new Error('Slack returned invalid JSON');
76
+ }
77
+ if (typeof parsed !== 'object' || parsed === null || parsed.ok !== true) {
78
+ throw new Error('Slack rejected the feedback message');
79
+ }
80
+ return parsed;
81
+ }
82
+ }
83
+ function boundedSignal(signal) {
84
+ const timeout = AbortSignal.timeout(REQUEST_TIMEOUT_MS);
85
+ return signal === undefined ? timeout : AbortSignal.any([signal, timeout]);
86
+ }
87
+ export function assertSlackResponseUrl(value) {
88
+ const url = new URL(value);
89
+ if (url.protocol !== 'https:' ||
90
+ !['hooks.slack.com', 'hooks.slack-gov.com'].includes(url.hostname) ||
91
+ !url.pathname.startsWith('/actions/')) {
92
+ throw new Error('invalid Slack response URL');
93
+ }
94
+ }
95
+ //# sourceMappingURL=slack-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"slack-client.js","sourceRoot":"","sources":["../../src/feedback-operations/slack-client.ts"],"names":[],"mappings":"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"}