@usabledev/usable-chat 1.209.1 → 1.210.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 (2) hide show
  1. package/cli.js +166 -45
  2. package/package.json +1 -1
package/cli.js CHANGED
@@ -57298,15 +57298,15 @@ var require_image_q = __commonJS({
57298
57298
  const match2 = -1;
57299
57299
  let bestMatch = match2;
57300
57300
  let bestDistance = Number.MAX_VALUE;
57301
- for (let lookup2 = 0; lookup2 < this._colors; lookup2++) {
57302
- const foundRed = lookupRed[lookup2];
57303
- const foundGreen = lookupGreen[lookup2];
57304
- const foundBlue = lookupBlue[lookup2];
57305
- const foundAlpha = lookupAlpha[lookup2];
57301
+ for (let lookup3 = 0; lookup3 < this._colors; lookup3++) {
57302
+ const foundRed = lookupRed[lookup3];
57303
+ const foundGreen = lookupGreen[lookup3];
57304
+ const foundBlue = lookupBlue[lookup3];
57305
+ const foundAlpha = lookupAlpha[lookup3];
57306
57306
  const distance3 = this._distance.calculateRaw(foundRed, foundGreen, foundBlue, foundAlpha, color.r, color.g, color.b, color.a);
57307
57307
  if (distance3 < bestDistance) {
57308
57308
  bestDistance = distance3;
57309
- bestMatch = lookup2;
57309
+ bestMatch = lookup3;
57310
57310
  }
57311
57311
  }
57312
57312
  this._reds[bestMatch] += color.r;
@@ -83959,15 +83959,15 @@ var init_image_q = __esm({
83959
83959
  const match2 = -1;
83960
83960
  let bestMatch = match2;
83961
83961
  let bestDistance = Number.MAX_VALUE;
83962
- for (let lookup2 = 0; lookup2 < this._colors; lookup2++) {
83963
- const foundRed = lookupRed[lookup2];
83964
- const foundGreen = lookupGreen[lookup2];
83965
- const foundBlue = lookupBlue[lookup2];
83966
- const foundAlpha = lookupAlpha[lookup2];
83962
+ for (let lookup3 = 0; lookup3 < this._colors; lookup3++) {
83963
+ const foundRed = lookupRed[lookup3];
83964
+ const foundGreen = lookupGreen[lookup3];
83965
+ const foundBlue = lookupBlue[lookup3];
83966
+ const foundAlpha = lookupAlpha[lookup3];
83967
83967
  const distance3 = this._distance.calculateRaw(foundRed, foundGreen, foundBlue, foundAlpha, color.r, color.g, color.b, color.a);
83968
83968
  if (distance3 < bestDistance) {
83969
83969
  bestDistance = distance3;
83970
- bestMatch = lookup2;
83970
+ bestMatch = lookup3;
83971
83971
  }
83972
83972
  }
83973
83973
  this._reds[bestMatch] += color.r;
@@ -94237,11 +94237,12 @@ var init_env2 = __esm({
94237
94237
  // (/api/oauth/register). Pre-configured in Keycloak (memory-mesh) with PKCE +
94238
94238
  // whitelisted redirect URIs — clients never hit Keycloak's DCR endpoint.
94239
94239
  MCP_OAUTH_CLIENT_ID: external_exports.string().min(1).default("mcp_oauth_client"),
94240
- // Optional azp (authorized party) allowlist for Bearer JWT auth on API routes.
94241
- // Comma-separated Keycloak client IDs. UNSET = no azp enforcement (issuer +
94242
- // signature + expiry only — pre-existing behaviour, keeps embed integrations
94243
- // working). SET = bearer tokens whose azp is not in the list are rejected
94244
- // with 401 JWT_AZP_REJECTED. See docs/chat-v2-contract.md#authentication.
94240
+ // Optional override for Bearer JWT client trust. Bearer tokens are accepted
94241
+ // when Usable trusts their azp (first-party SSO, mcp_oauth_client, any
94242
+ // marketplace app — looked up via /api/oauth/client-trust). Client IDs listed
94243
+ // here (comma-separated) are always trusted without a lookup: use it only for
94244
+ // clients Usable does not know, or as break-glass during a Usable outage.
94245
+ // See docs/chat-v2-contract.md#authentication.
94245
94246
  CHAT_TRUSTED_CLIENT_IDS: external_exports.string().optional(),
94246
94247
  USABLE_MOBILE_CHAT_HOST_ENABLED: external_exports.enum(["true", "false"]).default("false"),
94247
94248
  USABLE_MOBILE_CHAT_HOST_ORIGIN: external_exports.string().url().optional(),
@@ -94547,8 +94548,8 @@ var init_config = __esm({
94547
94548
  /** Static public client_id returned by the MCP OAuth registration endpoint. */
94548
94549
  mcpOAuthClientId: env.MCP_OAUTH_CLIENT_ID || DEFAULT_MCP_OAUTH_CLIENT_ID2,
94549
94550
  /**
94550
- * Optional azp allowlist for Bearer JWT auth (comma-separated client IDs).
94551
- * Empty/unset disables enforcement. See docs/chat-v2-contract.md.
94551
+ * Optional override list of Bearer JWT client IDs trusted without asking
94552
+ * Usable (comma-separated). See oauth-client-trust.service.ts.
94552
94553
  */
94553
94554
  trustedClientIdsRaw: env.CHAT_TRUSTED_CLIENT_IDS
94554
94555
  },
@@ -167172,7 +167173,7 @@ function buildBlock(artifact) {
167172
167173
  ${body2}
167173
167174
  </referenced-artifact>`;
167174
167175
  }
167175
- async function resolveArtifactMentions(text5, lookup2) {
167176
+ async function resolveArtifactMentions(text5, lookup3) {
167176
167177
  if (!text5 || typeof text5 !== "string") return text5;
167177
167178
  const ids = /* @__PURE__ */ new Set();
167178
167179
  const matches = text5.matchAll(ARTIFACT_MENTION_RE);
@@ -167180,7 +167181,7 @@ async function resolveArtifactMentions(text5, lookup2) {
167180
167181
  ids.add(match2[1].toLowerCase());
167181
167182
  }
167182
167183
  if (ids.size === 0) return text5;
167183
- const resolved = await lookup2(Array.from(ids));
167184
+ const resolved = await lookup3(Array.from(ids));
167184
167185
  const byId = /* @__PURE__ */ new Map();
167185
167186
  for (const artifact of resolved) {
167186
167187
  byId.set(artifact.id.toLowerCase(), artifact);
@@ -167211,14 +167212,14 @@ function buildElement(user) {
167211
167212
  const handle = escapeXml2(`@${handleSource}`);
167212
167213
  return `<slack-user id="${user.id}" handle="${handle}" name="${name18}" />`;
167213
167214
  }
167214
- async function resolveSlackMentions(text5, lookup2) {
167215
+ async function resolveSlackMentions(text5, lookup3) {
167215
167216
  if (!text5 || typeof text5 !== "string") return text5;
167216
167217
  const ids = /* @__PURE__ */ new Set();
167217
167218
  for (const match2 of text5.matchAll(SLACK_MENTION_RE)) {
167218
167219
  ids.add(match2[1].toUpperCase());
167219
167220
  }
167220
167221
  if (ids.size === 0) return text5;
167221
- const resolved = await lookup2(Array.from(ids));
167222
+ const resolved = await lookup3(Array.from(ids));
167222
167223
  const byId = /* @__PURE__ */ new Map();
167223
167224
  for (const user of resolved) {
167224
167225
  byId.set(user.id.toUpperCase(), user);
@@ -178067,12 +178068,12 @@ var require_base64_js = __commonJS({
178067
178068
  exports2.byteLength = byteLength;
178068
178069
  exports2.toByteArray = toByteArray;
178069
178070
  exports2.fromByteArray = fromByteArray;
178070
- var lookup2 = [];
178071
+ var lookup3 = [];
178071
178072
  var revLookup = [];
178072
178073
  var Arr = typeof Uint8Array !== "undefined" ? Uint8Array : Array;
178073
178074
  var code = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
178074
178075
  for (i19 = 0, len = code.length; i19 < len; ++i19) {
178075
- lookup2[i19] = code[i19];
178076
+ lookup3[i19] = code[i19];
178076
178077
  revLookup[code.charCodeAt(i19)] = i19;
178077
178078
  }
178078
178079
  var i19;
@@ -178125,7 +178126,7 @@ var require_base64_js = __commonJS({
178125
178126
  return arr;
178126
178127
  }
178127
178128
  function tripletToBase64(num) {
178128
- return lookup2[num >> 18 & 63] + lookup2[num >> 12 & 63] + lookup2[num >> 6 & 63] + lookup2[num & 63];
178129
+ return lookup3[num >> 18 & 63] + lookup3[num >> 12 & 63] + lookup3[num >> 6 & 63] + lookup3[num & 63];
178129
178130
  }
178130
178131
  function encodeChunk(uint8, start, end) {
178131
178132
  var tmp;
@@ -178148,12 +178149,12 @@ var require_base64_js = __commonJS({
178148
178149
  if (extraBytes === 1) {
178149
178150
  tmp = uint8[len2 - 1];
178150
178151
  parts.push(
178151
- lookup2[tmp >> 2] + lookup2[tmp << 4 & 63] + "=="
178152
+ lookup3[tmp >> 2] + lookup3[tmp << 4 & 63] + "=="
178152
178153
  );
178153
178154
  } else if (extraBytes === 2) {
178154
178155
  tmp = (uint8[len2 - 2] << 8) + uint8[len2 - 1];
178155
178156
  parts.push(
178156
- lookup2[tmp >> 10] + lookup2[tmp >> 4 & 63] + lookup2[tmp << 2 & 63] + "="
178157
+ lookup3[tmp >> 10] + lookup3[tmp >> 4 & 63] + lookup3[tmp << 2 & 63] + "="
178157
178158
  );
178158
178159
  }
178159
178160
  return parts.join("");
@@ -212213,7 +212214,7 @@ var require_redis_errors = __commonJS({
212213
212214
  var require_lib3 = __commonJS({
212214
212215
  "node_modules/.pnpm/cluster-key-slot@1.1.2/node_modules/cluster-key-slot/lib/index.js"(exports2, module) {
212215
212216
  "use strict";
212216
- var lookup2 = [
212217
+ var lookup3 = [
212217
212218
  0,
212218
212219
  4129,
212219
212220
  8258,
@@ -212513,11 +212514,11 @@ var require_lib3 = __commonJS({
212513
212514
  start = i19;
212514
212515
  }
212515
212516
  } else if (char2 !== 125) {
212516
- resultHash = lookup2[(char2 ^ resultHash >> 8) & 255] ^ resultHash << 8;
212517
+ resultHash = lookup3[(char2 ^ resultHash >> 8) & 255] ^ resultHash << 8;
212517
212518
  } else if (i19 - 1 !== start) {
212518
212519
  return resultHash & 16383;
212519
212520
  }
212520
- result = lookup2[(char2 ^ result >> 8) & 255] ^ result << 8;
212521
+ result = lookup3[(char2 ^ result >> 8) & 255] ^ result << 8;
212521
212522
  }
212522
212523
  return result & 16383;
212523
212524
  };
@@ -350204,7 +350205,7 @@ var require_issuer = __commonJS({
350204
350205
  var { keystores } = require_weak_cache();
350205
350206
  var processResponse = require_process_response();
350206
350207
  var request = require_request2();
350207
- var inFlight = /* @__PURE__ */ new WeakMap();
350208
+ var inFlight2 = /* @__PURE__ */ new WeakMap();
350208
350209
  var caches = /* @__PURE__ */ new WeakMap();
350209
350210
  var lrus = (ctx) => {
350210
350211
  if (!caches.has(ctx)) {
@@ -350217,11 +350218,11 @@ var require_issuer = __commonJS({
350217
350218
  const keystore = keystores.get(this);
350218
350219
  const cache10 = lrus(this);
350219
350220
  if (reload || !keystore) {
350220
- if (inFlight.has(this)) {
350221
- return inFlight.get(this);
350221
+ if (inFlight2.has(this)) {
350222
+ return inFlight2.get(this);
350222
350223
  }
350223
350224
  cache10.reset();
350224
- inFlight.set(
350225
+ inFlight2.set(
350225
350226
  this,
350226
350227
  (async () => {
350227
350228
  const response = await request.call(this, {
@@ -350232,7 +350233,7 @@ var require_issuer = __commonJS({
350232
350233
  Accept: "application/json, application/jwk-set+json"
350233
350234
  }
350234
350235
  }).finally(() => {
350235
- inFlight.delete(this);
350236
+ inFlight2.delete(this);
350236
350237
  });
350237
350238
  const jwks2 = processResponse(response);
350238
350239
  const joseKeyStore = KeyStore.fromJWKS(jwks2, { onlyPublic: true });
@@ -350241,7 +350242,7 @@ var require_issuer = __commonJS({
350241
350242
  return joseKeyStore;
350242
350243
  })()
350243
350244
  );
350244
- return inFlight.get(this);
350245
+ return inFlight2.get(this);
350245
350246
  }
350246
350247
  return keystore;
350247
350248
  }
@@ -360154,15 +360155,126 @@ var init_embed_auth = __esm({
360154
360155
  }
360155
360156
  });
360156
360157
 
360158
+ // src/lib/services/oauth-client-trust.service.ts
360159
+ function overrideClientIds() {
360160
+ return (config2.auth.trustedClientIdsRaw ?? "").split(",").map((id) => id.trim()).filter(Boolean);
360161
+ }
360162
+ function getClientTrustUrl() {
360163
+ const mcpUrl = config2.mcp.url;
360164
+ if (!mcpUrl || !MCP_PATH.test(mcpUrl)) return null;
360165
+ return `${mcpUrl.replace(MCP_PATH, "")}/api/oauth/client-trust`;
360166
+ }
360167
+ function remember(azp, verdict) {
360168
+ if (!verdictCache.has(azp) && verdictCache.size >= CACHE_MAX_ENTRIES) {
360169
+ const oldest = verdictCache.keys().next().value;
360170
+ if (typeof oldest === "string") verdictCache.delete(oldest);
360171
+ }
360172
+ const now2 = Date.now();
360173
+ verdictCache.set(
360174
+ azp,
360175
+ verdict === "trusted" ? {
360176
+ verdict,
360177
+ expiresAt: now2 + TRUSTED_TTL_MS,
360178
+ staleUntil: now2 + TRUSTED_TTL_MS + STALE_IF_ERROR_MS
360179
+ } : { verdict, expiresAt: now2 + UNTRUSTED_TTL_MS }
360180
+ );
360181
+ }
360182
+ async function lookup2(azp, accessToken, fetchImpl) {
360183
+ const url3 = getClientTrustUrl();
360184
+ if (!url3) {
360185
+ logger.error("oauth-client-trust", "USABLE_MCP_URL is missing or does not end in /api/mcp");
360186
+ return null;
360187
+ }
360188
+ try {
360189
+ const response = await fetchImpl(url3, {
360190
+ method: "GET",
360191
+ headers: { Authorization: `Bearer ${accessToken}` },
360192
+ cache: "no-store",
360193
+ signal: AbortSignal.timeout(LOOKUP_TIMEOUT_MS)
360194
+ });
360195
+ if (!response.ok) {
360196
+ logger.warn("oauth-client-trust", "Client trust lookup failed", {
360197
+ azp,
360198
+ status: response.status
360199
+ });
360200
+ return null;
360201
+ }
360202
+ const parsed = trustResponseSchema.safeParse(await response.json());
360203
+ if (!parsed.success || parsed.data.clientId !== azp) {
360204
+ logger.warn("oauth-client-trust", "Client trust lookup returned an unexpected body", { azp });
360205
+ return null;
360206
+ }
360207
+ const verdict = parsed.data.trusted ? "trusted" : "untrusted";
360208
+ remember(azp, verdict);
360209
+ return verdict;
360210
+ } catch (error41) {
360211
+ logger.warn("oauth-client-trust", "Client trust lookup errored", {
360212
+ azp,
360213
+ error: error41 instanceof Error ? error41.message : String(error41)
360214
+ });
360215
+ return null;
360216
+ }
360217
+ }
360218
+ async function resolveOAuthClientTrust(azp, accessToken, options2 = {}) {
360219
+ if (!azp) return "untrusted";
360220
+ if (azp === config2.auth.mcpOAuthClientId || overrideClientIds().includes(azp)) return "trusted";
360221
+ const cached2 = verdictCache.get(azp);
360222
+ if (!options2.bypassCache && cached2 && cached2.expiresAt > Date.now()) {
360223
+ return cached2.verdict;
360224
+ }
360225
+ let pending = inFlight.get(azp);
360226
+ if (!pending) {
360227
+ pending = lookup2(azp, accessToken, options2.fetchImpl ?? fetch).finally(() => {
360228
+ inFlight.delete(azp);
360229
+ });
360230
+ inFlight.set(azp, pending);
360231
+ }
360232
+ const verdict = await pending;
360233
+ if (verdict) return verdict;
360234
+ const stale = verdictCache.get(azp);
360235
+ if (!options2.bypassCache && stale?.verdict === "trusted" && stale.staleUntil !== void 0 && stale.staleUntil > Date.now()) {
360236
+ logger.warn("oauth-client-trust", "Serving stale trusted verdict while Usable is unavailable", {
360237
+ azp
360238
+ });
360239
+ return "trusted";
360240
+ }
360241
+ return "unavailable";
360242
+ }
360243
+ var TRUSTED_TTL_MS, UNTRUSTED_TTL_MS, STALE_IF_ERROR_MS, LOOKUP_TIMEOUT_MS, CACHE_MAX_ENTRIES, trustResponseSchema, verdictCache, inFlight, MCP_PATH;
360244
+ var init_oauth_client_trust_service = __esm({
360245
+ "src/lib/services/oauth-client-trust.service.ts"() {
360246
+ "use strict";
360247
+ init_zod();
360248
+ init_config();
360249
+ init_logger();
360250
+ TRUSTED_TTL_MS = 5 * 6e4;
360251
+ UNTRUSTED_TTL_MS = 3e4;
360252
+ STALE_IF_ERROR_MS = 60 * 6e4;
360253
+ LOOKUP_TIMEOUT_MS = 5e3;
360254
+ CACHE_MAX_ENTRIES = 500;
360255
+ trustResponseSchema = external_exports.union([
360256
+ external_exports.object({
360257
+ clientId: external_exports.string().min(1),
360258
+ trusted: external_exports.literal(true),
360259
+ kind: external_exports.enum(["first-party-sso", "mcp-oauth", "marketplace-app"]),
360260
+ appId: external_exports.string().optional()
360261
+ }),
360262
+ external_exports.object({
360263
+ clientId: external_exports.string().min(1).nullable(),
360264
+ trusted: external_exports.literal(false)
360265
+ })
360266
+ ]);
360267
+ verdictCache = /* @__PURE__ */ new Map();
360268
+ inFlight = /* @__PURE__ */ new Map();
360269
+ MCP_PATH = /\/api\/mcp\/?$/;
360270
+ }
360271
+ });
360272
+
360157
360273
  // src/lib/auth-helper.ts
360158
360274
  function parseTrustedClientIds(raw) {
360159
360275
  if (!raw) return [];
360160
360276
  return raw.split(",").map((id) => id.trim()).filter(Boolean);
360161
360277
  }
360162
- function isTrustedAzp(azp, trustedClientIds) {
360163
- if (trustedClientIds.length === 0) return true;
360164
- return azp !== void 0 && trustedClientIds.includes(azp);
360165
- }
360166
360278
  function hasConfiguredSessionCookie(cookieHeader, sessionCookieName) {
360167
360279
  if (!cookieHeader || !sessionCookieName) return false;
360168
360280
  return cookieHeader.split(";").some((cookie) => {
@@ -360227,15 +360339,23 @@ async function getAuthFromRequest(request, options2) {
360227
360339
  const token = authHeader.slice(7);
360228
360340
  const result = await verifyJWT(token);
360229
360341
  if (result.success) {
360230
- const trustedClientIds = parseTrustedClientIds(config2.auth.trustedClientIdsRaw);
360231
- if (!isTrustedAzp(result.payload.azp, trustedClientIds)) {
360342
+ const clientTrust = await resolveOAuthClientTrust(result.payload.azp, token);
360343
+ if (clientTrust === "untrusted") {
360232
360344
  console.warn(
360233
- `[AuthHelper] Bearer JWT azp rejected: ${result.payload.azp ?? "<missing>"} not in CHAT_TRUSTED_CLIENT_IDS`
360345
+ `[AuthHelper] Bearer JWT azp rejected: ${result.payload.azp ?? "<missing>"} is not a client trusted by Usable`
360234
360346
  );
360235
360347
  lastRejection = {
360236
360348
  code: "JWT_AZP_REJECTED",
360237
360349
  message: "JWT authorized party (azp) is not a trusted client for this service"
360238
360350
  };
360351
+ } else if (clientTrust === "unavailable") {
360352
+ console.warn(
360353
+ `[AuthHelper] Bearer JWT azp ${result.payload.azp} could not be checked: Usable client trust lookup unavailable`
360354
+ );
360355
+ lastRejection = {
360356
+ code: "CLIENT_TRUST_UNAVAILABLE",
360357
+ message: "Client authorization is temporarily unavailable. Please try again."
360358
+ };
360239
360359
  } else {
360240
360360
  const userId = result.payload.usable_user_id || result.payload.sub;
360241
360361
  if (!userId) {
@@ -360313,6 +360433,7 @@ var init_auth_helper = __esm({
360313
360433
  init_config();
360314
360434
  import_server2 = __toESM(require_server());
360315
360435
  init_embed_auth();
360436
+ init_oauth_client_trust_service();
360316
360437
  jwks = null;
360317
360438
  }
360318
360439
  });
@@ -391814,7 +391935,7 @@ init_tui_select();
391814
391935
  init_model_registry();
391815
391936
 
391816
391937
  // package.json
391817
- var version2 = "1.209.1";
391938
+ var version2 = "1.210.0";
391818
391939
 
391819
391940
  // src/adapters/cli/model-catalog.ts
391820
391941
  init_codex_auth();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@usabledev/usable-chat",
3
- "version": "1.209.1",
3
+ "version": "1.210.0",
4
4
  "description": "usable-chat — terminal harness for usable-chat (headless + TUI)",
5
5
  "type": "module",
6
6
  "bin": {