@usabledev/usable-chat 1.209.2 → 1.211.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 +462 -168
  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
  },
@@ -95133,6 +95134,12 @@ var init_beta_features = __esm({
95133
95134
  name: "Research mode",
95134
95135
  description: "Run structured, sourced research with specialist workers and an HTML report."
95135
95136
  },
95137
+ "auto-model": {
95138
+ key: "auto-model",
95139
+ name: "Auto model",
95140
+ description: "Use Jev to choose a curated model for each text request.",
95141
+ failureMessage: "Could not save Auto model. Try again."
95142
+ },
95136
95143
  "configurable-hooks": {
95137
95144
  key: "configurable-hooks",
95138
95145
  name: "Configurable hooks",
@@ -95159,7 +95166,8 @@ var init_beta_features = __esm({
95159
95166
  betaFeatureGrantSchema = external_exports.object({
95160
95167
  enabled: external_exports.boolean(),
95161
95168
  enabledAt: external_exports.string().datetime(),
95162
- enabledBy: external_exports.string()
95169
+ enabledBy: external_exports.string(),
95170
+ autoModelChoices: external_exports.object({ simple: external_exports.string(), advanced: external_exports.string() }).optional()
95163
95171
  });
95164
95172
  userBetaFeaturesSchema = external_exports.record(external_exports.string(), betaFeatureGrantSchema);
95165
95173
  }
@@ -106895,11 +106903,11 @@ var require_randomUUID = __commonJS({
106895
106903
  var require_dist_cjs31 = __commonJS({
106896
106904
  "node_modules/.pnpm/@smithy+uuid@1.1.2/node_modules/@smithy/uuid/dist-cjs/index.js"(exports2) {
106897
106905
  "use strict";
106898
- var randomUUID15 = require_randomUUID();
106906
+ var randomUUID16 = require_randomUUID();
106899
106907
  var decimalToHex = Array.from({ length: 256 }, (_16, i19) => i19.toString(16).padStart(2, "0"));
106900
106908
  var v43 = () => {
106901
- if (randomUUID15.randomUUID) {
106902
- return randomUUID15.randomUUID();
106909
+ if (randomUUID16.randomUUID) {
106910
+ return randomUUID16.randomUUID();
106903
106911
  }
106904
106912
  const rnds = new Uint8Array(16);
106905
106913
  crypto.getRandomValues(rnds);
@@ -144783,8 +144791,8 @@ var require_utils = __commonJS({
144783
144791
  var HOST_DELIMS = { "@": "%40", "/": "%2F", "?": "%3F", "#": "%23", ":": "%3A" };
144784
144792
  var HOST_DELIM_RE = /[@/?#:]/g;
144785
144793
  var HOST_DELIM_NO_COLON_RE = /[@/?#]/g;
144786
- function reescapeHostDelimiters(host, isIP3) {
144787
- const re10 = isIP3 ? HOST_DELIM_NO_COLON_RE : HOST_DELIM_RE;
144794
+ function reescapeHostDelimiters(host, isIP4) {
144795
+ const re10 = isIP4 ? HOST_DELIM_NO_COLON_RE : HOST_DELIM_RE;
144788
144796
  re10.lastIndex = 0;
144789
144797
  return host.replace(re10, (ch) => HOST_DELIMS[ch]);
144790
144798
  }
@@ -145266,7 +145274,7 @@ var require_fast_uri = __commonJS({
145266
145274
  fragment: void 0
145267
145275
  };
145268
145276
  let malformedAuthorityOrPort = false;
145269
- let isIP3 = false;
145277
+ let isIP4 = false;
145270
145278
  if (options2.reference === "suffix") {
145271
145279
  if (options2.scheme) {
145272
145280
  uri = options2.scheme + ":" + uri;
@@ -145296,9 +145304,9 @@ var require_fast_uri = __commonJS({
145296
145304
  if (ipv4result === false) {
145297
145305
  const ipv6result = normalizeIPv6(parsed.host);
145298
145306
  parsed.host = ipv6result.host.toLowerCase();
145299
- isIP3 = ipv6result.isIPV6;
145307
+ isIP4 = ipv6result.isIPV6;
145300
145308
  } else {
145301
- isIP3 = true;
145309
+ isIP4 = true;
145302
145310
  }
145303
145311
  }
145304
145312
  if (parsed.scheme === void 0 && parsed.userinfo === void 0 && parsed.host === void 0 && parsed.port === void 0 && parsed.query === void 0 && !parsed.path) {
@@ -145315,7 +145323,7 @@ var require_fast_uri = __commonJS({
145315
145323
  }
145316
145324
  const schemeHandler = getSchemeHandler(options2.scheme || parsed.scheme);
145317
145325
  if (!options2.unicodeSupport && (!schemeHandler || !schemeHandler.unicodeSupport)) {
145318
- if (parsed.host && (options2.domainHost || schemeHandler && schemeHandler.domainHost) && isIP3 === false && nonSimpleDomain(parsed.host)) {
145326
+ if (parsed.host && (options2.domainHost || schemeHandler && schemeHandler.domainHost) && isIP4 === false && nonSimpleDomain(parsed.host)) {
145319
145327
  try {
145320
145328
  parsed.host = URL.domainToASCII(parsed.host.toLowerCase());
145321
145329
  } catch (e15) {
@@ -145329,7 +145337,7 @@ var require_fast_uri = __commonJS({
145329
145337
  parsed.scheme = unescape(parsed.scheme);
145330
145338
  }
145331
145339
  if (parsed.host !== void 0) {
145332
- parsed.host = reescapeHostDelimiters(unescape(parsed.host), isIP3);
145340
+ parsed.host = reescapeHostDelimiters(unescape(parsed.host), isIP4);
145333
145341
  }
145334
145342
  }
145335
145343
  if (parsed.path) {
@@ -162763,6 +162771,12 @@ var init_schema_mcp = __esm({
162763
162771
  lastError: text2("last_error"),
162764
162772
  deletedAt: timestamp("deleted_at", { withTimezone: true }),
162765
162773
  sourceEventId: varchar("source_event_id", { length: 255 }),
162774
+ // User approval for credential-bearing profiles (see services/mcp-approval.ts).
162775
+ // Existing rows are grandfathered with approved_via='legacy' and no fingerprint.
162776
+ origin: varchar("origin", { length: 16 }).notNull().default("legacy").$type(),
162777
+ approvedFingerprint: varchar("approved_fingerprint", { length: 80 }),
162778
+ approvedAt: timestamp("approved_at", { withTimezone: true }),
162779
+ approvedVia: varchar("approved_via", { length: 32 }),
162766
162780
  createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
162767
162781
  updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow()
162768
162782
  },
@@ -163434,6 +163448,41 @@ var init_tool = __esm({
163434
163448
  }
163435
163449
  });
163436
163450
 
163451
+ // src/core/pause/envelope.ts
163452
+ function isMcpApprovalPause(output) {
163453
+ if (!output || typeof output !== "object") return false;
163454
+ const pause = output.__pause;
163455
+ return pause !== void 0 && pause.kind === MCP_APPROVAL_PAUSE_KIND && typeof pause.resumeToken === "string" && typeof pause.mcpApproval?.profileId === "string" && typeof pause.mcpApproval.fingerprint === "string";
163456
+ }
163457
+ function detectPause(toolResult, toolCallId) {
163458
+ if (isAskQuestionPause(toolResult)) {
163459
+ return {
163460
+ resumeToken: toolResult.__pause.resumeToken,
163461
+ questions: toolResult.__pause.questions,
163462
+ context: toolResult.__pause.context,
163463
+ toolCallId
163464
+ };
163465
+ }
163466
+ if (isMcpApprovalPause(toolResult)) {
163467
+ return {
163468
+ kind: MCP_APPROVAL_PAUSE_KIND,
163469
+ resumeToken: toolResult.__pause.resumeToken,
163470
+ mcpApproval: toolResult.__pause.mcpApproval,
163471
+ action: toolResult.__pause.action,
163472
+ toolCallId
163473
+ };
163474
+ }
163475
+ return null;
163476
+ }
163477
+ var MCP_APPROVAL_PAUSE_KIND;
163478
+ var init_envelope = __esm({
163479
+ "src/core/pause/envelope.ts"() {
163480
+ "use strict";
163481
+ init_tool();
163482
+ MCP_APPROVAL_PAUSE_KIND = "mcp-approval";
163483
+ }
163484
+ });
163485
+
163437
163486
  // src/core/ai/provider-attempt.ts
163438
163487
  function providerAttemptAbortError(attempt) {
163439
163488
  const reason = attempt.signal.reason;
@@ -164099,15 +164148,10 @@ async function adaptClaudeStream(stream, options2) {
164099
164148
  });
164100
164149
  multiplexer.send(cardEvent);
164101
164150
  }
164102
- if (isAskQuestionPause(toolResult)) {
164151
+ const pause = detectPause(toolResult, id);
164152
+ if (pause) {
164103
164153
  pauseDetected = true;
164104
- if (onPauseDetected) {
164105
- await onPauseDetected({
164106
- resumeToken: toolResult.__pause.resumeToken,
164107
- questions: toolResult.__pause.questions,
164108
- context: toolResult.__pause.context
164109
- });
164110
- }
164154
+ if (onPauseDetected) await onPauseDetected(pause);
164111
164155
  return { toolResult };
164112
164156
  }
164113
164157
  if (toolResult?.__parentToolCall === true) {
@@ -164380,15 +164424,10 @@ async function adaptClaudeStream(stream, options2) {
164380
164424
  });
164381
164425
  multiplexer.send(cardEvent);
164382
164426
  }
164383
- if (isAskQuestionPause(toolResult)) {
164427
+ const pause = detectPause(toolResult, id);
164428
+ if (pause) {
164384
164429
  pauseDetected = true;
164385
- if (onPauseDetected) {
164386
- await onPauseDetected({
164387
- resumeToken: toolResult.__pause.resumeToken,
164388
- questions: toolResult.__pause.questions,
164389
- context: toolResult.__pause.context
164390
- });
164391
- }
164430
+ if (onPauseDetected) await onPauseDetected(pause);
164392
164431
  return { toolResult };
164393
164432
  }
164394
164433
  if (toolResult?.__parentToolCall === true) {
@@ -164608,7 +164647,7 @@ var init_claude_stream_adapter = __esm({
164608
164647
  "use strict";
164609
164648
  init_logger();
164610
164649
  init_store();
164611
- init_tool();
164650
+ init_envelope();
164612
164651
  init_provider_attempt();
164613
164652
  }
164614
164653
  });
@@ -164724,15 +164763,10 @@ async function adaptOpenRouterStream(stream, options2) {
164724
164763
  });
164725
164764
  multiplexer.send(cardEvent);
164726
164765
  }
164727
- if (isAskQuestionPause(toolResult)) {
164766
+ const pause = detectPause(toolResult, id);
164767
+ if (pause) {
164728
164768
  pauseDetected = true;
164729
- if (onPauseDetected) {
164730
- await onPauseDetected({
164731
- resumeToken: toolResult.__pause.resumeToken,
164732
- questions: toolResult.__pause.questions,
164733
- context: toolResult.__pause.context
164734
- });
164735
- }
164769
+ if (onPauseDetected) await onPauseDetected(pause);
164736
164770
  return;
164737
164771
  }
164738
164772
  if (toolResult?.__parentToolCall === true) {
@@ -165309,7 +165343,7 @@ var init_openrouter_stream_adapter = __esm({
165309
165343
  "use strict";
165310
165344
  init_logger();
165311
165345
  init_store();
165312
- init_tool();
165346
+ init_envelope();
165313
165347
  init_provider_attempt();
165314
165348
  }
165315
165349
  });
@@ -167172,7 +167206,7 @@ function buildBlock(artifact) {
167172
167206
  ${body2}
167173
167207
  </referenced-artifact>`;
167174
167208
  }
167175
- async function resolveArtifactMentions(text5, lookup2) {
167209
+ async function resolveArtifactMentions(text5, lookup3) {
167176
167210
  if (!text5 || typeof text5 !== "string") return text5;
167177
167211
  const ids = /* @__PURE__ */ new Set();
167178
167212
  const matches = text5.matchAll(ARTIFACT_MENTION_RE);
@@ -167180,7 +167214,7 @@ async function resolveArtifactMentions(text5, lookup2) {
167180
167214
  ids.add(match2[1].toLowerCase());
167181
167215
  }
167182
167216
  if (ids.size === 0) return text5;
167183
- const resolved = await lookup2(Array.from(ids));
167217
+ const resolved = await lookup3(Array.from(ids));
167184
167218
  const byId = /* @__PURE__ */ new Map();
167185
167219
  for (const artifact of resolved) {
167186
167220
  byId.set(artifact.id.toLowerCase(), artifact);
@@ -167211,14 +167245,14 @@ function buildElement(user) {
167211
167245
  const handle = escapeXml2(`@${handleSource}`);
167212
167246
  return `<slack-user id="${user.id}" handle="${handle}" name="${name18}" />`;
167213
167247
  }
167214
- async function resolveSlackMentions(text5, lookup2) {
167248
+ async function resolveSlackMentions(text5, lookup3) {
167215
167249
  if (!text5 || typeof text5 !== "string") return text5;
167216
167250
  const ids = /* @__PURE__ */ new Set();
167217
167251
  for (const match2 of text5.matchAll(SLACK_MENTION_RE)) {
167218
167252
  ids.add(match2[1].toUpperCase());
167219
167253
  }
167220
167254
  if (ids.size === 0) return text5;
167221
- const resolved = await lookup2(Array.from(ids));
167255
+ const resolved = await lookup3(Array.from(ids));
167222
167256
  const byId = /* @__PURE__ */ new Map();
167223
167257
  for (const user of resolved) {
167224
167258
  byId.set(user.id.toUpperCase(), user);
@@ -175113,7 +175147,7 @@ var require_gaxios = __commonJS({
175113
175147
  var retry_js_1 = require_retry();
175114
175148
  var stream_1 = __require("stream");
175115
175149
  var interceptor_js_1 = require_interceptor();
175116
- var randomUUID15 = async () => globalThis.crypto?.randomUUID() || (await import("crypto")).randomUUID();
175150
+ var randomUUID16 = async () => globalThis.crypto?.randomUUID() || (await import("crypto")).randomUUID();
175117
175151
  var HTTP_STATUS_NO_CONTENT = 204;
175118
175152
  var Gaxios = class {
175119
175153
  agentCache = /* @__PURE__ */ new Map();
@@ -175386,7 +175420,7 @@ var require_gaxios = __commonJS({
175386
175420
  */
175387
175421
  ["Blob", "File", "FormData"].includes(opts.data?.constructor?.name || "");
175388
175422
  if (opts.multipart?.length) {
175389
- const boundary = await randomUUID15();
175423
+ const boundary = await randomUUID16();
175390
175424
  preparedHeaders.set("content-type", `multipart/related; boundary=${boundary}`);
175391
175425
  opts.body = stream_1.Readable.from(this.getMultipartRequest(opts.multipart, boundary));
175392
175426
  } else if (shouldDirectlyPassData) {
@@ -178067,12 +178101,12 @@ var require_base64_js = __commonJS({
178067
178101
  exports2.byteLength = byteLength;
178068
178102
  exports2.toByteArray = toByteArray;
178069
178103
  exports2.fromByteArray = fromByteArray;
178070
- var lookup2 = [];
178104
+ var lookup3 = [];
178071
178105
  var revLookup = [];
178072
178106
  var Arr = typeof Uint8Array !== "undefined" ? Uint8Array : Array;
178073
178107
  var code = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
178074
178108
  for (i19 = 0, len = code.length; i19 < len; ++i19) {
178075
- lookup2[i19] = code[i19];
178109
+ lookup3[i19] = code[i19];
178076
178110
  revLookup[code.charCodeAt(i19)] = i19;
178077
178111
  }
178078
178112
  var i19;
@@ -178125,7 +178159,7 @@ var require_base64_js = __commonJS({
178125
178159
  return arr;
178126
178160
  }
178127
178161
  function tripletToBase64(num) {
178128
- return lookup2[num >> 18 & 63] + lookup2[num >> 12 & 63] + lookup2[num >> 6 & 63] + lookup2[num & 63];
178162
+ return lookup3[num >> 18 & 63] + lookup3[num >> 12 & 63] + lookup3[num >> 6 & 63] + lookup3[num & 63];
178129
178163
  }
178130
178164
  function encodeChunk(uint8, start, end) {
178131
178165
  var tmp;
@@ -178148,12 +178182,12 @@ var require_base64_js = __commonJS({
178148
178182
  if (extraBytes === 1) {
178149
178183
  tmp = uint8[len2 - 1];
178150
178184
  parts.push(
178151
- lookup2[tmp >> 2] + lookup2[tmp << 4 & 63] + "=="
178185
+ lookup3[tmp >> 2] + lookup3[tmp << 4 & 63] + "=="
178152
178186
  );
178153
178187
  } else if (extraBytes === 2) {
178154
178188
  tmp = (uint8[len2 - 2] << 8) + uint8[len2 - 1];
178155
178189
  parts.push(
178156
- lookup2[tmp >> 10] + lookup2[tmp >> 4 & 63] + lookup2[tmp << 2 & 63] + "="
178190
+ lookup3[tmp >> 10] + lookup3[tmp >> 4 & 63] + lookup3[tmp << 2 & 63] + "="
178157
178191
  );
178158
178192
  }
178159
178193
  return parts.join("");
@@ -205822,15 +205856,10 @@ async function callGeminiDirect({
205822
205856
  try {
205823
205857
  if (abortSignal?.aborted) break;
205824
205858
  let toolResult = await executeToolByName(toolName, toolArgs, { ...context3, toolCallId });
205825
- if (isAskQuestionPause(toolResult)) {
205859
+ const pause = detectPause(toolResult, toolCallId);
205860
+ if (pause) {
205826
205861
  pauseDetected = true;
205827
- if (onPauseDetected) {
205828
- await onPauseDetected({
205829
- resumeToken: toolResult.__pause.resumeToken,
205830
- questions: toolResult.__pause.questions,
205831
- context: toolResult.__pause.context
205832
- });
205833
- }
205862
+ if (onPauseDetected) await onPauseDetected(pause);
205834
205863
  completedToolCalls.push({ id: toolCallId, name: toolName, arguments: toolArgs });
205835
205864
  break;
205836
205865
  }
@@ -205995,15 +206024,10 @@ async function callGeminiDirect({
205995
206024
  try {
205996
206025
  if (abortSignal?.aborted) break;
205997
206026
  let toolResult = await executeToolByName(toolName, toolArgs, { ...context3, toolCallId });
205998
- if (isAskQuestionPause(toolResult)) {
206027
+ const pause = detectPause(toolResult, toolCallId);
206028
+ if (pause) {
205999
206029
  pauseDetected = true;
206000
- if (onPauseDetected) {
206001
- await onPauseDetected({
206002
- resumeToken: toolResult.__pause.resumeToken,
206003
- questions: toolResult.__pause.questions,
206004
- context: toolResult.__pause.context
206005
- });
206006
- }
206030
+ if (onPauseDetected) await onPauseDetected(pause);
206007
206031
  completedToolCalls.push({ id: toolCallId, name: toolName, arguments: toolArgs });
206008
206032
  break;
206009
206033
  }
@@ -206260,7 +206284,7 @@ var init_gemini_direct_stream_adapter = __esm({
206260
206284
  init_node2();
206261
206285
  init_esm2();
206262
206286
  init_store();
206263
- init_tool();
206287
+ init_envelope();
206264
206288
  init_provider_attempt();
206265
206289
  }
206266
206290
  });
@@ -207714,6 +207738,7 @@ var init_tool_categories = __esm({
207714
207738
  "update-project",
207715
207739
  "mcp_config_apply",
207716
207740
  "mcp_config_request_secret",
207741
+ "mcp_server_request_approval",
207717
207742
  "mcp_call_tool",
207718
207743
  "mcp_server_create",
207719
207744
  "mcp_server_update",
@@ -212213,7 +212238,7 @@ var require_redis_errors = __commonJS({
212213
212238
  var require_lib3 = __commonJS({
212214
212239
  "node_modules/.pnpm/cluster-key-slot@1.1.2/node_modules/cluster-key-slot/lib/index.js"(exports2, module) {
212215
212240
  "use strict";
212216
- var lookup2 = [
212241
+ var lookup3 = [
212217
212242
  0,
212218
212243
  4129,
212219
212244
  8258,
@@ -212513,11 +212538,11 @@ var require_lib3 = __commonJS({
212513
212538
  start = i19;
212514
212539
  }
212515
212540
  } else if (char2 !== 125) {
212516
- resultHash = lookup2[(char2 ^ resultHash >> 8) & 255] ^ resultHash << 8;
212541
+ resultHash = lookup3[(char2 ^ resultHash >> 8) & 255] ^ resultHash << 8;
212517
212542
  } else if (i19 - 1 !== start) {
212518
212543
  return resultHash & 16383;
212519
212544
  }
212520
- result = lookup2[(char2 ^ result >> 8) & 255] ^ result << 8;
212545
+ result = lookup3[(char2 ^ result >> 8) & 255] ^ result << 8;
212521
212546
  }
212522
212547
  return result & 16383;
212523
212548
  };
@@ -220243,6 +220268,7 @@ function buildMcpServerToolsPromptSection({
220243
220268
  "- Prefer user-wide profile tools: `mcp_server_list`, `mcp_server_create`, `mcp_server_update`, `mcp_server_delete`, and `mcp_server_test`.",
220244
220269
  "- Use legacy sandbox config tools `mcp_config_list`, `mcp_config_propose`, `mcp_config_test`, and `mcp_config_apply` only when the user explicitly asks for legacy per-sandbox MCP config.",
220245
220270
  "- Never ask the user to reveal secret values. Use `vault_secret_list` to find full secret IDs, or exact Vault secret names when the create/update tool allows them.",
220271
+ "- A server that would receive Vault secrets stays inactive until the user approves it in the app. You cannot approve it: when a tool result says `approvalRequired`, share the `approvalUrl` and wait. Changing its URL, command, or secret bindings requires approval again.",
220246
220272
  "- For npx/uvx API-key MCP servers, put command/header flags in `args` and bind secret values through `envSecretRefs`, not `headersSecretRefs`.",
220247
220273
  '- Groundcover API-key setup uses `runner: "npx"`, `package: "mcp-remote"`, args including `https://mcp.groundcover.com/api/mcp`, `--header`, `Authorization:${GROUNDCOVER_API_KEY}`, and `envSecretRefs: { "GROUNDCOVER_API_KEY": "<secret-id-or-name>" }`.',
220248
220274
  "- After command-based MCP config changes, use `sandbox_list` then `sandbox_restart` if a running execution environment needs a fresh start."
@@ -220521,6 +220547,43 @@ var init_store2 = __esm({
220521
220547
  }
220522
220548
  });
220523
220549
 
220550
+ // src/core/pause/mcp-approval.ts
220551
+ var mcp_approval_exports = {};
220552
+ __export2(mcp_approval_exports, {
220553
+ buildMcpApprovalPause: () => buildMcpApprovalPause,
220554
+ buildMcpApprovalPlaceholder: () => buildMcpApprovalPlaceholder,
220555
+ formatMcpApprovalDecision: () => formatMcpApprovalDecision
220556
+ });
220557
+ import { randomUUID as randomUUID5 } from "node:crypto";
220558
+ function buildMcpApprovalPause(approval, action5) {
220559
+ return {
220560
+ __pause: {
220561
+ kind: MCP_APPROVAL_PAUSE_KIND,
220562
+ resumeToken: randomUUID5(),
220563
+ mcpApproval: approval,
220564
+ action: action5
220565
+ }
220566
+ };
220567
+ }
220568
+ function buildMcpApprovalPlaceholder(approval) {
220569
+ return `[Awaiting user approval for MCP server "${approval.name}" \u2014 approval card is open]`;
220570
+ }
220571
+ function formatMcpApprovalDecision(params) {
220572
+ const { approval, action: action5, decision } = params;
220573
+ const subject = `MCP server "${approval.name}" (id ${approval.profileId}, ${approval.target})`;
220574
+ const done = action5 === "requested" ? "" : ` It was ${action5}.`;
220575
+ if (decision === "approved") {
220576
+ return `User approved ${subject} to use its Vault secrets.${done} It is now active: test it with mcp_server_test before using its tools.`;
220577
+ }
220578
+ return `User rejected ${subject}; it stays disabled and receives no secrets.${done} Do not retry or re-create it unless the user asks.`;
220579
+ }
220580
+ var init_mcp_approval = __esm({
220581
+ "src/core/pause/mcp-approval.ts"() {
220582
+ "use strict";
220583
+ init_envelope();
220584
+ }
220585
+ });
220586
+
220524
220587
  // src/lib/services/user.service.ts
220525
220588
  var user_service_exports = {};
220526
220589
  __export2(user_service_exports, {
@@ -223153,32 +223216,15 @@ var init_hook_definition = __esm({
223153
223216
  }
223154
223217
  });
223155
223218
 
223156
- // src/lib/hooks/hook-runtime.ts
223157
- import { lookup } from "node:dns/promises";
223158
- import { request as httpRequest } from "node:http";
223159
- import { request as httpsRequest } from "node:https";
223219
+ // src/lib/net/guarded-fetch.ts
223160
223220
  import { isIP as isIP2 } from "node:net";
223161
- function boundedJson(value, maxBytes, code = "HOOK_RUNTIME_LIMIT") {
223162
- const json3 = JSON.stringify(value ?? null);
223163
- if (Buffer.byteLength(json3, "utf8") > maxBytes) throw new Error(code);
223164
- return json3;
223165
- }
223166
- function usableOrigin() {
223167
- try {
223168
- return config2.mcp.url ? new URL(config2.mcp.url).origin : null;
223169
- } catch {
223170
- return null;
223171
- }
223172
- }
223173
- function sanitizedUrl(url3) {
223174
- return `${url3.origin}${url3.pathname}`;
223175
- }
223176
223221
  function normalizeIpLiteral(address) {
223177
223222
  return address.toLowerCase().replace(/^\[|\]$/g, "");
223178
223223
  }
223179
223224
  function embeddedIpv4Address(address) {
223180
- const suffix = address.startsWith("::ffff:") ? address.slice("::ffff:".length) : address.startsWith("::") ? address.slice(2) : "";
223181
- if (!suffix) return null;
223225
+ const prefix2 = ["::ffff:", "64:ff9b::", "::"].find((candidate) => address.startsWith(candidate));
223226
+ if (!prefix2) return null;
223227
+ const suffix = address.slice(prefix2.length);
223182
223228
  if (isIP2(suffix) === 4) return suffix;
223183
223229
  const match2 = /^([0-9a-f]{1,4}):([0-9a-f]{1,4})$/.exec(suffix);
223184
223230
  if (!match2) return null;
@@ -223195,7 +223241,40 @@ function isForbiddenAddress(address) {
223195
223241
  const [a22 = 0, b32 = 0] = normalized.split(".").map(Number);
223196
223242
  return a22 === 0 || a22 === 10 || a22 === 127 || a22 === 100 && b32 >= 64 && b32 <= 127 || a22 === 169 && b32 === 254 || a22 === 172 && b32 >= 16 && b32 <= 31 || a22 === 192 && b32 === 0 || a22 === 192 && b32 === 168 || a22 === 198 && (b32 === 18 || b32 === 19) || a22 === 198 && b32 === 51 || a22 === 203 && b32 === 0 || a22 >= 224;
223197
223243
  }
223198
- return normalized.startsWith("fc") || normalized.startsWith("fd") || normalized.startsWith("fe8") || normalized.startsWith("fe9") || normalized.startsWith("fea") || normalized.startsWith("feb") || normalized.startsWith("ff") || normalized.startsWith("2001:db8");
223244
+ return normalized.startsWith("fc") || normalized.startsWith("fd") || /^fe[89a-f]/.test(normalized) || normalized.startsWith("ff") || normalized.startsWith("2001:db8");
223245
+ }
223246
+ var MCP_FETCH_POLICY;
223247
+ var init_guarded_fetch = __esm({
223248
+ "src/lib/net/guarded-fetch.ts"() {
223249
+ "use strict";
223250
+ MCP_FETCH_POLICY = {
223251
+ allowHttp: true,
223252
+ maxResponseBytes: 5 * 1024 * 1024,
223253
+ timeoutMs: 3e4,
223254
+ maxRedirects: 5
223255
+ };
223256
+ }
223257
+ });
223258
+
223259
+ // src/lib/hooks/hook-runtime.ts
223260
+ import { lookup } from "node:dns/promises";
223261
+ import { request as httpRequest } from "node:http";
223262
+ import { request as httpsRequest } from "node:https";
223263
+ import { isIP as isIP3 } from "node:net";
223264
+ function boundedJson(value, maxBytes, code = "HOOK_RUNTIME_LIMIT") {
223265
+ const json3 = JSON.stringify(value ?? null);
223266
+ if (Buffer.byteLength(json3, "utf8") > maxBytes) throw new Error(code);
223267
+ return json3;
223268
+ }
223269
+ function usableOrigin() {
223270
+ try {
223271
+ return config2.mcp.url ? new URL(config2.mcp.url).origin : null;
223272
+ } catch {
223273
+ return null;
223274
+ }
223275
+ }
223276
+ function sanitizedUrl(url3) {
223277
+ return `${url3.origin}${url3.pathname}`;
223199
223278
  }
223200
223279
  async function validateDestination(url3) {
223201
223280
  if (Buffer.byteLength(url3.toString()) > LIMITS.urlBytes || url3.username || url3.password) {
@@ -223212,8 +223291,8 @@ async function validateDestination(url3) {
223212
223291
  if (url3.hostname === "localhost" || url3.hostname.endsWith(".local")) {
223213
223292
  throw new Error("HOOK_FETCH_DESTINATION_BLOCKED");
223214
223293
  }
223215
- const hostname2 = normalizeIpLiteral(url3.hostname);
223216
- const addresses = isIP2(hostname2) ? [{ address: hostname2 }] : await lookup(hostname2, { all: true, verbatim: true });
223294
+ const hostname2 = url3.hostname.toLowerCase().replace(/^\[|\]$/g, "");
223295
+ const addresses = isIP3(hostname2) ? [{ address: hostname2 }] : await lookup(hostname2, { all: true, verbatim: true });
223217
223296
  if (addresses.length === 0 || addresses.some(({ address }) => isForbiddenAddress(address))) {
223218
223297
  throw new Error("HOOK_FETCH_DESTINATION_BLOCKED");
223219
223298
  }
@@ -223611,6 +223690,7 @@ var init_hook_runtime = __esm({
223611
223690
  "use strict";
223612
223691
  init_config();
223613
223692
  init_dist15();
223693
+ init_guarded_fetch();
223614
223694
  LIMITS = {
223615
223695
  memoryBytes: 16 * 1024 * 1024,
223616
223696
  stackBytes: 256 * 1024,
@@ -225375,12 +225455,12 @@ var init_hook_runtime_token = __esm({
225375
225455
  });
225376
225456
 
225377
225457
  // src/lib/hooks/hook-test-confirmation.ts
225378
- import { randomUUID as randomUUID5 } from "node:crypto";
225458
+ import { randomUUID as randomUUID6 } from "node:crypto";
225379
225459
  async function issueHookTestConfirmation(userId, conversationId, turnId) {
225380
225460
  if (!await isRedisAvailable()) return null;
225381
225461
  const redis = getRedis();
225382
225462
  if (!redis) return null;
225383
- const token = randomUUID5();
225463
+ const token = randomUUID6();
225384
225464
  await redis.set(
225385
225465
  `hook-test-confirmation:${token}`,
225386
225466
  JSON.stringify({ userId, conversationId, turnId }),
@@ -230846,7 +230926,7 @@ var init_conversation_resource_authorization = __esm({
230846
230926
  });
230847
230927
 
230848
230928
  // src/lib/services/hook-definition.service.ts
230849
- import { createHash as createHash10, randomUUID as randomUUID6 } from "node:crypto";
230929
+ import { createHash as createHash10, randomUUID as randomUUID7 } from "node:crypto";
230850
230930
  function deterministicUuid(namespace, value) {
230851
230931
  const bytes = Buffer.from(
230852
230932
  createHash10("sha256").update(`${namespace}\0${value}`).digest().subarray(0, 16)
@@ -230857,8 +230937,8 @@ function deterministicUuid(namespace, value) {
230857
230937
  return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
230858
230938
  }
230859
230939
  async function createHookDefinition(pathway, userId, input, idempotencyKey) {
230860
- const hookId = idempotencyKey ? deterministicUuid("usable-chat:hook-definition", `${userId}:${idempotencyKey}`) : randomUUID6();
230861
- const revisionId = idempotencyKey ? deterministicUuid("usable-chat:hook-revision", hookId) : randomUUID6();
230940
+ const hookId = idempotencyKey ? deterministicUuid("usable-chat:hook-definition", `${userId}:${idempotencyKey}`) : randomUUID7();
230941
+ const revisionId = idempotencyKey ? deterministicUuid("usable-chat:hook-revision", hookId) : randomUUID7();
230862
230942
  const existingHook = await getHookDefinition(userId, hookId);
230863
230943
  if (existingHook) return existingHook;
230864
230944
  const timestamp2 = (/* @__PURE__ */ new Date()).toISOString();
@@ -230872,7 +230952,7 @@ async function createHookDefinition(pathway, userId, input, idempotencyKey) {
230872
230952
  const order = (existing.at(-1)?.order ?? -1) + 1;
230873
230953
  await pathway.write("hook-definition.0/hook-definition.created.0", {
230874
230954
  data: {
230875
- id: randomUUID6(),
230955
+ id: randomUUID7(),
230876
230956
  timestamp: timestamp2,
230877
230957
  hookId,
230878
230958
  revisionId,
@@ -230921,11 +231001,11 @@ async function reviseHookDefinition(pathway, userId, hookId, baseRevision, input
230921
231001
  ).orderBy(asc(hookDefinitions.order));
230922
231002
  order = (rows.at(-1)?.order ?? -1) + 1;
230923
231003
  }
230924
- const revisionId = randomUUID6();
231004
+ const revisionId = randomUUID7();
230925
231005
  try {
230926
231006
  await pathway.write("hook-definition.0/hook-definition.revised.0", {
230927
231007
  data: {
230928
- id: randomUUID6(),
231008
+ id: randomUUID7(),
230929
231009
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
230930
231010
  hookId,
230931
231011
  revisionId,
@@ -230953,7 +231033,7 @@ async function setHookActivation(pathway, userId, hookId, enabled2) {
230953
231033
  if (!await getHookDefinition(userId, hookId)) return null;
230954
231034
  await pathway.write("hook-definition.0/hook-definition.activation-changed.0", {
230955
231035
  data: {
230956
- id: randomUUID6(),
231036
+ id: randomUUID7(),
230957
231037
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
230958
231038
  hookId,
230959
231039
  userId,
@@ -230965,7 +231045,7 @@ async function setHookActivation(pathway, userId, hookId, enabled2) {
230965
231045
  async function reorderHookDefinitions(pathway, userId, input) {
230966
231046
  await pathway.write("hook-definition.0/hook-definition.reordered.0", {
230967
231047
  data: {
230968
- id: randomUUID6(),
231048
+ id: randomUUID7(),
230969
231049
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
230970
231050
  userId,
230971
231051
  ...input
@@ -301776,7 +301856,7 @@ var init_httpHeaders = __esm({
301776
301856
  });
301777
301857
 
301778
301858
  // node_modules/.pnpm/@typespec+ts-http-runtime@0.3.4/node_modules/@typespec/ts-http-runtime/dist/esm/util/uuidUtils.js
301779
- function randomUUID7() {
301859
+ function randomUUID8() {
301780
301860
  return crypto.randomUUID();
301781
301861
  }
301782
301862
  var init_uuidUtils = __esm({
@@ -301830,7 +301910,7 @@ var init_pipelineRequest = __esm({
301830
301910
  this.abortSignal = options2.abortSignal;
301831
301911
  this.onUploadProgress = options2.onUploadProgress;
301832
301912
  this.onDownloadProgress = options2.onDownloadProgress;
301833
- this.requestId = options2.requestId || randomUUID7();
301913
+ this.requestId = options2.requestId || randomUUID8();
301834
301914
  this.allowInsecureConnection = options2.allowInsecureConnection ?? false;
301835
301915
  this.enableBrowserStreams = options2.enableBrowserStreams ?? false;
301836
301916
  this.requestOverrides = options2.requestOverrides;
@@ -303534,7 +303614,7 @@ var init_concat = __esm({
303534
303614
 
303535
303615
  // node_modules/.pnpm/@typespec+ts-http-runtime@0.3.4/node_modules/@typespec/ts-http-runtime/dist/esm/policies/multipartPolicy.js
303536
303616
  function generateBoundary() {
303537
- return `----AzSDKFormBoundary${randomUUID7()}`;
303617
+ return `----AzSDKFormBoundary${randomUUID8()}`;
303538
303618
  }
303539
303619
  function encodeHeaders(headers) {
303540
303620
  let result = "";
@@ -304163,8 +304243,8 @@ var init_typeGuards2 = __esm({
304163
304243
  function isError2(e15) {
304164
304244
  return isError(e15);
304165
304245
  }
304166
- function randomUUID8() {
304167
- return randomUUID7();
304246
+ function randomUUID9() {
304247
+ return randomUUID8();
304168
304248
  }
304169
304249
  var isNodeLike2;
304170
304250
  var init_esm40 = __esm({
@@ -333526,7 +333606,7 @@ var init_BlobLeaseClient = __esm({
333526
333606
  this._containerOrBlobOperation = clientContext.blob;
333527
333607
  }
333528
333608
  if (!leaseId2) {
333529
- leaseId2 = randomUUID8();
333609
+ leaseId2 = randomUUID9();
333530
333610
  }
333531
333611
  this._leaseId = leaseId2;
333532
333612
  }
@@ -337933,7 +338013,7 @@ var init_Clients = __esm({
337933
338013
  throw new RangeError(`The buffer's size is too big or the BlockSize is too small;the number of blocks must be <= ${BLOCK_BLOB_MAX_BLOCKS}`);
337934
338014
  }
337935
338015
  const blockList = [];
337936
- const blockIDPrefix = randomUUID8();
338016
+ const blockIDPrefix = randomUUID9();
337937
338017
  let transferProgress = 0;
337938
338018
  const batch = new Batch(options2.concurrency);
337939
338019
  for (let i19 = 0; i19 < numBlocks; i19++) {
@@ -338014,7 +338094,7 @@ var init_Clients = __esm({
338014
338094
  }
338015
338095
  return tracingClient.withSpan("BlockBlobClient-uploadStream", options2, async (updatedOptions) => {
338016
338096
  let blockNum = 0;
338017
- const blockIDPrefix = randomUUID8();
338097
+ const blockIDPrefix = randomUUID9();
338018
338098
  let transferProgress = 0;
338019
338099
  const blockList = [];
338020
338100
  const scheduler = new BufferScheduler(
@@ -339139,7 +339219,7 @@ var init_BlobBatch = __esm({
339139
339219
  constructor() {
339140
339220
  this.operationCount = 0;
339141
339221
  this.body = "";
339142
- const tempGuid = randomUUID8();
339222
+ const tempGuid = randomUUID9();
339143
339223
  this.boundary = `batch_${tempGuid}`;
339144
339224
  this.subRequestPrefix = `--${this.boundary}${HTTP_LINE_ENDING}${HeaderConstants.CONTENT_TYPE}: application/http${HTTP_LINE_ENDING}${HeaderConstants.CONTENT_TRANSFER_ENCODING}: binary`;
339145
339225
  this.multipartContentType = `multipart/mixed; boundary=${this.boundary}`;
@@ -350204,7 +350284,7 @@ var require_issuer = __commonJS({
350204
350284
  var { keystores } = require_weak_cache();
350205
350285
  var processResponse = require_process_response();
350206
350286
  var request = require_request2();
350207
- var inFlight = /* @__PURE__ */ new WeakMap();
350287
+ var inFlight2 = /* @__PURE__ */ new WeakMap();
350208
350288
  var caches = /* @__PURE__ */ new WeakMap();
350209
350289
  var lrus = (ctx) => {
350210
350290
  if (!caches.has(ctx)) {
@@ -350217,11 +350297,11 @@ var require_issuer = __commonJS({
350217
350297
  const keystore = keystores.get(this);
350218
350298
  const cache10 = lrus(this);
350219
350299
  if (reload || !keystore) {
350220
- if (inFlight.has(this)) {
350221
- return inFlight.get(this);
350300
+ if (inFlight2.has(this)) {
350301
+ return inFlight2.get(this);
350222
350302
  }
350223
350303
  cache10.reset();
350224
- inFlight.set(
350304
+ inFlight2.set(
350225
350305
  this,
350226
350306
  (async () => {
350227
350307
  const response = await request.call(this, {
@@ -350232,7 +350312,7 @@ var require_issuer = __commonJS({
350232
350312
  Accept: "application/json, application/jwk-set+json"
350233
350313
  }
350234
350314
  }).finally(() => {
350235
- inFlight.delete(this);
350315
+ inFlight2.delete(this);
350236
350316
  });
350237
350317
  const jwks2 = processResponse(response);
350238
350318
  const joseKeyStore = KeyStore.fromJWKS(jwks2, { onlyPublic: true });
@@ -350241,7 +350321,7 @@ var require_issuer = __commonJS({
350241
350321
  return joseKeyStore;
350242
350322
  })()
350243
350323
  );
350244
- return inFlight.get(this);
350324
+ return inFlight2.get(this);
350245
350325
  }
350246
350326
  return keystore;
350247
350327
  }
@@ -359211,7 +359291,7 @@ __export2(auth_refresh_coordination_exports, {
359211
359291
  compressAccessTokenRotation: () => compressAccessTokenRotation,
359212
359292
  coordinateAccessTokenRefresh: () => coordinateAccessTokenRefresh
359213
359293
  });
359214
- import { createHash as createHash12, randomUUID as randomUUID9 } from "node:crypto";
359294
+ import { createHash as createHash12, randomUUID as randomUUID10 } from "node:crypto";
359215
359295
  async function coordinateAccessTokenRefresh(token, refresh2) {
359216
359296
  const redis = getRedis();
359217
359297
  const secret = process.env.NEXTAUTH_SECRET;
@@ -359263,7 +359343,7 @@ async function coordinateAccessTokenRefresh(token, refresh2) {
359263
359343
  }
359264
359344
  throw new Error("refresh_rotation_chain_limit");
359265
359345
  }
359266
- const owner = randomUUID9();
359346
+ const owner = randomUUID10();
359267
359347
  if (!await redis.set(key2, owner, "EX", TTL, "NX")) {
359268
359348
  for (let attempt = 0; attempt < 20; attempt++) {
359269
359349
  await new Promise((resolve9) => setTimeout(resolve9, 250));
@@ -360154,15 +360234,126 @@ var init_embed_auth = __esm({
360154
360234
  }
360155
360235
  });
360156
360236
 
360237
+ // src/lib/services/oauth-client-trust.service.ts
360238
+ function overrideClientIds() {
360239
+ return (config2.auth.trustedClientIdsRaw ?? "").split(",").map((id) => id.trim()).filter(Boolean);
360240
+ }
360241
+ function getClientTrustUrl() {
360242
+ const mcpUrl = config2.mcp.url;
360243
+ if (!mcpUrl || !MCP_PATH.test(mcpUrl)) return null;
360244
+ return `${mcpUrl.replace(MCP_PATH, "")}/api/oauth/client-trust`;
360245
+ }
360246
+ function remember(azp, verdict) {
360247
+ if (!verdictCache.has(azp) && verdictCache.size >= CACHE_MAX_ENTRIES) {
360248
+ const oldest = verdictCache.keys().next().value;
360249
+ if (typeof oldest === "string") verdictCache.delete(oldest);
360250
+ }
360251
+ const now2 = Date.now();
360252
+ verdictCache.set(
360253
+ azp,
360254
+ verdict === "trusted" ? {
360255
+ verdict,
360256
+ expiresAt: now2 + TRUSTED_TTL_MS,
360257
+ staleUntil: now2 + TRUSTED_TTL_MS + STALE_IF_ERROR_MS
360258
+ } : { verdict, expiresAt: now2 + UNTRUSTED_TTL_MS }
360259
+ );
360260
+ }
360261
+ async function lookup2(azp, accessToken, fetchImpl) {
360262
+ const url3 = getClientTrustUrl();
360263
+ if (!url3) {
360264
+ logger.error("oauth-client-trust", "USABLE_MCP_URL is missing or does not end in /api/mcp");
360265
+ return null;
360266
+ }
360267
+ try {
360268
+ const response = await fetchImpl(url3, {
360269
+ method: "GET",
360270
+ headers: { Authorization: `Bearer ${accessToken}` },
360271
+ cache: "no-store",
360272
+ signal: AbortSignal.timeout(LOOKUP_TIMEOUT_MS)
360273
+ });
360274
+ if (!response.ok) {
360275
+ logger.warn("oauth-client-trust", "Client trust lookup failed", {
360276
+ azp,
360277
+ status: response.status
360278
+ });
360279
+ return null;
360280
+ }
360281
+ const parsed = trustResponseSchema.safeParse(await response.json());
360282
+ if (!parsed.success || parsed.data.clientId !== azp) {
360283
+ logger.warn("oauth-client-trust", "Client trust lookup returned an unexpected body", { azp });
360284
+ return null;
360285
+ }
360286
+ const verdict = parsed.data.trusted ? "trusted" : "untrusted";
360287
+ remember(azp, verdict);
360288
+ return verdict;
360289
+ } catch (error41) {
360290
+ logger.warn("oauth-client-trust", "Client trust lookup errored", {
360291
+ azp,
360292
+ error: error41 instanceof Error ? error41.message : String(error41)
360293
+ });
360294
+ return null;
360295
+ }
360296
+ }
360297
+ async function resolveOAuthClientTrust(azp, accessToken, options2 = {}) {
360298
+ if (!azp) return "untrusted";
360299
+ if (azp === config2.auth.mcpOAuthClientId || overrideClientIds().includes(azp)) return "trusted";
360300
+ const cached2 = verdictCache.get(azp);
360301
+ if (!options2.bypassCache && cached2 && cached2.expiresAt > Date.now()) {
360302
+ return cached2.verdict;
360303
+ }
360304
+ let pending = inFlight.get(azp);
360305
+ if (!pending) {
360306
+ pending = lookup2(azp, accessToken, options2.fetchImpl ?? fetch).finally(() => {
360307
+ inFlight.delete(azp);
360308
+ });
360309
+ inFlight.set(azp, pending);
360310
+ }
360311
+ const verdict = await pending;
360312
+ if (verdict) return verdict;
360313
+ const stale = verdictCache.get(azp);
360314
+ if (!options2.bypassCache && stale?.verdict === "trusted" && stale.staleUntil !== void 0 && stale.staleUntil > Date.now()) {
360315
+ logger.warn("oauth-client-trust", "Serving stale trusted verdict while Usable is unavailable", {
360316
+ azp
360317
+ });
360318
+ return "trusted";
360319
+ }
360320
+ return "unavailable";
360321
+ }
360322
+ var TRUSTED_TTL_MS, UNTRUSTED_TTL_MS, STALE_IF_ERROR_MS, LOOKUP_TIMEOUT_MS, CACHE_MAX_ENTRIES, trustResponseSchema, verdictCache, inFlight, MCP_PATH;
360323
+ var init_oauth_client_trust_service = __esm({
360324
+ "src/lib/services/oauth-client-trust.service.ts"() {
360325
+ "use strict";
360326
+ init_zod();
360327
+ init_config();
360328
+ init_logger();
360329
+ TRUSTED_TTL_MS = 5 * 6e4;
360330
+ UNTRUSTED_TTL_MS = 3e4;
360331
+ STALE_IF_ERROR_MS = 60 * 6e4;
360332
+ LOOKUP_TIMEOUT_MS = 5e3;
360333
+ CACHE_MAX_ENTRIES = 500;
360334
+ trustResponseSchema = external_exports.union([
360335
+ external_exports.object({
360336
+ clientId: external_exports.string().min(1),
360337
+ trusted: external_exports.literal(true),
360338
+ kind: external_exports.enum(["first-party-sso", "mcp-oauth", "marketplace-app"]),
360339
+ appId: external_exports.string().optional()
360340
+ }),
360341
+ external_exports.object({
360342
+ clientId: external_exports.string().min(1).nullable(),
360343
+ trusted: external_exports.literal(false)
360344
+ })
360345
+ ]);
360346
+ verdictCache = /* @__PURE__ */ new Map();
360347
+ inFlight = /* @__PURE__ */ new Map();
360348
+ MCP_PATH = /\/api\/mcp\/?$/;
360349
+ }
360350
+ });
360351
+
360157
360352
  // src/lib/auth-helper.ts
360158
360353
  function parseTrustedClientIds(raw) {
360159
360354
  if (!raw) return [];
360160
360355
  return raw.split(",").map((id) => id.trim()).filter(Boolean);
360161
360356
  }
360162
- function isTrustedAzp(azp, trustedClientIds) {
360163
- if (trustedClientIds.length === 0) return true;
360164
- return azp !== void 0 && trustedClientIds.includes(azp);
360165
- }
360166
360357
  function hasConfiguredSessionCookie(cookieHeader, sessionCookieName) {
360167
360358
  if (!cookieHeader || !sessionCookieName) return false;
360168
360359
  return cookieHeader.split(";").some((cookie) => {
@@ -360227,15 +360418,23 @@ async function getAuthFromRequest(request, options2) {
360227
360418
  const token = authHeader.slice(7);
360228
360419
  const result = await verifyJWT(token);
360229
360420
  if (result.success) {
360230
- const trustedClientIds = parseTrustedClientIds(config2.auth.trustedClientIdsRaw);
360231
- if (!isTrustedAzp(result.payload.azp, trustedClientIds)) {
360421
+ const clientTrust = await resolveOAuthClientTrust(result.payload.azp, token);
360422
+ if (clientTrust === "untrusted") {
360232
360423
  console.warn(
360233
- `[AuthHelper] Bearer JWT azp rejected: ${result.payload.azp ?? "<missing>"} not in CHAT_TRUSTED_CLIENT_IDS`
360424
+ `[AuthHelper] Bearer JWT azp rejected: ${result.payload.azp ?? "<missing>"} is not a client trusted by Usable`
360234
360425
  );
360235
360426
  lastRejection = {
360236
360427
  code: "JWT_AZP_REJECTED",
360237
360428
  message: "JWT authorized party (azp) is not a trusted client for this service"
360238
360429
  };
360430
+ } else if (clientTrust === "unavailable") {
360431
+ console.warn(
360432
+ `[AuthHelper] Bearer JWT azp ${result.payload.azp} could not be checked: Usable client trust lookup unavailable`
360433
+ );
360434
+ lastRejection = {
360435
+ code: "CLIENT_TRUST_UNAVAILABLE",
360436
+ message: "Client authorization is temporarily unavailable. Please try again."
360437
+ };
360239
360438
  } else {
360240
360439
  const userId = result.payload.usable_user_id || result.payload.sub;
360241
360440
  if (!userId) {
@@ -360313,6 +360512,7 @@ var init_auth_helper = __esm({
360313
360512
  init_config();
360314
360513
  import_server2 = __toESM(require_server());
360315
360514
  init_embed_auth();
360515
+ init_oauth_client_trust_service();
360316
360516
  jwks = null;
360317
360517
  }
360318
360518
  });
@@ -361534,7 +361734,7 @@ var init_host_jwt = __esm({
361534
361734
  });
361535
361735
 
361536
361736
  // src/lib/work-runtime/resume-proof.ts
361537
- import { createHash as createHash14, createHmac as createHmac4, randomUUID as randomUUID10, timingSafeEqual as timingSafeEqual2 } from "node:crypto";
361737
+ import { createHash as createHash14, createHmac as createHmac4, randomUUID as randomUUID11, timingSafeEqual as timingSafeEqual2 } from "node:crypto";
361538
361738
  function encoded(value) {
361539
361739
  return Buffer.from(value).toString("base64url");
361540
361740
  }
@@ -362379,7 +362579,7 @@ __export2(file_storage_service_exports, {
362379
362579
  FileStorageService: () => FileStorageService,
362380
362580
  fileStorageService: () => fileStorageService
362381
362581
  });
362382
- import { randomUUID as randomUUID11 } from "node:crypto";
362582
+ import { randomUUID as randomUUID12 } from "node:crypto";
362383
362583
  function estimateBase64Bytes2(base643) {
362384
362584
  const content = base643.includes(",") ? base643.split(",")[1] : base643;
362385
362585
  const len = content.length;
@@ -362462,7 +362662,7 @@ var init_file_storage_service = __esm({
362462
362662
  mimeType: stored2.mimeType ?? params.mimeType
362463
362663
  };
362464
362664
  }
362465
- const id = randomUUID11();
362665
+ const id = randomUUID12();
362466
362666
  const preview = params.previewBytes ?? params.bytes;
362467
362667
  const llm = params.llmBytes ?? preview;
362468
362668
  const estimatedTokens = Math.ceil(Math.ceil(llm.byteLength * 4 / 3) / 100);
@@ -362629,7 +362829,7 @@ var init_file_storage_service = __esm({
362629
362829
  * For documents: keeps extractedContent as text and optional originalContent as base64 bytes.
362630
362830
  */
362631
362831
  async storeFile(params) {
362632
- const fileId = randomUUID11();
362832
+ const fileId = randomUUID12();
362633
362833
  let extractedContent = params.extractedContent;
362634
362834
  let originalContent = params.originalContent ?? null;
362635
362835
  if (params.fileType === "image" && params.contentType === "base64") {
@@ -362918,7 +363118,7 @@ var init_file_storage_service = __esm({
362918
363118
  paths = await storage.uploadVariants({
362919
363119
  fileId,
362920
363120
  userId,
362921
- namespace: `migration-${randomUUID11()}`,
363121
+ namespace: `migration-${randomUUID12()}`,
362922
363122
  variants: {
362923
363123
  original: { bytes: original2, mimeType },
362924
363124
  preview: { bytes: preview, mimeType: "image/webp" },
@@ -363183,7 +363383,7 @@ var init_file_storage_service = __esm({
363183
363383
  });
363184
363384
 
363185
363385
  // src/core/orchestrator/index.ts
363186
- import { createHash as createHash17, randomUUID as randomUUID12 } from "node:crypto";
363386
+ import { createHash as createHash17, randomUUID as randomUUID13 } from "node:crypto";
363187
363387
  function shouldRecoverSilentStop({
363188
363388
  text: text5,
363189
363389
  lastStepHadToolErrors,
@@ -363738,21 +363938,24 @@ function createPauseCallback({
363738
363938
  pauseSignaled
363739
363939
  }) {
363740
363940
  return async (data2) => {
363741
- orchestrationLogger.info("Pause detected \u2014 askUserQuestion (deferred persist)", {
363742
- resumeToken: data2.resumeToken,
363743
- questionCount: data2.questions.length
363744
- });
363941
+ orchestrationLogger.info(
363942
+ data2.kind === "mcp-approval" ? "Pause detected \u2014 MCP approval (deferred persist)" : "Pause detected \u2014 askUserQuestion (deferred persist)",
363943
+ {
363944
+ resumeToken: data2.resumeToken,
363945
+ ...data2.kind === "mcp-approval" ? { profileId: data2.mcpApproval.profileId } : { questionCount: data2.questions.length }
363946
+ }
363947
+ );
363745
363948
  pendingPauseRef.current = { data: data2 };
363746
363949
  pauseSignaled.current = true;
363747
363950
  };
363748
363951
  }
363749
- function replacePauseToolResultPlaceholder(messages4, toolUseId) {
363952
+ function replacePauseToolResultPlaceholder(messages4, toolUseId, placeholder2 = ASK_QUESTION_PLACEHOLDER) {
363750
363953
  for (const msg of messages4) {
363751
363954
  if (msg.role === "user" && Array.isArray(msg.content)) {
363752
363955
  const parts = msg.content;
363753
363956
  for (const part of parts) {
363754
363957
  if (part.type === "tool_result" && part.tool_use_id === toolUseId) {
363755
- part.content = ASK_QUESTION_PLACEHOLDER;
363958
+ part.content = placeholder2;
363756
363959
  return true;
363757
363960
  }
363758
363961
  }
@@ -363760,7 +363963,7 @@ function replacePauseToolResultPlaceholder(messages4, toolUseId) {
363760
363963
  const oaiMsg = msg;
363761
363964
  if (oaiMsg.role === "tool" && oaiMsg.tool_call_id === toolUseId) {
363762
363965
  ;
363763
- msg.content = ASK_QUESTION_PLACEHOLDER;
363966
+ msg.content = placeholder2;
363764
363967
  return true;
363765
363968
  }
363766
363969
  }
@@ -363782,6 +363985,22 @@ async function drainPendingPause({
363782
363985
  resolveStreamComplete
363783
363986
  }) {
363784
363987
  const { data: data2 } = pending;
363988
+ if (data2.kind === "mcp-approval") {
363989
+ return drainMcpApprovalPause({
363990
+ data: data2,
363991
+ toolCalls,
363992
+ emitter,
363993
+ multiplexer,
363994
+ context: context3,
363995
+ messageId,
363996
+ correlationId,
363997
+ conversationMessages,
363998
+ persona,
363999
+ config: config3,
364000
+ embedSnapshot,
364001
+ resolveStreamComplete
364002
+ });
364003
+ }
363785
364004
  const askToolCall = toolCalls.find((tc) => tc.name === "ask-user-question");
363786
364005
  const pausedToolUseId = askToolCall?.id;
363787
364006
  if (pausedToolUseId) {
@@ -363822,7 +364041,7 @@ async function drainPendingPause({
363822
364041
  if (durable && conversationId && pausedToolUseId) {
363823
364042
  await context3.sessionPathway.write("chat-interaction.0/chat-interaction.updated.0", {
363824
364043
  data: {
363825
- id: randomUUID12(),
364044
+ id: randomUUID13(),
363826
364045
  interactionId: data2.resumeToken,
363827
364046
  conversationId,
363828
364047
  assistantMessageId: messageId,
@@ -363906,6 +364125,81 @@ async function drainPendingPause({
363906
364125
  multiplexer.close();
363907
364126
  resolveStreamComplete();
363908
364127
  }
364128
+ async function drainMcpApprovalPause({
364129
+ data: data2,
364130
+ toolCalls,
364131
+ emitter,
364132
+ multiplexer,
364133
+ context: context3,
364134
+ messageId,
364135
+ correlationId,
364136
+ conversationMessages,
364137
+ persona,
364138
+ config: config3,
364139
+ embedSnapshot,
364140
+ resolveStreamComplete
364141
+ }) {
364142
+ const { buildMcpApprovalPlaceholder: buildMcpApprovalPlaceholder2 } = await Promise.resolve().then(() => (init_mcp_approval(), mcp_approval_exports));
364143
+ const pausedToolUseId = toolCalls.find((tc) => tc.id === data2.toolCallId)?.id ?? [...toolCalls].reverse().find((tc) => tc.name.startsWith("mcp_server_"))?.id;
364144
+ if (pausedToolUseId) {
364145
+ replacePauseToolResultPlaceholder(
364146
+ conversationMessages,
364147
+ pausedToolUseId,
364148
+ buildMcpApprovalPlaceholder2(data2.mcpApproval)
364149
+ );
364150
+ } else {
364151
+ orchestrationLogger.warn("MCP approval pause signaled but its tool_call was not found");
364152
+ }
364153
+ const snapshot2 = ensureToolCallIntegrity([...conversationMessages]);
364154
+ const lastSequence = emitter.getSequence();
364155
+ const now2 = /* @__PURE__ */ new Date();
364156
+ try {
364157
+ const { storePauseState: storePauseState2 } = await Promise.resolve().then(() => (init_store2(), store_exports));
364158
+ await storePauseState2({
364159
+ kind: "mcp-approval",
364160
+ mcpApproval: data2.mcpApproval,
364161
+ mcpAction: data2.action,
364162
+ resumeToken: data2.resumeToken,
364163
+ userId: context3.session?.user?.id || "",
364164
+ conversationId: context3.metadata?.conversationId,
364165
+ messageId,
364166
+ correlationId,
364167
+ lastSequence,
364168
+ conversationMessages: snapshot2,
364169
+ pausedToolUseId,
364170
+ persona,
364171
+ config: config3,
364172
+ questions: [],
364173
+ temporary: context3.metadata?.temporary === true,
364174
+ embedSnapshot,
364175
+ pausedAt: now2.toISOString(),
364176
+ expiresAt: new Date(now2.getTime() + 60 * 60 * 1e3).toISOString()
364177
+ });
364178
+ } catch (error41) {
364179
+ orchestrationLogger.warn("MCP approval pause cache unavailable; approval still works", {
364180
+ resumeToken: data2.resumeToken,
364181
+ error: error41 instanceof Error ? error41.message : String(error41)
364182
+ });
364183
+ }
364184
+ const approvalEvent = emitter.emit(
364185
+ "mcp-approval-request",
364186
+ {
364187
+ interactionId: data2.resumeToken,
364188
+ resumeToken: data2.resumeToken,
364189
+ approval: data2.mcpApproval,
364190
+ action: data2.action
364191
+ }
364192
+ );
364193
+ multiplexer.send(approvalEvent);
364194
+ orchestrationLogger.info("MCP approval requested, closing stream", {
364195
+ resumeToken: data2.resumeToken,
364196
+ profileId: data2.mcpApproval.profileId,
364197
+ pausedToolUseId,
364198
+ lastSequence
364199
+ });
364200
+ multiplexer.close();
364201
+ resolveStreamComplete();
364202
+ }
363909
364203
  function safeProviderErrorCategory(value) {
363910
364204
  return typeof value === "string" && providerErrorCategories.has(value) ? value : void 0;
363911
364205
  }
@@ -363917,7 +364211,7 @@ function beginNumberedProviderAttempt(budget, route) {
363917
364211
  const attempt = budget.begin(route);
363918
364212
  const number4 = (providerAttemptNumbers.get(budget) ?? 0) + 1;
363919
364213
  providerAttemptNumbers.set(budget, number4);
363920
- return { attempt, id: randomUUID12(), number: number4, startedAt: Date.now() };
364214
+ return { attempt, id: randomUUID13(), number: number4, startedAt: Date.now() };
363921
364215
  }
363922
364216
  function hasProviderAttemptCapacity(budget) {
363923
364217
  return (providerAttemptNumbers.get(budget) ?? 0) < budget.maxAttempts;
@@ -364016,7 +364310,7 @@ async function callLLM({
364016
364310
  }
364017
364311
  const effectiveZdr2 = openRouterPolicy.effectiveZdr;
364018
364312
  const resolveDirectPolicy = (route) => resolveProviderPolicy({ modelId: selectedModelId, route, ...providerPrivacy });
364019
- const safeCorrelationId = safeProviderIdentifier(context3.correlationId) ?? safeProviderIdentifier(context3.messageId) ?? randomUUID12();
364313
+ const safeCorrelationId = safeProviderIdentifier(context3.correlationId) ?? safeProviderIdentifier(context3.messageId) ?? randomUUID13();
364020
364314
  const safeModel = getModelById(selectedModelId) || getModelEntry(selectedModelId) ? selectedModelId : void 0;
364021
364315
  const endpointIdentity = (endpoint) => createHash17("sha256").update(endpoint).digest("hex").slice(0, 16);
364022
364316
  const circuitPolicy = (endpoint) => `zdr:${effectiveZdr2 ? "required" : "disabled"}:endpoint:${endpointIdentity(endpoint)}`;
@@ -366181,7 +366475,7 @@ Folder behavior:
366181
366475
  }
366182
366476
  };
366183
366477
  }
366184
- const planMessage = config3.enableTools ? "\u{1F527} Engaging model with tools..." : "\u{1F4AC} Responding with selected model...";
366478
+ const planMessage = config3.autoModelNotice ?? (config3.enableTools ? "\u{1F527} Engaging model with tools..." : "\u{1F4AC} Responding with selected model...");
366185
366479
  const planEvent = emitter.emit("plan", {
366186
366480
  plan: planMessage,
366187
366481
  steps: config3.maxSteps
@@ -368347,7 +368641,7 @@ Continue once without hiding or replacing your prior response.`
368347
368641
  };
368348
368642
  }
368349
368643
  function createOrchestratorRequest(messages4, context3, config3, persona, apiKey, mcpTools, anthropicApiKey, providerBaseUrl, providerAuthToken, codexAuth) {
368350
- const messageId = context3.messageId || randomUUID12();
368644
+ const messageId = context3.messageId || randomUUID13();
368351
368645
  if (!context3.sessionPathway) {
368352
368646
  throw new Error(
368353
368647
  "sessionPathway is required but not provided. Ensure createSessionPathwaysForAPI() is called in the API route and sessionPathway is passed to createOrchestratorRequest."
@@ -370245,7 +370539,7 @@ var init_skills3 = __esm({
370245
370539
  // src/adapters/cli/subagent.ts
370246
370540
  import { spawn as spawn5 } from "node:child_process";
370247
370541
  import { basename as basename8 } from "node:path";
370248
- import { randomUUID as randomUUID13 } from "node:crypto";
370542
+ import { randomUUID as randomUUID14 } from "node:crypto";
370249
370543
  function currentSubagentDepth() {
370250
370544
  const d21 = Number(process.env.USABLE_SUBAGENT_DEPTH ?? 0);
370251
370545
  return Number.isFinite(d21) && d21 > 0 ? Math.floor(d21) : 0;
@@ -370344,7 +370638,7 @@ function publicTask(task) {
370344
370638
  };
370345
370639
  }
370346
370640
  function startCliSubagentTask(input) {
370347
- const taskId = input.taskId ?? randomUUID13();
370641
+ const taskId = input.taskId ?? randomUUID14();
370348
370642
  const abortController = new AbortController();
370349
370643
  const startedAt = (/* @__PURE__ */ new Date()).toISOString();
370350
370644
  const run2 = input._run ?? runCliSubagent;
@@ -385819,7 +386113,7 @@ var init_todo_checklist = __esm({
385819
386113
  // src/cli/warp-agent.ts
385820
386114
  import { openSync, writeSync } from "node:fs";
385821
386115
  import { basename as basename9 } from "node:path";
385822
- import { randomUUID as randomUUID14 } from "node:crypto";
386116
+ import { randomUUID as randomUUID15 } from "node:crypto";
385823
386117
  function isWarpAgentHost() {
385824
386118
  return !!process.env.WARP_CLI_AGENT_PROTOCOL_VERSION && process.platform !== "win32";
385825
386119
  }
@@ -385872,7 +386166,7 @@ var init_warp_agent = __esm({
385872
386166
  "use strict";
385873
386167
  WARP_AGENT_SLUG = "usable-chat";
385874
386168
  APP_NAME = "usable-chat";
385875
- sessionId = randomUUID14();
386169
+ sessionId = randomUUID15();
385876
386170
  }
385877
386171
  });
385878
386172
 
@@ -391814,7 +392108,7 @@ init_tui_select();
391814
392108
  init_model_registry();
391815
392109
 
391816
392110
  // package.json
391817
- var version2 = "1.209.2";
392111
+ var version2 = "1.211.0";
391818
392112
 
391819
392113
  // src/adapters/cli/model-catalog.ts
391820
392114
  init_codex_auth();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@usabledev/usable-chat",
3
- "version": "1.209.2",
3
+ "version": "1.211.0",
4
4
  "description": "usable-chat — terminal harness for usable-chat (headless + TUI)",
5
5
  "type": "module",
6
6
  "bin": {