@vellumai/vellum-gateway 0.12.2-staging.1 → 0.12.2-staging.2
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.
|
@@ -113,7 +113,7 @@ export async function buildSlackUserLabelMap(
|
|
|
113
113
|
// that label directly; only queue a lookup when the label is missing,
|
|
114
114
|
// empty, or ID-shaped. Mirrors the channel builder below.
|
|
115
115
|
const [, embeddedLabel] = splitSlackLabel(match[0].slice(1, -1));
|
|
116
|
-
const sanitizedEmbeddedLabel =
|
|
116
|
+
const sanitizedEmbeddedLabel = sanitizeEmbeddedSlackLabel(embeddedLabel);
|
|
117
117
|
if (sanitizedEmbeddedLabel && sanitizedEmbeddedLabel !== id) continue;
|
|
118
118
|
if (seen.has(id)) continue;
|
|
119
119
|
seen.add(id);
|
|
@@ -151,7 +151,7 @@ export async function buildSlackChannelLabelMap(
|
|
|
151
151
|
for (const match of text.matchAll(SLACK_CHANNEL_REFERENCE_RE)) {
|
|
152
152
|
const id = match[1];
|
|
153
153
|
const [, embeddedLabel] = splitSlackLabel(match[0].slice(1, -1));
|
|
154
|
-
const sanitizedEmbeddedLabel =
|
|
154
|
+
const sanitizedEmbeddedLabel = sanitizeEmbeddedSlackLabel(embeddedLabel);
|
|
155
155
|
if (sanitizedEmbeddedLabel && sanitizedEmbeddedLabel !== id) continue;
|
|
156
156
|
if (!seen.has(id)) {
|
|
157
157
|
seen.add(id);
|
|
@@ -192,9 +192,7 @@ function renderUserMention(
|
|
|
192
192
|
// prefer it over a lookup, mirroring renderChannelReference. The embedded
|
|
193
193
|
// label is Slack-sourced (part of the token), so entities decode before
|
|
194
194
|
// sanitization; the caller-resolved label below is not.
|
|
195
|
-
const embeddedLabel =
|
|
196
|
-
label === undefined ? undefined : decodeSlackHtmlEntities(label),
|
|
197
|
-
);
|
|
195
|
+
const embeddedLabel = sanitizeEmbeddedSlackLabel(label);
|
|
198
196
|
if (embeddedLabel && embeddedLabel !== id) {
|
|
199
197
|
return `@${embeddedLabel}`;
|
|
200
198
|
}
|
|
@@ -219,9 +217,7 @@ function renderChannelReference(
|
|
|
219
217
|
);
|
|
220
218
|
// The embedded label is Slack-sourced (part of the token), so entities
|
|
221
219
|
// decode before sanitization; the caller-resolved label below is not.
|
|
222
|
-
const embeddedLabel =
|
|
223
|
-
label === undefined ? undefined : decodeSlackHtmlEntities(label),
|
|
224
|
-
);
|
|
220
|
+
const embeddedLabel = sanitizeEmbeddedSlackLabel(label);
|
|
225
221
|
if (embeddedLabel && embeddedLabel !== channelId) {
|
|
226
222
|
return `#${embeddedLabel}`;
|
|
227
223
|
}
|
|
@@ -320,6 +316,15 @@ export function sanitizeSlackLabel(
|
|
|
320
316
|
return sanitized || undefined;
|
|
321
317
|
}
|
|
322
318
|
|
|
319
|
+
// Slack-sourced labels are decoded before validation in both lookup and render.
|
|
320
|
+
function sanitizeEmbeddedSlackLabel(
|
|
321
|
+
label: string | undefined,
|
|
322
|
+
): string | undefined {
|
|
323
|
+
return sanitizeOptionalLabel(
|
|
324
|
+
label === undefined ? undefined : decodeSlackHtmlEntities(label),
|
|
325
|
+
);
|
|
326
|
+
}
|
|
327
|
+
|
|
323
328
|
function sanitizeOptionalLabel(label: string | undefined): string | undefined {
|
|
324
329
|
return sanitizeSlackLabel(label);
|
|
325
330
|
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { describe, test } from "bun:test";
|
|
3
|
+
import {
|
|
4
|
+
buildSlackChannelLabelMap,
|
|
5
|
+
buildSlackUserLabelMap,
|
|
6
|
+
renderSlackTextForModel,
|
|
7
|
+
} from "./index.js";
|
|
8
|
+
|
|
9
|
+
describe("Slack embedded-label lookup parity", () => {
|
|
10
|
+
const cases = [
|
|
11
|
+
{ id: "U123", prefix: "@", build: buildSlackUserLabelMap, mapKey: "userLabels" },
|
|
12
|
+
{
|
|
13
|
+
id: "C123",
|
|
14
|
+
prefix: "#",
|
|
15
|
+
build: buildSlackChannelLabelMap,
|
|
16
|
+
mapKey: "channelLabels",
|
|
17
|
+
},
|
|
18
|
+
] as const;
|
|
19
|
+
|
|
20
|
+
for (const { id, prefix, build, mapKey } of cases) {
|
|
21
|
+
for (const label of ["<>", `<${id}>`, "< @# >"]) {
|
|
22
|
+
test(`${prefix} resolves label ${label} after Slack entity decoding`, async () => {
|
|
23
|
+
const text = `<${prefix}${id}|${label}>`;
|
|
24
|
+
const requested: string[] = [];
|
|
25
|
+
const labels = await build([text], async (value) => {
|
|
26
|
+
requested.push(value);
|
|
27
|
+
return "Example Name";
|
|
28
|
+
});
|
|
29
|
+
assert.deepEqual(requested, [id]);
|
|
30
|
+
assert.equal(
|
|
31
|
+
renderSlackTextForModel(text, { [mapKey]: labels }),
|
|
32
|
+
`${prefix}Example Name`,
|
|
33
|
+
);
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
for (const label of ["<Example>", "R&D", "&lt;&gt;"]) {
|
|
38
|
+
test(`${prefix} keeps usable embedded label ${label} without lookup`, async () => {
|
|
39
|
+
const requested: string[] = [];
|
|
40
|
+
const text = `<${prefix}${id}|${label}>`;
|
|
41
|
+
const labels = await build([text], async (value) => {
|
|
42
|
+
requested.push(value);
|
|
43
|
+
return "Wrong Label";
|
|
44
|
+
});
|
|
45
|
+
assert.deepEqual(requested, []);
|
|
46
|
+
assert.deepEqual(labels, {});
|
|
47
|
+
const expected =
|
|
48
|
+
label === "<Example>"
|
|
49
|
+
? "Example"
|
|
50
|
+
: label === "R&D"
|
|
51
|
+
? "R&D"
|
|
52
|
+
: "<>";
|
|
53
|
+
assert.equal(
|
|
54
|
+
renderSlackTextForModel(text, { [mapKey]: labels }),
|
|
55
|
+
`${prefix}${expected}`,
|
|
56
|
+
);
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
test(`${prefix} deduplicates required lookups across encoded and bare mentions`, async () => {
|
|
61
|
+
const requested: string[] = [];
|
|
62
|
+
const labels = await build(
|
|
63
|
+
[`<${prefix}${id}|<>>`, `<${prefix}${id}>`],
|
|
64
|
+
async (value) => {
|
|
65
|
+
requested.push(value);
|
|
66
|
+
return "Example";
|
|
67
|
+
},
|
|
68
|
+
);
|
|
69
|
+
assert.deepEqual(requested, [id]);
|
|
70
|
+
assert.deepEqual(labels, { [id]: "Example" });
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
test(`${prefix} does not decode caller-resolved entity text`, async () => {
|
|
74
|
+
const text = `<${prefix}${id}|<>>`;
|
|
75
|
+
const labels = await build([text], async () => "<Example>");
|
|
76
|
+
assert.equal(
|
|
77
|
+
renderSlackTextForModel(text, { [mapKey]: labels }),
|
|
78
|
+
`${prefix}<Example>`,
|
|
79
|
+
);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
test(`${prefix} preserves unknown fallback after a failed required lookup`, async () => {
|
|
83
|
+
let attempts = 0;
|
|
84
|
+
const text = `<${prefix}${id}|<>>`;
|
|
85
|
+
const labels = await build([text], async () => {
|
|
86
|
+
attempts++;
|
|
87
|
+
throw new Error("unavailable");
|
|
88
|
+
});
|
|
89
|
+
assert.equal(attempts, 1);
|
|
90
|
+
assert.deepEqual(labels, {});
|
|
91
|
+
const expected = prefix === "@" ? "@unknown-user" : "#unknown-channel";
|
|
92
|
+
assert.equal(renderSlackTextForModel(text, { [mapKey]: labels }), expected);
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
});
|
package/package.json
CHANGED
|
@@ -175,7 +175,7 @@
|
|
|
175
175
|
"scope": "client",
|
|
176
176
|
"key": "teleport",
|
|
177
177
|
"label": "Teleport",
|
|
178
|
-
"description": "Enable teleport UI in General settings
|
|
178
|
+
"description": "Enable the local-to-platform teleport UI in General settings. Platform-to-local teleport is GA and not gated by this flag",
|
|
179
179
|
"defaultEnabled": false
|
|
180
180
|
},
|
|
181
181
|
{
|
|
@@ -474,6 +474,14 @@
|
|
|
474
474
|
"description": "Gates the seeded Figma OAuth provider. When off, the provider is hidden from GET /v1/oauth/providers, its get-by-id route, and the connect/update routes, so it cannot be listed or connected from the CLI, gateway, or web integrations list. The row is still seeded into oauth_providers on startup, and existing connections keep injecting credentials; only its visibility changes.",
|
|
475
475
|
"defaultEnabled": false
|
|
476
476
|
},
|
|
477
|
+
{
|
|
478
|
+
"id": "quickbooks-oauth",
|
|
479
|
+
"scope": "assistant",
|
|
480
|
+
"key": "quickbooks-oauth",
|
|
481
|
+
"label": "QuickBooks Integration",
|
|
482
|
+
"description": "Gates the seeded QuickBooks OAuth provider. When off, the provider is hidden from GET /v1/oauth/providers, its get-by-id route, and the connect/update routes, so it cannot be listed or connected from the CLI, gateway, or web integrations list. The row is still seeded into oauth_providers on startup, and existing connections keep injecting credentials; only its visibility changes.",
|
|
483
|
+
"defaultEnabled": false
|
|
484
|
+
},
|
|
477
485
|
{
|
|
478
486
|
"id": "shopify-oauth",
|
|
479
487
|
"scope": "assistant",
|