@bike4mind/cli 0.20.2 → 0.21.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.
@@ -7,7 +7,6 @@ import path from "path";
7
7
  import { v4 } from "uuid";
8
8
  import * as z$2 from "zod";
9
9
  import z, { ZodError, z as z$1 } from "zod";
10
- import { actorKindSchema, hearthEventKindSchema, hearthEventRefsSchema, hearthMachineBodySchema } from "@bike4mind/hearth";
11
10
  import dayjs from "dayjs";
12
11
  import timezone from "dayjs/plugin/timezone.js";
13
12
  import utc from "dayjs/plugin/utc.js";
@@ -52,7 +51,7 @@ const extractSnippetMeta = (content) => {
52
51
  }
53
52
  return { sections };
54
53
  };
55
- function getHeader(headers, name) {
54
+ function getHeader$1(headers, name) {
56
55
  if (!headers || typeof headers !== "object") return null;
57
56
  if (typeof headers.get === "function") {
58
57
  const val = headers.get(name);
@@ -76,10 +75,10 @@ function parseNumber(value) {
76
75
  * - `Retry-After` - seconds to wait (on 429 responses), or an HTTP-date
77
76
  */
78
77
  function parseRateLimitHeaders(headers) {
79
- const limitStr = getHeader(headers, "X-RateLimit-Limit") ?? getHeader(headers, "x-ratelimit-limit");
80
- const remainingStr = getHeader(headers, "X-RateLimit-Remaining") ?? getHeader(headers, "x-ratelimit-remaining");
81
- const resetStr = getHeader(headers, "X-RateLimit-Reset") ?? getHeader(headers, "x-ratelimit-reset");
82
- const retryAfterStr = getHeader(headers, "Retry-After") ?? getHeader(headers, "retry-after");
78
+ const limitStr = getHeader$1(headers, "X-RateLimit-Limit") ?? getHeader$1(headers, "x-ratelimit-limit");
79
+ const remainingStr = getHeader$1(headers, "X-RateLimit-Remaining") ?? getHeader$1(headers, "x-ratelimit-remaining");
80
+ const resetStr = getHeader$1(headers, "X-RateLimit-Reset") ?? getHeader$1(headers, "x-ratelimit-reset");
81
+ const retryAfterStr = getHeader$1(headers, "Retry-After") ?? getHeader$1(headers, "retry-after");
83
82
  const limit = parseNumber(limitStr);
84
83
  const remaining = parseNumber(remainingStr);
85
84
  let resetAt = null;
@@ -110,13 +109,6 @@ function parseRateLimitHeaders(headers) {
110
109
  usagePercent
111
110
  };
112
111
  }
113
- /**
114
- * Check whether the current rate limit usage is near the threshold.
115
- *
116
- * @param info - Parsed rate limit info
117
- * @param thresholdPercent - Usage percentage threshold (default: 80)
118
- * @returns true if usage is at or above the threshold
119
- */
120
112
  function isNearLimit(info, thresholdPercent = 80) {
121
113
  if (info.usagePercent === null) return false;
122
114
  return info.usagePercent >= thresholdPercent;
@@ -140,6 +132,151 @@ function buildRateLimitLogEntry(integration, endpoint, info, wasThrottled = fals
140
132
  };
141
133
  }
142
134
  //#endregion
135
+ //#region ../../b4m-core/hearth/dist/index.mjs
136
+ /**
137
+ * Zod validation for data crossing the Hearth boundary (API routes, CLI
138
+ * tools, gateways). Must stay in sync with the types in types.ts.
139
+ */
140
+ const actorKindSchema = z$1.enum([
141
+ "human",
142
+ "agent",
143
+ "gateway",
144
+ "device",
145
+ "system"
146
+ ]);
147
+ /**
148
+ * The kinds a caller may claim FOR ITSELF. 'human' and 'system' are reserved:
149
+ * the human actor is derived from the authenticated session, never from a
150
+ * request body, so no credential can post an event that renders as the account
151
+ * owner. Claiming one of these three is a downgrade in trust, not a spoof.
152
+ *
153
+ * Single source for both self-identification paths - the actor override and the
154
+ * per-session kind (see HearthActorParamSchema / HearthSessionParamSchema in
155
+ * the client's hearthWire) - so the reserved set cannot drift between them.
156
+ */
157
+ const selfClaimedActorKindSchema = z$1.enum([
158
+ "agent",
159
+ "gateway",
160
+ "device"
161
+ ]);
162
+ const hearthEventKindSchema = z$1.enum([
163
+ "message",
164
+ "edit",
165
+ "reaction",
166
+ "artifact",
167
+ "presence",
168
+ "delegation",
169
+ "quest.update",
170
+ "gate.request",
171
+ "gate.resolve",
172
+ "system"
173
+ ]);
174
+ const hearthHumanBodySchema = z$1.object({
175
+ text: z$1.string().min(1),
176
+ format: z$1.enum(["md", "text"])
177
+ });
178
+ const hearthMachineBodySchema = z$1.object({
179
+ schema: z$1.string().min(1),
180
+ payload: z$1.unknown()
181
+ });
182
+ const hearthEventRefsSchema = z$1.object({
183
+ threadRootId: z$1.string().min(1).optional(),
184
+ replyToId: z$1.string().min(1).optional(),
185
+ questId: z$1.string().min(1).optional(),
186
+ externalId: z$1.string().min(1).optional()
187
+ });
188
+ z$1.object({
189
+ channelId: z$1.string().min(1),
190
+ actorId: z$1.string().min(1),
191
+ kind: hearthEventKindSchema,
192
+ human: hearthHumanBodySchema,
193
+ machine: hearthMachineBodySchema.optional(),
194
+ refs: hearthEventRefsSchema
195
+ });
196
+ /** djb2. Deterministic across processes and restarts, which is the whole point. */
197
+ function hashOf(value) {
198
+ let hash = 5381;
199
+ for (let i = 0; i < value.length; i++) hash = (hash << 5) + hash + value.charCodeAt(i) | 0;
200
+ return Math.abs(hash);
201
+ }
202
+ /**
203
+ * Stable palette slot for an actor. Hash-derived, never array index or arrival
204
+ * order: those repaint every actor whenever the tail changes or a reload
205
+ * reorders the buffer, and a shifting color is worse than no color for telling
206
+ * two agents apart.
207
+ */
208
+ function actorColorIndex(actorId) {
209
+ if (!actorId) return 0;
210
+ return hashOf(actorId) % 4;
211
+ }
212
+ const ACTOR_COLOR_SLOTS = [
213
+ {
214
+ light: "#2a78d6",
215
+ dark: "#3987e5"
216
+ },
217
+ {
218
+ light: "#eda100",
219
+ dark: "#c98500"
220
+ },
221
+ {
222
+ light: "#e87ba4",
223
+ dark: "#d55181"
224
+ },
225
+ {
226
+ light: "#008300",
227
+ dark: "#008300"
228
+ }
229
+ ];
230
+ /** Single-character marker for text-only surfaces (the CLI /hearth listing). */
231
+ const ACTOR_KIND_MARKERS = {
232
+ human: "H",
233
+ agent: "A",
234
+ gateway: "G",
235
+ device: "D",
236
+ system: "S"
237
+ };
238
+ /**
239
+ * Read through a Partial view on purpose. The type is closed within one build,
240
+ * but the SPA bare-casts the WS payload, so a client running stale code against
241
+ * a server that has added a kind receives one that is absent from these maps -
242
+ * and a blank badge reads as "no kind stated" rather than "kind unknown", which
243
+ * is the opposite of what the mitigation needs to say.
244
+ */
245
+ function lookup(map, kind, fallback) {
246
+ if (!kind) return fallback;
247
+ return map[kind] ?? fallback;
248
+ }
249
+ function actorKindMarker(kind) {
250
+ return lookup(ACTOR_KIND_MARKERS, kind, "?");
251
+ }
252
+ z$1.object({
253
+ /** Claude Code lifecycle event name; the reason fallback for tiers 0 and 1. */
254
+ hook_event_name: z$1.string().nullish(),
255
+ session_id: z$1.string().nullish(),
256
+ slug: z$1.string().nullish(),
257
+ /** Workspace BASENAME. Never a full path - that is content. */
258
+ workspace: z$1.string().nullish(),
259
+ /**
260
+ * Which reporter wrote this. A LOOSE string, not an enum: a fourth surface
261
+ * posting an unrecognized value must land on the roster with its detail
262
+ * intact, and a strict enum would fail the whole parse and drop the row - the
263
+ * exact "a third reporter is expensive" cost this contract exists to remove.
264
+ */
265
+ surface: z$1.string().nullish(),
266
+ /** Engine driving the session, where the reporter knows it. */
267
+ source: z$1.string().nullish(),
268
+ claude_version: z$1.string().nullish(),
269
+ activity: z$1.object({
270
+ reason: z$1.string().nullish(),
271
+ tool: z$1.string().nullish(),
272
+ permission_mode: z$1.string().nullish(),
273
+ effort: z$1.string().nullish(),
274
+ duration_ms: z$1.number().nullish(),
275
+ subagent: z$1.string().nullish(),
276
+ background_tasks: z$1.number().int().min(0).nullish()
277
+ }).nullish()
278
+ });
279
+ //#endregion
143
280
  //#region ../../b4m-core/common/dist/index.mjs
144
281
  let HttpStatus = /* @__PURE__ */ function(HttpStatus) {
145
282
  HttpStatus[HttpStatus["Ok"] = 200] = "Ok";
@@ -148,9 +285,11 @@ let HttpStatus = /* @__PURE__ */ function(HttpStatus) {
148
285
  HttpStatus[HttpStatus["Unauthorized"] = 401] = "Unauthorized";
149
286
  HttpStatus[HttpStatus["Forbidden"] = 403] = "Forbidden";
150
287
  HttpStatus[HttpStatus["NotFound"] = 404] = "NotFound";
288
+ HttpStatus[HttpStatus["Conflict"] = 409] = "Conflict";
151
289
  HttpStatus[HttpStatus["UnprocessableEntity"] = 422] = "UnprocessableEntity";
152
290
  HttpStatus[HttpStatus["TooManyRequests"] = 429] = "TooManyRequests";
153
291
  HttpStatus[HttpStatus["InternalServerError"] = 500] = "InternalServerError";
292
+ HttpStatus[HttpStatus["BadGateway"] = 502] = "BadGateway";
154
293
  return HttpStatus;
155
294
  }({});
156
295
  var HTTPError = class extends Error {
@@ -312,6 +451,7 @@ const b4mLLMTools = z$1.enum([
312
451
  "chess_engine",
313
452
  "retrieve_knowledge_content",
314
453
  "count_knowledge_base",
454
+ "describe_knowledge_base",
315
455
  "delegate_to_agent",
316
456
  "optihashi_schedule",
317
457
  "optihashi_formulate",
@@ -785,6 +925,62 @@ let ImageModels = /* @__PURE__ */ function(ImageModels) {
785
925
  Object.values(ImageModels);
786
926
  z$1.enum(ImageModels);
787
927
  /**
928
+ * Image size constraints and options
929
+ */
930
+ const IMAGE_SIZE_CONSTRAINTS = {
931
+ BFL: {
932
+ minWidth: 256,
933
+ maxWidth: 1440,
934
+ minHeight: 256,
935
+ maxHeight: 1440,
936
+ stepSize: 32,
937
+ defaultSize: "1280x960",
938
+ sizes: [
939
+ "1280x960",
940
+ "1024x768",
941
+ "800x600",
942
+ "1280x720",
943
+ "1024x576",
944
+ "1440x810",
945
+ "1024x1024",
946
+ "768x768",
947
+ "512x512",
948
+ "960x1280",
949
+ "768x1024",
950
+ "600x800"
951
+ ]
952
+ },
953
+ GPT_IMAGE_1: {
954
+ sizes: [
955
+ "1024x1024",
956
+ "1024x1536",
957
+ "1536x1024"
958
+ ],
959
+ defaultSize: "1024x1024"
960
+ },
961
+ GPT_IMAGE_2: {
962
+ /** Popular preset sizes shown in the UI. The API accepts any resolution meeting the constraints. */
963
+ sizes: [
964
+ "1024x1024",
965
+ "1536x1024",
966
+ "1024x1536",
967
+ "2048x2048",
968
+ "2048x1152",
969
+ "3840x2160",
970
+ "2160x3840"
971
+ ],
972
+ defaultSize: "1024x1024",
973
+ /** Constraints for custom/flexible sizes */
974
+ constraints: {
975
+ maxEdge: 3840,
976
+ minTotalPixels: 655360,
977
+ maxTotalPixels: 8294400,
978
+ edgeMultiple: 16,
979
+ maxAspectRatio: 3
980
+ }
981
+ }
982
+ };
983
+ /**
788
984
  * Chat Models
789
985
  *
790
986
  * https://platform.openai.com/docs/models/continuous-model-upgrades
@@ -1122,6 +1318,14 @@ const SUBQUEST_STATUS_VALUES = [
1122
1318
  "deleted"
1123
1319
  ];
1124
1320
  /**
1321
+ * Status of a review gate on a sub-quest
1322
+ */
1323
+ const REVIEW_GATE_STATUS_VALUES = [
1324
+ "pending",
1325
+ "approved",
1326
+ "rejected"
1327
+ ];
1328
+ /**
1125
1329
  * Valid complexity ratings for quests.
1126
1330
  * Canonical vocabulary - matches what the planner generates and validates
1127
1331
  * (Easy < 1 hour, Medium 1-4 hours, Hard > 4 hours).
@@ -2188,6 +2392,13 @@ z$1.object({
2188
2392
  createdAt: z$1.date(),
2189
2393
  updatedAt: z$1.date()
2190
2394
  });
2395
+ let McpServerName = /* @__PURE__ */ function(McpServerName) {
2396
+ McpServerName["LinkedIn"] = "linkedin";
2397
+ McpServerName["Github"] = "github";
2398
+ McpServerName["Atlassian"] = "atlassian";
2399
+ McpServerName["Notion"] = "notion";
2400
+ return McpServerName;
2401
+ }({});
2191
2402
  /**
2192
2403
  * Check if a value is a placeholder (not configured or SST default).
2193
2404
  * Uses case-insensitive comparison and trims whitespace to prevent bypass attempts.
@@ -2233,6 +2444,42 @@ function isPlaceholderApiKey(value) {
2233
2444
  if (!normalized) return true;
2234
2445
  return PLACEHOLDER_API_KEY_REGEX.test(normalized);
2235
2446
  }
2447
+ /**
2448
+ * Lake lifecycle. Stable states (draft/active/archived/deleted) plus transitional
2449
+ * states (archiving/unarchiving/restoring/deleting/purging) that exist to drive UI and make a crashed
2450
+ * mid-operation observable. draft -> active is one-way. It happens implicitly once the lake
2451
+ * holds its first member file (see `activateIfDraft` below), and unconditionally when an
2452
+ * archived or deleted lake is restored, which is how an empty lake can end up active.
2453
+ *
2454
+ * `purging` is the one transitional state that is NOT recoverable by retrying the same action:
2455
+ * it is claimed the moment a phase-2 hard delete is ACCEPTED (#1744), before the background
2456
+ * sweep runs, so that `listDeletedDataLakes` stops offering Restore on a lake whose
2457
+ * destruction is already irreversible. Everything else that reads `status` must treat it as
2458
+ * "going away", never as a lake to act on.
2459
+ */
2460
+ const DATA_LAKE_STATUSES = [
2461
+ "draft",
2462
+ "active",
2463
+ "archiving",
2464
+ "archived",
2465
+ "unarchiving",
2466
+ "restoring",
2467
+ "deleting",
2468
+ "deleted",
2469
+ "purging"
2470
+ ];
2471
+ /**
2472
+ * Stable (non-transitional) lake statuses - a lake sitting in one of these is at rest, not
2473
+ * mid-operation. Load-bearing as the INPUT to `DATA_LAKE_TRANSITIONAL_STATUSES` below, which is
2474
+ * what drives the needs-attention list; it is not itself a filter any list path applies.
2475
+ */
2476
+ const DATA_LAKE_STABLE_STATUSES = [
2477
+ "draft",
2478
+ "active",
2479
+ "archived",
2480
+ "deleted"
2481
+ ];
2482
+ DATA_LAKE_STATUSES.filter((s) => !DATA_LAKE_STABLE_STATUSES.includes(s));
2236
2483
  z$1.object({
2237
2484
  /**
2238
2485
  * The granting lake's Mongo `_id`. ALWAYS a persisted DB lake: a hardcoded/fallback lake has no
@@ -2263,6 +2510,23 @@ z$1.object({
2263
2510
  */
2264
2511
  expiresAt: z$1.date().nullish()
2265
2512
  });
2513
+ /** One prefix-arm content tag a lake held on a file, as captured at removal for later restore. */
2514
+ const LakeMembershipRemovalContentTag = z$1.object({
2515
+ name: z$1.string(),
2516
+ strength: z$1.number()
2517
+ });
2518
+ z$1.object({
2519
+ dataLakeId: z$1.string(),
2520
+ fabFileId: z$1.string(),
2521
+ /** The principal who performed the removal - audit only, never a restore authorization input. */
2522
+ actorUserId: z$1.string(),
2523
+ /** The lake's prefix-arm tags the file carried, captured by `lakeMembershipSignals`. May be
2524
+ * empty - a meta-tag-only member and a non-creator-owned file both removed with no content
2525
+ * tags to restore, which is a complete and legitimate answer, not "nothing to record". */
2526
+ contentTags: z$1.array(LakeMembershipRemovalContentTag),
2527
+ removedAt: z$1.date(),
2528
+ expiresAt: z$1.date()
2529
+ });
2266
2530
  /**
2267
2531
  * Every `IDataLake` field, classified as audited or not. A TOTAL map keyed by `keyof IDataLake`,
2268
2532
  * exactly like `LAKE_FIELD_VISIBILITY` in redactLakeForActor.ts and for the same reason: a list of
@@ -2289,6 +2553,7 @@ const LAKE_CONFIG_FIELD_AUDIT = {
2289
2553
  organizationId: "audited",
2290
2554
  isPublic: "audited",
2291
2555
  auditQueryTextEnabled: "audited",
2556
+ lakeMemoryEnabled: "audited",
2292
2557
  status: "audited",
2293
2558
  createdByUserId: "audited",
2294
2559
  lastUpdatedByUserId: "excluded",
@@ -2300,7 +2565,10 @@ const LAKE_CONFIG_FIELD_AUDIT = {
2300
2565
  filesDeletedAt: "excluded",
2301
2566
  filesArchivedAt: "excluded",
2302
2567
  lakeMemoryExtractionAt: "excluded",
2303
- lakeMemoryCursor: "excluded"
2568
+ lakeMemoryCursor: "excluded",
2569
+ lakeMemoryPurgedAt: "excluded",
2570
+ inconsistencyReport: "excluded",
2571
+ inconsistencyComputedAt: "excluded"
2304
2572
  };
2305
2573
  [...Object.keys(LAKE_CONFIG_FIELD_AUDIT).filter((field) => LAKE_CONFIG_FIELD_AUDIT[field] === "audited")];
2306
2574
  /**
@@ -3094,6 +3362,71 @@ const usdToCreditsStochastic = (usd, rng = cryptoUniform, rate = CREDITS_PER_USD
3094
3362
  const fraction = raw - base;
3095
3363
  return base + (rng() < fraction ? 1 : 0);
3096
3364
  };
3365
+ /**
3366
+ * Output tokens the pre-flight credit hold prices when a request's max_tokens
3367
+ * ceiling exceeds it.
3368
+ *
3369
+ * max_tokens is a *ceiling*, not a prediction: adaptive reasoning models stop at
3370
+ * end_turn well short of it, so pricing the hold at the full window (128K on the
3371
+ * flagship models) reserved several thousand credits per turn regardless of answer
3372
+ * length. The excess was always refunded at settlement, but the hold IS the
3373
+ * insufficient-funds gate, so users whose balance sat between their real cost and
3374
+ * the worst case were falsely blocked.
3375
+ *
3376
+ * 16K covers the realistic long answer with headroom - the largest replies seen in
3377
+ * practice are HTML-artifact turns at roughly 10-11K output tokens (see
3378
+ * buildThinkingParams in llm-adapters/thinkingParams.ts, whose max_tokens floor was
3379
+ * sized off the same measurement).
3380
+ *
3381
+ * UNDER-RESERVATION IS ACCEPTED, NOT PREVENTED. A turn that emits more than this
3382
+ * settles as a shortfall debit at reconciliation, which is already a supported path
3383
+ * (provider-basis settlement could always exceed a hold priced on the local
3384
+ * estimate). The two sites clamp the shortfall differently: chat's
3385
+ * computeSettlementDelta (services/llm/ChatCompletionProcess.ts) floors the debit at
3386
+ * the balance snapshot taken at its OWN admission and reports the remainder as
3387
+ * writtenOffCredits; cliCompletions.ts does an unclamped $inc on success and only
3388
+ * logs an ALERT if it lands negative. Neither is a non-negativity guarantee: the
3389
+ * chat snapshot predates any sibling turn's spend (see that function's own "best
3390
+ * effort" note), and when the shortfall still fits the stale snapshot the debit
3391
+ * applies in full - writtenOffCredits 0, and no BILLING_SHORTFALL_CLAMP log - so a
3392
+ * concurrent holder can land negative there too. Either way the resulting balance
3393
+ * fails the *next* turn's own admission gate - but that bound is per turn, not per
3394
+ * holder: turns admitted concurrently are each checked against the balance at their
3395
+ * own admission and settle against a snapshot that predates their siblings' spend,
3396
+ * so a holder running turns in parallel can be shorted once per in-flight turn, not
3397
+ * once total.
3398
+ */
3399
+ const PREFLIGHT_RESERVATION_OUTPUT_TOKENS = 16384;
3400
+ /**
3401
+ * Reservation ceiling for models that spend reasoning tokens inside their output
3402
+ * budget (see reasonsWithinOutputBudget in llm-adapters/thinkingParams.ts). Those
3403
+ * tokens bill as output on top of the visible answer, so the 16K figure above -
3404
+ * which was measured on visible artifact size alone - under-reserves them badly.
3405
+ *
3406
+ * Deliberately below ADAPTIVE_THINKING_MAX_TOKENS_FLOOR (64K), which sizes the
3407
+ * request's real ceiling and therefore has to cover the worst case: exceeding it
3408
+ * truncates a reply mid-tag, an unrecoverable failure. A hold has no such duty -
3409
+ * exceeding it settles as a shortfall debit - so it is sized for the long turn
3410
+ * (roughly 3x the largest observed visible answer, leaving the rest for the trace)
3411
+ * rather than the worst one, which is what keeps the gate off affordable requests.
3412
+ */
3413
+ const PREFLIGHT_RESERVATION_REASONING_OUTPUT_TOKENS = 32768;
3414
+ /**
3415
+ * Output-token figure to price a pre-flight credit hold at, given the max_tokens
3416
+ * this request will actually send. Never raises the caller's ceiling: a request
3417
+ * that asks for less than the cap holds only what it can possibly spend.
3418
+ *
3419
+ * Reserving only, never gating: the per-member org credit cap is still priced on
3420
+ * the unshrunk ceiling at both call sites, since that check has no settlement
3421
+ * counterpart to correct an under-estimate. That is a strictly larger figure than
3422
+ * the hold, not a true upper bound on the turn - it prices one model round trip,
3423
+ * at the uncached input rate, on the primary model - so it still under-counts a
3424
+ * multi-round tool loop, a cache-write turn, or a fallback hop onto pricier pricing.
3425
+ *
3426
+ * @param reasonsWithinOutputBudget - reasonsWithinOutputBudget(modelInfo); passed as
3427
+ * a boolean because common cannot import llm-adapters.
3428
+ */
3429
+ const reservationOutputTokens = (requestedMaxTokens, reasonsWithinOutputBudget = false) => Math.min(requestedMaxTokens, reasonsWithinOutputBudget ? PREFLIGHT_RESERVATION_REASONING_OUTPUT_TOKENS : PREFLIGHT_RESERVATION_OUTPUT_TOKENS);
3097
3430
  z.enum([
3098
3431
  "openai",
3099
3432
  "test",
@@ -3101,6 +3434,58 @@ z.enum([
3101
3434
  "xai",
3102
3435
  "gemini"
3103
3436
  ]);
3437
+ z$1.enum([
3438
+ "inject",
3439
+ "auto-fire",
3440
+ "hidden"
3441
+ ]);
3442
+ /**
3443
+ * Modes acceptable at AUTHORING time. 'hidden' is intentionally excluded until
3444
+ * the host has true hidden-send support - accepting it would persist a value
3445
+ * that silently behaves as 'auto-fire' (a surprising downgrade). It stays in
3446
+ * ExecutionModeSchema/the stored enum for forward-compat.
3447
+ */
3448
+ const AuthorableExecutionModeSchema = z$1.enum(["inject", "auto-fire"]);
3449
+ /**
3450
+ * Tools a prompt may require - constrained to the host's closed tool set, MINUS
3451
+ * integration-gated tools that act on the caller's own credentials/account. A
3452
+ * shared system prompt must not be able to inject e.g. blog-publishing into a
3453
+ * non-author's session via requiredTools. (Per-user entitlement of the remaining
3454
+ * tools is still the chat pipeline's responsibility - see follow-up note in the
3455
+ * briefcase blueprint; this allowlist is the storage-layer floor.)
3456
+ */
3457
+ const BRIEFCASE_DISALLOWED_TOOLS = [
3458
+ "blog_publish",
3459
+ "blog_edit",
3460
+ "blog_draft"
3461
+ ];
3462
+ const BriefcaseRequiredToolsSchema = z$1.array(b4mLLMTools.refine((t) => !BRIEFCASE_DISALLOWED_TOOLS.includes(t), "This tool is not permitted in a briefcase prompt")).max(16);
3463
+ z$1.string().regex(/^[a-f0-9]{24}$/i, "Invalid prompt id");
3464
+ /** Shared cap for every free-text prompt body a caller can supply. Exported so the
3465
+ * caller-prompt caps on the chat request, the invoke params and the CLI tool import it
3466
+ * rather than each restating the literal. */
3467
+ const PROMPT_TEXT_MAX = 16e3;
3468
+ const TAGS_MAX = 20;
3469
+ z$1.object({
3470
+ type: z$1.string().min(1).max(100),
3471
+ name: z$1.string().min(1).max(200),
3472
+ description: z$1.string().max(500).optional(),
3473
+ promptText: z$1.string().min(1).max(PROMPT_TEXT_MAX),
3474
+ tags: z$1.array(z$1.string().min(1).max(50)).max(TAGS_MAX).optional(),
3475
+ executionMode: AuthorableExecutionModeSchema.optional(),
3476
+ requiredTools: BriefcaseRequiredToolsSchema.optional()
3477
+ }).partial();
3478
+ /**
3479
+ * One catalog sub-query. Exactly one selector is used, in precedence order:
3480
+ * `personal` (resolved to the caller server-side) > `tags` > `type`.
3481
+ */
3482
+ const PromptBatchQuerySchema = z$1.object({
3483
+ key: z$1.string().min(1).max(100),
3484
+ tags: z$1.array(z$1.string().min(1).max(50)).max(TAGS_MAX).optional(),
3485
+ type: z$1.string().max(100).optional(),
3486
+ personal: z$1.boolean().optional()
3487
+ });
3488
+ z$1.object({ queries: z$1.array(PromptBatchQuerySchema).min(1).max(32).refine((qs) => new Set(qs.map((q) => q.key)).size === qs.length, { message: "Batch query keys must be unique" }) });
3104
3489
  z$1.object({
3105
3490
  sessionId: z$1.string().nullish(),
3106
3491
  message: z$1.string(),
@@ -3116,7 +3501,7 @@ z$1.object({
3116
3501
  wait: z$1.boolean().prefault(false),
3117
3502
  enableTools: z$1.boolean().prefault(false),
3118
3503
  toolMode: z$1.enum(["fast", "smart"]).optional(),
3119
- tools: z$1.array(z$1.string()).optional(),
3504
+ tools: z$1.array(z$1.string()).optional().describe("Explicit tool ids to offer the model. A non-empty array enables tools on its own; no companion `toolMode` or `enableTools` is required. Merged with the auto-selected set under `toolMode: \"smart\"`, and ignored under `toolMode: \"fast\"`. This list ADDS to what is offered rather than restricting it - the server still offers tools of its own (for example knowledge retrieval when the session has reachable documents). Unrecognized ids are dropped rather than rejecting the request; the response reports the surviving set as `tools.effectiveTools` and the ids that are not tools in this deployment as `tools.unrecognizedTools`, since no endpoint enumerates the valid ids. `unrecognizedTools` is reported under every `toolMode` - it describes the ids, not what the mode did with them - so under `toolMode: \"fast\"` an id can be absent from `effectiveTools` (the mode discarded it) without being unrecognized. Both reported lists are deduplicated, and `unrecognizedTools` names at most the first 10 distinct ids."),
3120
3505
  enableQuestMaster: z$1.boolean().optional(),
3121
3506
  enableMementos: z$1.boolean().optional(),
3122
3507
  enableAgents: z$1.boolean().optional(),
@@ -3125,8 +3510,10 @@ z$1.object({
3125
3510
  "grounded",
3126
3511
  "surface"
3127
3512
  ]).optional(),
3513
+ skip_auto_offers: z$1.boolean().optional().describe("Suppress tools the server would otherwise attach on its own for this session (the knowledge-base search offer, in-app view navigation, blog drafting/editing/publishing, and skill invocation). Tools you request explicitly are unaffected. One system-prompt block goes with them: withholding in-app view navigation also drops the view-registry block that exists only to describe it. No other prompt content changes. This does not switch off retrieval: a session with forced knowledge retrieval still retrieves, and documents already attached to the session are still placed in the prompt directly. Any promptMode suppresses these too, so false has no effect alongside one."),
3128
3514
  includePromptDetails: z$1.boolean().optional(),
3129
- includeSystemPrompt: z$1.boolean().optional()
3515
+ includeSystemPrompt: z$1.boolean().optional(),
3516
+ systemPrompt: z$1.string().max(PROMPT_TEXT_MAX).optional().describe("System-prompt text for this request only, never persisted. Rendered as a defended block appended after every other system-prompt source, with prose instructing the model to defer to organization, session and data-lake guidance. Over the cap is a 422, never truncated.")
3130
3517
  });
3131
3518
  z$1.object({
3132
3519
  id: z$1.string(),
@@ -3135,6 +3522,12 @@ z$1.object({
3135
3522
  timestamp: z$1.string(),
3136
3523
  model: z$1.string(),
3137
3524
  message: z$1.string().optional(),
3525
+ tools: z$1.object({
3526
+ toolMode: z$1.enum(["fast", "smart"]).optional(),
3527
+ autoSelectedTools: z$1.array(z$1.string()).optional(),
3528
+ effectiveTools: z$1.array(z$1.string()),
3529
+ unrecognizedTools: z$1.array(z$1.string()).optional()
3530
+ }).optional(),
3138
3531
  tracking_info: z$1.object({
3139
3532
  quest_id: z$1.string(),
3140
3533
  check_status_url: z$1.string(),
@@ -3357,13 +3750,93 @@ const AGENT_EXECUTION_STATUSES = [
3357
3750
  "failed",
3358
3751
  "aborted"
3359
3752
  ];
3753
+ /**
3754
+ * Operator allow-list for the client-authored Mongo filter carried on a `subscribe_query` frame.
3755
+ *
3756
+ * The WS data-subscribe handler forwards that filter to `Model.find` and persists it on the
3757
+ * QuerySubscription the separate subscriber-fanout service replays, so an unconstrained
3758
+ * `$`-prefixed key lets any authenticated socket ask the database to run server-side JavaScript
3759
+ * (`$where`, `$expr` + `$function`) or an unbounded `$regex` - either of which pins a pooled
3760
+ * connection for as long as it runs.
3761
+ *
3762
+ * Live subscriptions only ever need equality, membership and range matching, so anything outside
3763
+ * this list is REFUSED rather than stripped: silently dropping an operator would quietly widen the
3764
+ * document set the caller ends up subscribed to.
3765
+ *
3766
+ * The scan is deliberately structural rather than a model of Mongo's grammar - it flags any
3767
+ * `$`-prefixed key anywhere in the tree that isn't allow-listed. It can therefore accept an
3768
+ * allow-listed operator in a position Mongo would treat as a literal sub-document field name, but
3769
+ * that is inert; what matters is that no disallowed operator can reach the driver.
3770
+ */
3771
+ /** Combinators whose operands are themselves filters. */
3772
+ const SUBSCRIPTION_FILTER_LOGICAL_OPERATORS = [
3773
+ "$and",
3774
+ "$or",
3775
+ "$nor",
3776
+ "$not"
3777
+ ];
3778
+ /** Value-level operators a subscription filter may use. */
3779
+ const SUBSCRIPTION_FILTER_VALUE_OPERATORS = [
3780
+ "$eq",
3781
+ "$ne",
3782
+ "$gt",
3783
+ "$gte",
3784
+ "$lt",
3785
+ "$lte",
3786
+ "$in",
3787
+ "$nin",
3788
+ "$exists",
3789
+ "$type",
3790
+ "$size",
3791
+ "$all",
3792
+ "$elemMatch"
3793
+ ];
3794
+ const ALLOWED_OPERATORS = /* @__PURE__ */ new Set([...SUBSCRIPTION_FILTER_LOGICAL_OPERATORS, ...SUBSCRIPTION_FILTER_VALUE_OPERATORS]);
3795
+ /**
3796
+ * Returns a dotted path for every part of `filter` a subscription may not send: a disallowed
3797
+ * `$`-prefixed key, a `RegExp` operand, or nesting past {@link SUBSCRIPTION_FILTER_MAX_DEPTH}.
3798
+ * An empty array means the filter is safe to forward to the database.
3799
+ */
3800
+ function findDisallowedSubscriptionFilterKeys(filter) {
3801
+ const violations = [];
3802
+ const walk = (node, path, depth) => {
3803
+ if (depth > 12) {
3804
+ violations.push(`${path} (nested deeper than 12)`);
3805
+ return;
3806
+ }
3807
+ if (node instanceof RegExp) {
3808
+ violations.push(`${path} (regular expression)`);
3809
+ return;
3810
+ }
3811
+ if (Array.isArray(node)) {
3812
+ node.forEach((entry, i) => walk(entry, `${path}[${i}]`, depth + 1));
3813
+ return;
3814
+ }
3815
+ if (node === null || typeof node !== "object") return;
3816
+ for (const [key, value] of Object.entries(node)) {
3817
+ const childPath = path ? `${path}.${key}` : key;
3818
+ if (key.startsWith("$") && !ALLOWED_OPERATORS.has(key)) {
3819
+ violations.push(childPath);
3820
+ continue;
3821
+ }
3822
+ walk(value, childPath, depth + 1);
3823
+ }
3824
+ };
3825
+ walk(filter, "", 0);
3826
+ return violations;
3827
+ }
3360
3828
  const DataSubscribeRequestAction = z$1.object({
3361
3829
  action: z$1.literal("subscribe_query"),
3362
3830
  accessToken: z$1.string().optional(),
3363
3831
  subscriptionId: z$1.string(),
3364
3832
  collectionName: z$1.string(),
3365
- query: z$1.looseObject({}),
3366
- fields: z$1.looseObject({}),
3833
+ query: z$1.looseObject({}).superRefine((filter, ctx) => {
3834
+ for (const key of findDisallowedSubscriptionFilterKeys(filter)) ctx.addIssue({
3835
+ code: "custom",
3836
+ message: `Disallowed subscription filter: ${key}`
3837
+ });
3838
+ }),
3839
+ fields: z$1.record(z$1.string(), z$1.union([z$1.boolean(), z$1.number()])),
3367
3840
  fetchInitialData: z$1.boolean().prefault(true).optional(),
3368
3841
  clientId: z$1.string().optional()
3369
3842
  });
@@ -3450,6 +3923,17 @@ const DataSubscriptionUpdateAction = z$1.object({
3450
3923
  id: z$1.string()
3451
3924
  })
3452
3925
  });
3926
+ /**
3927
+ * Server -> Client: a `subscribe_query` frame was refused (the operator allow-list, `fields`
3928
+ * validation) or its initial fetch was aborted (`maxTimeMS`). Neither failure throws an
3929
+ * UnauthorizedError/JsonWebTokenError, so withWebSocketContext's status code never reaches the
3930
+ * client as a frame - this is the only signal the caller gets that the subscription never took.
3931
+ */
3932
+ const DataSubscribeErrorAction = z$1.object({
3933
+ action: z$1.literal("data_subscribe_error"),
3934
+ subscriptionId: z$1.string(),
3935
+ error: z$1.string()
3936
+ });
3453
3937
  const LLMStatusUpdateAction = z$1.object({
3454
3938
  action: z$1.literal("llm_status_update"),
3455
3939
  status: z$1.string().nullable(),
@@ -4682,6 +5166,7 @@ const OptiHashiRunUpdatedAction = z$1.object({
4682
5166
  });
4683
5167
  z$1.discriminatedUnion("action", [
4684
5168
  DataSubscriptionUpdateAction,
5169
+ DataSubscribeErrorAction,
4685
5170
  InboxRefetchAction,
4686
5171
  LLMStatusUpdateAction,
4687
5172
  InvitesRefetchAction,
@@ -4739,6 +5224,126 @@ z$1.discriminatedUnion("action", [
4739
5224
  PermissionRequestAction,
4740
5225
  ReconnectResultAction
4741
5226
  ]);
5227
+ z$1.object({
5228
+ session_id: z$1.string().min(1),
5229
+ message: z$1.string().min(1),
5230
+ /** Falls back to the deployment's default chat model when omitted. */
5231
+ model: z$1.string().optional(),
5232
+ /**
5233
+ * Run as a specific persisted agent. Omit to let the executor pick the profile for
5234
+ * the session: a session on a dedicated surface gets that surface's own profile,
5235
+ * otherwise a synthetic one built from admin orchestration defaults. Omitting this
5236
+ * is what reproduces the product UI's Agent Mode toggle.
5237
+ */
5238
+ agent_id: z$1.string().optional(),
5239
+ /**
5240
+ * Bill this run to an organization's credit pool. The caller must belong to it; a
5241
+ * non-member gets 404. Omit to bill the caller personally.
5242
+ */
5243
+ organization_id: z$1.string().optional(),
5244
+ /**
5245
+ * Tool-id allowlist for the run. Omit to use the resolved profile's own list.
5246
+ *
5247
+ * Doubles as pre-approval: REST runs have no interactive client to answer a
5248
+ * permission prompt, so tools named here are treated as approved. A run that calls
5249
+ * an approval-gated tool NOT named here fails with that tool named in `error`.
5250
+ */
5251
+ tools: z$1.array(z$1.string()).optional(),
5252
+ /**
5253
+ * Hard ceiling on ReAct iterations. Each one is a full LLM round-trip, so the cap is
5254
+ * bounded at 100 regardless of what the profile would allow.
5255
+ */
5256
+ max_iterations: z$1.number().int().positive().max(100).optional(),
5257
+ temperature: z$1.number().min(0).max(2).optional(),
5258
+ max_tokens: z$1.number().int().positive().optional(),
5259
+ thinking: z$1.object({
5260
+ enabled: z$1.boolean(),
5261
+ /** Bounded at 32000: Anthropic rejects rather than clamps an oversized budget. */
5262
+ budget_tokens: z$1.number().int().positive().max(32e3).optional()
5263
+ }).optional(),
5264
+ /** Per-message file attachments (fabFile ids), materialized into the first iteration. */
5265
+ file_ids: z$1.array(z$1.string()).optional(),
5266
+ /** Workbench-level file ids for the session, forwarded as a dispatch-time snapshot. */
5267
+ session_file_ids: z$1.array(z$1.string()).optional(),
5268
+ enable_mementos: z$1.boolean().optional(),
5269
+ enable_lattice: z$1.boolean().optional(),
5270
+ /**
5271
+ * Opt out of the artifact-emission prompt and artifact persistence for this run.
5272
+ *
5273
+ * ANDed with the deployment's admin `EnableArtifacts` setting, so this can only ever
5274
+ * withhold artifacts, never force them on. Omitting it means "no preference" and
5275
+ * leaves the admin setting as the only gate; only an explicit `false` opts out.
5276
+ * Inherited by any subagent this run dispatches, so a delegating agent cannot route
5277
+ * around the opt-out.
5278
+ *
5279
+ * Worth setting on a REST run: the emission prompt costs roughly 2.8k tokens per
5280
+ * iteration, and nothing on this transport renders an artifact back to a human.
5281
+ */
5282
+ enable_artifacts: z$1.boolean().optional()
5283
+ });
5284
+ z$1.object({
5285
+ id: z$1.string(),
5286
+ status: z$1.literal("pending"),
5287
+ session_id: z$1.string(),
5288
+ model: z$1.string(),
5289
+ timestamp: z$1.string(),
5290
+ tracking_info: z$1.object({
5291
+ execution_id: z$1.string(),
5292
+ /**
5293
+ * The chat-history Quest holding the prompt, which gains the reply when the run
5294
+ * completes. Absent when that best-effort write failed; the run still proceeds.
5295
+ */
5296
+ quest_id: z$1.string().optional(),
5297
+ poll_url: z$1.string()
5298
+ })
5299
+ });
5300
+ /** One step of a published reasoning trace - a public projection of `IAgentStep`. */
5301
+ const AgentExecutionStepSchema = z$1.object({
5302
+ type: z$1.enum([
5303
+ "thought",
5304
+ "action",
5305
+ "observation",
5306
+ "final_answer"
5307
+ ]),
5308
+ content: z$1.string(),
5309
+ /** 0-indexed iteration. Absent on traces checkpointed before the field existed. */
5310
+ iteration: z$1.number().int().nonnegative().optional(),
5311
+ /** Set on `action` steps: the tool the agent invoked. */
5312
+ tool_name: z$1.string().optional()
5313
+ });
5314
+ z$1.object({
5315
+ id: z$1.string(),
5316
+ status: z$1.enum([
5317
+ "pending",
5318
+ "running",
5319
+ "continuing",
5320
+ "awaiting_permission",
5321
+ "awaiting_subagent",
5322
+ "awaiting_dag_children",
5323
+ "paused",
5324
+ "completed",
5325
+ "failed",
5326
+ "aborted"
5327
+ ]),
5328
+ session_id: z$1.string().nullable(),
5329
+ answer: z$1.string().nullable(),
5330
+ /**
5331
+ * Why the run ended without an answer. Set only on `failed`; null otherwise.
5332
+ * Without this a caller polling a terminal run sees `failed` + a null answer and
5333
+ * cannot tell an approval-gated tool from a model error from a timeout.
5334
+ *
5335
+ * Approval-gate failures name the offending tool, since that is what the caller acts
5336
+ * on. Everything else is reduced to a coarse category (billing, rate limit, timeout,
5337
+ * auth) or a generic message: the stored reason is a raw internal exception, and
5338
+ * those carry infrastructure identifiers. Full detail stays in the server logs.
5339
+ */
5340
+ error: z$1.string().nullable(),
5341
+ steps: z$1.array(AgentExecutionStepSchema),
5342
+ total_iterations: z$1.number().nullable(),
5343
+ created_at: z$1.string(),
5344
+ updated_at: z$1.string()
5345
+ });
5346
+ z$1.object({ id: z$1.string().min(1) });
4742
5347
  /**
4743
5348
  * Tool schema matching ICompletionOptionTools.toolSchema. The Zod surface only
4744
5349
  * covers wire-format fields (toolFn is server-side). Replaces the historical
@@ -5813,43 +6418,129 @@ z$2.object({
5813
6418
  */
5814
6419
  const OVERSIZED_PASSAGE_TOKEN_THRESHOLD = 1500;
5815
6420
  /**
5816
- * `FabFile.notes` marker written when the data-lake convergence kill switch abandons a vectorize
5817
- * (#1676). The file keeps its chunks but has no vectors, so it is unsearchable until re-indexed, and
5818
- * it does NOT auto-resume.
6421
+ * Why a file's chunk/vector pipeline is STALLED, stored in `FabFile.chunkStallReason`.
5819
6422
  *
5820
- * Lives here rather than beside its writer (apps/client fabFileVectorize) because it is a
5821
- * cross-layer contract: the queue handler writes it and the lake-health evaluator
5822
- * (constants/lakeHealth.ts) reads it to tell a permanently-stalled file from one still in flight.
5823
- * b4m-core cannot import from apps/client, so a copy there would have to drift silently.
6423
+ * - `vectorizePaused`: the data-lake convergence kill switch abandoned a vectorize (#1676). The
6424
+ * file keeps its chunks but has no vectors, so it is unsearchable until re-indexed.
6425
+ * - `rechunkPaused`: the OTHER half of the same switch dropped a re-chunk before it ran
6426
+ * (#1676/#1681). The damage is worse - the producer resets a wave's chunk state BEFORE the
6427
+ * messages are handled, so a file halted here has NO chunks at all.
6428
+ * - `unchunkedPaused`: the same chunk half, on a file that never had passages to lose. The rescue
6429
+ * sweep selects on `chunkCount: 0` and enqueues without resetting anything, so a file it routes
6430
+ * into the halt branch arrives already empty. The halted STATE is identical to `rechunkPaused`
6431
+ * and every reader keys on both (CHUNKLESS_STALL_REASONS) - the split exists so the owner is not
6432
+ * told that passages were removed which never existed.
6433
+ *
6434
+ * None auto-resumes; each needs a reprocess or a lifted switch.
6435
+ *
6436
+ * Without a marker the state is misread by every surface at once, which is the failure the field
6437
+ * exists to prevent: `chunkCount: 0` with `error: null` reads as an image or a pending upload, so
6438
+ * health drops it from the denominator, convergence grades it `conformant` (its stale stamp still
6439
+ * matches), and search does not withhold it because it is not "in flight". The file's passages are
6440
+ * simply gone and nothing REPORTS it. The rescue sweep is the one exception and deliberately so: an
6441
+ * unmarked file matches its filter and gets re-chunked, which is repair rather than reporting. That
6442
+ * is why the sweep excludes a stalled file only while the switch is ON - see
6443
+ * buildFabFileChunkScanFilter.
6444
+ *
6445
+ * A dedicated field rather than prose in `FabFile.notes` (#2016): `notes` is the USER's note, and
6446
+ * while the markers lived there every writer of the field clobbered the others - a "Rebuild
6447
+ * passages" wave silently deleted whatever the owner had typed.
6448
+ *
6449
+ * Lives here rather than beside its writers (apps/client's chunk and vectorize handlers) because it
6450
+ * is a cross-layer contract: the queue handlers write it and b4m-core's evaluators
6451
+ * (constants/lakeHealth.ts, constants/lakeConvergence.ts, dataLakeService/retrievalUnavailable.ts)
6452
+ * read it to tell a permanently-stalled file from one still in flight. b4m-core cannot import from
6453
+ * apps/client, so a copy there would have to drift silently.
6454
+ */
6455
+ const CHUNK_STALL_REASONS = [
6456
+ "vectorizePaused",
6457
+ "rechunkPaused",
6458
+ "unchunkedPaused"
6459
+ ];
6460
+ /**
6461
+ * Whether a file is stalled by the convergence kill switch, by any arm. THE predicate every
6462
+ * reader uses, so adding a stall reason reaches health, convergence and retrieval without separate
6463
+ * comparisons drifting apart. Also the in-memory mirror of a Mongo
6464
+ * `chunkStallReason: { $in: [...CHUNK_STALL_REASONS] }`.
6465
+ */
6466
+ function isChunkStalled(reason) {
6467
+ return CHUNK_STALL_REASONS.includes(reason);
6468
+ }
6469
+ /**
6470
+ * Which reasons leave the file with NO passages, as opposed to passages with no vectors. A `Record`
6471
+ * over every reason rather than a hand-written subset array: a new stall reason then cannot compile
6472
+ * until it is classified, where a member missing from a literal array would just make a health count
6473
+ * silently wrong.
6474
+ */
6475
+ const STALL_LEAVES_NO_PASSAGES = {
6476
+ vectorizePaused: false,
6477
+ rechunkPaused: true,
6478
+ unchunkedPaused: true
6479
+ };
6480
+ CHUNK_STALL_REASONS.filter((reason) => STALL_LEAVES_NO_PASSAGES[reason]);
6481
+ /**
6482
+ * Owner-facing prose for a stall reason, and the ONLY place it is worded.
6483
+ *
6484
+ * `vectorizePaused` and `rechunkPaused` are the exact strings those markers used while they lived in
6485
+ * `notes`, which is also what the #2016 migration matches on to derive the field for existing rows -
6486
+ * do not reword either without updating it. `unchunkedPaused` postdates that migration and was never
6487
+ * written to `notes`, so its wording is free to change: nothing matches on it.
5824
6488
  */
5825
- const CONVERGENCE_PAUSED_NOTE = "Indexing paused by the data-lake convergence kill switch - reprocess to complete.";
6489
+ const CHUNK_STALL_NOTICES = {
6490
+ vectorizePaused: "Indexing paused by the data-lake convergence kill switch - reprocess to complete.",
6491
+ rechunkPaused: "Re-chunking paused by the data-lake convergence kill switch - its passages were removed and are rebuilt when convergence resumes.",
6492
+ unchunkedPaused: "Chunking paused by the data-lake convergence kill switch - this file has no passages yet and they are built when convergence resumes."
6493
+ };
6494
+ CHUNK_STALL_NOTICES.vectorizePaused;
6495
+ CHUNK_STALL_NOTICES.rechunkPaused;
5826
6496
  /**
5827
- * `FabFile.notes` marker for the OTHER half of the same kill switch: a re-chunk dropped before it
5828
- * ran (#1676/#1681). Distinct from `CONVERGENCE_PAUSED_NOTE` because the damage is worse and the
5829
- * wording has to say so - the producer resets a wave's chunk state BEFORE the messages are handled,
5830
- * so a file halted here has NO chunks at all rather than chunks without vectors.
6497
+ * TRANSITIONAL, and the ONE stall predicate every RETRIEVAL path must use until #2016's migration
6498
+ * has run in every environment. Reads the new field, then falls back to the legacy prose that the
6499
+ * pre-migration rows still carry in `notes`.
6500
+ *
6501
+ * It exists for the FORWARD window only: `migratorInvocation` is a `dependsOn` of the web stack
6502
+ * only (infra/web.ts); the queue stack has none, so the executor can serve forced retrieval and
6503
+ * `knowledge_base_search` while rows still carry the marker in `notes` and no `chunkStallReason`. A
6504
+ * row stalled by the chunk arm then reads as a plain unindexed file: `isRetrievalExcluded` drops it
6505
+ * upstream of the withhold on a vectorizedOnly lake, and `partitionByIndexAvailability` calls it
6506
+ * servable everywhere else. The turn answers around a passage-less file and reports FULL coverage -
6507
+ * the silent degradation this whole path exists to prevent.
5831
6508
  *
5832
- * Without a marker this state is invisible to every surface at once, which is the failure it exists
5833
- * to prevent: `chunkCount: 0` with `error: null` reads as an image or a pending upload, so health
5834
- * drops it from the denominator, convergence grades it `conformant` (its stale stamp still matches),
5835
- * search does not withhold it because it is not "in flight", and the rescue sweep's own filter
5836
- * passes over it. The file's passages are simply gone and nothing reports it.
6509
+ * A code ROLLBACK is the mirror image and this arm CANNOT cover it: the rows are already migrated
6510
+ * (`chunkStallReason` set, `notes` unset) and the code restored is pre-#2016, which does not contain
6511
+ * this function. Nothing reverts the data on its own either - `migratorInvocation` only ever runs
6512
+ * `up` and `migrate down` is a manual CLI step - so `migrate down` is a REQUIRED step of any
6513
+ * rollback past #2016, not an optional tidy-up. What this arm does buy is that `down()` is safe to
6514
+ * run FIRST: whichever stack is still new keeps honoring the prose it restores, so a staggered
6515
+ * rollback has no window where a restored marker is invisible. `down()` is a PARTIAL restore
6516
+ * though - it skips a row whose owner typed a note after `up()`, and that row grades as unstalled
6517
+ * on both stacks once the field is dropped. See its own comment.
5837
6518
  *
5838
- * Same cross-layer reason as the constant above for living here: the queue handler writes it and
5839
- * b4m-core's evaluators read it, and b4m-core cannot import from apps/client.
6519
+ * Deliberately NOT used by the grading/health/UI readers: they are gated behind the web stack, and
6520
+ * a legacy row there renders the notice line AND the identical text as the owner's note.
6521
+ *
6522
+ * Mirrored in Mongo by `buildFabFileSearchQuery`'s `vectorizedOnly` exemption. Delete the legacy arm
6523
+ * from both together, one release after the migration has landed everywhere.
6524
+ *
6525
+ * Pinned to the two reasons the migration backfilled rather than every notice: `unchunkedPaused`
6526
+ * postdates it, so no row carries its prose, and including it would read an owner who happens to type
6527
+ * that sentence into `notes` as stalled.
5840
6528
  */
5841
- const CONVERGENCE_PAUSED_CHUNK_NOTE = "Re-chunking paused by the data-lake convergence kill switch - its passages were removed and are rebuilt when convergence resumes.";
6529
+ const LEGACY_CHUNK_STALL_NOTES = [CHUNK_STALL_NOTICES.vectorizePaused, CHUNK_STALL_NOTICES.rechunkPaused];
6530
+ function isChunkStalledFile(file) {
6531
+ return isChunkStalled(file.chunkStallReason) || LEGACY_CHUNK_STALL_NOTES.includes(file.notes ?? "");
6532
+ }
5842
6533
  /**
5843
6534
  * `FabFile.chunkRebuildRequestedAt`: stamped by `resetChunkStateByIds` in the SAME write that
5844
6535
  * clears a file's chunk rollups, so "this file's passages are being rebuilt" can never be lost the
5845
6536
  * way the pair of steps that creates the state can be. The reset and the queue send are two
5846
6537
  * operations - kill the producer between them, or lose the consumer's marker write, and the file
5847
- * sits at `chunkCount: 0` with `error: null` and `notes: ''`, a shape indistinguishable from an
6538
+ * sits at `chunkCount: 0` with `error: null` and no stall reason, a shape indistinguishable from an
5848
6539
  * image or a still-uploading row. It then drops out of lake health's denominator, out of the
5849
6540
  * convergence plan and out of the retrieval withhold at the same moment: every rollup says its
5850
6541
  * passages are gone, and nothing reports it.
5851
6542
  *
5852
- * Deliberately NOT `CONVERGENCE_PAUSED_CHUNK_NOTE` pre-written by the producer, which is the obvious
6543
+ * Deliberately NOT the `rechunkPaused` stall reason pre-written by the producer, which is the obvious
5853
6544
  * fix and the wrong one: that marker means "halted, needs an administrator", so a file awaiting an
5854
6545
  * ORDINARY rebuild would read to every reader as permanently paused for the whole rebuild - search
5855
6546
  * would tell readers it does not return on its own, health would hard-fail P3, and "Rebuild
@@ -5861,9 +6552,9 @@ const CONVERGENCE_PAUSED_CHUNK_NOTE = "Re-chunking paused by the data-lake conve
5861
6552
  * upgrade therefore degrades to mislabelled-but-visible rather than invisible, which is the trade
5862
6553
  * this field exists to make - invisibility is the real harm, labelling is secondary.
5863
6554
  *
5864
- * A dedicated field rather than a third `notes` string on purpose: `notes` already carries two
5865
- * unrelated facts (the user's own note / NO_EXTRACTABLE_TEXT, and the kill-switch markers), so every
5866
- * writer of it clobbers the others.
6555
+ * A dedicated field on purpose, and the precedent #2016 followed for the other two machine-written
6556
+ * facts: while they all shared `notes` every writer of that field clobbered the others, including
6557
+ * the user's own note.
5867
6558
  *
5868
6559
  * Cleared by `commitFabFileChunks` (the rebuild landed) and by the chunk handler's pause write (the
5869
6560
  * rebuild was halted instead). A file carrying `error` is settled regardless - see
@@ -5872,20 +6563,6 @@ const CONVERGENCE_PAUSED_CHUNK_NOTE = "Re-chunking paused by the data-lake conve
5872
6563
  function isChunkRebuildPending(requestedAt) {
5873
6564
  return requestedAt !== null && requestedAt !== void 0 && requestedAt !== "";
5874
6565
  }
5875
- /**
5876
- * Whether a file's `notes` marks it as stalled by the convergence kill switch, by either arm.
5877
- * THE predicate every reader uses, so adding a third stall marker reaches health, convergence and
5878
- * retrieval without three separate string comparisons drifting apart.
5879
- */
5880
- function isConvergencePausedNote(notes) {
5881
- return CONVERGENCE_PAUSED_NOTES.includes(notes);
5882
- }
5883
- /**
5884
- * Datastore mirror of `isConvergencePausedNote`, for a Mongo `notes: { $in: [...] }`. Exported so a
5885
- * query and the in-memory predicate cannot drift: adding a third stall marker to this array reaches
5886
- * both. Declared after the two constants it names so the function above can close over it.
5887
- */
5888
- const CONVERGENCE_PAUSED_NOTES = [CONVERGENCE_PAUSED_NOTE, CONVERGENCE_PAUSED_CHUNK_NOTE];
5889
6566
  /** Ceiling so "adjustable" cannot mean "unbounded" in either direction. */
5890
6567
  const LAKE_ACCESS_AUDIT_RETENTION_MAX_DAYS = 2555;
5891
6568
  /**
@@ -5917,14 +6594,13 @@ const LAKE_CONFIG_AUDIT_RETENTION_DEFAULT_DAYS = 1095;
5917
6594
  /** Ceiling so "adjustable" cannot mean "unbounded" in either direction. */
5918
6595
  const LAKE_CONFIG_AUDIT_RETENTION_MAX_DAYS = 3650;
5919
6596
  /**
5920
- * Forced-retrieval budget defaults, shared between the admin-settings schema in this package and
5921
- * `ChatCompletionFeatures.ts` (which cannot import from `common`'s settings schema without a
5922
- * dependency cycle, so the constant lives here instead).
6597
+ * Forced-retrieval budget and relevance defaults, shared between the admin-settings schema in this
6598
+ * package and `ChatCompletionFeatures.ts` (which cannot import from `common`'s settings schema
6599
+ * without a dependency cycle, so the constants live here instead).
5923
6600
  *
5924
- * Only `FORCED_RETRIEVAL_CHAR_BUDGET_DEFAULT` is a lever (see the `forcedRetrievalCharBudget`
5925
- * setting) - the char budget is the measured binding constraint on how much of a corpus reaches
5926
- * the model on every Data-Lake-mode turn. The relevance floor is exported alongside it so the two
5927
- * stay next to each other, not because it is tunable today.
6601
+ * All three are levers. The char budget is the measured binding constraint on how much of a corpus
6602
+ * reaches the model on every Data-Lake-mode turn; the two floors decide which passages are eligible
6603
+ * to spend it (see `forcedRetrievalRelativeFloorPct` and `forcedRetrievalMinSimilarityPct`).
5928
6604
  */
5929
6605
  /** Total characters of retrieved chunk text injected into a forced-retrieval prompt. */
5930
6606
  const FORCED_RETRIEVAL_CHAR_BUDGET_DEFAULT = 12e3;
@@ -6082,6 +6758,76 @@ const HELP_CENTER_PROMPT = `HELP CENTER: Bike4Mind has a built-in Help Center th
6082
6758
  */
6083
6759
  const ABSTENTION_PROMPT = `When a request is underspecified or your sources do not cover it, say so and name what is missing. "I do not have enough to answer that" is a correct, high-value answer. Never invent facts about the user, their business, or their data, and never state a specific customer, competitor, deal, or figure as fact - or cite a source for it - unless your sources support it, even when the question assumes it.`;
6084
6760
  /**
6761
+ * Default text for the web-search freshness nudge, and the `WebSearchFreshnessPrompt` admin
6762
+ * setting's default.
6763
+ *
6764
+ * Unlike ABSTENTION_PROMPT / ARTIFACT_EMISSION_PROMPT / HELP_CENTER_PROMPT, this setting
6765
+ * distinguishes an absent row from a cleared one. ChatCompletionProcess reads it 2-arg, so an
6766
+ * absent row still falls back to this constant as the setting's registered default, but a cleared
6767
+ * '' is returned verbatim and drops the section rather than reverting. The siblings are read 3-arg
6768
+ * and collapse both cases to the constant. That divergence is deliberate - this section has no
6769
+ * companion boolean, so clearing the field is the only off switch it has. Keep the setting's
6770
+ * description in sync with that if either changes.
6771
+ *
6772
+ * Names no tool but `web_search`: the section is gated on web_search being offered, and web_fetch
6773
+ * is an independent toggle that may well be off.
6774
+ */
6775
+ const WEB_SEARCH_FRESHNESS_PROMPT = `# WEB SEARCH AND FRESHNESS
6776
+
6777
+ Your training data has a cutoff. The current date is supplied to you in this conversation's system context - treat it as authoritative, and assume anything time-sensitive may have changed since your training.
6778
+
6779
+ Call \`web_search\` BEFORE answering when the answer depends on a fact that changes over time: current prices or rates, product availability or roadmap status, funding, organizational or personnel changes, published benchmarks or performance figures, competitive positioning, or anything the user frames as "current", "latest", "now", or "as of today". When a stale answer would mislead, search instead of answering from memory. When a search surfaces a specific page that matters, or the user names one, read that page directly rather than answering from the snippet.
6780
+
6781
+ You do not need to search for stable knowledge (definitions, mathematics, established theory), or for questions answerable purely from this conversation or from documents already retrieved for you.
6782
+
6783
+ When you report a time-sensitive fact, state what it is as of - the date of the source you used - and say plainly when you could not verify something and are answering from training data instead. Never present an unverified recollection as a current fact.`;
6784
+ /**
6785
+ * Default text for the knowledge-base retrieval nudge, and the `KnowledgeBaseRetrievalPrompt`
6786
+ * admin setting's default.
6787
+ *
6788
+ * The gap this closes: the tool prompt has a when-to-use section for the clock, for web search,
6789
+ * for MCP and for agent delegation, and none for the user's own corpus. The
6790
+ * `search_knowledge_base` description is entirely HOW to search ("Make ONE good search per
6791
+ * distinct topic") and never WHEN, so on the optional path the model decides unaided - and over 30
6792
+ * days of production it reached for the corpus on 20.1% of the turns it was offered on.
6793
+ *
6794
+ * Read 2-arg by ChatCompletionProcess, exactly as WEB_SEARCH_FRESHNESS_PROMPT is and unlike the
6795
+ * 3-arg siblings: an absent row falls back to this constant as the registered default, but a
6796
+ * cleared '' is returned verbatim and drops the section instead of reverting. Deliberate - the
6797
+ * section has no companion boolean, so clearing the field is its only off switch, and that off
6798
+ * switch is what makes it A/B-able without a deploy. Keep the setting's description in sync.
6799
+ *
6800
+ * Names no tool but `search_knowledge_base`, for the same reason the web-search section names no
6801
+ * `web_fetch`: the companion `retrieve_knowledge_content` is paired in at build time but a session
6802
+ * denylist can still strip it (ChatCompletionProcess warns on exactly that case), and instructing
6803
+ * the model to call a tool it was not given makes it emit the call as leaked JSON text.
6804
+ *
6805
+ * The "do not search" paragraph is load-bearing, not padding. A when-to-retrieve nudge without a
6806
+ * don't-retrieve clause buys retrieval on turns that need none - the same failure mode global
6807
+ * forced retrieval already shows on out-of-corpus questions, reached by a different route. Three of
6808
+ * its clauses are load-bearing for a specific co-resident path, not general hedging:
6809
+ * - "from an attached document" - a small attached corpus is INLINED rather than deferred to
6810
+ * retrieval (`shouldDeferCorpusToRetrieval`), and forced retrieval deliberately steps aside on
6811
+ * an attached-files turn (`forcedRetrievalAbstention` emits nothing there). Without this clause
6812
+ * the section tells the model to go searching for content already sitting in its context.
6813
+ * - "already been searched on this turn" - on a forced turn that found nothing,
6814
+ * `forcedRetrievalNoContextPrompt` instructs the model to say the library does not cover the
6815
+ * question. A nudge to search then invites a second identical query - a billed query embedding,
6816
+ * and a chance to talk itself out of a correct abstention.
6817
+ * - the opening scope, "unless its content has been placed in this conversation" - the reason the
6818
+ * first paragraph does not simply claim the documents are invisible, which is false whenever a
6819
+ * corpus was inlined.
6820
+ */
6821
+ const KNOWLEDGE_BASE_RETRIEVAL_PROMPT = `# KNOWLEDGE BASE
6822
+
6823
+ \`search_knowledge_base\` searches a library of documents the user has made available to you - their own uploads, and any shared or organization library they can reach. You cannot see what a document holds unless its content has been placed in this conversation or you search for it; file names and tags are labels, not content.
6824
+
6825
+ Call \`search_knowledge_base\` BEFORE answering when that library would settle the question: anything about their organization, projects, customers, products, processes or people; a term, name, acronym or identifier that is not general public knowledge; a policy, decision, figure or date specific to them; or a question that assumes context this conversation never gave you. If you are about to answer in general terms a question the user means specifically, search first. A general-knowledge answer that sounds right is the failure this library exists to prevent.
6826
+
6827
+ Do not search when the answer is already in front of you or out of scope: general knowledge (definitions, mathematics, established theory, public facts); anything answerable from this conversation, from an attached document, or from content already retrieved for you this turn; or a request to transform, summarize or reformat text the user has just supplied. If the library has already been searched on this turn, do not search it again for the same question - a repeat spends a round trip to return the same passages.
6828
+
6829
+ When a search does not turn up what was asked for, say so plainly rather than filling the gap from training data, and never imply an answer came from the user's documents when it did not.`;
6830
+ /**
6085
6831
  * Default text for the formatting system message. Runtime fallback used by
6086
6832
  * `includeHardcodedSystemMessage` (b4m-core/utils/src/llm/utils.ts) when the `FormatPromptTemplate`
6087
6833
  * admin setting is blank; that setting's own default is intentionally '' - keep this the sole home.
@@ -6114,6 +6860,8 @@ z$1.enum([
6114
6860
  "ArtifactEmissionPrompt",
6115
6861
  "HelpCenterPrompt",
6116
6862
  "AbstentionPrompt",
6863
+ "WebSearchFreshnessPrompt",
6864
+ "KnowledgeBaseRetrievalPrompt",
6117
6865
  "UseFormatPrompt",
6118
6866
  "EnableQuestMaster",
6119
6867
  "EnableQuestMasterDefault",
@@ -6132,11 +6880,11 @@ z$1.enum([
6132
6880
  "EnableLattice",
6133
6881
  "EnableLatticeDefault",
6134
6882
  "EnableDataLakes",
6135
- "EnableDataLakesDefault",
6136
6883
  "EnableDataLakeSlackAdd",
6137
6884
  "EnableDataLakeGroundingMode",
6138
6885
  "EnableLakeMemory",
6139
6886
  "EnableDataLakeVectorSearch",
6887
+ "EnableRetrievalSupersessionCollapse",
6140
6888
  "PauseLakeConvergence",
6141
6889
  "LakeConvergenceBulkChangeSharePct",
6142
6890
  "EnforceLakeReadGrants",
@@ -6234,15 +6982,19 @@ z$1.enum([
6234
6982
  "dataLakeSearchMaxFiles",
6235
6983
  "dataLakeSearchMaxChunks",
6236
6984
  "forcedRetrievalCharBudget",
6985
+ "lakeMemoryRecallK",
6237
6986
  "kbSearchDefaultResults",
6238
6987
  "kbSearchResultTokenBudget",
6239
6988
  "kbSearchMinRelevancePct",
6989
+ "forcedRetrievalRelativeFloorPct",
6990
+ "forcedRetrievalMinSimilarityPct",
6240
6991
  "dataLakeEmbeddingSpendEnabled",
6241
6992
  "dataLakeEmbeddingBudgetPerRunUsd",
6242
6993
  "dataLakeEmbeddingBudgetPerLakeUsd",
6243
6994
  "dataLakeEmbeddingBudgetPerPeriodUsd",
6244
6995
  "dataLakeEmbeddingBudgetPeriodHours",
6245
6996
  "dataLakeEmbeddingMaxCallsPerMinute",
6997
+ "dataLakeEmbeddingMaxTokensPerMinute",
6246
6998
  "dataLakeVectorizeChunkBatchSize",
6247
6999
  "dataLakeEmbeddingTierMultiplierIndividual",
6248
7000
  "dataLakeEmbeddingTierMultiplierOrganization",
@@ -6291,7 +7043,6 @@ z$1.enum([
6291
7043
  "EnableBmPiDefault",
6292
7044
  "EnableBmPiJira",
6293
7045
  "EnableOptiHashi",
6294
- "EnableOptiHashiDefault",
6295
7046
  "EnableComputeSubmission",
6296
7047
  "EnableFamilyCompute",
6297
7048
  "EnableHybridCompute",
@@ -6361,10 +7112,12 @@ const OrchestrationDefaultsSchema = z$1.object({
6361
7112
  "mermaid_chart"
6362
7113
  ]),
6363
7114
  /**
6364
- * Tool names explicitly forbidden. Enforced as a final subtraction in
6365
- * `pickEffectiveEnabledTools` - wins even over payload-pinned tools - so this
6366
- * is the defense-in-depth backstop for the case where an admin broadens
6367
- * `allowedTools` without realizing a parallel denylist is also needed.
7115
+ * Tool names explicitly forbidden. Enforced in two places: as a final subtraction in
7116
+ * `pickEffectiveEnabledTools` (wins even over payload-pinned tools), and - for the two
7117
+ * delegation tools, which are injected as objects and never registered by name - at the
7118
+ * dependency gate in agentExecutor (`delegationOffer` withholds `agentStore` /
7119
+ * `dagDispatcher`). The name subtraction alone cannot reach those two; see
7120
+ * agentExecutor.sessionToolPolicy.
6368
7121
  *
6369
7122
  * Seeded with every tool that mutates user data (the spec's
6370
7123
  * "anything tagged `mutates_user_data`"): destructive/overwriting filesystem
@@ -6442,8 +7195,30 @@ const DATA_LAKE_SEARCH_MAX_CHUNKS_DEFAULT = 1e5;
6442
7195
  const DATA_LAKE_EMBEDDING_BUDGET_PER_LAKE_USD_MAX = 1e4;
6443
7196
  const DATA_LAKE_EMBEDDING_BUDGET_PER_PERIOD_USD_MAX = 5e3;
6444
7197
  const DATA_LAKE_EMBEDDING_MAX_CALLS_PER_MINUTE_MAX = 1e4;
7198
+ /**
7199
+ * The TOKEN half of the throughput cap, and the one that maps to what providers actually meter.
7200
+ * A call cap alone does not bound tokens: one call carries up to
7201
+ * DATA_LAKE_VECTORIZE_CHUNK_BATCH_SIZE_DEFAULT passages of DEFAULT_PASSAGE_TOKEN_TARGET tokens, so
7202
+ * 120 calls/min permits ~3.1M tokens/min - several times the smallest paid embeddings tier. The two
7203
+ * levers are complementary: calls/min bounds RPM, this bounds TPM, and a call must fit both.
7204
+ *
7205
+ * The default is deliberately LOW - it has to be safe on the smallest tier any deployment might
7206
+ * be on, including self-hosts nobody here can see. It is not a claim about what any particular
7207
+ * account can do, and reading it as one is the mistake to avoid: a provider tier is a property of
7208
+ * the provider organization, so it cannot be derived from this codebase at all.
7209
+ *
7210
+ * The real number is measurable per deployment: Admin -> Settings -> AI -> Data Lake Cost
7211
+ * Governance reads the configured provider's live ceiling (GET /api/admin/embedding-limits) and
7212
+ * shows it beside this lever, so an operator sets this from their own measured quota rather than
7213
+ * from a guess baked in here. Leave headroom below the measured ceiling for QUERY-side embedding,
7214
+ * which is exempt from this gate (see enforceEmbeddingSpendGate) and shares the same per-model
7215
+ * pool - a retrieval query must not queue behind a backfill.
7216
+ */
7217
+ const DATA_LAKE_EMBEDDING_MAX_TOKENS_PER_MINUTE_DEFAULT = 6e5;
7218
+ const DATA_LAKE_EMBEDDING_MAX_TOKENS_PER_MINUTE_MAX = 5e7;
6445
7219
  function makeNumberSetting(config) {
6446
7220
  let numberSchema = z$1.coerce.number();
7221
+ if (config.int) numberSchema = numberSchema.int();
6447
7222
  if (config.min !== void 0) numberSchema = numberSchema.min(config.min);
6448
7223
  if (config.max !== void 0) numberSchema = numberSchema.max(config.max);
6449
7224
  return {
@@ -7010,6 +7785,14 @@ const API_SERVICE_GROUPS = {
7010
7785
  {
7011
7786
  key: "AbstentionPrompt",
7012
7787
  order: 11
7788
+ },
7789
+ {
7790
+ key: "WebSearchFreshnessPrompt",
7791
+ order: 12
7792
+ },
7793
+ {
7794
+ key: "KnowledgeBaseRetrievalPrompt",
7795
+ order: 13
7013
7796
  }
7014
7797
  ]
7015
7798
  },
@@ -7046,6 +7829,18 @@ const API_SERVICE_GROUPS = {
7046
7829
  {
7047
7830
  key: "kbSearchMinRelevancePct",
7048
7831
  order: 7
7832
+ },
7833
+ {
7834
+ key: "lakeMemoryRecallK",
7835
+ order: 8
7836
+ },
7837
+ {
7838
+ key: "forcedRetrievalRelativeFloorPct",
7839
+ order: 9
7840
+ },
7841
+ {
7842
+ key: "forcedRetrievalMinSimilarityPct",
7843
+ order: 10
7049
7844
  }
7050
7845
  ]
7051
7846
  },
@@ -7080,16 +7875,20 @@ const API_SERVICE_GROUPS = {
7080
7875
  order: 6
7081
7876
  },
7082
7877
  {
7083
- key: "dataLakeVectorizeChunkBatchSize",
7878
+ key: "dataLakeEmbeddingMaxTokensPerMinute",
7084
7879
  order: 7
7085
7880
  },
7086
7881
  {
7087
- key: "dataLakeEmbeddingTierMultiplierIndividual",
7882
+ key: "dataLakeVectorizeChunkBatchSize",
7088
7883
  order: 8
7089
7884
  },
7090
7885
  {
7091
- key: "dataLakeEmbeddingTierMultiplierOrganization",
7886
+ key: "dataLakeEmbeddingTierMultiplierIndividual",
7092
7887
  order: 9
7888
+ },
7889
+ {
7890
+ key: "dataLakeEmbeddingTierMultiplierOrganization",
7891
+ order: 10
7093
7892
  }
7094
7893
  ]
7095
7894
  },
@@ -7486,10 +8285,6 @@ const API_SERVICE_GROUPS = {
7486
8285
  key: "EnableOptiHashi",
7487
8286
  order: 80
7488
8287
  },
7489
- {
7490
- key: "EnableOptiHashiDefault",
7491
- order: 81
7492
- },
7493
8288
  {
7494
8289
  key: "EnableComputeSubmission",
7495
8290
  order: 82
@@ -7985,16 +8780,6 @@ const settingsMap = {
7985
8780
  group: API_SERVICE_GROUPS.EXPERIMENTAL.id,
7986
8781
  order: 88
7987
8782
  }),
7988
- EnableDataLakesDefault: makeBooleanSetting({
7989
- key: "EnableDataLakesDefault",
7990
- name: "Data Lakes: On by default for users",
7991
- defaultValue: false,
7992
- description: "When enabled, Data Lakes is active for users who have never explicitly toggled it.",
7993
- category: "Experimental",
7994
- group: API_SERVICE_GROUPS.EXPERIMENTAL.id,
7995
- order: 89,
7996
- dependsOn: "EnableDataLakes"
7997
- }),
7998
8783
  EnableDataLakeSlackAdd: makeBooleanSetting({
7999
8784
  key: "EnableDataLakeSlackAdd",
8000
8785
  name: "Data Lakes: Slack \"@datalake add\" path",
@@ -8019,7 +8804,7 @@ const settingsMap = {
8019
8804
  key: "EnableLakeMemory",
8020
8805
  name: "Data Lakes: Lake memory profile (extraction)",
8021
8806
  defaultValue: false,
8022
- description: "Server-side gate for the lake memory producer - LLM extraction of a data lake's documents into a durable memory profile on ingest. Off by default (measurement rollout); the consumer that injects the profile is inert until this is on and a lake has been extracted.",
8807
+ description: "Master gate for lake memory, on all three sides: LLM extraction of a data lake's documents into a durable memory profile, recall of that profile into chats grounded in the lake, and whether the per-lake opt-in is offered at all. Off by default (measurement rollout). Turning it off stops recall immediately and stops new extractions from being queued or picked up, though a run already in flight finishes its slice (bounded by the handler timeout). It is NOT destructive - each lake keeps its own opt-in and its built profile, so flipping this back on resumes where it left off. Erasing a profile is a separate, explicit per-lake action.",
8023
8808
  category: "Experimental",
8024
8809
  group: API_SERVICE_GROUPS.EXPERIMENTAL.id,
8025
8810
  order: 91,
@@ -8035,11 +8820,21 @@ const settingsMap = {
8035
8820
  order: 92,
8036
8821
  dependsOn: "EnableDataLakes"
8037
8822
  }),
8823
+ EnableRetrievalSupersessionCollapse: makeBooleanSetting({
8824
+ key: "EnableRetrievalSupersessionCollapse",
8825
+ name: "Data Lakes: Collapse superseded members before ranking",
8826
+ defaultValue: false,
8827
+ description: "When a lake holds two generations of the same document (a re-upload, a Drive sync, a migration), rank only the newest and report the suppression. Off by default: the weakest identity tier is a bare file name, so two genuinely different documents sharing a name in one lake would collapse to one - turn this on only after checking the reported collapse counts on real lakes. Suppression is recoverable either way; a collapsed member is still reachable by id or name through retrieve_knowledge_content.",
8828
+ category: "Experimental",
8829
+ group: API_SERVICE_GROUPS.EXPERIMENTAL.id,
8830
+ order: 97,
8831
+ dependsOn: "EnableDataLakes"
8832
+ }),
8038
8833
  PauseLakeConvergence: makeBooleanSetting({
8039
8834
  key: "PauseLakeConvergence",
8040
8835
  name: "Data Lakes: Pause background convergence work",
8041
8836
  defaultValue: false,
8042
- description: "Kill switch for background data-lake ingestion work (convergence sweeps, rescue re-chunking) - NOT real-time user uploads, which are always honored. Off by default. Turn ON to halt in-flight background chunk/vectorize messages the next time the handler picks them up (a re-check inside the shared handler, so it takes effect on work already queued, not just the next scheduling pass). The platform value pauses every lake at once; a per-lake (or per-org / per-owner) override pauses a subset while the rest keep running. A platform-level flip applies immediately to lake-wide work and within ~5 min to per-lake-scoped work (settings cache).",
8837
+ description: "Kill switch for background data-lake ingestion work (convergence sweeps, rescue re-chunking) - NOT real-time user uploads, which are always honored. Off by default. Turn ON to halt in-flight background chunk/vectorize messages the next time the handler picks them up (a re-check inside the shared handler, so it takes effect on work already queued, not just the next scheduling pass). The platform value pauses every lake at once; a per-lake (or per-org / per-owner) override pauses a subset while the rest keep running - including overriding a platform-wide pause back OFF for one lake. Every producer honors the override, the global chunk rescue sweep included: it resolves each candidate against the lake it belongs to (#2157), so a file in no lake at all follows the platform value, which is correct for it. A platform-level flip applies immediately to lake-wide work and within ~5 min to per-lake-scoped work (settings cache).",
8043
8838
  category: "Experimental",
8044
8839
  group: API_SERVICE_GROUPS.EXPERIMENTAL.id,
8045
8840
  order: 93,
@@ -8069,8 +8864,8 @@ const settingsMap = {
8069
8864
  EnforceLakeReadGrants: makeBooleanSetting({
8070
8865
  key: "EnforceLakeReadGrants",
8071
8866
  name: "Data Lakes: Enforce read-time grant resolution",
8072
- defaultValue: false,
8073
- description: "Read-time grant cutover (#1673). OFF by default = report-only: the read gate resolves a persisted READER/org grant into an ephemeral membership view and logs where it WOULD change access ([lakeReadGrantCutover] lines), but the enforced decision stays the legacy owner/org/tag/entitlement/public rule so no one gains or loses access. NOTE: turning this ON is currently a NO-OP guarded by a source-level interlock (READ_GRANT_ENFORCEMENT_READY) - enforcement will not activate until the follow-up code (member-management write path + retrieval arm) lands and flips it, and a premature toggle just logs a warning and stays report-only. This is deliberate so the setting cannot half-enable a half-wired gate. Platform altitude on purpose: a one-time install-wide migration cutover, not a per-lake lever. Tag and entitlement grants always resolve live and are never affected by this flag; only persisted reader/org rows are gated by it.",
8867
+ defaultValue: true,
8868
+ description: "Read-time grant resolution (#1673). ON is the shipped default: a persisted READER or ORG grant is resolved into the read decision, so a principal a lake was shared with can browse it, open it and ground on it. Resolution is purely ADDITIVE (legacy OR grant), so it takes no access away; this arm contains an ORG grant to the granting org, and expired rows never resolve. This is the standing KILL SWITCH for that arm, not a migration phase: turning it OFF returns to report-only, where the gate still resolves grants and logs where they WOULD change access ([lakeReadGrantCutover] lines) but the enforced decision falls back to the legacy owner/org/tag/entitlement/public rule - so those log lines are the diagnostic for a lake someone can no longer reach while the switch is off. Platform altitude on purpose: install-wide, not a per-lake lever. Tag and entitlement grants always resolve live and are never affected by this flag; only persisted reader/org rows are gated by it.",
8074
8869
  category: "Experimental",
8075
8870
  group: API_SERVICE_GROUPS.EXPERIMENTAL.id,
8076
8871
  order: 94,
@@ -8320,6 +9115,7 @@ const settingsMap = {
8320
9115
  }),
8321
9116
  DefaultChunkSize: makeNumberSetting({
8322
9117
  key: "DefaultChunkSize",
9118
+ userReadable: true,
8323
9119
  name: "Default Chunk Size",
8324
9120
  defaultValue: 512,
8325
9121
  min: 64,
@@ -8378,6 +9174,22 @@ const settingsMap = {
8378
9174
  category: "AI",
8379
9175
  order: 11
8380
9176
  }),
9177
+ WebSearchFreshnessPrompt: makeStringSetting({
9178
+ key: "WebSearchFreshnessPrompt",
9179
+ name: "Web Search Freshness Prompt",
9180
+ defaultValue: WEB_SEARCH_FRESHNESS_PROMPT,
9181
+ description: "System prompt telling the model when to reach for web_search rather than answer from training data, and to state the as-of date of any time-sensitive fact. Injected only when the web_search tool is offered for the request - a model instructed to search without a search tool tends to claim it searched. Clearing this field turns the section OFF rather than restoring the built-in default, and it is the only off switch this section has; to get the stock wording back, paste it in. A change is not instantaneous: the settings cache is per-instance, so it applies immediately on the instance that served the change and within ~5 min (one cache TTL) everywhere else. After an upgrade, diff a saved copy against the built-in default: a saved copy pins the wording from whenever it was saved and will not pick up fixes made since.",
9182
+ category: "AI",
9183
+ order: 12
9184
+ }),
9185
+ KnowledgeBaseRetrievalPrompt: makeStringSetting({
9186
+ key: "KnowledgeBaseRetrievalPrompt",
9187
+ name: "Knowledge Base Retrieval Prompt",
9188
+ defaultValue: KNOWLEDGE_BASE_RETRIEVAL_PROMPT,
9189
+ description: "System prompt telling the model when to reach for search_knowledge_base rather than answer from training data, and when NOT to. Injected only when the search_knowledge_base tool is offered for the request - a model instructed to search a corpus it has no tool for tends to claim it searched. Clearing this field turns the section OFF rather than restoring the built-in default, and it is the only off switch this section has; to get the stock wording back, paste it in. A change is not instantaneous: the settings cache is per-instance, so it applies immediately on the instance that served the change and within ~5 min (one cache TTL) everywhere else. After an upgrade, diff a saved copy against the built-in default: a saved copy pins the wording from whenever it was saved and will not pick up fixes made since.",
9190
+ category: "AI",
9191
+ order: 13
9192
+ }),
8381
9193
  UseFormatPrompt: makeBooleanSetting({
8382
9194
  key: "UseFormatPrompt",
8383
9195
  name: "Use Format Prompt",
@@ -8396,6 +9208,7 @@ const settingsMap = {
8396
9208
  }),
8397
9209
  pricePerCredit: makeNumberSetting({
8398
9210
  key: "pricePerCredit",
9211
+ userReadable: true,
8399
9212
  name: "Price Per Credit",
8400
9213
  defaultValue: 50,
8401
9214
  description: "The price per credit for purchasing credits.",
@@ -8473,7 +9286,8 @@ const settingsMap = {
8473
9286
  name: "Referal Credits Amount",
8474
9287
  defaultValue: 1e4,
8475
9288
  description: "Credits to give to the referred user.",
8476
- category: "Referrals"
9289
+ category: "Referrals",
9290
+ userReadable: true
8477
9291
  }),
8478
9292
  EnableReferralToEmail: makeBooleanSetting({
8479
9293
  key: "EnableReferralToEmail",
@@ -8662,8 +9476,10 @@ const settingsMap = {
8662
9476
  }),
8663
9477
  MaxFileSize: makeNumberSetting({
8664
9478
  key: "MaxFileSize",
9479
+ userReadable: true,
8665
9480
  name: "Max File Size",
8666
9481
  defaultValue: 30,
9482
+ min: 1,
8667
9483
  description: "The maximum file size allowed for uploads in MB.",
8668
9484
  category: "Knowledge",
8669
9485
  group: API_SERVICE_GROUPS.KNOWLEDGE.id,
@@ -8770,6 +9586,7 @@ const settingsMap = {
8770
9586
  }),
8771
9587
  enforceCredits: makeBooleanSetting({
8772
9588
  key: "enforceCredits",
9589
+ userReadable: true,
8773
9590
  name: "Enforce Credits",
8774
9591
  defaultValue: process.env.B4M_SELF_HOST === "true" ? false : true,
8775
9592
  description: "Whether to enforce credits for users",
@@ -8786,6 +9603,7 @@ const settingsMap = {
8786
9603
  }),
8787
9604
  enableTeamPlan: makeBooleanSetting({
8788
9605
  key: "enableTeamPlan",
9606
+ userReadable: true,
8789
9607
  name: "Enable Team Plan",
8790
9608
  defaultValue: false,
8791
9609
  description: "Whether to enable team plans",
@@ -8891,7 +9709,8 @@ const settingsMap = {
8891
9709
  description: "The global system prompt files to be used for AI model configuration.",
8892
9710
  category: "AI",
8893
9711
  group: API_SERVICE_GROUPS.OPENAI.id,
8894
- order: 8
9712
+ order: 8,
9713
+ userReadable: true
8895
9714
  }),
8896
9715
  OpenWeatherKey: makeStringSetting({
8897
9716
  key: "OpenWeatherKey",
@@ -9045,6 +9864,7 @@ const settingsMap = {
9045
9864
  }),
9046
9865
  MaxContentLength: makeNumberSetting({
9047
9866
  key: "MaxContentLength",
9867
+ userReadable: true,
9048
9868
  name: "Max Content Length",
9049
9869
  defaultValue: 5e4,
9050
9870
  description: "The maximum character length for file content displayed in workbench (truncated if larger).",
@@ -9210,6 +10030,7 @@ const settingsMap = {
9210
10030
  }),
9211
10031
  defaultEmbeddingModel: makeStringSetting({
9212
10032
  key: "defaultEmbeddingModel",
10033
+ userReadable: true,
9213
10034
  name: "Default Embedding Model",
9214
10035
  defaultValue: defaultEmbeddingModelForEnv(),
9215
10036
  description: "The default embedding model to use",
@@ -9231,11 +10052,7 @@ const settingsMap = {
9231
10052
  category: "AI",
9232
10053
  group: API_SERVICE_GROUPS.EMBEDDING.id,
9233
10054
  order: 2,
9234
- scope: { settableAt: [
9235
- "organization",
9236
- "owner",
9237
- "lake"
9238
- ] }
10055
+ scope: { settableAt: ["organization", "owner"] }
9239
10056
  }),
9240
10057
  dataLakeSearchMaxChunks: makeNumberSetting({
9241
10058
  key: "dataLakeSearchMaxChunks",
@@ -9246,11 +10063,7 @@ const settingsMap = {
9246
10063
  category: "AI",
9247
10064
  group: API_SERVICE_GROUPS.EMBEDDING.id,
9248
10065
  order: 3,
9249
- scope: { settableAt: [
9250
- "organization",
9251
- "owner",
9252
- "lake"
9253
- ] }
10066
+ scope: { settableAt: ["organization", "owner"] }
9254
10067
  }),
9255
10068
  forcedRetrievalCharBudget: makeNumberSetting({
9256
10069
  key: "forcedRetrievalCharBudget",
@@ -9258,10 +10071,11 @@ const settingsMap = {
9258
10071
  defaultValue: FORCED_RETRIEVAL_CHAR_BUDGET_DEFAULT,
9259
10072
  min: 1e3,
9260
10073
  max: 1e5,
9261
- description: "Total characters of retrieved chunk text injected into a Data-Lake-mode turn. Measured saturating on every turn against a 47-document lake, so this is the binding constraint on how much of a corpus reaches the model - not the relevance floor. Raising it admits more passages at the cost of prompt tokens and latency on every Data-Lake turn; it is NOT automatically better, since more context can dilute ranking. Platform-only for now: this read does not go through the scoped-settings resolver, so a `settableAt` block here would be inert metadata at best and could arm the resolver's fail-loud owner check at worst.",
10074
+ description: "Total characters of retrieved chunk text injected into a Data-Lake-mode turn. Measured saturating on every turn against a 47-document lake, so this is the binding constraint on how much of a corpus reaches the model - not the relevance floor. Raising it admits more passages at the cost of prompt tokens and latency on every Data-Lake turn; it is NOT automatically better, since more context can dilute ranking. Overridable per organization and per owner, the same altitude as the two relevance floors resolved alongside it on the same turn.",
9262
10075
  category: "AI",
9263
10076
  group: API_SERVICE_GROUPS.EMBEDDING.id,
9264
- order: 4
10077
+ order: 4,
10078
+ scope: { settableAt: ["organization", "owner"] }
9265
10079
  }),
9266
10080
  kbSearchDefaultResults: makeNumberSetting({
9267
10081
  key: "kbSearchDefaultResults",
@@ -9269,7 +10083,7 @@ const settingsMap = {
9269
10083
  defaultValue: 5,
9270
10084
  min: 1,
9271
10085
  max: 10,
9272
- description: "Passages the search_knowledge_base tool returns when a model call omits max_results, which is most calls. This is the exact bound while kbSearchResultTokenBudget is unset (0). Once a token budget is set, it takes over as the primary bound for search results (this setting's own value is then unused there, though it still governs the keyword-search fallback, and the count served if token pricing itself fails). Does NOT raise the tool's hard ceiling of 10 passages per call - a model that reads max_results up to 10 from its own tool schema won't ask for more than that regardless of this setting.",
10086
+ description: "Passages the search_knowledge_base tool returns when a model call omits max_results, which is most calls. This is the exact bound while kbSearchResultTokenBudget is unset (0). Once a token budget is set, it takes over as the primary bound for search results (this setting's own value is then unused there, though it still governs the keyword-search fallback, and the count served if token pricing itself fails). Does NOT raise the tool's hard ceiling of 10 passages per call - a model that reads max_results up to 10 from its own tool schema won't ask for more than that regardless of this setting. A change is not instantaneous: the settings cache is per-instance, so it applies immediately on the instance that served the change and within ~5 min (one cache TTL) everywhere else.",
9273
10087
  category: "AI",
9274
10088
  group: API_SERVICE_GROUPS.EMBEDDING.id,
9275
10089
  order: 5,
@@ -9281,7 +10095,7 @@ const settingsMap = {
9281
10095
  defaultValue: 0,
9282
10096
  min: 0,
9283
10097
  max: 2e4,
9284
- description: "Approximate tokens of served passage TEXT (post-trim, post-clip - what the model actually receives, not the raw stored chunk) the search_knowledge_base tool may return in one call. Counted with a fixed tokenizer as a proxy, not billed against any specific model. Replaces a passage count as the primary bound once set, since it is invariant to chunk size - a lake chunked smaller no longer silently returns less material for the same setting. 0 (default) disables it: search_knowledge_base then serves exactly kbSearchDefaultResults passages, unchanged from before this setting existed. The FIRST matching passage is always returned even if it alone exceeds the budget - a search that found something never returns nothing.",
10098
+ description: "Approximate tokens of served passage TEXT (post-trim, post-clip - what the model actually receives, not the raw stored chunk) the search_knowledge_base tool may return in one call. Counted with a fixed tokenizer as a proxy, not billed against any specific model. Replaces a passage count as the primary bound once set, since it is invariant to chunk size - a lake chunked smaller no longer silently returns less material for the same setting. 0 (default) disables it: search_knowledge_base then serves exactly kbSearchDefaultResults passages, unchanged from before this setting existed. The FIRST matching passage is always returned even if it alone exceeds the budget - a search that found something never returns nothing. A change is not instantaneous: the settings cache is per-instance, so it applies immediately on the instance that served the change and within ~5 min (one cache TTL) everywhere else.",
9285
10099
  category: "AI",
9286
10100
  group: API_SERVICE_GROUPS.EMBEDDING.id,
9287
10101
  order: 6,
@@ -9293,12 +10107,50 @@ const settingsMap = {
9293
10107
  defaultValue: 0,
9294
10108
  min: 0,
9295
10109
  max: 100,
9296
- description: "Minimum cosine relevance, as a percent, a passage must clear to be returned by search_knowledge_base. 0 (default) matches current behavior (no relevance floor beyond a non-negative cosine score). Raising it lets breadth adapt per query - a narrow question can return fewer, more relevant passages instead of always padding out to the configured count. Cosine similarity is not comparable across embedding models: a floor tuned for one model can filter out an entire alternate model, when a lake mixes embedding models, more aggressively than intended. Start low and raise gradually while watching the tool's own retrieval-skipped notices.",
10110
+ description: "Minimum cosine relevance, as a percent, a passage must clear to be returned by search_knowledge_base. 0 (default) matches current behavior (no relevance floor beyond a non-negative cosine score). Raising it lets breadth adapt per query - a narrow question can return fewer, more relevant passages instead of always padding out to the configured count. Cosine similarity is not comparable across embedding models: a floor tuned for one model can filter out an entire alternate model, when a lake mixes embedding models, more aggressively than intended. Start low and raise gradually while watching the tool's own retrieval-skipped notices. A change is not instantaneous: the settings cache is per-instance, so it applies immediately on the instance that served the change and within ~5 min (one cache TTL) everywhere else.",
9297
10111
  category: "AI",
9298
10112
  group: API_SERVICE_GROUPS.EMBEDDING.id,
9299
10113
  order: 7,
9300
10114
  scope: { settableAt: ["organization", "owner"] }
9301
10115
  }),
10116
+ lakeMemoryRecallK: makeNumberSetting({
10117
+ key: "lakeMemoryRecallK",
10118
+ name: "Lake Memory Belief Budget",
10119
+ defaultValue: 24,
10120
+ min: 1,
10121
+ max: 200,
10122
+ int: true,
10123
+ description: "Most beliefs the lake memory hot-card injects on a Data-Lake-mode turn, shared across every lake in scope. Recall still applies its cosine floor and the source-reachability gate first, so raising this does not admit low-quality beliefs - it raises the ceiling on how many QUALIFYING beliefs can actually be used, which was pinned at 8 (inherited from personal-memento recall) on no evidence beyond that inheritance. The sibling lever on the same turn is Forced Retrieval Char Budget, which governs raw chunk text rather than extracted beliefs. Platform-only for now, unlike that sibling: this read goes through plain getSettingsValue, which ignores settableAt, so a scope block here would be silently inert - every override written against it would resolve to nothing. Pointing the read at the scoped resolver is the prerequisite, not extra metadata.",
10124
+ category: "AI",
10125
+ group: API_SERVICE_GROUPS.EMBEDDING.id,
10126
+ order: 8
10127
+ }),
10128
+ forcedRetrievalRelativeFloorPct: makeNumberSetting({
10129
+ key: "forcedRetrievalRelativeFloorPct",
10130
+ name: "Forced Retrieval Relative Floor (%)",
10131
+ defaultValue: 85,
10132
+ min: 0,
10133
+ max: 100,
10134
+ int: true,
10135
+ description: "How close to the best-scoring passage of the SAME turn a chunk must score to be injected on a Data-Lake-mode turn, as a percent of that top score. This is the floor that ranks; the absolute floor below only rejects. Unlike an absolute cosine line, it moves with the turn, so it keeps working when a corpus or an embedding model puts the whole score band somewhere else. Raising it injects fewer, more sharply-ranked passages and leaves char budget unspent; lowering it admits more of the tail. 0 disables the relative floor and leaves the absolute one as the only gate (the pre-#2497 behavior). The default is behavior-preserving rather than tuned: it admits everything the absolute floor admitted on the measured band, so it changes nothing until raised. Tune it AFTER an embedding-model change, never before - a migration shifts the band any value fitted to today would have been chosen against.",
10136
+ category: "AI",
10137
+ group: API_SERVICE_GROUPS.EMBEDDING.id,
10138
+ order: 9,
10139
+ scope: { settableAt: ["organization", "owner"] }
10140
+ }),
10141
+ forcedRetrievalMinSimilarityPct: makeNumberSetting({
10142
+ key: "forcedRetrievalMinSimilarityPct",
10143
+ name: "Forced Retrieval Absolute Floor (%)",
10144
+ defaultValue: 75,
10145
+ min: 1,
10146
+ max: 100,
10147
+ int: true,
10148
+ description: "Absolute minimum cosine similarity, as a percent, a chunk must clear to be injected on a Data-Lake-mode turn. This is a sanity floor for genuinely unrelated content, NOT the ranking gate - the relative floor above does the ranking. Measured over 166 injected chunks on a production lake the 75 default never once bound (the whole band sat between 80 and 91), so it currently reads like a quality gate while providing no protection. Lowering it toward 30-40 is the intended companion to raising the relative floor: it lets the relative rule govern a corpus whose band sits low, which a 75 line would otherwise reject wholesale. Cosine similarity is not comparable across embedding models, so a value tuned for one model does not transfer to another.",
10149
+ category: "AI",
10150
+ group: API_SERVICE_GROUPS.EMBEDDING.id,
10151
+ order: 10,
10152
+ scope: { settableAt: ["organization", "owner"] }
10153
+ }),
9302
10154
  LakeAccessAuditRetentionDays: makeNumberSetting({
9303
10155
  key: "LakeAccessAuditRetentionDays",
9304
10156
  name: "Lake Access Audit Retention (days)",
@@ -9334,6 +10186,7 @@ const settingsMap = {
9334
10186
  }),
9335
10187
  dataLakeEmbeddingSpendEnabled: makeBooleanSetting({
9336
10188
  key: "dataLakeEmbeddingSpendEnabled",
10189
+ userReadable: true,
9337
10190
  name: "Data Lake Embedding Spend Enabled",
9338
10191
  defaultValue: true,
9339
10192
  description: "Master switch for data-lake embedding spend (ingestion, reprocessing, convergence). Off halts all provider embedding calls on those paths; cached embeddings still apply.",
@@ -9343,6 +10196,7 @@ const settingsMap = {
9343
10196
  }),
9344
10197
  dataLakeEmbeddingBudgetPerRunUsd: makeNumberSetting({
9345
10198
  key: "dataLakeEmbeddingBudgetPerRunUsd",
10199
+ userReadable: true,
9346
10200
  name: "Embedding Budget Per Run (USD)",
9347
10201
  defaultValue: 5,
9348
10202
  min: 0,
@@ -9396,6 +10250,17 @@ const settingsMap = {
9396
10250
  group: API_SERVICE_GROUPS.DATA_LAKE_COST.id,
9397
10251
  order: 6
9398
10252
  }),
10253
+ dataLakeEmbeddingMaxTokensPerMinute: makeNumberSetting({
10254
+ key: "dataLakeEmbeddingMaxTokensPerMinute",
10255
+ name: "Embedding Max Tokens Per Minute",
10256
+ defaultValue: DATA_LAKE_EMBEDDING_MAX_TOKENS_PER_MINUTE_DEFAULT,
10257
+ min: 0,
10258
+ max: DATA_LAKE_EMBEDDING_MAX_TOKENS_PER_MINUTE_MAX,
10259
+ description: "Most provider embedding TOKENS per minute across all data-lake work, which is the quantity providers actually meter. The calls-per-minute lever alone does not bound this: one call carries a whole batch of passages. Set it from your provider dashboard TPM, leaving headroom for query-side embedding (exempt, so a search never queues behind a backfill). 0 stops all calls.",
10260
+ category: "AI",
10261
+ group: API_SERVICE_GROUPS.DATA_LAKE_COST.id,
10262
+ order: 7
10263
+ }),
9399
10264
  dataLakeVectorizeChunkBatchSize: makeNumberSetting({
9400
10265
  key: "dataLakeVectorizeChunkBatchSize",
9401
10266
  name: "Vectorize Chunk Batch Size",
@@ -9405,7 +10270,7 @@ const settingsMap = {
9405
10270
  description: "How many chunks the chunk handler packs into one vectorize-queue message. Smaller batches smooth the fan-out; not a spend value, so min 1.",
9406
10271
  category: "AI",
9407
10272
  group: API_SERVICE_GROUPS.DATA_LAKE_COST.id,
9408
- order: 7
10273
+ order: 8
9409
10274
  }),
9410
10275
  dataLakeEmbeddingTierMultiplierIndividual: makeNumberSetting({
9411
10276
  key: "dataLakeEmbeddingTierMultiplierIndividual",
@@ -9416,7 +10281,7 @@ const settingsMap = {
9416
10281
  description: "Scales the per-run and per-lake embedding budgets for lakes owned by an individual user. 1 means those lakes get exactly the configured budgets; 0 stops them spending at all. The effective budget is still capped by the same hard rail as the untiered value.",
9417
10282
  category: "AI",
9418
10283
  group: API_SERVICE_GROUPS.DATA_LAKE_COST.id,
9419
- order: 8
10284
+ order: 9
9420
10285
  }),
9421
10286
  dataLakeEmbeddingTierMultiplierOrganization: makeNumberSetting({
9422
10287
  key: "dataLakeEmbeddingTierMultiplierOrganization",
@@ -9427,7 +10292,7 @@ const settingsMap = {
9427
10292
  description: "Scales the per-run and per-lake embedding budgets for lakes owned by an organization, which serve a whole team rather than one person. 0 stops org-owned lakes spending at all. The effective budget is still capped by the same hard rail as the untiered value.",
9428
10293
  category: "AI",
9429
10294
  group: API_SERVICE_GROUPS.DATA_LAKE_COST.id,
9430
- order: 9
10295
+ order: 10
9431
10296
  }),
9432
10297
  slackSigningSecret: makeStringSetting({
9433
10298
  key: "slackSigningSecret",
@@ -9529,6 +10394,7 @@ const settingsMap = {
9529
10394
  }),
9530
10395
  enableVoiceSession: makeBooleanSetting({
9531
10396
  key: "enableVoiceSession",
10397
+ userReadable: true,
9532
10398
  name: "Enable Voice Session",
9533
10399
  defaultValue: false,
9534
10400
  description: "Whether to enable the voice session.",
@@ -9538,6 +10404,7 @@ const settingsMap = {
9538
10404
  }),
9539
10405
  voiceV2Enabled: makeBooleanSetting({
9540
10406
  key: "voiceV2Enabled",
10407
+ userReadable: true,
9541
10408
  name: "Enable Voice v2 (Model-Agnostic)",
9542
10409
  defaultValue: false,
9543
10410
  description: "Gate for the Voice v2 feature (ElevenLabs Conversational AI + any B4M reasoning model). When disabled, /api/voice/v2/sessions returns 403.",
@@ -9557,6 +10424,7 @@ const settingsMap = {
9557
10424
  }),
9558
10425
  voiceSessionAiVoice: makeStringSetting({
9559
10426
  key: "voiceSessionAiVoice",
10427
+ userReadable: true,
9560
10428
  name: "Default Assistant Voice",
9561
10429
  defaultValue: "alloy",
9562
10430
  description: "The default voice for the assistant in the voice session.",
@@ -9858,16 +10726,6 @@ const settingsMap = {
9858
10726
  group: API_SERVICE_GROUPS.EXPERIMENTAL.id,
9859
10727
  order: 80
9860
10728
  }),
9861
- EnableOptiHashiDefault: makeBooleanSetting({
9862
- key: "EnableOptiHashiDefault",
9863
- name: "OptiHashi: On by default for users",
9864
- defaultValue: false,
9865
- description: "When enabled, OptiHashi is active for users who have never explicitly toggled it.",
9866
- category: "Experimental",
9867
- group: API_SERVICE_GROUPS.EXPERIMENTAL.id,
9868
- order: 81,
9869
- dependsOn: "EnableOptiHashi"
9870
- }),
9871
10729
  EnableLibreOncology: makeBooleanSetting({
9872
10730
  key: "EnableLibreOncology",
9873
10731
  name: "Enable LibreOncology",
@@ -10068,6 +10926,7 @@ const settingsMap = {
10068
10926
  }),
10069
10927
  orchestrationDefaults: makeObjectSetting({
10070
10928
  key: "orchestrationDefaults",
10929
+ userReadable: true,
10071
10930
  name: "Agent Orchestration Defaults",
10072
10931
  defaultValue: OrchestrationDefaultsSchema.parse({}),
10073
10932
  description: "Default ReAct profile for agentless executions (#8922). Drives allowed/denied tools, iteration ceilings, default thoroughness, and fallback models when the agent_executor is invoked without a persisted IAgent (e.g. the upcoming Agent-mode toggle).",
@@ -10247,7 +11106,8 @@ const SystemPromptDetailSchema = z$1.object({
10247
11106
  "user",
10248
11107
  "project",
10249
11108
  "session",
10250
- "org"
11109
+ "org",
11110
+ "caller"
10251
11111
  ]),
10252
11112
  /** e.g., "date_context", "tool_guidance" */
10253
11113
  name: z$1.string(),
@@ -10641,6 +11501,7 @@ const PromptMetaContextSchema = z$1.object({
10641
11501
  }).optional(),
10642
11502
  lakeMemory: z$1.object({
10643
11503
  beliefCount: z$1.number(),
11504
+ beliefBudget: z$1.number().optional(),
10644
11505
  dataLakeTags: z$1.array(z$1.string())
10645
11506
  }).optional(),
10646
11507
  contextWindowUsage: z$1.object({
@@ -10685,7 +11546,15 @@ const PromptMetaPerformanceSchema = z$1.object({
10685
11546
  totalResponseTime: z$1.number().optional(),
10686
11547
  contextRetrievalTime: z$1.number().optional(),
10687
11548
  modelInferenceTime: z$1.number().optional(),
11549
+ /**
11550
+ * Time to First Visible Token: elapsed ms until the first chunk the user can actually
11551
+ * see. Left unset when a turn streamed nothing visible (thinking-only, or a turn that
11552
+ * errored before answering), so absence reads as "never rendered" rather than as fast.
11553
+ * Pair with firstChunkTime to tell a slow model from a long hidden-reasoning window.
11554
+ */
10688
11555
  firstTokenTime: z$1.number().optional(),
11556
+ /** Elapsed ms until the first chunk of any kind, including a hidden thinking block. */
11557
+ firstChunkTime: z$1.number().optional(),
10689
11558
  clientFirstTokenTime: z$1.number().optional(),
10690
11559
  streamingPerformance: z$1.object({
10691
11560
  chunkCount: z$1.number().optional(),
@@ -10766,11 +11635,16 @@ const CitableSourceSchema = z$1.object({
10766
11635
  * zero-result retrieval, so a turn that legitimately found nothing is indistinguishable from one
10767
11636
  * where retrieval never ran at all.
10768
11637
  *
10769
- * Deliberately holds NO counts and NO chunk/document identifiers. Counts already exist and are
10770
- * more precise: `citables.filter(c => c.type === 'document')` is deduped by id/url/title in
11638
+ * Holds NO chunk/document identifiers, and no DOCUMENT count. A document count already exists and
11639
+ * is more precise: `citables.filter(c => c.type === 'document')` is deduped by id/url/title in
10771
11640
  * `applyQuestStatusChanges`, while this shape cannot dedupe (no identifiers to dedupe by) and
10772
- * would have to sum - producing a second, disagreeing number for the same question. Similarity
10773
- * scores live on `LakeAccessEvent`, not here.
11641
+ * would have to sum - producing a second, disagreeing number for the same question.
11642
+ *
11643
+ * `injected` below is NOT that number and does not reopen it: it counts PASSAGES and characters,
11644
+ * neither of which `citables` can express - one document contributes many passages, and a turn
11645
+ * that injected nothing emits no citable to count at all. Similarity scores otherwise live on
11646
+ * `LakeAccessEvent`; the single `injected.topScore` is here because that row is written only on a
11647
+ * turn that grounded, so it cannot carry the near-miss score of a turn that grounded on nothing.
10774
11648
  *
10775
11649
  * CAUTION, not a guarantee: the absence of chunk/document identifiers is what keeps this shape
10776
11650
  * OUT of `promptMetaRedaction.ts`'s scope (that helper is a functionCalls-only denylist and would
@@ -10789,28 +11663,301 @@ const CitableSourceSchema = z$1.object({
10789
11663
  *
10790
11664
  * Absent-or-fully-present, matching `lakeMemory` above - see the Mongoose-side subSchema comment
10791
11665
  * in QuestModel.ts for why partial-write and default-array shapes are unsafe here.
11666
+ *
11667
+ * WIDER THAN ITS NAME SUGGESTS as of `mode` (#1394). The field is no longer written only when
11668
+ * retrieval ran: it is now seeded on every turn that could have retrieved (forced retrieval
11669
+ * enabled, or the knowledge tool offered), so `attempted: false` is a recorded fact rather than
11670
+ * an absence to be inferred. Presence therefore means "this turn was in a position to retrieve",
11671
+ * and turns with no knowledge in scope still carry no field at all. The distinction matters to a
11672
+ * rollup: absence is now ambiguous between "not a retrieval turn" and "written before this
11673
+ * existed", which is why `mode` documents its own date-bounding requirement.
10792
11674
  */
10793
11675
  const RetrievalSummarySchema = z$1.object({
10794
11676
  /** True once a retrieval-capable surface actually ran (not merely offered) this turn. */
10795
11677
  attempted: z$1.boolean(),
10796
11678
  /**
11679
+ * Present if and only if `attempted` is true - an outcome describes a run, and a turn that
11680
+ * never ran retrieval has none. A reader testing for a specific value is unaffected (absence
11681
+ * is not any of them); a reader switching exhaustively must handle undefined.
11682
+ *
10797
11683
  * 'ok' - ran, whether or not anything came back (the zero case is a legitimate 'ok').
10798
11684
  * 'no_lakes' - ran but the user had no entitled/selected lake in scope.
10799
- * 'failed' - threw; recall did not complete.
10800
- * On multiple retrieval calls within one turn, merge priority is failed > ok > no_lakes (see
10801
- * retrievalSummaryMerge.ts's mergeRetrievalSummary): a single failure is never masked by a later
10802
- * success or abstain, and a real success on one surface is never masked by another surface's
10803
- * "no lakes in scope" abstain in the same turn.
11685
+ * 'not_indexed' - ran to completion having compared nothing: the corpus in scope carries no
11686
+ * usable vector (never indexed, or embedded with a foreign model), so no passage was ever
11687
+ * scored against the query. Distinct from 'ok' because the library was not searched at all,
11688
+ * and reporting that as a topical zero ("your documents do not cover this") is exactly the
11689
+ * confident-wrong-answer this field exists to catch. Distinct from 'failed' because nothing
11690
+ * broke: the remedy is re-vectorizing, which the corpus owner can do themselves, and a retry
11691
+ * never helps.
11692
+ * COVERAGE: recorded by forced retrieval (KnowledgeRetrievalFeature's `scoredCount === 0`
11693
+ * exit) and by knowledgeBaseSearch, whose semantic arms carry the same verdict through to the
11694
+ * keyword arm's write - the two agree on "not one passage was compared against the query",
11695
+ * not on any withholding flag, so a relevance floor that emptied a real search and a partial
11696
+ * withholding alongside a real search both stay 'ok' on both surfaces.
11697
+ * knowledgeBaseRetrieve cannot reach this state: it fetches named files rather than ranking
11698
+ * against a query embedding, so it has no comparison to come up empty.
11699
+ * 'failed' - recall did not complete: it threw, OR the retrieval repository is not wired on
11700
+ * this host (the guards in ChatCompletionFeatures / knowledgeBaseSearch / knowledgeBaseRetrieve
11701
+ * record it without anything throwing). What separates it from 'not_indexed' is the remedy,
11702
+ * not the tempo: fix the outage or the host wiring, never re-index content. An unwired host
11703
+ * reports continuously too, so "chronic" alone does not pick out 'not_indexed'.
11704
+ * NOT this: a model-supplied argument that is not a well-formed id. knowledgeBaseRetrieve
11705
+ * shape-checks `file_id` and answers a malformed one as a single-file miss ('ok'), because the
11706
+ * remedy is for the model to search for the right id - there is nothing for an operator to
11707
+ * fix. It is logged rather than counted here, so the rate stays observable without this field
11708
+ * reporting an outage that is not happening.
11709
+ * On multiple retrieval calls within one turn, merge priority is failed > not_indexed > ok >
11710
+ * no_lakes (see retrievalSummaryMerge.ts's mergeRetrievalSummary): a single failure is never
11711
+ * masked by a later success or abstain, an unsearchable corpus outranks a legitimate zero so a
11712
+ * success on another surface cannot erase it, and a real success is never masked by another
11713
+ * surface's "no lakes in scope" abstain in the same turn.
10804
11714
  */
10805
11715
  outcome: z$1.enum([
10806
11716
  "ok",
10807
11717
  "no_lakes",
11718
+ "not_indexed",
10808
11719
  "failed"
10809
- ]),
11720
+ ]).optional(),
11721
+ /**
11722
+ * Whether forced retrieval was ENABLED for this turn, independent of whether it then ran.
11723
+ *
11724
+ * This is what makes the optional path measurable. `attempted` says retrieval happened;
11725
+ * without `mode` there is no way to ask the complementary question - of the turns where the
11726
+ * model was merely OFFERED the knowledge tools, how often did it choose to retrieve - because
11727
+ * a forced turn and an optional turn both land as `attempted: true`.
11728
+ *
11729
+ * Optional on the schema, and absence NEVER means 'optional' - it means unclassified. Two
11730
+ * sources, one historical and one ongoing: turns recorded before this field landed, and
11731
+ * agent-mode runs, which write a retrieval summary through `persistRunAsQuest` but never pass
11732
+ * the seed site at the `offeredTools` write in ChatCompletionProcess. So date-bounding a rollup
11733
+ * removes the first source but not the second; count the unclassified bucket rather than
11734
+ * assuming it empties.
11735
+ */
11736
+ mode: z$1.enum(["forced", "optional"]).optional(),
11737
+ /**
11738
+ * Whether the knowledge-base when-to-retrieve guidance section actually shipped in this turn's
11739
+ * tool prompt. Written only on turns that were OFFERED the knowledge tool, so absence means
11740
+ * "not an offered turn" or "recorded before this field landed" - it never means "cleared".
11741
+ *
11742
+ * `false` is the load-bearing value here, not filler. Clearing the KnowledgeBaseRetrievalPrompt
11743
+ * setting is the section's only off switch, so a turn recording `false` is the CONTROL arm of
11744
+ * the A/B this field exists to make readable. Anything merging or folding this must preserve an
11745
+ * explicit `false` rather than collapse it into absent - see mergeRetrievalSummary, which uses
11746
+ * `??` and deliberately not `||` for that reason.
11747
+ *
11748
+ * MUST STAY IN SYNC with TWO gates, not one. ToolBuilder.buildToolPrompt emits the section iff
11749
+ * the tool is offered AND the guidance string is non-empty; filterByPromptMode then drops the
11750
+ * whole `toolPrompt` source, which no promptMode admits, so an offered tool is not sufficient.
11751
+ * The ChatCompletionProcess seed site conjoins all three, and hands the first two to
11752
+ * buildToolPrompt as the same consts, so the flag and the actual emission cannot drift.
11753
+ */
11754
+ knowledgeBaseGuidanceInjected: z$1.boolean().optional(),
11755
+ /**
11756
+ * Why the forced arm did not run on a turn that had it enabled. Only ever set with
11757
+ * `mode: 'forced'`, and only for the deliberate suppressions in
11758
+ * ChatCompletionFeatures.getContextMessages - a forced turn that ran and failed reports that
11759
+ * through `outcome`, not here.
11760
+ *
11761
+ * These turns are the reason this field exists: forced retrieval is configured, a rule
11762
+ * suppresses it, and the model falls back to the offered tool. That is exactly the population
11763
+ * the per-turn routing question is about, and before this it was indistinguishable from a turn
11764
+ * where forced retrieval was never configured at all.
11765
+ */
11766
+ forcedSkipReason: z$1.enum(["attached_files", "personal_corpus"]).optional(),
10810
11767
  /** Which retrieval-capable surface(s) ran this turn, e.g. 'lake-memory', 'knowledgeBaseSearch'. */
10811
11768
  surfaces: z$1.array(z$1.string()),
10812
11769
  /** Lakes resolved at the moment retrieval ran, stamped point-in-time (not read live from the session). */
10813
- dataLakeTags: z$1.array(z$1.string())
11770
+ dataLakeTags: z$1.array(z$1.string()),
11771
+ /**
11772
+ * Ids of the lakes whose `systemPrompt` was injected this turn (getAccessibleDataLakePrompts),
11773
+ * across every injection site (forced retrieval and the model-driven knowledge tools). NOT the
11774
+ * prompt text itself - that already reaches the model in the completion, and copying it here
11775
+ * widens exposure for nothing. Absent means no injection site ran; present-and-empty means one
11776
+ * ran but nothing qualified (untrusted, or an empty systemPrompt).
11777
+ */
11778
+ injectedLakePromptIds: z$1.array(z$1.string()).optional(),
11779
+ /** mementoCount/mementoIds precedent: mirrors injectedLakePromptIds.length. */
11780
+ injectedLakePromptCount: z$1.number().optional(),
11781
+ /**
11782
+ * How much retrieved content actually reached the model this turn: `chunks` passages totalling
11783
+ * `chars` characters of retrieved CONTENT (headings and framing excluded, so the number means
11784
+ * the same thing on every surface), plus `topScore`, the best similarity among the compared
11785
+ * passages the reporting surface can SEE - which is not the same population on every surface.
11786
+ * Forced retrieval scores every chunk itself and so reports true near-misses; knowledgeBaseSearch's
11787
+ * semantic arm only ever sees `minScore` survivors, and reports no `topScore` at all on a starve,
11788
+ * so a sub-floor near-miss there is invisible rather than recorded.
11789
+ *
11790
+ * PRESENCE CONTRACT: present if and only if at least one surface COMPLETED a search this turn.
11791
+ * `chunks: 0` is a RECORDED STARVE - the library was searched and nothing was injected, which is
11792
+ * the case this field exists to make visible: without it, a forced-retrieval turn that injected
11793
+ * nothing is byte-identical to one that injected its whole character budget (both `outcome:
11794
+ * 'ok'`). Absence means the volume is UNKNOWN, which is what a turn carries when no surface
11795
+ * completed a search: retrieval was never attempted, nothing was in scope to search
11796
+ * ('no_lakes'), or the one surface that ran broke mid-flight, where a zero would be a lie.
11797
+ * A surface that completed but CANNOT know the turn's passage volume also stays silent rather
11798
+ * than claiming a zero - knowledgeBaseSearch's keyword arm on a hit is the case: it injects
11799
+ * file metadata and hands the model retrieve_knowledge_content, which injects the text and
11800
+ * reports no volume, so its zero would survive the merge as a starve that did not happen.
11801
+ *
11802
+ * KNOWN HOLE in that rule, while retrieve_knowledge_content stays uninstrumented: a recorded zero
11803
+ * is not PROOF of a starve. Forced retrieval and the knowledge tools are not mutually exclusive
11804
+ * (ChatCompletionProcess seeds on `forcedRetrievalEnabled || knowledgeToolOffered`), so the forced
11805
+ * arm can complete empty, write its honest zero, and the model can then ground the same turn
11806
+ * through retrieve_knowledge_content, which contributes no volume to oppose it. The zero is
11807
+ * per-surface-truthful and turn-level-misleading. Any rollup counting starves should treat a zero
11808
+ * as "nothing was injected by a surface that reports volume" and, until that tool reports its own,
11809
+ * cross-check `functionCalls` before calling the turn ungrounded.
11810
+ *
11811
+ * Per SURFACE, not per turn: a surface that breaks contributes nothing while a surface that
11812
+ * completed alongside it still reports its own volume, so a turn CAN read 'failed' next to a
11813
+ * recorded zero. That pairing means "one surface broke, and everything that did finish injected
11814
+ * nothing" - which is exactly what a reader needs, and strictly more than the outcome alone.
11815
+ *
11816
+ * SUMMED across surfaces, so this field and `outcome` can legitimately disagree in tone on a
11817
+ * multi-surface turn: forced retrieval grounding on 12 passages while knowledgeBaseSearch throws
11818
+ * gives `outcome: 'failed'` alongside `chunks: 12`. That is correct - `outcome` is worst-of,
11819
+ * `injected` is sum-of-completions.
11820
+ *
11821
+ * `topScore` is optional because only cosine-similarity surfaces have one to report. Lake
11822
+ * memory's belief `relevance` is a different scale and forced retrieval's pre-scan value is a
11823
+ * -1 sentinel; neither is ever written here, because a `max` across mixed scales, or against a
11824
+ * sentinel, is a number that reads as a similarity and is not one.
11825
+ *
11826
+ * Date-bound any rollup, the same caveat `mode` documents on itself: turns recorded before this
11827
+ * landed carry no volume, and no backfill is possible - the volume of a past turn is gone.
11828
+ *
11829
+ * `preRelativeFloorCandidates` and `postRelativeFloorCandidates` are the ONE pair here that is
11830
+ * not "what reached the model": `ranked.length` and `scored.length` in KnowledgeRetrievalFeature
11831
+ * - the candidates left after the absolute similarity floor, and after the relative floor
11832
+ * trims them. `chunks` is what survived the char budget on top of that, so the three
11833
+ * numbers bracket two independent trimmers:
11834
+ *
11835
+ * pre -> [relative floor] -> post -> [char budget] -> chunks
11836
+ *
11837
+ * They exist so a low `chunks` is diagnosable - a small corpus and a floor that trimmed a large
11838
+ * pool end in the same `chunks`. `pre - post` is the floor's own effect and nothing else;
11839
+ * `pre - chunks` is NOT, because the budget trims the same walk. Both optional: only forced
11840
+ * retrieval computes a ranked pool, a surface without one (lake memory, the knowledge tools)
11841
+ * never writes either, and absence must not read as zero candidates. SUMMED like `chunks`, with
11842
+ * the same absent-is-not-zero handling as `topScore`.
11843
+ *
11844
+ * COMPARE THE PAIR ONLY TO ITSELF, never to `chunks`, unless `surfaces` is forced retrieval
11845
+ * alone. `chunks` and `chars` sum across ALL surfaces while this pair is forced-only, so a mixed
11846
+ * turn can store `chunks` above `pre` - inverting the relationship the pair exposes. Lake memory
11847
+ * is the common case, not the exotic one: it is enabled inside the same forced-retrieval gate,
11848
+ * so on a lake-memory lake it writes on nearly every forced turn. Its chunks can be backed out
11849
+ * via `context.lakeMemory.beliefCount` (approximately - that count is pre-sanitization); its
11850
+ * CHARS land only inside the shared sum, with no per-surface field to subtract them back out, so
11851
+ * `chars` cannot be decontaminated at all. `pre - post` needs neither, which is the point of
11852
+ * storing both.
11853
+ *
11854
+ * BOTH SATURATE, so `pre` counts what the SCAN REACHED, not what the corpus holds: `pool` is
11855
+ * truncated in-scan at FORCED_RETRIEVAL_MAX_SCORED_CHUNKS (256), over a scan itself bounded by
11856
+ * FORCED_RETRIEVAL_MAX_SCANNED_CHUNKS (4000) across FORCED_RETRIEVAL_MAX_CANDIDATE_FILES (100).
11857
+ * 2000 qualifying chunks and 300 both record 256; above the cap a rollup is a plateau.
11858
+ */
11859
+ injected: z$1.object({
11860
+ chunks: z$1.number(),
11861
+ chars: z$1.number(),
11862
+ topScore: z$1.number().optional(),
11863
+ preRelativeFloorCandidates: z$1.number().optional(),
11864
+ postRelativeFloorCandidates: z$1.number().optional()
11865
+ }).optional(),
11866
+ /**
11867
+ * Could the corpus in scope have answered this turn, whether or not the model went looking?
11868
+ *
11869
+ * The denominator the optional-path retrieval rate has always been missing (#1394). A rate of
11870
+ * "the model retrieved on 20% of offered turns" cannot say whether the other 80% were misses or
11871
+ * turns with nothing to find, and the two argue for opposite things: the first for routing work,
11872
+ * the second for leaving the optional path alone. Crossing this field with the rate separates
11873
+ * them.
11874
+ *
11875
+ * THE ONLY FIELD IN THIS BLOCK NOT WRITTEN BY THE TURN. Every sibling is stamped point-in-time
11876
+ * while the turn runs; this one is written afterwards by an offline replay
11877
+ * (packages/scripts/retrieval/answerability-replay.ts) that re-scores the recorded prompt against
11878
+ * the corpus. That is deliberate - the population it exists to measure is the turns where
11879
+ * retrieval did NOT run, so computing it live would mean adding a full brute-force chunk scan
11880
+ * (ChatCompletionFeatures' forced path, which has no ANN index) to exactly the turns that pay
11881
+ * nothing for retrieval today. The measurement is not worth that latency on live traffic.
11882
+ *
11883
+ * BEING A RECONSTRUCTION, IT CARRIES TWO DRIFTS THE OTHER FIELDS DO NOT:
11884
+ * 1. Corpus CONTENT moves. A document added or reindexed between the turn and the replay is
11885
+ * scored as though it had been there. `probedAt` discloses the gap; a replay run long after
11886
+ * the window is weak evidence, not strong.
11887
+ * 2. Corpus SCOPE is inferred, not recorded. The seed writes `dataLakeTags: []` on a turn where
11888
+ * retrieval never ran (ChatCompletionProcess), so the replay reconstructs scope from the
11889
+ * session's lakes as they stand at replay time. A session whose lake selection changed is
11890
+ * replayed against a corpus the turn never had, and NOTHING here flags that. Recording real
11891
+ * scope at seed time would fix it for future turns and is not done yet.
11892
+ * 3. The QUESTION can move out from under it. The probe is keyed to the quest, not to the
11893
+ * prompt text it scored, so a turn whose prompt is later rewritten in place keeps a probe
11894
+ * describing the question it used to ask. mergeRetrievalSummary preserves the probe across
11895
+ * a runtime write deliberately - dropping it would erase the backfill - so nothing
11896
+ * invalidates a stale one. Re-run the replay with --force over a window whose turns were
11897
+ * edited.
11898
+ *
11899
+ * RAW SCORE, NOT A VERDICT, so the cutoff lives in the reader. summarizeOptionalPathRetrieval
11900
+ * applies it at fold time, which lets the same replay be re-thresholded without re-running -
11901
+ * the point of storing the number, given the two live floors disagree by construction (forced
11902
+ * retrieval's absolute default is 0.75, the knowledge tool's is 0).
11903
+ *
11904
+ * `topScore` is the same raw cosine scale as `injected.topScore` and comparable to it. It is NOT
11905
+ * comparable to lake memory's belief relevance, for the reason `injected` documents at length.
11906
+ *
11907
+ * `scanTruncated` inherits forced retrieval's saturation: the replay bounds its scan the same
11908
+ * way, so a low `topScore` on a truncated scan is not proof the corpus lacked an answer - it is
11909
+ * proof the part that was scanned did. Treat those turns as unknown rather than as negatives.
11910
+ *
11911
+ * Absence means NOT PROBED - never "not answerable". Every turn predating the replay, and every
11912
+ * turn the replay skipped or failed on, is absent, so a fold must keep it as its own arm rather
11913
+ * than letting it fall in with the negatives.
11914
+ */
11915
+ answerability: z$1.object({
11916
+ /** Best cosine the replay found across the reconstructed corpus. */
11917
+ topScore: z$1.number(),
11918
+ /** Chunks at or above `floor`. Separates "one lucky match" from "a rich seam". */
11919
+ candidatesAboveFloor: z$1.number(),
11920
+ /** The absolute floor the replay counted `candidatesAboveFloor` against, as a fraction. */
11921
+ floor: z$1.number(),
11922
+ /** The scan hit its chunk ceiling, so `topScore` is a floor on the true best, not the best. */
11923
+ scanTruncated: z$1.boolean(),
11924
+ /** When the replay ran, NOT when the turn ran - the disclosure for content drift above. */
11925
+ probedAt: JsonSafeDate
11926
+ }).optional(),
11927
+ /**
11928
+ * Which of this turn's injected lake prompt ids were BOTH in the session's pre-authorized (manage-
11929
+ * but-not-member admission) set AND injected on this turn - see unionPreauthorizedLakeAccess and
11930
+ * pages/api/sessions/create.ts. A subset of injectedLakePromptIds, never a superset. Narrows the
11931
+ * session's static `preauthorizedLakeIds` (what was ADMITTED) to what a given turn actually used.
11932
+ *
11933
+ * MEMBERSHIP, NOT CAUSATION. An admitted lake the caller could already reach - its creator, or a
11934
+ * member of its org - injects through the ordinary trust arm and is listed here all the same, so a
11935
+ * non-empty value does not prove the admission is what made the injection possible. Absent means no
11936
+ * admitted id was among this turn's injections, including every turn on a session with none.
11937
+ */
11938
+ preauthorizedLakeIdsUsed: z$1.array(z$1.string()).optional()
11939
+ });
11940
+ /**
11941
+ * Why a grounded turn's library scan stopped short of the whole library.
11942
+ *
11943
+ * Written ONLY on a partially-covered turn (reportCoverage returns early otherwise), so presence
11944
+ * means "partial" and `partial` is always true - the flag is explicit anyway because a reader
11945
+ * checking `retrievalCoverage.partial` should not have to know that absence is the other half of
11946
+ * the contract.
11947
+ *
11948
+ * Single producer (ChatCompletionFeatures.reportCoverage), which is why - unlike `warnings`,
11949
+ * `citables` and `retrieval` - this field needs no merge case in applyQuestStatusChanges: a
11950
+ * later tool-arm write that omits it is preserved by the one-level spread.
11951
+ *
11952
+ * `reasons` is the same diagnostic prose the warnings entry interpolates. It is shown to the
11953
+ * reader behind a disclosure rather than in the banner body, because only some reasons are
11954
+ * actionable (a document mid-reindex returns on its own; a per-turn chunk budget does not).
11955
+ */
11956
+ const RetrievalCoverageSchema = z$1.object({
11957
+ /** Always true - see the presence contract above. */
11958
+ partial: z$1.boolean(),
11959
+ /** One entry per distinct cause, e.g. a candidate cap, a scan budget, an embedding mismatch. */
11960
+ reasons: z$1.array(z$1.string())
10814
11961
  });
10815
11962
  z$1.object({
10816
11963
  model: PromptMetaModelSchema.optional(),
@@ -10820,6 +11967,9 @@ z$1.object({
10820
11967
  * deliberately: applyQuestStatusChanges does a one-level spread merge, so a field nested under
10821
11968
  * `context` would be replaced wholesale by any tool-arm write instead of merging. */
10822
11969
  retrieval: RetrievalSummarySchema.optional(),
11970
+ /** Partial-grounding-coverage detail - see RetrievalCoverageSchema. Top-level for the same
11971
+ * one-level-spread-merge reason as `retrieval` above. */
11972
+ retrievalCoverage: RetrievalCoverageSchema.optional(),
10823
11973
  functionCalls: z$1.array(PromptMetaFunctionCallSchema).optional(),
10824
11974
  /**
10825
11975
  * Names of the tools actually offered to the model this turn - the output of `buildTools`
@@ -11513,6 +12663,7 @@ z.object({
11513
12663
  requiredUserTag: z.union([z.literal(""), z.string().min(1).max(100)]).optional(),
11514
12664
  requiredEntitlement: z.union([z.literal(""), z.string().min(3).max(100).refine((s) => s.includes(":") && s.split(":").every((part) => part.length > 0), "Entitlement key must be namespaced with non-empty parts (e.g. \"product:pro\")")]).optional(),
11515
12665
  auditQueryTextEnabled: z.boolean().optional(),
12666
+ lakeMemoryEnabled: z.boolean().optional(),
11516
12667
  requiredPassageTokenTarget: z.number().int().min(64).max(OVERSIZED_PASSAGE_TOKEN_THRESHOLD).nullable().optional()
11517
12668
  });
11518
12669
  z.object({
@@ -11587,6 +12738,7 @@ tags: z.array(TaxonomyTagInput).max(100) });
11587
12738
  z.object({
11588
12739
  /** User description of the data (helps the AI) */
11589
12740
  context: z.string().max(2e3).optional() });
12741
+ z.object({ tags: z.array(z.string().min(1).max(130).regex(/^[^\r\n]*$/)).max(100) });
11590
12742
  z.object({ hashes: z.array(z.string().regex(sha256Regex)).min(1).max(500) });
11591
12743
  const SyncDeltaFileEntry = z.object({
11592
12744
  relativePath: z.string(),
@@ -11618,55 +12770,6 @@ z.object({
11618
12770
  skip: z.array(z.string())
11619
12771
  })
11620
12772
  });
11621
- z$1.enum([
11622
- "inject",
11623
- "auto-fire",
11624
- "hidden"
11625
- ]);
11626
- /**
11627
- * Modes acceptable at AUTHORING time. 'hidden' is intentionally excluded until
11628
- * the host has true hidden-send support - accepting it would persist a value
11629
- * that silently behaves as 'auto-fire' (a surprising downgrade). It stays in
11630
- * ExecutionModeSchema/the stored enum for forward-compat.
11631
- */
11632
- const AuthorableExecutionModeSchema = z$1.enum(["inject", "auto-fire"]);
11633
- /**
11634
- * Tools a prompt may require - constrained to the host's closed tool set, MINUS
11635
- * integration-gated tools that act on the caller's own credentials/account. A
11636
- * shared system prompt must not be able to inject e.g. blog-publishing into a
11637
- * non-author's session via requiredTools. (Per-user entitlement of the remaining
11638
- * tools is still the chat pipeline's responsibility - see follow-up note in the
11639
- * briefcase blueprint; this allowlist is the storage-layer floor.)
11640
- */
11641
- const BRIEFCASE_DISALLOWED_TOOLS = [
11642
- "blog_publish",
11643
- "blog_edit",
11644
- "blog_draft"
11645
- ];
11646
- const BriefcaseRequiredToolsSchema = z$1.array(b4mLLMTools.refine((t) => !BRIEFCASE_DISALLOWED_TOOLS.includes(t), "This tool is not permitted in a briefcase prompt")).max(16);
11647
- z$1.string().regex(/^[a-f0-9]{24}$/i, "Invalid prompt id");
11648
- const PROMPT_TEXT_MAX = 16e3;
11649
- const TAGS_MAX = 20;
11650
- z$1.object({
11651
- type: z$1.string().min(1).max(100),
11652
- name: z$1.string().min(1).max(200),
11653
- description: z$1.string().max(500).optional(),
11654
- promptText: z$1.string().min(1).max(PROMPT_TEXT_MAX),
11655
- tags: z$1.array(z$1.string().min(1).max(50)).max(TAGS_MAX).optional(),
11656
- executionMode: AuthorableExecutionModeSchema.optional(),
11657
- requiredTools: BriefcaseRequiredToolsSchema.optional()
11658
- }).partial();
11659
- /**
11660
- * One catalog sub-query. Exactly one selector is used, in precedence order:
11661
- * `personal` (resolved to the caller server-side) > `tags` > `type`.
11662
- */
11663
- const PromptBatchQuerySchema = z$1.object({
11664
- key: z$1.string().min(1).max(100),
11665
- tags: z$1.array(z$1.string().min(1).max(50)).max(TAGS_MAX).optional(),
11666
- type: z$1.string().max(100).optional(),
11667
- personal: z$1.boolean().optional()
11668
- });
11669
- z$1.object({ queries: z$1.array(PromptBatchQuerySchema).min(1).max(32).refine((qs) => new Set(qs.map((q) => q.key)).size === qs.length, { message: "Batch query keys must be unique" }) });
11670
12773
  z$1.string().regex(/^[a-f0-9]{24}$/i, "Invalid template id");
11671
12774
  /**
11672
12775
  * The bound model. Reuses the legacy-remap preprocess so a template saved under
@@ -11969,6 +13072,31 @@ z$1.object({
11969
13072
  "grounded",
11970
13073
  "surface"
11971
13074
  ]).optional(),
13075
+ /**
13076
+ * Suppress OUR server-side auto-offers without entering a promptMode. Exists because promptMode
13077
+ * was the only switch for the offer and it also strips every authored prompt, so no caller could
13078
+ * have an arm that went unoffered AND kept the abstention licence.
13079
+ *
13080
+ * Gates the three auto-add sites (the knowledge offer in resolveEnabledTools, the navigate_view
13081
+ * auto-add, the blog/skill gate), unioned with `Boolean(promptMode)` by
13082
+ * resolveSkipAutoOffers. A force-on, not an override: `false` under a promptMode still suppresses.
13083
+ * Withholding navigate_view also drops the viewRegistry system block, which only describes it.
13084
+ *
13085
+ * Withholds the OFFER, not knowledge: `session.forceKnowledgeRetrieval` is untouched, and an
13086
+ * already-attached corpus is inlined rather than deferred to the tool. An arm that must see no
13087
+ * knowledge at all also needs a session with no attachments and forced retrieval off.
13088
+ */
13089
+ skipAutoOffers: z$1.boolean().optional(),
13090
+ /**
13091
+ * Caller-supplied system-prompt text. Rendered as a defended, deference-postured block
13092
+ * appended last in the system-prompt stack. Reached by both POST /api/chat and /api/ai/llm.
13093
+ *
13094
+ * This cap is the universal backstop, not a duplicate of a route check: the parse that opens
13095
+ * invoke() runs outside any try and before a quest row is written, so it holds for every caller
13096
+ * including ones that pass through no route schema. Do not drop it on the assumption that
13097
+ * whoever called validated first.
13098
+ */
13099
+ systemPrompt: z$1.string().max(PROMPT_TEXT_MAX).optional(),
11972
13100
  /** Whether Mementos is enabled */
11973
13101
  enableMementos: z$1.boolean().optional(),
11974
13102
  /** Whether Artifacts is enabled */
@@ -12942,7 +14070,7 @@ Array.from(new Set([
12942
14070
  id: "opti.root",
12943
14071
  section: "opti",
12944
14072
  label: "OptiHashi Home",
12945
- description: "The OptiHashi Optimizer landing page showing all 8 pattern family cards",
14073
+ description: "The OptiHashi Optimizer landing page showing the pattern family cards",
12946
14074
  navigationType: "route",
12947
14075
  target: "/opti",
12948
14076
  keywords: [
@@ -13843,6 +14971,66 @@ Array.from(new Set([
13843
14971
  const [, top] = v.target.split("/");
13844
14972
  return `/${top}`;
13845
14973
  })));
14974
+ function getHeader(headers, name) {
14975
+ if (!headers || typeof headers !== "object") return null;
14976
+ if (typeof headers.get === "function") {
14977
+ const value = headers.get(name);
14978
+ return typeof value === "string" ? value : null;
14979
+ }
14980
+ const value = headers[name] ?? headers[name.toLowerCase()];
14981
+ return typeof value === "string" ? value : null;
14982
+ }
14983
+ function parseCount(value) {
14984
+ if (value === null) return null;
14985
+ const trimmed = value.trim();
14986
+ if (!trimmed) return null;
14987
+ const parsed = Number(trimmed);
14988
+ return Number.isFinite(parsed) ? parsed : null;
14989
+ }
14990
+ const UNIT_MS = {
14991
+ ms: 1,
14992
+ s: 1e3,
14993
+ m: 6e4,
14994
+ h: 36e5
14995
+ };
14996
+ const DURATION_PART = /(\d+(?:\.\d+)?)(ms|h|m|s)/g;
14997
+ /**
14998
+ * Parse a Go-style duration ("6ms", "0s", "1m30s", "1h2m3s") to milliseconds.
14999
+ *
15000
+ * Exported for its own tests: it is the part of this module that can be wrong in a way the
15001
+ * numbers still look plausible.
15002
+ */
15003
+ function parseDurationMs(value) {
15004
+ if (typeof value !== "string") return null;
15005
+ const trimmed = value.trim();
15006
+ if (!trimmed) return null;
15007
+ DURATION_PART.lastIndex = 0;
15008
+ let total = 0;
15009
+ let matched = 0;
15010
+ let consumed = 0;
15011
+ for (const part of trimmed.matchAll(DURATION_PART)) {
15012
+ total += Number(part[1]) * UNIT_MS[part[2]];
15013
+ consumed += part[0].length;
15014
+ matched += 1;
15015
+ }
15016
+ if (matched === 0 || consumed !== trimmed.length) return null;
15017
+ return total;
15018
+ }
15019
+ /** Read both rate-limit dimensions off a provider response. */
15020
+ function parseEmbeddingRateLimitHeaders(headers) {
15021
+ return {
15022
+ limitTokens: parseCount(getHeader(headers, "x-ratelimit-limit-tokens")),
15023
+ limitRequests: parseCount(getHeader(headers, "x-ratelimit-limit-requests")),
15024
+ remainingTokens: parseCount(getHeader(headers, "x-ratelimit-remaining-tokens")),
15025
+ remainingRequests: parseCount(getHeader(headers, "x-ratelimit-remaining-requests")),
15026
+ resetTokensMs: parseDurationMs(getHeader(headers, "x-ratelimit-reset-tokens")),
15027
+ resetRequestsMs: parseDurationMs(getHeader(headers, "x-ratelimit-reset-requests"))
15028
+ };
15029
+ }
15030
+ /** True when the provider reported at least one usable ceiling. */
15031
+ function hasUsableLimits(snapshot) {
15032
+ return snapshot.limitTokens !== null || snapshot.limitRequests !== null;
15033
+ }
13846
15034
  dayjs.extend(utc);
13847
15035
  dayjs.extend(timezone);
13848
15036
  dayjs.extend(relativeTime);
@@ -13911,16 +15099,34 @@ function isUserInitiatedAbort(error, userSignal) {
13911
15099
  return isAbortError && !userSignal;
13912
15100
  }
13913
15101
  /**
13914
- * Extract retry delay from error response (e.g., Retry-After header)
15102
+ * A Retry-After hint is only useful if it asks us to wait. `Retry-After: 0`, a negative value, or an
15103
+ * HTTP date that has already passed all carry no timing information - and every `withRetry` in this
15104
+ * repo treats a non-null hint as authoritative *over* its exponential backoff, so returning 0 does
15105
+ * not mean "wait a moment", it means "abandon the backoff entirely and retry immediately".
15106
+ *
15107
+ * That inverts the retry budget exactly when it matters: a server sends Retry-After when it is
15108
+ * already struggling, so honouring a zero turns the remaining attempts into an instant burst against
15109
+ * a service asking for room. Null instead, so the caller falls through to its own backoff.
15110
+ *
15111
+ * Exported because the rule, not the code, is the thing worth sharing: `Retry-After` is parsed in
15112
+ * more than one package (fab-pipeline's `getOpenSearchRetryAfterMs`), and a four-token predicate
15113
+ * copied around is a rule that drifts. Feed it a delay already converted to ms.
15114
+ */
15115
+ function retryAfterHintOrNull(ms) {
15116
+ return ms > 0 ? ms : null;
15117
+ }
15118
+ /**
15119
+ * Extract retry delay from error response (e.g., Retry-After header). Returns null when the header is
15120
+ * absent, unparseable, or does not ask us to wait - see retryAfterHintOrNull.
13915
15121
  */
13916
15122
  function getRetryAfterMs(error) {
13917
15123
  if (!isAxiosError(error)) return null;
13918
15124
  const retryAfter = error.response?.headers?.["retry-after"];
13919
15125
  if (!retryAfter) return null;
13920
15126
  const seconds = parseInt(retryAfter, 10);
13921
- if (!isNaN(seconds)) return seconds * 1e3;
15127
+ if (!isNaN(seconds)) return retryAfterHintOrNull(seconds * 1e3);
13922
15128
  const date = Date.parse(retryAfter);
13923
- if (!isNaN(date)) return Math.max(0, date - Date.now());
15129
+ if (!isNaN(date)) return retryAfterHintOrNull(date - Date.now());
13924
15130
  return null;
13925
15131
  }
13926
15132
  /**
@@ -15335,4 +16541,4 @@ var ConfigStore = class {
15335
16541
  }
15336
16542
  };
15337
16543
  //#endregion
15338
- export { VoyageAIEmbeddingModel as $, HTTPError as A, resolveHistoryFetchLimit as At, PermissionDeniedError as B, isNearLimit as Bt, DEFAULT_MUSIC_MODEL_ID as C, isRetryableError as Ct, FIXED_TEMPERATURE_MODELS as D, isZodError as Dt, FIELD_GROUP_OF as E, isUserInitiatedAbort as Et, ModelBackend as F, usdToCredits as Ft, SpeechToTextModels as G, REASONING_SUPPORTED_MODELS as H, NO_TEMPERATURE_MODELS as I, usdToCreditsStochastic as It, TooManyRequestsError as J, SupportedFabFileMimeTypes as K, NotFoundError as L, withRetry as Lt, ImageModels as M, settingsMap as Mt, InternalServerError as N, toModelInfo as Nt, FORMAT_PROMPT_TEMPLATE as O, mapMimeTypeToArtifactType as Ot, MODEL_INFO_FIELD_GROUP_OF as P, toModelRecord as Pt, VideoModels as Q, OllamaEmbeddingModel as R, buildRateLimitLogEntry as Rt, CorruptedFileError as S, isRenderableModelType as St, DEGENERATE_FINISH_REASON as T, isUnlimitedHistory as Tt, REFUSAL_FALLBACK_MODELS as U, REASONING_EFFORT_INCOMPATIBLE_WITH_TOOLS_MODELS as V, parseRateLimitHeaders as Vt, RESPONSES_API_TOOL_MODELS as W, UnprocessableEntityError as X, UnauthorizedError as Y, VIDEO_SIZE_CONSTRAINTS as Z, BadRequestError as _, isImageServeable as _t, getCreditsUrl as a, getMcpProviderMetadata as at, CREDIT_DEDUCT_TRANSACTION_TYPES as b, isModelDeprecated as bt, requireApiUrl as c, isAudioMimeType as ct, AGENT_QUEST_MANIFEST as d, isEarlyStop as dt, WORK_ITEM_STATUSES as et, AGENT_QUEST_MCP_URI as f, isFieldGroup as ft, BFL_SAFETY_TOLERANCE as g, isImageAttachment as gt, BEDROCK_NO_PROMPT_CACHING_MODELS as h, isGeminiModelId as ht, LOCAL_DEV_URL as i, defaultEmbeddingModelForEnv as it, HttpStatus as j, secureParameters as jt, ForbiddenError as k, obfuscateApiKey as kt, resolveApiEndpoint as l, isChunkRebuildPending as lt, ApiKeyType as m, isGPTImageModel as mt, logger as n, calculateRetryDelay as nt, getEnvironmentName as o, getQuestErrorCode as ot, ARTIFACT_ATTRS_PATTERN as p, isGPTImage2Model as pt, TTS_MAX_INPUT_CHARS as q, ApiEndpointUnconfiguredError as r, dayjsConfig_default as rt, parseApiUrl as s, getRetryAfterMs as st, ConfigStore as t, applyModelPriceCatalog as tt, AGENT_QUEST_ID as u, isConvergencePausedNote as ut, BedrockEmbeddingModel as v, isMediaModelType as vt, DEFAULT_UNKNOWN_CONTEXT_WINDOW as w, isSupportedFabFileMimeType as wt, ChatModels as x, isPlaceholderApiKey as xt, CONTEXT_WINDOW_SAFETY_BUFFER_TOKENS as y, isModelAccessible as yt, OpenAIEmbeddingModel as z, extractSnippetMeta as zt };
16544
+ export { SupportedFabFileMimeTypes as $, buildRateLimitLogEntry as $t, HTTPError as A, isRenderableModelType as At, OPENAI_GPT_IMAGE_1_IMAGE_SIZES as B, resolveHistoryFetchLimit as Bt, DEFAULT_MUSIC_MODEL_ID as C, isGeminiModelId as Ct, FIXED_TEMPERATURE_MODELS as D, isModelAccessible as Dt, FIELD_GROUP_OF as E, isMediaModelType as Et, MODEL_INFO_FIELD_GROUP_OF as F, isZodError as Ft, PermissionDeniedError as G, usdToCredits as Gt, OllamaEmbeddingModel as H, settingsMap as Ht, McpServerName as I, mapMimeTypeToArtifactType as It, REFUSAL_FALLBACK_MODELS as J, ACTOR_COLOR_SLOTS as Jt, REASONING_EFFORT_INCOMPATIBLE_WITH_TOOLS_MODELS as K, usdToCreditsStochastic as Kt, ModelBackend as L, obfuscateApiKey as Lt, IMAGE_SIZE_CONSTRAINTS as M, isSupportedFabFileMimeType as Mt, ImageModels as N, isUnlimitedHistory as Nt, FORMAT_PROMPT_TEMPLATE as O, isModelDeprecated as Ot, InternalServerError as P, isUserInitiatedAbort as Pt, SpeechToTextModels as Q, selfClaimedActorKindSchema as Qt, NO_TEMPERATURE_MODELS as R, parseEmbeddingRateLimitHeaders as Rt, CorruptedFileError as S, isGPTImageModel as St, DEGENERATE_FINISH_REASON as T, isImageServeable as Tt, OpenAIEmbeddingModel as U, toModelInfo as Ut, OPENAI_GPT_IMAGE_2_IMAGE_SIZES as V, secureParameters as Vt, PROMPT_TEXT_MAX as W, toModelRecord as Wt, REVIEW_GATE_STATUS_VALUES as X, actorKindMarker as Xt, RESPONSES_API_TOOL_MODELS as Y, actorColorIndex as Yt, SUBQUEST_STATUS_VALUES as Z, actorKindSchema as Zt, BadRequestError as _, isChunkRebuildPending as _t, getCreditsUrl as a, VideoModels as at, CREDIT_DEDUCT_TRANSACTION_TYPES as b, isFieldGroup as bt, requireApiUrl as c, applyModelPriceCatalog as ct, AGENT_QUEST_MANIFEST as d, defaultEmbeddingModelForEnv as dt, extractSnippetMeta as en, TTS_MAX_INPUT_CHARS as et, AGENT_QUEST_MCP_URI as f, getMcpProviderMetadata as ft, BFL_SAFETY_TOLERANCE as g, isAudioMimeType as gt, BEDROCK_NO_PROMPT_CACHING_MODELS as h, hasUsableLimits as ht, LOCAL_DEV_URL as i, VIDEO_SIZE_CONSTRAINTS as it, HttpStatus as j, isRetryableError as jt, ForbiddenError as k, isPlaceholderApiKey as kt, resolveApiEndpoint as l, calculateRetryDelay as lt, ApiKeyType as m, getRetryAfterMs as mt, logger as n, parseRateLimitHeaders as nn, UnauthorizedError as nt, getEnvironmentName as o, VoyageAIEmbeddingModel as ot, ARTIFACT_ATTRS_PATTERN as p, getQuestErrorCode as pt, REASONING_SUPPORTED_MODELS as q, withRetry as qt, ApiEndpointUnconfiguredError as r, UnprocessableEntityError as rt, parseApiUrl as s, WORK_ITEM_STATUSES as st, ConfigStore as t, isNearLimit as tn, TooManyRequestsError as tt, AGENT_QUEST_ID as u, dayjsConfig_default as ut, BedrockEmbeddingModel as v, isChunkStalledFile as vt, DEFAULT_UNKNOWN_CONTEXT_WINDOW as w, isImageAttachment as wt, ChatModels as x, isGPTImage2Model as xt, CONTEXT_WINDOW_SAFETY_BUFFER_TOKENS as y, isEarlyStop as yt, NotFoundError as z, reservationOutputTokens as zt };