@ai-matrx/agents 0.21.8 → 0.21.10

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.
@@ -31,6 +31,7 @@ __export(context_exports, {
31
31
  contextValueChars: () => contextValueChars,
32
32
  formatChars: () => formatChars,
33
33
  isRichEnvelope: () => isRichEnvelope,
34
+ isWireDirective: () => isWireDirective,
34
35
  lookupSavedRule: () => lookupSavedRule,
35
36
  receiptRowToResolved: () => receiptRowToResolved,
36
37
  resolveContextRow: () => resolveContextRow,
@@ -223,10 +224,19 @@ function unresolvedSize(value) {
223
224
  if (isResourceRef(value)) return true;
224
225
  return (isRichEnvelope(value) || isLazyPointer(value)) && hasLazySource(value) && (value.content === null || value.content === void 0);
225
226
  }
227
+ var KIND_RESOURCE_REF = "resource_ref";
228
+ function isWireDirective(key, value) {
229
+ if (isPlainObject(value)) return value.__kind === KIND_RESOURCE_REF;
230
+ return typeof value === "string" && key.trim().toLowerCase().endsWith("_id") && UUID_RE.test(value.trim());
231
+ }
226
232
  function buildContextWire(rows) {
227
233
  const wire = {};
228
234
  for (const row of rows) {
229
235
  if (!row.include) continue;
236
+ if (isWireDirective(row.key, row.value)) {
237
+ wire[row.key] = row.value;
238
+ continue;
239
+ }
230
240
  const rich = isRichEnvelope(row.value) || isLazyPointer(row.value);
231
241
  const envelope = rich ? { ...row.value } : { content: row.value };
232
242
  delete envelope.max_inline_chars;
@@ -302,9 +312,31 @@ function receiptRowToResolved(row) {
302
312
  max_inline_chars: row.max_inline_chars,
303
313
  delivery: row.delivery,
304
314
  decided_by: { include: row.decided_by.include, max_inline_chars: row.decided_by.max_inline_chars },
305
- clamped: row.clamped
315
+ clamped: row.clamped,
316
+ layers: receiptLayers(row)
306
317
  };
307
318
  }
319
+ function receiptLayers(row) {
320
+ const layers = {};
321
+ const limit = row.max_inline_chars;
322
+ const limitBy = row.decided_by.max_inline_chars;
323
+ const includeBy = row.decided_by.include;
324
+ if (limitBy === "default") layers.default_max = limit;
325
+ const pageOff = includeBy === "page" && !row.include || includeBy === "agent" && row.include;
326
+ if (limitBy === "page" || pageOff) {
327
+ layers.surface = {
328
+ declared: true,
329
+ auto_context: !pageOff,
330
+ max_inline_chars: limitBy === "page" ? limit : null
331
+ };
332
+ }
333
+ if (limitBy === "agent") {
334
+ layers.agent = { declared: true, max_inline_chars: limit };
335
+ } else if (includeBy === "agent") {
336
+ layers.agent = row.include ? { declared: true } : { declared: false, kill_switch: true };
337
+ }
338
+ return layers;
339
+ }
308
340
  function formatChars(n) {
309
341
  if (n === null || n === void 0 || !Number.isFinite(n)) return "\u2014";
310
342
  const abs = Math.abs(n);
@@ -1 +1 @@
1
- {"version":3,"sources":["../../context/index.ts"],"sourcesContent":["/**\n * @ai-matrx/agents/context — the context value rules, headless.\n *\n * Contract: common-docs/systems/scopes-context/context-delivery/RULES.md.\n * Parity corpus: ./rules-corpus.json (run here by rules.test.ts and by aidream's\n * pytest against the Python twin `resolve_context_rule`). Every function here\n * is pure, zero-React, and safe to import on a server.\n *\n * The one flow a client runs per turn:\n * rows = values.map(v => resolveContextRow(v, savedRows, cap)) // what the table shows\n * context = buildContextWire(rows) // what the request sends\n * …on `context_receipt`: compareReceipt(rows, receipt) // screen vs server\n */\n\n// ── Constants ───────────────────────────────────────────────────────────────\n\n/** `users.user_surface_state.feature` for the person's saved rules. */\nexport const CONTEXT_RULES_FEATURE = \"context_rules\";\n/** Surface key for values no surface emitted (attachments, server-added values). */\nexport const DEFAULT_SURFACE_KEY = \"_default\";\n/** The server's default inline threshold when no layer sets one. */\nexport const DEFAULT_INLINE_MAX_CHARS = 200;\n/** The default ceiling every resolved limit is clamped to (a server knob). */\nexport const DEFAULT_INLINE_CAP = 50000;\n/** A lazy document's server default (RULES.md §2: \"a small lazy document 6000\"). */\nexport const LAZY_SOURCE_DEFAULT_MAX = 6000;\n\n// ── Types ───────────────────────────────────────────────────────────────────\n\n/** Which layer decided a field — lowest to highest. */\nexport type ContextLayer = \"default\" | \"page\" | \"agent\" | \"you\";\nexport type ContextDelivery = \"inline\" | \"on_request\" | \"off\";\n\n/** One saved per-person rule (one key on one surface). Both fields optional. */\nexport interface SavedContextRule {\n include?: boolean;\n max_inline_chars?: number;\n}\n\n/** `{ [surface_key]: { [context key]: SavedContextRule } }` — the person's rows. */\nexport type SavedContextRuleRows = Record<string, Record<string, SavedContextRule>>;\n\n/** The agent layer for one key (RULES.md §2). */\nexport interface ContextAgentLayer {\n /** The agent's Context Policy declares this key (or the value is the agent's own seed). */\n declared?: boolean;\n /** The policy's ceiling; only read when `declared`. */\n max_inline_chars?: number | null;\n /** `auto_context_disabled` — closes every undeclared key. */\n kill_switch?: boolean;\n}\n\n/** The page layer for one key: the surface that emitted the value. */\nexport interface ContextSurfaceLayer {\n /** The surface declares this value (a `ui_surface_value` row / manifest entry). */\n declared?: boolean;\n /** `false` drops the value unless the agent declares it. */\n auto_context?: boolean | null;\n /** `inlineUpTo` / envelope `max_inline_chars` / DB column. */\n max_inline_chars?: number | null;\n}\n\n/** The non-person layers for one value — what the table's layer breakdown shows. */\nexport interface ContextRuleLayers {\n /** The server's per-key default limit; `null`/absent = 200. */\n default_max?: number | null;\n agent?: ContextAgentLayer | null;\n surface?: ContextSurfaceLayer | null;\n}\n\n/**\n * Input to `resolveContextRule`. Snake case on purpose: it is the corpus shape\n * and the Python twin's shape. `user` is typed loosely because invalid saved\n * values (non-boolean include, non-integer or negative limit) must be ignored,\n * not trusted.\n */\nexport interface ContextRuleInput extends ContextRuleLayers {\n /** Size in code points; `null` = an unresolved lazy source. */\n chars: number | null;\n user?: { include?: unknown; max_inline_chars?: unknown } | null;\n /** Ceiling; default `DEFAULT_INLINE_CAP`. */\n cap?: number;\n}\n\nexport interface ContextRuleResult {\n include: boolean;\n /** Effective limit, after the clamp. */\n max_inline_chars: number;\n delivery: ContextDelivery;\n decided_by: { include: ContextLayer; max_inline_chars: ContextLayer };\n clamped: boolean;\n}\n\n/** One receipt row — RULES.md §5, wire fields exactly. */\nexport interface ContextReceiptRow {\n key: string;\n label: string;\n surface_key: string;\n origin: \"client\" | \"server\" | \"surroundings\" | \"rule\";\n chars: number | null;\n include: boolean;\n max_inline_chars: number;\n delivery: ContextDelivery;\n decided_by: { include: ContextLayer; max_inline_chars: ContextLayer };\n user_rule: { include?: boolean; max_inline_chars?: number } | null;\n clamped: boolean;\n /** The CLIENT itself sent a value the person turned off. */\n client_sent_excluded: boolean;\n /** `\"model\"`: nothing reached a model that reads no context. */\n blocked_by: \"model\" | null;\n}\n\n/** The per-turn receipt the server streams as `context_receipt` — RULES.md §5. */\nexport interface ContextReceipt {\n version: 1;\n surface: string | null;\n cap: number;\n /** False: this turn's model cannot read context at all (every row `blocked_by: \"model\"`). */\n model_reads_context: boolean;\n /** The person's saved rules could not be read; the turn ran without them. */\n rules_error: string | null;\n rows: ContextReceiptRow[];\n}\n\nexport type ContextRowOrigin = \"page\" | \"attached\" | \"system\";\n\n/** A client's resolved row: what its table renders and what `buildContextWire` sends. */\nexport interface ResolvedContextRow extends ContextRuleResult {\n key: string;\n label: string;\n surfaceKey: string;\n origin: ContextRowOrigin;\n /** The raw value, or an already-rich envelope (`{ content, type, … }`). */\n value: unknown;\n chars: number | null;\n /** The saved rule that was applied (merged `_default` + surface row), or null. */\n userRule: SavedContextRule | null;\n description?: string;\n /** Context object type for the wire envelope (`text`, `json`, …). */\n type?: string;\n /** The non-person layers the rule was resolved from. Absent = default only. */\n layers?: ContextRuleLayers;\n}\n\n/** What a client knows about one value before resolution. */\nexport interface ContextRowSource {\n key: string;\n label: string;\n surfaceKey?: string;\n origin: ContextRowOrigin;\n value: unknown;\n /** Override the measured size (e.g. `null` for an unresolved lazy source). */\n chars?: number | null;\n description?: string;\n type?: string;\n layers?: ContextRuleLayers;\n}\n\n// ── Helpers ─────────────────────────────────────────────────────────────────\n\nconst isNum = (v: unknown): v is number => typeof v === \"number\" && Number.isFinite(v);\nconst isValidLimit = (v: unknown): v is number =>\n typeof v === \"number\" && Number.isInteger(v) && v >= 0;\n\n// ── resolveContextRule — RULES.md §2, line for line ─────────────────────────\n\nexport function resolveContextRule(input: ContextRuleInput): ContextRuleResult {\n const agent = input.agent ?? null;\n const surface = input.surface ?? null;\n const user = input.user ?? null;\n const cap = isNum(input.cap) ? input.cap : DEFAULT_INLINE_CAP;\n const agentDeclared = agent?.declared === true;\n\n let include = true;\n let includeBy: ContextLayer = \"default\";\n if (surface?.declared === true && surface.auto_context === false) {\n if (agentDeclared) {\n include = true;\n includeBy = \"agent\";\n } else {\n include = false;\n includeBy = \"page\";\n }\n }\n if (agent?.kill_switch === true && !agentDeclared) {\n include = false;\n includeBy = \"agent\";\n }\n if (user && typeof user.include === \"boolean\") {\n include = user.include;\n includeBy = \"you\";\n }\n\n let limit = isNum(input.default_max) ? input.default_max : DEFAULT_INLINE_MAX_CHARS;\n let limitBy: ContextLayer = \"default\";\n const a = agentDeclared && isNum(agent?.max_inline_chars) ? agent.max_inline_chars : null;\n const s = isNum(surface?.max_inline_chars) ? surface.max_inline_chars : null;\n if (a !== null && s !== null) {\n if (s < a) {\n limit = s;\n limitBy = \"page\";\n } else {\n limit = a;\n limitBy = \"agent\";\n }\n } else if (a !== null) {\n limit = a;\n limitBy = \"agent\";\n } else if (s !== null) {\n limit = s;\n limitBy = \"page\";\n }\n if (user && isValidLimit(user.max_inline_chars)) {\n limit = user.max_inline_chars;\n limitBy = \"you\";\n }\n const clamped = limit > cap;\n limit = Math.min(limit, cap);\n\n const chars = input.chars;\n const delivery: ContextDelivery = !include\n ? \"off\"\n : chars === null || chars === undefined || limit === 0 || chars > limit\n ? \"on_request\"\n : \"inline\";\n\n return {\n include,\n max_inline_chars: limit,\n delivery,\n decided_by: { include: includeBy, max_inline_chars: limitBy },\n clamped,\n };\n}\n\n// ── contextValueChars — RULES.md §1 ─────────────────────────────────────────\n\n/** Count Unicode code points (never UTF-16 units: `\"😀\".length` is 2). */\nfunction codePoints(s: string): number {\n let n = 0;\n for (const _ of s) n += 1;\n return n;\n}\n\n/**\n * Size of a value in code points, exactly as the server's `content_as_str()`:\n * a string is itself; anything else is `JSON.stringify(value, null, 2)`\n * (Python `json.dumps(value, ensure_ascii=False, indent=2)`). `undefined` → 0.\n */\nexport function contextValueChars(value: unknown): number {\n if (value === undefined) return 0;\n if (typeof value === \"string\") return codePoints(value);\n let text: string | undefined;\n try {\n text = JSON.stringify(value, null, 2);\n } catch {\n text = String(value);\n }\n return text === undefined ? 0 : codePoints(text);\n}\n\n// ── lookupSavedRule — RULES.md §3 ───────────────────────────────────────────\n\nfunction sanitizeRule(raw: unknown): SavedContextRule {\n const out: SavedContextRule = {};\n if (!raw || typeof raw !== \"object\") return out;\n const r = raw as Record<string, unknown>;\n if (typeof r.include === \"boolean\") out.include = r.include;\n if (isValidLimit(r.max_inline_chars)) out.max_inline_chars = r.max_inline_chars;\n return out;\n}\n\n/**\n * The person's rule for one key on one surface: `_default[key]` merged per\n * field under `rows[surfaceKey][key]`, invalid fields stripped. `null` when\n * nothing valid is saved.\n */\nexport function lookupSavedRule(\n rows: SavedContextRuleRows | null | undefined,\n surfaceKey: string,\n key: string,\n): SavedContextRule | null {\n if (!rows || typeof rows !== \"object\") return null;\n const base = sanitizeRule(rows[DEFAULT_SURFACE_KEY]?.[key]);\n const own = surfaceKey === DEFAULT_SURFACE_KEY ? {} : sanitizeRule(rows[surfaceKey]?.[key]);\n const merged: SavedContextRule = { ...base, ...own };\n return Object.keys(merged).length === 0 ? null : merged;\n}\n\n// ── serverDefaultMax — the server's per-key default, read from the wire value ─\n\n/**\n * The keys that make a dict the rich envelope form — exactly aidream\n * `context_objects._RICH_FORM_KEYS`. A dict with ANY other key is raw content\n * on the server, `content` key or not.\n */\nexport const RICH_ENVELOPE_KEYS: ReadonlySet<string> = new Set([\n \"content\",\n \"mutable\",\n \"persist\",\n \"source\",\n \"type\",\n \"label\",\n \"description\",\n \"max_inline_chars\",\n \"default_max_inline_chars\",\n \"summary_agent_id\",\n]);\n\nconst UUID_RE = /^[0-9a-f]{8}-?[0-9a-f]{4}-?[0-9a-f]{4}-?[0-9a-f]{4}-?[0-9a-f]{12}$/i;\n\nfunction isPlainObject(v: unknown): v is Record<string, unknown> {\n return !!v && typeof v === \"object\" && !Array.isArray(v);\n}\n\n/** A canonical resource reference the server promotes (aidream `parse_resource_reference`). */\nfunction isResourceRef(v: Record<string, unknown>): boolean {\n if (v.__kind !== \"resource_ref\") return false;\n const type = v.resource_type || v.type;\n const id = v.resource_id || v.id;\n return (\n typeof type === \"string\" &&\n type.trim() !== \"\" &&\n typeof id === \"string\" &&\n UUID_RE.test(id.trim())\n );\n}\n\nfunction hasOwn(o: Record<string, unknown>, k: string): boolean {\n return Object.prototype.hasOwnProperty.call(o, k);\n}\n\nfunction onlyRichKeys(o: Record<string, unknown>): boolean {\n return Object.keys(o).every((k) => RICH_ENVELOPE_KEYS.has(k));\n}\n\nfunction hasLazySource(o: Record<string, unknown>): boolean {\n return isPlainObject(o.source) && typeof o.source.kind === \"string\";\n}\n\n/**\n * A rich envelope exactly as the server reads one (aidream\n * `_split_rich_context_value`): a plain object with its own `content` whose\n * every key is in `RICH_ENVELOPE_KEYS`. A record that merely HAS `content`\n * (a note `{ id, title, content }`) is raw content, not an envelope.\n */\nexport function isRichEnvelope(value: unknown): value is Record<string, unknown> {\n return isPlainObject(value) && hasOwn(value, \"content\") && onlyRichKeys(value);\n}\n\n/** The source-only lazy pointer form: rich keys only, no `content`, a `source` with a `kind`. */\nfunction isLazyPointer(value: unknown): value is Record<string, unknown> {\n return isPlainObject(value) && !hasOwn(value, \"content\") && onlyRichKeys(value) && hasLazySource(value);\n}\n\n/**\n * Split one wire value into `{ content, overrides }` exactly as aidream\n * `_split_rich_context_value`: a rich envelope or lazy pointer → its `content`\n * (null when absent) and every other key; anything else → the whole value as\n * content, no overrides.\n */\nexport function splitContextValue(value: unknown): {\n content: unknown;\n overrides: Record<string, unknown>;\n} {\n if (isRichEnvelope(value) || isLazyPointer(value)) {\n const { content = null, ...overrides } = value;\n return { content, overrides };\n }\n return { content: value, overrides: {} };\n}\n\n/**\n * The server's default inline limit for one wire value — the `default` layer\n * the server will use, before page/agent/you. Mirrors aidream\n * `context_rules._server_default_max` over the object `ContextManifest.build`\n * makes from the value (after `normalize_resource_context`):\n *\n * - a rich envelope (every key in the rich-form set, and `content` present or a\n * lazy `source` with a string `kind`) carrying a whole-number, non-negative\n * `default_max_inline_chars` → that;\n * - a top-level resource reference (`__kind: \"resource_ref\"` with a type and a\n * UUID id), or a lazy source whose `extra.resource_type` is set → 0\n * (descriptor-first);\n * - a lazy source (a `source` and null/absent `content`) → 6000;\n * - anything else → null (the server's 200).\n *\n * A resource reference nested as an envelope's `content` is NOT promoted by the\n * server (it reads top-level values only), so it is plain JSON content → null.\n */\nexport function serverDefaultMax(value: unknown): number | null {\n if (!isPlainObject(value)) return null;\n if (isResourceRef(value)) return 0;\n if (!isRichEnvelope(value) && !isLazyPointer(value)) return null;\n if (isValidLimit(value.default_max_inline_chars)) return value.default_max_inline_chars;\n if (hasLazySource(value) && (value.content === null || value.content === undefined)) {\n const src = value.source as Record<string, unknown>;\n const extra = isPlainObject(src.extra) ? src.extra : {};\n return extra.resource_type ? 0 : LAZY_SOURCE_DEFAULT_MAX;\n }\n return null;\n}\n\n// ── resolveContextRow — the glue a client runs per value ────────────────────\n\n/** Resolve one value into the row the table renders and the wire sends. */\nexport function resolveContextRow(\n source: ContextRowSource,\n savedRows: SavedContextRuleRows | null | undefined,\n cap: number = DEFAULT_INLINE_CAP,\n): ResolvedContextRow {\n const surfaceKey = source.surfaceKey ?? DEFAULT_SURFACE_KEY;\n const chars =\n source.chars !== undefined\n ? source.chars\n : unresolvedSize(source.value)\n ? null\n : contextValueChars(splitContextValue(source.value).content);\n const userRule = lookupSavedRule(savedRows, surfaceKey, source.key);\n const layers: ContextRuleLayers = { ...(source.layers ?? {}) };\n if (layers.default_max === undefined || layers.default_max === null) {\n const serverDefault = serverDefaultMax(source.value);\n if (serverDefault !== null) layers.default_max = serverDefault;\n }\n const result = resolveContextRule({ ...layers, chars, user: userRule, cap });\n const row: ResolvedContextRow = {\n ...result,\n key: source.key,\n label: source.label,\n surfaceKey,\n origin: source.origin,\n value: source.value,\n chars,\n userRule,\n };\n if (source.description !== undefined) row.description = source.description;\n if (source.type !== undefined) row.type = source.type;\n if (source.layers !== undefined || layers.default_max !== undefined) row.layers = layers;\n return row;\n}\n\n// ── buildContextWire — the ONLY producer of a request's `context` ──────────\n\n/**\n * Size the server cannot know yet: a lazy pointer (or an envelope with a lazy\n * source and null content), or a resource reference it will promote to one.\n */\nfunction unresolvedSize(value: unknown): boolean {\n if (!isPlainObject(value)) return false;\n if (isResourceRef(value)) return true;\n return (\n (isRichEnvelope(value) || isLazyPointer(value)) &&\n hasLazySource(value) &&\n (value.content === null || value.content === undefined)\n );\n}\n\n/**\n * Build the request's `context` object from the resolved rows. This is the\n * ONLY function that may produce it, so what the table shows and what the\n * request carries cannot drift apart.\n *\n * - A row whose `include` is false is omitted entirely — it never reaches the\n * wire (RULES.md §1: off means off everywhere).\n * - An included row becomes `{ content, type?, label, description?,\n * max_inline_chars? }`. An already-rich envelope (`isRichEnvelope`, e.g. a\n * working document carrying `source`/`mutable`/`persist`, or a lazy pointer)\n * keeps every non-rule field it already has; `label`/`description`/`type`\n * are only filled when missing.\n * - `max_inline_chars` on the envelope is the PAGE layer's value only\n * (`layers.surface.max_inline_chars`, else the envelope's own page value).\n * The server reads the person's saved rules itself every turn (RULES.md §3)\n * and the agent layer from its own policy, so the wire must never carry the\n * person's number or the resolved effective limit dressed up as a page rule\n * — that would make a stale client value outrank the server's fresh read.\n */\nexport function buildContextWire(rows: readonly ResolvedContextRow[]): Record<string, unknown> {\n const wire: Record<string, unknown> = {};\n for (const row of rows) {\n if (!row.include) continue;\n // Rich: an envelope with `content`, or a source-only lazy pointer. Every\n // non-rule field survives — including `default_max_inline_chars`, the\n // server's per-key default (not a person's or a page's number).\n // A record that merely has `content` is NOT rich: it is wrapped whole, or\n // the server would read it as raw content and drop the envelope fields.\n const rich = isRichEnvelope(row.value) || isLazyPointer(row.value);\n const envelope: Record<string, unknown> = rich\n ? { ...(row.value as Record<string, unknown>) }\n : { content: row.value };\n delete envelope.max_inline_chars;\n if (envelope.type === undefined && row.type !== undefined) envelope.type = row.type;\n if (envelope.label === undefined) envelope.label = row.label;\n if (envelope.description === undefined && row.description !== undefined) {\n envelope.description = row.description;\n }\n const pageLimit = row.layers?.surface?.max_inline_chars;\n const ownPageLimit = rich ? (row.value as Record<string, unknown>).max_inline_chars : undefined;\n if (isValidLimit(pageLimit)) envelope.max_inline_chars = pageLimit;\n else if (isValidLimit(ownPageLimit)) envelope.max_inline_chars = ownPageLimit;\n wire[row.key] = envelope;\n }\n return wire;\n}\n\n// ── compareReceipt — RULES.md §6 ────────────────────────────────────────────\n\nexport interface ContextReceiptMismatch {\n key: string;\n field: \"include\" | \"max_inline_chars\" | \"delivery\" | \"user_rule\" | \"missing\";\n expected: unknown;\n actual: unknown;\n}\n\nfunction normalizeUserRule(rule: SavedContextRule | null | undefined): SavedContextRule | null {\n const clean = sanitizeRule(rule);\n return Object.keys(clean).length === 0 ? null : clean;\n}\n\nfunction sameRule(a: SavedContextRule | null, b: SavedContextRule | null): boolean {\n if (a === null || b === null) return a === b;\n return a.include === b.include && a.max_inline_chars === b.max_inline_chars;\n}\n\n/**\n * Compare the rows the client resolved (and rendered, and sent) with the\n * receipt the server emitted for the same turn.\n *\n * For each expected row, the receipt row is matched by key + surface key, then\n * by key alone. Compared: `include`, `max_inline_chars`, `delivery` (only when\n * both sides know the size) and `user_rule`. An expected row absent from the\n * receipt is a `missing` mismatch — except a row the client correctly withheld\n * for a reason other than the person's own rule (page `auto_context` off, the\n * agent's kill switch): the client never sent it and the server lists only\n * the person's saved `include: false` rules as `origin: \"rule\"` rows, so its\n * absence is the truth, not a lie. Receipt rows nobody expected are returned\n * as `systemRows` — shown, never hidden.\n */\nexport function compareReceipt(\n expected: readonly ResolvedContextRow[],\n receipt: ContextReceipt,\n): { mismatches: ContextReceiptMismatch[]; systemRows: ContextReceiptRow[]; modelReadsContext: boolean } {\n const mismatches: ContextReceiptMismatch[] = [];\n // A turn whose model reads no context delivers nothing by design: every row\n // is `delivery: \"off\"`, `blocked_by: \"model\"`. That is not a lie about the\n // person's rules, so delivery is not compared; include / limit / rule still are.\n const modelReadsContext = receipt.model_reads_context !== false;\n const used = new Set<ContextReceiptRow>();\n const rows = receipt.rows ?? [];\n\n for (const exp of expected) {\n const actual =\n rows.find((r) => !used.has(r) && r.key === exp.key && r.surface_key === exp.surfaceKey) ??\n rows.find((r) => !used.has(r) && r.key === exp.key);\n if (!actual) {\n const withheldByOthers = !exp.include && exp.decided_by.include !== \"you\";\n if (!withheldByOthers) {\n mismatches.push({ key: exp.key, field: \"missing\", expected: exp.delivery, actual: null });\n }\n continue;\n }\n used.add(actual);\n if (actual.include !== exp.include) {\n mismatches.push({ key: exp.key, field: \"include\", expected: exp.include, actual: actual.include });\n }\n if (actual.max_inline_chars !== exp.max_inline_chars) {\n mismatches.push({\n key: exp.key,\n field: \"max_inline_chars\",\n expected: exp.max_inline_chars,\n actual: actual.max_inline_chars,\n });\n }\n if (\n modelReadsContext &&\n exp.chars !== null &&\n actual.chars !== null &&\n actual.delivery !== exp.delivery\n ) {\n mismatches.push({ key: exp.key, field: \"delivery\", expected: exp.delivery, actual: actual.delivery });\n }\n const expRule = normalizeUserRule(exp.userRule);\n const actRule = normalizeUserRule(actual.user_rule);\n if (!sameRule(expRule, actRule)) {\n mismatches.push({ key: exp.key, field: \"user_rule\", expected: expRule, actual: actRule });\n }\n }\n\n return { mismatches, systemRows: rows.filter((r) => !used.has(r)), modelReadsContext };\n}\n\n// ── receiptRowToResolved — read-only rows for a sent message ────────────────\n\n/**\n * Turn one receipt row into the row a read-only table renders (a sent\n * message's context, read back from `chat.message.model_context`). The value\n * itself is not in the receipt, so `value` is undefined.\n */\nexport function receiptRowToResolved(row: ContextReceiptRow): ResolvedContextRow {\n const origin: ContextRowOrigin =\n row.surface_key !== DEFAULT_SURFACE_KEY ? \"page\" : row.origin === \"client\" ? \"attached\" : \"system\";\n return {\n key: row.key,\n label: row.label,\n surfaceKey: row.surface_key,\n origin,\n value: undefined,\n chars: row.chars,\n userRule: normalizeUserRule(row.user_rule),\n include: row.include,\n max_inline_chars: row.max_inline_chars,\n delivery: row.delivery,\n decided_by: { include: row.decided_by.include, max_inline_chars: row.decided_by.max_inline_chars },\n clamped: row.clamped,\n };\n}\n\n// ── Display ─────────────────────────────────────────────────────────────────\n\n/** Compact size for a tabular column: `950`, `18.4k`, `184k`, `1.2M`, `—` for unknown. */\nexport function formatChars(n: number | null | undefined): string {\n if (n === null || n === undefined || !Number.isFinite(n)) return \"—\";\n const abs = Math.abs(n);\n const trim = (s: string) => s.replace(/\\.0$/, \"\");\n if (abs < 1000) return String(Math.round(n));\n if (abs < 100_000) return `${trim((n / 1000).toFixed(1))}k`;\n if (abs < 1_000_000) return `${Math.round(n / 1000)}k`;\n return `${trim((n / 1_000_000).toFixed(1))}M`;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAiBO,IAAM,wBAAwB;AAE9B,IAAM,sBAAsB;AAE5B,IAAM,2BAA2B;AAEjC,IAAM,qBAAqB;AAE3B,IAAM,0BAA0B;AAuIvC,IAAM,QAAQ,CAAC,MAA4B,OAAO,MAAM,YAAY,OAAO,SAAS,CAAC;AACrF,IAAM,eAAe,CAAC,MACpB,OAAO,MAAM,YAAY,OAAO,UAAU,CAAC,KAAK,KAAK;AAIhD,SAAS,mBAAmB,OAA4C;AAC7E,QAAM,QAAQ,MAAM,SAAS;AAC7B,QAAM,UAAU,MAAM,WAAW;AACjC,QAAM,OAAO,MAAM,QAAQ;AAC3B,QAAM,MAAM,MAAM,MAAM,GAAG,IAAI,MAAM,MAAM;AAC3C,QAAM,gBAAgB,OAAO,aAAa;AAE1C,MAAI,UAAU;AACd,MAAI,YAA0B;AAC9B,MAAI,SAAS,aAAa,QAAQ,QAAQ,iBAAiB,OAAO;AAChE,QAAI,eAAe;AACjB,gBAAU;AACV,kBAAY;AAAA,IACd,OAAO;AACL,gBAAU;AACV,kBAAY;AAAA,IACd;AAAA,EACF;AACA,MAAI,OAAO,gBAAgB,QAAQ,CAAC,eAAe;AACjD,cAAU;AACV,gBAAY;AAAA,EACd;AACA,MAAI,QAAQ,OAAO,KAAK,YAAY,WAAW;AAC7C,cAAU,KAAK;AACf,gBAAY;AAAA,EACd;AAEA,MAAI,QAAQ,MAAM,MAAM,WAAW,IAAI,MAAM,cAAc;AAC3D,MAAI,UAAwB;AAC5B,QAAM,IAAI,iBAAiB,MAAM,OAAO,gBAAgB,IAAI,MAAM,mBAAmB;AACrF,QAAM,IAAI,MAAM,SAAS,gBAAgB,IAAI,QAAQ,mBAAmB;AACxE,MAAI,MAAM,QAAQ,MAAM,MAAM;AAC5B,QAAI,IAAI,GAAG;AACT,cAAQ;AACR,gBAAU;AAAA,IACZ,OAAO;AACL,cAAQ;AACR,gBAAU;AAAA,IACZ;AAAA,EACF,WAAW,MAAM,MAAM;AACrB,YAAQ;AACR,cAAU;AAAA,EACZ,WAAW,MAAM,MAAM;AACrB,YAAQ;AACR,cAAU;AAAA,EACZ;AACA,MAAI,QAAQ,aAAa,KAAK,gBAAgB,GAAG;AAC/C,YAAQ,KAAK;AACb,cAAU;AAAA,EACZ;AACA,QAAM,UAAU,QAAQ;AACxB,UAAQ,KAAK,IAAI,OAAO,GAAG;AAE3B,QAAM,QAAQ,MAAM;AACpB,QAAM,WAA4B,CAAC,UAC/B,QACA,UAAU,QAAQ,UAAU,UAAa,UAAU,KAAK,QAAQ,QAC9D,eACA;AAEN,SAAO;AAAA,IACL;AAAA,IACA,kBAAkB;AAAA,IAClB;AAAA,IACA,YAAY,EAAE,SAAS,WAAW,kBAAkB,QAAQ;AAAA,IAC5D;AAAA,EACF;AACF;AAKA,SAAS,WAAW,GAAmB;AACrC,MAAI,IAAI;AACR,aAAW,KAAK,EAAG,MAAK;AACxB,SAAO;AACT;AAOO,SAAS,kBAAkB,OAAwB;AACxD,MAAI,UAAU,OAAW,QAAO;AAChC,MAAI,OAAO,UAAU,SAAU,QAAO,WAAW,KAAK;AACtD,MAAI;AACJ,MAAI;AACF,WAAO,KAAK,UAAU,OAAO,MAAM,CAAC;AAAA,EACtC,QAAQ;AACN,WAAO,OAAO,KAAK;AAAA,EACrB;AACA,SAAO,SAAS,SAAY,IAAI,WAAW,IAAI;AACjD;AAIA,SAAS,aAAa,KAAgC;AACpD,QAAM,MAAwB,CAAC;AAC/B,MAAI,CAAC,OAAO,OAAO,QAAQ,SAAU,QAAO;AAC5C,QAAM,IAAI;AACV,MAAI,OAAO,EAAE,YAAY,UAAW,KAAI,UAAU,EAAE;AACpD,MAAI,aAAa,EAAE,gBAAgB,EAAG,KAAI,mBAAmB,EAAE;AAC/D,SAAO;AACT;AAOO,SAAS,gBACd,MACA,YACA,KACyB;AACzB,MAAI,CAAC,QAAQ,OAAO,SAAS,SAAU,QAAO;AAC9C,QAAM,OAAO,aAAa,KAAK,mBAAmB,IAAI,GAAG,CAAC;AAC1D,QAAM,MAAM,eAAe,sBAAsB,CAAC,IAAI,aAAa,KAAK,UAAU,IAAI,GAAG,CAAC;AAC1F,QAAM,SAA2B,EAAE,GAAG,MAAM,GAAG,IAAI;AACnD,SAAO,OAAO,KAAK,MAAM,EAAE,WAAW,IAAI,OAAO;AACnD;AASO,IAAM,qBAA0C,oBAAI,IAAI;AAAA,EAC7D;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAED,IAAM,UAAU;AAEhB,SAAS,cAAc,GAA0C;AAC/D,SAAO,CAAC,CAAC,KAAK,OAAO,MAAM,YAAY,CAAC,MAAM,QAAQ,CAAC;AACzD;AAGA,SAAS,cAAc,GAAqC;AAC1D,MAAI,EAAE,WAAW,eAAgB,QAAO;AACxC,QAAM,OAAO,EAAE,iBAAiB,EAAE;AAClC,QAAM,KAAK,EAAE,eAAe,EAAE;AAC9B,SACE,OAAO,SAAS,YAChB,KAAK,KAAK,MAAM,MAChB,OAAO,OAAO,YACd,QAAQ,KAAK,GAAG,KAAK,CAAC;AAE1B;AAEA,SAAS,OAAO,GAA4B,GAAoB;AAC9D,SAAO,OAAO,UAAU,eAAe,KAAK,GAAG,CAAC;AAClD;AAEA,SAAS,aAAa,GAAqC;AACzD,SAAO,OAAO,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,mBAAmB,IAAI,CAAC,CAAC;AAC9D;AAEA,SAAS,cAAc,GAAqC;AAC1D,SAAO,cAAc,EAAE,MAAM,KAAK,OAAO,EAAE,OAAO,SAAS;AAC7D;AAQO,SAAS,eAAe,OAAkD;AAC/E,SAAO,cAAc,KAAK,KAAK,OAAO,OAAO,SAAS,KAAK,aAAa,KAAK;AAC/E;AAGA,SAAS,cAAc,OAAkD;AACvE,SAAO,cAAc,KAAK,KAAK,CAAC,OAAO,OAAO,SAAS,KAAK,aAAa,KAAK,KAAK,cAAc,KAAK;AACxG;AAQO,SAAS,kBAAkB,OAGhC;AACA,MAAI,eAAe,KAAK,KAAK,cAAc,KAAK,GAAG;AACjD,UAAM,EAAE,UAAU,MAAM,GAAG,UAAU,IAAI;AACzC,WAAO,EAAE,SAAS,UAAU;AAAA,EAC9B;AACA,SAAO,EAAE,SAAS,OAAO,WAAW,CAAC,EAAE;AACzC;AAoBO,SAAS,iBAAiB,OAA+B;AAC9D,MAAI,CAAC,cAAc,KAAK,EAAG,QAAO;AAClC,MAAI,cAAc,KAAK,EAAG,QAAO;AACjC,MAAI,CAAC,eAAe,KAAK,KAAK,CAAC,cAAc,KAAK,EAAG,QAAO;AAC5D,MAAI,aAAa,MAAM,wBAAwB,EAAG,QAAO,MAAM;AAC/D,MAAI,cAAc,KAAK,MAAM,MAAM,YAAY,QAAQ,MAAM,YAAY,SAAY;AACnF,UAAM,MAAM,MAAM;AAClB,UAAM,QAAQ,cAAc,IAAI,KAAK,IAAI,IAAI,QAAQ,CAAC;AACtD,WAAO,MAAM,gBAAgB,IAAI;AAAA,EACnC;AACA,SAAO;AACT;AAKO,SAAS,kBACd,QACA,WACA,MAAc,oBACM;AACpB,QAAM,aAAa,OAAO,cAAc;AACxC,QAAM,QACJ,OAAO,UAAU,SACb,OAAO,QACP,eAAe,OAAO,KAAK,IACzB,OACA,kBAAkB,kBAAkB,OAAO,KAAK,EAAE,OAAO;AACjE,QAAM,WAAW,gBAAgB,WAAW,YAAY,OAAO,GAAG;AAClE,QAAM,SAA4B,EAAE,GAAI,OAAO,UAAU,CAAC,EAAG;AAC7D,MAAI,OAAO,gBAAgB,UAAa,OAAO,gBAAgB,MAAM;AACnE,UAAM,gBAAgB,iBAAiB,OAAO,KAAK;AACnD,QAAI,kBAAkB,KAAM,QAAO,cAAc;AAAA,EACnD;AACA,QAAM,SAAS,mBAAmB,EAAE,GAAG,QAAQ,OAAO,MAAM,UAAU,IAAI,CAAC;AAC3E,QAAM,MAA0B;AAAA,IAC9B,GAAG;AAAA,IACH,KAAK,OAAO;AAAA,IACZ,OAAO,OAAO;AAAA,IACd;AAAA,IACA,QAAQ,OAAO;AAAA,IACf,OAAO,OAAO;AAAA,IACd;AAAA,IACA;AAAA,EACF;AACA,MAAI,OAAO,gBAAgB,OAAW,KAAI,cAAc,OAAO;AAC/D,MAAI,OAAO,SAAS,OAAW,KAAI,OAAO,OAAO;AACjD,MAAI,OAAO,WAAW,UAAa,OAAO,gBAAgB,OAAW,KAAI,SAAS;AAClF,SAAO;AACT;AAQA,SAAS,eAAe,OAAyB;AAC/C,MAAI,CAAC,cAAc,KAAK,EAAG,QAAO;AAClC,MAAI,cAAc,KAAK,EAAG,QAAO;AACjC,UACG,eAAe,KAAK,KAAK,cAAc,KAAK,MAC7C,cAAc,KAAK,MAClB,MAAM,YAAY,QAAQ,MAAM,YAAY;AAEjD;AAqBO,SAAS,iBAAiB,MAA8D;AAC7F,QAAM,OAAgC,CAAC;AACvC,aAAW,OAAO,MAAM;AACtB,QAAI,CAAC,IAAI,QAAS;AAMlB,UAAM,OAAO,eAAe,IAAI,KAAK,KAAK,cAAc,IAAI,KAAK;AACjE,UAAM,WAAoC,OACtC,EAAE,GAAI,IAAI,MAAkC,IAC5C,EAAE,SAAS,IAAI,MAAM;AACzB,WAAO,SAAS;AAChB,QAAI,SAAS,SAAS,UAAa,IAAI,SAAS,OAAW,UAAS,OAAO,IAAI;AAC/E,QAAI,SAAS,UAAU,OAAW,UAAS,QAAQ,IAAI;AACvD,QAAI,SAAS,gBAAgB,UAAa,IAAI,gBAAgB,QAAW;AACvE,eAAS,cAAc,IAAI;AAAA,IAC7B;AACA,UAAM,YAAY,IAAI,QAAQ,SAAS;AACvC,UAAM,eAAe,OAAQ,IAAI,MAAkC,mBAAmB;AACtF,QAAI,aAAa,SAAS,EAAG,UAAS,mBAAmB;AAAA,aAChD,aAAa,YAAY,EAAG,UAAS,mBAAmB;AACjE,SAAK,IAAI,GAAG,IAAI;AAAA,EAClB;AACA,SAAO;AACT;AAWA,SAAS,kBAAkB,MAAoE;AAC7F,QAAM,QAAQ,aAAa,IAAI;AAC/B,SAAO,OAAO,KAAK,KAAK,EAAE,WAAW,IAAI,OAAO;AAClD;AAEA,SAAS,SAAS,GAA4B,GAAqC;AACjF,MAAI,MAAM,QAAQ,MAAM,KAAM,QAAO,MAAM;AAC3C,SAAO,EAAE,YAAY,EAAE,WAAW,EAAE,qBAAqB,EAAE;AAC7D;AAgBO,SAAS,eACd,UACA,SACuG;AACvG,QAAM,aAAuC,CAAC;AAI9C,QAAM,oBAAoB,QAAQ,wBAAwB;AAC1D,QAAM,OAAO,oBAAI,IAAuB;AACxC,QAAM,OAAO,QAAQ,QAAQ,CAAC;AAE9B,aAAW,OAAO,UAAU;AAC1B,UAAM,SACJ,KAAK,KAAK,CAAC,MAAM,CAAC,KAAK,IAAI,CAAC,KAAK,EAAE,QAAQ,IAAI,OAAO,EAAE,gBAAgB,IAAI,UAAU,KACtF,KAAK,KAAK,CAAC,MAAM,CAAC,KAAK,IAAI,CAAC,KAAK,EAAE,QAAQ,IAAI,GAAG;AACpD,QAAI,CAAC,QAAQ;AACX,YAAM,mBAAmB,CAAC,IAAI,WAAW,IAAI,WAAW,YAAY;AACpE,UAAI,CAAC,kBAAkB;AACrB,mBAAW,KAAK,EAAE,KAAK,IAAI,KAAK,OAAO,WAAW,UAAU,IAAI,UAAU,QAAQ,KAAK,CAAC;AAAA,MAC1F;AACA;AAAA,IACF;AACA,SAAK,IAAI,MAAM;AACf,QAAI,OAAO,YAAY,IAAI,SAAS;AAClC,iBAAW,KAAK,EAAE,KAAK,IAAI,KAAK,OAAO,WAAW,UAAU,IAAI,SAAS,QAAQ,OAAO,QAAQ,CAAC;AAAA,IACnG;AACA,QAAI,OAAO,qBAAqB,IAAI,kBAAkB;AACpD,iBAAW,KAAK;AAAA,QACd,KAAK,IAAI;AAAA,QACT,OAAO;AAAA,QACP,UAAU,IAAI;AAAA,QACd,QAAQ,OAAO;AAAA,MACjB,CAAC;AAAA,IACH;AACA,QACE,qBACA,IAAI,UAAU,QACd,OAAO,UAAU,QACjB,OAAO,aAAa,IAAI,UACxB;AACA,iBAAW,KAAK,EAAE,KAAK,IAAI,KAAK,OAAO,YAAY,UAAU,IAAI,UAAU,QAAQ,OAAO,SAAS,CAAC;AAAA,IACtG;AACA,UAAM,UAAU,kBAAkB,IAAI,QAAQ;AAC9C,UAAM,UAAU,kBAAkB,OAAO,SAAS;AAClD,QAAI,CAAC,SAAS,SAAS,OAAO,GAAG;AAC/B,iBAAW,KAAK,EAAE,KAAK,IAAI,KAAK,OAAO,aAAa,UAAU,SAAS,QAAQ,QAAQ,CAAC;AAAA,IAC1F;AAAA,EACF;AAEA,SAAO,EAAE,YAAY,YAAY,KAAK,OAAO,CAAC,MAAM,CAAC,KAAK,IAAI,CAAC,CAAC,GAAG,kBAAkB;AACvF;AASO,SAAS,qBAAqB,KAA4C;AAC/E,QAAM,SACJ,IAAI,gBAAgB,sBAAsB,SAAS,IAAI,WAAW,WAAW,aAAa;AAC5F,SAAO;AAAA,IACL,KAAK,IAAI;AAAA,IACT,OAAO,IAAI;AAAA,IACX,YAAY,IAAI;AAAA,IAChB;AAAA,IACA,OAAO;AAAA,IACP,OAAO,IAAI;AAAA,IACX,UAAU,kBAAkB,IAAI,SAAS;AAAA,IACzC,SAAS,IAAI;AAAA,IACb,kBAAkB,IAAI;AAAA,IACtB,UAAU,IAAI;AAAA,IACd,YAAY,EAAE,SAAS,IAAI,WAAW,SAAS,kBAAkB,IAAI,WAAW,iBAAiB;AAAA,IACjG,SAAS,IAAI;AAAA,EACf;AACF;AAKO,SAAS,YAAY,GAAsC;AAChE,MAAI,MAAM,QAAQ,MAAM,UAAa,CAAC,OAAO,SAAS,CAAC,EAAG,QAAO;AACjE,QAAM,MAAM,KAAK,IAAI,CAAC;AACtB,QAAM,OAAO,CAAC,MAAc,EAAE,QAAQ,QAAQ,EAAE;AAChD,MAAI,MAAM,IAAM,QAAO,OAAO,KAAK,MAAM,CAAC,CAAC;AAC3C,MAAI,MAAM,IAAS,QAAO,GAAG,MAAM,IAAI,KAAM,QAAQ,CAAC,CAAC,CAAC;AACxD,MAAI,MAAM,IAAW,QAAO,GAAG,KAAK,MAAM,IAAI,GAAI,CAAC;AACnD,SAAO,GAAG,MAAM,IAAI,KAAW,QAAQ,CAAC,CAAC,CAAC;AAC5C;","names":[]}
1
+ {"version":3,"sources":["../../context/index.ts"],"sourcesContent":["/**\n * @ai-matrx/agents/context — the context value rules, headless.\n *\n * Contract: common-docs/systems/scopes-context/context-delivery/RULES.md.\n * Parity corpus: ./rules-corpus.json (run here by rules.test.ts and by aidream's\n * pytest against the Python twin `resolve_context_rule`). Every function here\n * is pure, zero-React, and safe to import on a server.\n *\n * The one flow a client runs per turn:\n * rows = values.map(v => resolveContextRow(v, savedRows, cap)) // what the table shows\n * context = buildContextWire(rows) // what the request sends\n * …on `context_receipt`: compareReceipt(rows, receipt) // screen vs server\n */\n\n// ── Constants ───────────────────────────────────────────────────────────────\n\n/** `users.user_surface_state.feature` for the person's saved rules. */\nexport const CONTEXT_RULES_FEATURE = \"context_rules\";\n/** Surface key for values no surface emitted (attachments, server-added values). */\nexport const DEFAULT_SURFACE_KEY = \"_default\";\n/** The server's default inline threshold when no layer sets one. */\nexport const DEFAULT_INLINE_MAX_CHARS = 200;\n/** The default ceiling every resolved limit is clamped to (a server knob). */\nexport const DEFAULT_INLINE_CAP = 50000;\n/** A lazy document's server default (RULES.md §2: \"a small lazy document 6000\"). */\nexport const LAZY_SOURCE_DEFAULT_MAX = 6000;\n\n// ── Types ───────────────────────────────────────────────────────────────────\n\n/** Which layer decided a field — lowest to highest. */\nexport type ContextLayer = \"default\" | \"page\" | \"agent\" | \"you\";\nexport type ContextDelivery = \"inline\" | \"on_request\" | \"off\";\n\n/** One saved per-person rule (one key on one surface). Both fields optional. */\nexport interface SavedContextRule {\n include?: boolean;\n max_inline_chars?: number;\n}\n\n/** `{ [surface_key]: { [context key]: SavedContextRule } }` — the person's rows. */\nexport type SavedContextRuleRows = Record<string, Record<string, SavedContextRule>>;\n\n/** The agent layer for one key (RULES.md §2). */\nexport interface ContextAgentLayer {\n /** The agent's Context Policy declares this key (or the value is the agent's own seed). */\n declared?: boolean;\n /** The policy's ceiling; only read when `declared`. */\n max_inline_chars?: number | null;\n /** `auto_context_disabled` — closes every undeclared key. */\n kill_switch?: boolean;\n}\n\n/** The page layer for one key: the surface that emitted the value. */\nexport interface ContextSurfaceLayer {\n /** The surface declares this value (a `ui_surface_value` row / manifest entry). */\n declared?: boolean;\n /** `false` drops the value unless the agent declares it. */\n auto_context?: boolean | null;\n /** `inlineUpTo` / envelope `max_inline_chars` / DB column. */\n max_inline_chars?: number | null;\n}\n\n/** The non-person layers for one value — what the table's layer breakdown shows. */\nexport interface ContextRuleLayers {\n /** The server's per-key default limit; `null`/absent = 200. */\n default_max?: number | null;\n agent?: ContextAgentLayer | null;\n surface?: ContextSurfaceLayer | null;\n}\n\n/**\n * Input to `resolveContextRule`. Snake case on purpose: it is the corpus shape\n * and the Python twin's shape. `user` is typed loosely because invalid saved\n * values (non-boolean include, non-integer or negative limit) must be ignored,\n * not trusted.\n */\nexport interface ContextRuleInput extends ContextRuleLayers {\n /** Size in code points; `null` = an unresolved lazy source. */\n chars: number | null;\n user?: { include?: unknown; max_inline_chars?: unknown } | null;\n /** Ceiling; default `DEFAULT_INLINE_CAP`. */\n cap?: number;\n}\n\nexport interface ContextRuleResult {\n include: boolean;\n /** Effective limit, after the clamp. */\n max_inline_chars: number;\n delivery: ContextDelivery;\n decided_by: { include: ContextLayer; max_inline_chars: ContextLayer };\n clamped: boolean;\n}\n\n/** One receipt row — RULES.md §5, wire fields exactly. */\nexport interface ContextReceiptRow {\n key: string;\n label: string;\n surface_key: string;\n origin: \"client\" | \"server\" | \"surroundings\" | \"rule\";\n chars: number | null;\n include: boolean;\n max_inline_chars: number;\n delivery: ContextDelivery;\n decided_by: { include: ContextLayer; max_inline_chars: ContextLayer };\n user_rule: { include?: boolean; max_inline_chars?: number } | null;\n clamped: boolean;\n /** The CLIENT itself sent a value the person turned off. */\n client_sent_excluded: boolean;\n /** `\"model\"`: nothing reached a model that reads no context. */\n blocked_by: \"model\" | null;\n}\n\n/** The per-turn receipt the server streams as `context_receipt` — RULES.md §5. */\nexport interface ContextReceipt {\n version: 1;\n surface: string | null;\n cap: number;\n /** False: this turn's model cannot read context at all (every row `blocked_by: \"model\"`). */\n model_reads_context: boolean;\n /** The person's saved rules could not be read; the turn ran without them. */\n rules_error: string | null;\n rows: ContextReceiptRow[];\n}\n\nexport type ContextRowOrigin = \"page\" | \"attached\" | \"system\";\n\n/** A client's resolved row: what its table renders and what `buildContextWire` sends. */\nexport interface ResolvedContextRow extends ContextRuleResult {\n key: string;\n label: string;\n surfaceKey: string;\n origin: ContextRowOrigin;\n /** The raw value, or an already-rich envelope (`{ content, type, … }`). */\n value: unknown;\n chars: number | null;\n /** The saved rule that was applied (merged `_default` + surface row), or null. */\n userRule: SavedContextRule | null;\n description?: string;\n /** Context object type for the wire envelope (`text`, `json`, …). */\n type?: string;\n /** The non-person layers the rule was resolved from. Absent = default only. */\n layers?: ContextRuleLayers;\n}\n\n/** What a client knows about one value before resolution. */\nexport interface ContextRowSource {\n key: string;\n label: string;\n surfaceKey?: string;\n origin: ContextRowOrigin;\n value: unknown;\n /** Override the measured size (e.g. `null` for an unresolved lazy source). */\n chars?: number | null;\n description?: string;\n type?: string;\n layers?: ContextRuleLayers;\n}\n\n// ── Helpers ─────────────────────────────────────────────────────────────────\n\nconst isNum = (v: unknown): v is number => typeof v === \"number\" && Number.isFinite(v);\nconst isValidLimit = (v: unknown): v is number =>\n typeof v === \"number\" && Number.isInteger(v) && v >= 0;\n\n// ── resolveContextRule — RULES.md §2, line for line ─────────────────────────\n\nexport function resolveContextRule(input: ContextRuleInput): ContextRuleResult {\n const agent = input.agent ?? null;\n const surface = input.surface ?? null;\n const user = input.user ?? null;\n const cap = isNum(input.cap) ? input.cap : DEFAULT_INLINE_CAP;\n const agentDeclared = agent?.declared === true;\n\n let include = true;\n let includeBy: ContextLayer = \"default\";\n if (surface?.declared === true && surface.auto_context === false) {\n if (agentDeclared) {\n include = true;\n includeBy = \"agent\";\n } else {\n include = false;\n includeBy = \"page\";\n }\n }\n if (agent?.kill_switch === true && !agentDeclared) {\n include = false;\n includeBy = \"agent\";\n }\n if (user && typeof user.include === \"boolean\") {\n include = user.include;\n includeBy = \"you\";\n }\n\n let limit = isNum(input.default_max) ? input.default_max : DEFAULT_INLINE_MAX_CHARS;\n let limitBy: ContextLayer = \"default\";\n const a = agentDeclared && isNum(agent?.max_inline_chars) ? agent.max_inline_chars : null;\n const s = isNum(surface?.max_inline_chars) ? surface.max_inline_chars : null;\n if (a !== null && s !== null) {\n if (s < a) {\n limit = s;\n limitBy = \"page\";\n } else {\n limit = a;\n limitBy = \"agent\";\n }\n } else if (a !== null) {\n limit = a;\n limitBy = \"agent\";\n } else if (s !== null) {\n limit = s;\n limitBy = \"page\";\n }\n if (user && isValidLimit(user.max_inline_chars)) {\n limit = user.max_inline_chars;\n limitBy = \"you\";\n }\n const clamped = limit > cap;\n limit = Math.min(limit, cap);\n\n const chars = input.chars;\n const delivery: ContextDelivery = !include\n ? \"off\"\n : chars === null || chars === undefined || limit === 0 || chars > limit\n ? \"on_request\"\n : \"inline\";\n\n return {\n include,\n max_inline_chars: limit,\n delivery,\n decided_by: { include: includeBy, max_inline_chars: limitBy },\n clamped,\n };\n}\n\n// ── contextValueChars — RULES.md §1 ─────────────────────────────────────────\n\n/** Count Unicode code points (never UTF-16 units: `\"😀\".length` is 2). */\nfunction codePoints(s: string): number {\n let n = 0;\n for (const _ of s) n += 1;\n return n;\n}\n\n/**\n * Size of a value in code points, exactly as the server's `content_as_str()`:\n * a string is itself; anything else is `JSON.stringify(value, null, 2)`\n * (Python `json.dumps(value, ensure_ascii=False, indent=2)`). `undefined` → 0.\n */\nexport function contextValueChars(value: unknown): number {\n if (value === undefined) return 0;\n if (typeof value === \"string\") return codePoints(value);\n let text: string | undefined;\n try {\n text = JSON.stringify(value, null, 2);\n } catch {\n text = String(value);\n }\n return text === undefined ? 0 : codePoints(text);\n}\n\n// ── lookupSavedRule — RULES.md §3 ───────────────────────────────────────────\n\nfunction sanitizeRule(raw: unknown): SavedContextRule {\n const out: SavedContextRule = {};\n if (!raw || typeof raw !== \"object\") return out;\n const r = raw as Record<string, unknown>;\n if (typeof r.include === \"boolean\") out.include = r.include;\n if (isValidLimit(r.max_inline_chars)) out.max_inline_chars = r.max_inline_chars;\n return out;\n}\n\n/**\n * The person's rule for one key on one surface: `_default[key]` merged per\n * field under `rows[surfaceKey][key]`, invalid fields stripped. `null` when\n * nothing valid is saved.\n */\nexport function lookupSavedRule(\n rows: SavedContextRuleRows | null | undefined,\n surfaceKey: string,\n key: string,\n): SavedContextRule | null {\n if (!rows || typeof rows !== \"object\") return null;\n const base = sanitizeRule(rows[DEFAULT_SURFACE_KEY]?.[key]);\n const own = surfaceKey === DEFAULT_SURFACE_KEY ? {} : sanitizeRule(rows[surfaceKey]?.[key]);\n const merged: SavedContextRule = { ...base, ...own };\n return Object.keys(merged).length === 0 ? null : merged;\n}\n\n// ── serverDefaultMax — the server's per-key default, read from the wire value ─\n\n/**\n * The keys that make a dict the rich envelope form — exactly aidream\n * `context_objects._RICH_FORM_KEYS`. A dict with ANY other key is raw content\n * on the server, `content` key or not.\n */\nexport const RICH_ENVELOPE_KEYS: ReadonlySet<string> = new Set([\n \"content\",\n \"mutable\",\n \"persist\",\n \"source\",\n \"type\",\n \"label\",\n \"description\",\n \"max_inline_chars\",\n \"default_max_inline_chars\",\n \"summary_agent_id\",\n]);\n\nconst UUID_RE = /^[0-9a-f]{8}-?[0-9a-f]{4}-?[0-9a-f]{4}-?[0-9a-f]{4}-?[0-9a-f]{12}$/i;\n\nfunction isPlainObject(v: unknown): v is Record<string, unknown> {\n return !!v && typeof v === \"object\" && !Array.isArray(v);\n}\n\n/** A canonical resource reference the server promotes (aidream `parse_resource_reference`). */\nfunction isResourceRef(v: Record<string, unknown>): boolean {\n if (v.__kind !== \"resource_ref\") return false;\n const type = v.resource_type || v.type;\n const id = v.resource_id || v.id;\n return (\n typeof type === \"string\" &&\n type.trim() !== \"\" &&\n typeof id === \"string\" &&\n UUID_RE.test(id.trim())\n );\n}\n\nfunction hasOwn(o: Record<string, unknown>, k: string): boolean {\n return Object.prototype.hasOwnProperty.call(o, k);\n}\n\nfunction onlyRichKeys(o: Record<string, unknown>): boolean {\n return Object.keys(o).every((k) => RICH_ENVELOPE_KEYS.has(k));\n}\n\nfunction hasLazySource(o: Record<string, unknown>): boolean {\n return isPlainObject(o.source) && typeof o.source.kind === \"string\";\n}\n\n/**\n * A rich envelope exactly as the server reads one (aidream\n * `_split_rich_context_value`): a plain object with its own `content` whose\n * every key is in `RICH_ENVELOPE_KEYS`. A record that merely HAS `content`\n * (a note `{ id, title, content }`) is raw content, not an envelope.\n */\nexport function isRichEnvelope(value: unknown): value is Record<string, unknown> {\n return isPlainObject(value) && hasOwn(value, \"content\") && onlyRichKeys(value);\n}\n\n/** The source-only lazy pointer form: rich keys only, no `content`, a `source` with a `kind`. */\nfunction isLazyPointer(value: unknown): value is Record<string, unknown> {\n return isPlainObject(value) && !hasOwn(value, \"content\") && onlyRichKeys(value) && hasLazySource(value);\n}\n\n/**\n * Split one wire value into `{ content, overrides }` exactly as aidream\n * `_split_rich_context_value`: a rich envelope or lazy pointer → its `content`\n * (null when absent) and every other key; anything else → the whole value as\n * content, no overrides.\n */\nexport function splitContextValue(value: unknown): {\n content: unknown;\n overrides: Record<string, unknown>;\n} {\n if (isRichEnvelope(value) || isLazyPointer(value)) {\n const { content = null, ...overrides } = value;\n return { content, overrides };\n }\n return { content: value, overrides: {} };\n}\n\n/**\n * The server's default inline limit for one wire value — the `default` layer\n * the server will use, before page/agent/you. Mirrors aidream\n * `context_rules._server_default_max` over the object `ContextManifest.build`\n * makes from the value (after `normalize_resource_context`):\n *\n * - a rich envelope (every key in the rich-form set, and `content` present or a\n * lazy `source` with a string `kind`) carrying a whole-number, non-negative\n * `default_max_inline_chars` → that;\n * - a top-level resource reference (`__kind: \"resource_ref\"` with a type and a\n * UUID id), or a lazy source whose `extra.resource_type` is set → 0\n * (descriptor-first);\n * - a lazy source (a `source` and null/absent `content`) → 6000;\n * - anything else → null (the server's 200).\n *\n * A resource reference nested as an envelope's `content` is NOT promoted by the\n * server (it reads top-level values only), so it is plain JSON content → null.\n */\nexport function serverDefaultMax(value: unknown): number | null {\n if (!isPlainObject(value)) return null;\n if (isResourceRef(value)) return 0;\n if (!isRichEnvelope(value) && !isLazyPointer(value)) return null;\n if (isValidLimit(value.default_max_inline_chars)) return value.default_max_inline_chars;\n if (hasLazySource(value) && (value.content === null || value.content === undefined)) {\n const src = value.source as Record<string, unknown>;\n const extra = isPlainObject(src.extra) ? src.extra : {};\n return extra.resource_type ? 0 : LAZY_SOURCE_DEFAULT_MAX;\n }\n return null;\n}\n\n// ── resolveContextRow — the glue a client runs per value ────────────────────\n\n/** Resolve one value into the row the table renders and the wire sends. */\nexport function resolveContextRow(\n source: ContextRowSource,\n savedRows: SavedContextRuleRows | null | undefined,\n cap: number = DEFAULT_INLINE_CAP,\n): ResolvedContextRow {\n const surfaceKey = source.surfaceKey ?? DEFAULT_SURFACE_KEY;\n const chars =\n source.chars !== undefined\n ? source.chars\n : unresolvedSize(source.value)\n ? null\n : contextValueChars(splitContextValue(source.value).content);\n const userRule = lookupSavedRule(savedRows, surfaceKey, source.key);\n const layers: ContextRuleLayers = { ...(source.layers ?? {}) };\n if (layers.default_max === undefined || layers.default_max === null) {\n const serverDefault = serverDefaultMax(source.value);\n if (serverDefault !== null) layers.default_max = serverDefault;\n }\n const result = resolveContextRule({ ...layers, chars, user: userRule, cap });\n const row: ResolvedContextRow = {\n ...result,\n key: source.key,\n label: source.label,\n surfaceKey,\n origin: source.origin,\n value: source.value,\n chars,\n userRule,\n };\n if (source.description !== undefined) row.description = source.description;\n if (source.type !== undefined) row.type = source.type;\n if (source.layers !== undefined || layers.default_max !== undefined) row.layers = layers;\n return row;\n}\n\n// ── buildContextWire — the ONLY producer of a request's `context` ──────────\n\n/**\n * Size the server cannot know yet: a lazy pointer (or an envelope with a lazy\n * source and null content), or a resource reference it will promote to one.\n */\nfunction unresolvedSize(value: unknown): boolean {\n if (!isPlainObject(value)) return false;\n if (isResourceRef(value)) return true;\n return (\n (isRichEnvelope(value) || isLazyPointer(value)) &&\n hasLazySource(value) &&\n (value.content === null || value.content === undefined)\n );\n}\n\nconst KIND_RESOURCE_REF = \"resource_ref\";\n\n/**\n * Values the server acts on by their top-level shape in `request.context`\n * (aidream `normalize_resource_context` → `parse_resource_reference`, which\n * reads ONLY top-level values):\n *\n * - a resource reference — any object whose `__kind` is `\"resource_ref\"`\n * (its own promote/exclude/overlay policy rides inside it);\n * - a bare UUID string under a conventional `*_id` key (`file_id`,\n * `attached_file_id`, `current_note_id`, …), which the server promotes when\n * the noun is a registered resource type. The registry lives on the server,\n * so every `*_id` key with a UUID passes through; for an unregistered noun\n * the server reads the bare string exactly as it would read it wrapped.\n *\n * These ship exactly as-is: no label, type, description or limit added.\n */\nexport function isWireDirective(key: string, value: unknown): boolean {\n if (isPlainObject(value)) return value.__kind === KIND_RESOURCE_REF;\n return (\n typeof value === \"string\" &&\n key.trim().toLowerCase().endsWith(\"_id\") &&\n UUID_RE.test(value.trim())\n );\n}\n\n/**\n * Build the request's `context` object from the resolved rows. This is the\n * ONLY function that may produce it, so what the table shows and what the\n * request carries cannot drift apart.\n *\n * - A row whose `include` is false is omitted entirely — it never reaches the\n * wire (RULES.md §1: off means off everywhere).\n * - An included row becomes `{ content, type?, label, description?,\n * max_inline_chars? }`. An already-rich envelope (`isRichEnvelope`, e.g. a\n * working document carrying `source`/`mutable`/`persist`, or a lazy pointer)\n * keeps every non-rule field it already has; `label`/`description`/`type`\n * are only filled when missing.\n * - `max_inline_chars` on the envelope is the PAGE layer's value only\n * (`layers.surface.max_inline_chars`, else the envelope's own page value).\n * The server reads the person's saved rules itself every turn (RULES.md §3)\n * and the agent layer from its own policy, so the wire must never carry the\n * person's number or the resolved effective limit dressed up as a page rule\n * — that would make a stale client value outrank the server's fresh read.\n */\nexport function buildContextWire(rows: readonly ResolvedContextRow[]): Record<string, unknown> {\n const wire: Record<string, unknown> = {};\n for (const row of rows) {\n if (!row.include) continue;\n // Rich: an envelope with `content`, or a source-only lazy pointer. Every\n // non-rule field survives — including `default_max_inline_chars`, the\n // server's per-key default (not a person's or a page's number).\n // A record that merely has `content` is NOT rich: it is wrapped whole, or\n // the server would read it as raw content and drop the envelope fields.\n // A top-level directive the server acts on by SHAPE ships exactly as-is:\n // wrapped, it becomes plain JSON and silently stops resolving.\n if (isWireDirective(row.key, row.value)) {\n wire[row.key] = row.value;\n continue;\n }\n const rich = isRichEnvelope(row.value) || isLazyPointer(row.value);\n const envelope: Record<string, unknown> = rich\n ? { ...(row.value as Record<string, unknown>) }\n : { content: row.value };\n delete envelope.max_inline_chars;\n if (envelope.type === undefined && row.type !== undefined) envelope.type = row.type;\n if (envelope.label === undefined) envelope.label = row.label;\n if (envelope.description === undefined && row.description !== undefined) {\n envelope.description = row.description;\n }\n const pageLimit = row.layers?.surface?.max_inline_chars;\n const ownPageLimit = rich ? (row.value as Record<string, unknown>).max_inline_chars : undefined;\n if (isValidLimit(pageLimit)) envelope.max_inline_chars = pageLimit;\n else if (isValidLimit(ownPageLimit)) envelope.max_inline_chars = ownPageLimit;\n wire[row.key] = envelope;\n }\n return wire;\n}\n\n// ── compareReceipt — RULES.md §6 ────────────────────────────────────────────\n\nexport interface ContextReceiptMismatch {\n key: string;\n field: \"include\" | \"max_inline_chars\" | \"delivery\" | \"user_rule\" | \"missing\";\n expected: unknown;\n actual: unknown;\n}\n\nfunction normalizeUserRule(rule: SavedContextRule | null | undefined): SavedContextRule | null {\n const clean = sanitizeRule(rule);\n return Object.keys(clean).length === 0 ? null : clean;\n}\n\nfunction sameRule(a: SavedContextRule | null, b: SavedContextRule | null): boolean {\n if (a === null || b === null) return a === b;\n return a.include === b.include && a.max_inline_chars === b.max_inline_chars;\n}\n\n/**\n * Compare the rows the client resolved (and rendered, and sent) with the\n * receipt the server emitted for the same turn.\n *\n * For each expected row, the receipt row is matched by key + surface key, then\n * by key alone. Compared: `include`, `max_inline_chars`, `delivery` (only when\n * both sides know the size) and `user_rule`. An expected row absent from the\n * receipt is a `missing` mismatch — except a row the client correctly withheld\n * for a reason other than the person's own rule (page `auto_context` off, the\n * agent's kill switch): the client never sent it and the server lists only\n * the person's saved `include: false` rules as `origin: \"rule\"` rows, so its\n * absence is the truth, not a lie. Receipt rows nobody expected are returned\n * as `systemRows` — shown, never hidden.\n */\nexport function compareReceipt(\n expected: readonly ResolvedContextRow[],\n receipt: ContextReceipt,\n): { mismatches: ContextReceiptMismatch[]; systemRows: ContextReceiptRow[]; modelReadsContext: boolean } {\n const mismatches: ContextReceiptMismatch[] = [];\n // A turn whose model reads no context delivers nothing by design: every row\n // is `delivery: \"off\"`, `blocked_by: \"model\"`. That is not a lie about the\n // person's rules, so delivery is not compared; include / limit / rule still are.\n const modelReadsContext = receipt.model_reads_context !== false;\n const used = new Set<ContextReceiptRow>();\n const rows = receipt.rows ?? [];\n\n for (const exp of expected) {\n const actual =\n rows.find((r) => !used.has(r) && r.key === exp.key && r.surface_key === exp.surfaceKey) ??\n rows.find((r) => !used.has(r) && r.key === exp.key);\n if (!actual) {\n const withheldByOthers = !exp.include && exp.decided_by.include !== \"you\";\n if (!withheldByOthers) {\n mismatches.push({ key: exp.key, field: \"missing\", expected: exp.delivery, actual: null });\n }\n continue;\n }\n used.add(actual);\n if (actual.include !== exp.include) {\n mismatches.push({ key: exp.key, field: \"include\", expected: exp.include, actual: actual.include });\n }\n if (actual.max_inline_chars !== exp.max_inline_chars) {\n mismatches.push({\n key: exp.key,\n field: \"max_inline_chars\",\n expected: exp.max_inline_chars,\n actual: actual.max_inline_chars,\n });\n }\n if (\n modelReadsContext &&\n exp.chars !== null &&\n actual.chars !== null &&\n actual.delivery !== exp.delivery\n ) {\n mismatches.push({ key: exp.key, field: \"delivery\", expected: exp.delivery, actual: actual.delivery });\n }\n const expRule = normalizeUserRule(exp.userRule);\n const actRule = normalizeUserRule(actual.user_rule);\n if (!sameRule(expRule, actRule)) {\n mismatches.push({ key: exp.key, field: \"user_rule\", expected: expRule, actual: actRule });\n }\n }\n\n return { mismatches, systemRows: rows.filter((r) => !used.has(r)), modelReadsContext };\n}\n\n// ── receiptRowToResolved — read-only rows for a sent message ────────────────\n\n/**\n * Turn one receipt row into the row a read-only table renders (a sent\n * message's context, read back from `chat.message.model_context`). The value\n * itself is not in the receipt, so `value` is undefined.\n */\nexport function receiptRowToResolved(row: ContextReceiptRow): ResolvedContextRow {\n const origin: ContextRowOrigin =\n row.surface_key !== DEFAULT_SURFACE_KEY ? \"page\" : row.origin === \"client\" ? \"attached\" : \"system\";\n return {\n key: row.key,\n label: row.label,\n surfaceKey: row.surface_key,\n origin,\n value: undefined,\n chars: row.chars,\n userRule: normalizeUserRule(row.user_rule),\n include: row.include,\n max_inline_chars: row.max_inline_chars,\n delivery: row.delivery,\n decided_by: { include: row.decided_by.include, max_inline_chars: row.decided_by.max_inline_chars },\n clamped: row.clamped,\n layers: receiptLayers(row),\n };\n}\n\n/**\n * Rebuild the non-person layers from what the server applied, so a read-only\n * table shows the applied limit as the inherited value and marks the layer\n * that decided it. The limit goes on the layer named by\n * `decided_by.max_inline_chars` (`you` is carried by `userRule`); include is\n * rebuilt the same way for a page `auto_context` off or the agent's kill switch.\n */\nfunction receiptLayers(row: ContextReceiptRow): ContextRuleLayers {\n const layers: ContextRuleLayers = {};\n const limit = row.max_inline_chars;\n const limitBy = row.decided_by.max_inline_chars;\n const includeBy = row.decided_by.include;\n if (limitBy === \"default\") layers.default_max = limit;\n // Page auto_context off: the page dropped it, or the agent re-admitted it.\n const pageOff = (includeBy === \"page\" && !row.include) || (includeBy === \"agent\" && row.include);\n if (limitBy === \"page\" || pageOff) {\n layers.surface = {\n declared: true,\n auto_context: !pageOff,\n max_inline_chars: limitBy === \"page\" ? limit : null,\n };\n }\n if (limitBy === \"agent\") {\n // An agent limit means the key is declared; a declared key the agent decided to include\n // was re-admitted past a page auto_context off. (The kill switch never closes a declared key.)\n layers.agent = { declared: true, max_inline_chars: limit };\n } else if (includeBy === \"agent\") {\n // Off = the kill switch closing an undeclared key; on = a declaration re-admitting a\n // page auto_context-off value.\n layers.agent = row.include ? { declared: true } : { declared: false, kill_switch: true };\n }\n return layers;\n}\n\n// ── Display ─────────────────────────────────────────────────────────────────\n\n/** Compact size for a tabular column: `950`, `18.4k`, `184k`, `1.2M`, `—` for unknown. */\nexport function formatChars(n: number | null | undefined): string {\n if (n === null || n === undefined || !Number.isFinite(n)) return \"—\";\n const abs = Math.abs(n);\n const trim = (s: string) => s.replace(/\\.0$/, \"\");\n if (abs < 1000) return String(Math.round(n));\n if (abs < 100_000) return `${trim((n / 1000).toFixed(1))}k`;\n if (abs < 1_000_000) return `${Math.round(n / 1000)}k`;\n return `${trim((n / 1_000_000).toFixed(1))}M`;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAiBO,IAAM,wBAAwB;AAE9B,IAAM,sBAAsB;AAE5B,IAAM,2BAA2B;AAEjC,IAAM,qBAAqB;AAE3B,IAAM,0BAA0B;AAuIvC,IAAM,QAAQ,CAAC,MAA4B,OAAO,MAAM,YAAY,OAAO,SAAS,CAAC;AACrF,IAAM,eAAe,CAAC,MACpB,OAAO,MAAM,YAAY,OAAO,UAAU,CAAC,KAAK,KAAK;AAIhD,SAAS,mBAAmB,OAA4C;AAC7E,QAAM,QAAQ,MAAM,SAAS;AAC7B,QAAM,UAAU,MAAM,WAAW;AACjC,QAAM,OAAO,MAAM,QAAQ;AAC3B,QAAM,MAAM,MAAM,MAAM,GAAG,IAAI,MAAM,MAAM;AAC3C,QAAM,gBAAgB,OAAO,aAAa;AAE1C,MAAI,UAAU;AACd,MAAI,YAA0B;AAC9B,MAAI,SAAS,aAAa,QAAQ,QAAQ,iBAAiB,OAAO;AAChE,QAAI,eAAe;AACjB,gBAAU;AACV,kBAAY;AAAA,IACd,OAAO;AACL,gBAAU;AACV,kBAAY;AAAA,IACd;AAAA,EACF;AACA,MAAI,OAAO,gBAAgB,QAAQ,CAAC,eAAe;AACjD,cAAU;AACV,gBAAY;AAAA,EACd;AACA,MAAI,QAAQ,OAAO,KAAK,YAAY,WAAW;AAC7C,cAAU,KAAK;AACf,gBAAY;AAAA,EACd;AAEA,MAAI,QAAQ,MAAM,MAAM,WAAW,IAAI,MAAM,cAAc;AAC3D,MAAI,UAAwB;AAC5B,QAAM,IAAI,iBAAiB,MAAM,OAAO,gBAAgB,IAAI,MAAM,mBAAmB;AACrF,QAAM,IAAI,MAAM,SAAS,gBAAgB,IAAI,QAAQ,mBAAmB;AACxE,MAAI,MAAM,QAAQ,MAAM,MAAM;AAC5B,QAAI,IAAI,GAAG;AACT,cAAQ;AACR,gBAAU;AAAA,IACZ,OAAO;AACL,cAAQ;AACR,gBAAU;AAAA,IACZ;AAAA,EACF,WAAW,MAAM,MAAM;AACrB,YAAQ;AACR,cAAU;AAAA,EACZ,WAAW,MAAM,MAAM;AACrB,YAAQ;AACR,cAAU;AAAA,EACZ;AACA,MAAI,QAAQ,aAAa,KAAK,gBAAgB,GAAG;AAC/C,YAAQ,KAAK;AACb,cAAU;AAAA,EACZ;AACA,QAAM,UAAU,QAAQ;AACxB,UAAQ,KAAK,IAAI,OAAO,GAAG;AAE3B,QAAM,QAAQ,MAAM;AACpB,QAAM,WAA4B,CAAC,UAC/B,QACA,UAAU,QAAQ,UAAU,UAAa,UAAU,KAAK,QAAQ,QAC9D,eACA;AAEN,SAAO;AAAA,IACL;AAAA,IACA,kBAAkB;AAAA,IAClB;AAAA,IACA,YAAY,EAAE,SAAS,WAAW,kBAAkB,QAAQ;AAAA,IAC5D;AAAA,EACF;AACF;AAKA,SAAS,WAAW,GAAmB;AACrC,MAAI,IAAI;AACR,aAAW,KAAK,EAAG,MAAK;AACxB,SAAO;AACT;AAOO,SAAS,kBAAkB,OAAwB;AACxD,MAAI,UAAU,OAAW,QAAO;AAChC,MAAI,OAAO,UAAU,SAAU,QAAO,WAAW,KAAK;AACtD,MAAI;AACJ,MAAI;AACF,WAAO,KAAK,UAAU,OAAO,MAAM,CAAC;AAAA,EACtC,QAAQ;AACN,WAAO,OAAO,KAAK;AAAA,EACrB;AACA,SAAO,SAAS,SAAY,IAAI,WAAW,IAAI;AACjD;AAIA,SAAS,aAAa,KAAgC;AACpD,QAAM,MAAwB,CAAC;AAC/B,MAAI,CAAC,OAAO,OAAO,QAAQ,SAAU,QAAO;AAC5C,QAAM,IAAI;AACV,MAAI,OAAO,EAAE,YAAY,UAAW,KAAI,UAAU,EAAE;AACpD,MAAI,aAAa,EAAE,gBAAgB,EAAG,KAAI,mBAAmB,EAAE;AAC/D,SAAO;AACT;AAOO,SAAS,gBACd,MACA,YACA,KACyB;AACzB,MAAI,CAAC,QAAQ,OAAO,SAAS,SAAU,QAAO;AAC9C,QAAM,OAAO,aAAa,KAAK,mBAAmB,IAAI,GAAG,CAAC;AAC1D,QAAM,MAAM,eAAe,sBAAsB,CAAC,IAAI,aAAa,KAAK,UAAU,IAAI,GAAG,CAAC;AAC1F,QAAM,SAA2B,EAAE,GAAG,MAAM,GAAG,IAAI;AACnD,SAAO,OAAO,KAAK,MAAM,EAAE,WAAW,IAAI,OAAO;AACnD;AASO,IAAM,qBAA0C,oBAAI,IAAI;AAAA,EAC7D;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAED,IAAM,UAAU;AAEhB,SAAS,cAAc,GAA0C;AAC/D,SAAO,CAAC,CAAC,KAAK,OAAO,MAAM,YAAY,CAAC,MAAM,QAAQ,CAAC;AACzD;AAGA,SAAS,cAAc,GAAqC;AAC1D,MAAI,EAAE,WAAW,eAAgB,QAAO;AACxC,QAAM,OAAO,EAAE,iBAAiB,EAAE;AAClC,QAAM,KAAK,EAAE,eAAe,EAAE;AAC9B,SACE,OAAO,SAAS,YAChB,KAAK,KAAK,MAAM,MAChB,OAAO,OAAO,YACd,QAAQ,KAAK,GAAG,KAAK,CAAC;AAE1B;AAEA,SAAS,OAAO,GAA4B,GAAoB;AAC9D,SAAO,OAAO,UAAU,eAAe,KAAK,GAAG,CAAC;AAClD;AAEA,SAAS,aAAa,GAAqC;AACzD,SAAO,OAAO,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,mBAAmB,IAAI,CAAC,CAAC;AAC9D;AAEA,SAAS,cAAc,GAAqC;AAC1D,SAAO,cAAc,EAAE,MAAM,KAAK,OAAO,EAAE,OAAO,SAAS;AAC7D;AAQO,SAAS,eAAe,OAAkD;AAC/E,SAAO,cAAc,KAAK,KAAK,OAAO,OAAO,SAAS,KAAK,aAAa,KAAK;AAC/E;AAGA,SAAS,cAAc,OAAkD;AACvE,SAAO,cAAc,KAAK,KAAK,CAAC,OAAO,OAAO,SAAS,KAAK,aAAa,KAAK,KAAK,cAAc,KAAK;AACxG;AAQO,SAAS,kBAAkB,OAGhC;AACA,MAAI,eAAe,KAAK,KAAK,cAAc,KAAK,GAAG;AACjD,UAAM,EAAE,UAAU,MAAM,GAAG,UAAU,IAAI;AACzC,WAAO,EAAE,SAAS,UAAU;AAAA,EAC9B;AACA,SAAO,EAAE,SAAS,OAAO,WAAW,CAAC,EAAE;AACzC;AAoBO,SAAS,iBAAiB,OAA+B;AAC9D,MAAI,CAAC,cAAc,KAAK,EAAG,QAAO;AAClC,MAAI,cAAc,KAAK,EAAG,QAAO;AACjC,MAAI,CAAC,eAAe,KAAK,KAAK,CAAC,cAAc,KAAK,EAAG,QAAO;AAC5D,MAAI,aAAa,MAAM,wBAAwB,EAAG,QAAO,MAAM;AAC/D,MAAI,cAAc,KAAK,MAAM,MAAM,YAAY,QAAQ,MAAM,YAAY,SAAY;AACnF,UAAM,MAAM,MAAM;AAClB,UAAM,QAAQ,cAAc,IAAI,KAAK,IAAI,IAAI,QAAQ,CAAC;AACtD,WAAO,MAAM,gBAAgB,IAAI;AAAA,EACnC;AACA,SAAO;AACT;AAKO,SAAS,kBACd,QACA,WACA,MAAc,oBACM;AACpB,QAAM,aAAa,OAAO,cAAc;AACxC,QAAM,QACJ,OAAO,UAAU,SACb,OAAO,QACP,eAAe,OAAO,KAAK,IACzB,OACA,kBAAkB,kBAAkB,OAAO,KAAK,EAAE,OAAO;AACjE,QAAM,WAAW,gBAAgB,WAAW,YAAY,OAAO,GAAG;AAClE,QAAM,SAA4B,EAAE,GAAI,OAAO,UAAU,CAAC,EAAG;AAC7D,MAAI,OAAO,gBAAgB,UAAa,OAAO,gBAAgB,MAAM;AACnE,UAAM,gBAAgB,iBAAiB,OAAO,KAAK;AACnD,QAAI,kBAAkB,KAAM,QAAO,cAAc;AAAA,EACnD;AACA,QAAM,SAAS,mBAAmB,EAAE,GAAG,QAAQ,OAAO,MAAM,UAAU,IAAI,CAAC;AAC3E,QAAM,MAA0B;AAAA,IAC9B,GAAG;AAAA,IACH,KAAK,OAAO;AAAA,IACZ,OAAO,OAAO;AAAA,IACd;AAAA,IACA,QAAQ,OAAO;AAAA,IACf,OAAO,OAAO;AAAA,IACd;AAAA,IACA;AAAA,EACF;AACA,MAAI,OAAO,gBAAgB,OAAW,KAAI,cAAc,OAAO;AAC/D,MAAI,OAAO,SAAS,OAAW,KAAI,OAAO,OAAO;AACjD,MAAI,OAAO,WAAW,UAAa,OAAO,gBAAgB,OAAW,KAAI,SAAS;AAClF,SAAO;AACT;AAQA,SAAS,eAAe,OAAyB;AAC/C,MAAI,CAAC,cAAc,KAAK,EAAG,QAAO;AAClC,MAAI,cAAc,KAAK,EAAG,QAAO;AACjC,UACG,eAAe,KAAK,KAAK,cAAc,KAAK,MAC7C,cAAc,KAAK,MAClB,MAAM,YAAY,QAAQ,MAAM,YAAY;AAEjD;AAEA,IAAM,oBAAoB;AAiBnB,SAAS,gBAAgB,KAAa,OAAyB;AACpE,MAAI,cAAc,KAAK,EAAG,QAAO,MAAM,WAAW;AAClD,SACE,OAAO,UAAU,YACjB,IAAI,KAAK,EAAE,YAAY,EAAE,SAAS,KAAK,KACvC,QAAQ,KAAK,MAAM,KAAK,CAAC;AAE7B;AAqBO,SAAS,iBAAiB,MAA8D;AAC7F,QAAM,OAAgC,CAAC;AACvC,aAAW,OAAO,MAAM;AACtB,QAAI,CAAC,IAAI,QAAS;AAQlB,QAAI,gBAAgB,IAAI,KAAK,IAAI,KAAK,GAAG;AACvC,WAAK,IAAI,GAAG,IAAI,IAAI;AACpB;AAAA,IACF;AACA,UAAM,OAAO,eAAe,IAAI,KAAK,KAAK,cAAc,IAAI,KAAK;AACjE,UAAM,WAAoC,OACtC,EAAE,GAAI,IAAI,MAAkC,IAC5C,EAAE,SAAS,IAAI,MAAM;AACzB,WAAO,SAAS;AAChB,QAAI,SAAS,SAAS,UAAa,IAAI,SAAS,OAAW,UAAS,OAAO,IAAI;AAC/E,QAAI,SAAS,UAAU,OAAW,UAAS,QAAQ,IAAI;AACvD,QAAI,SAAS,gBAAgB,UAAa,IAAI,gBAAgB,QAAW;AACvE,eAAS,cAAc,IAAI;AAAA,IAC7B;AACA,UAAM,YAAY,IAAI,QAAQ,SAAS;AACvC,UAAM,eAAe,OAAQ,IAAI,MAAkC,mBAAmB;AACtF,QAAI,aAAa,SAAS,EAAG,UAAS,mBAAmB;AAAA,aAChD,aAAa,YAAY,EAAG,UAAS,mBAAmB;AACjE,SAAK,IAAI,GAAG,IAAI;AAAA,EAClB;AACA,SAAO;AACT;AAWA,SAAS,kBAAkB,MAAoE;AAC7F,QAAM,QAAQ,aAAa,IAAI;AAC/B,SAAO,OAAO,KAAK,KAAK,EAAE,WAAW,IAAI,OAAO;AAClD;AAEA,SAAS,SAAS,GAA4B,GAAqC;AACjF,MAAI,MAAM,QAAQ,MAAM,KAAM,QAAO,MAAM;AAC3C,SAAO,EAAE,YAAY,EAAE,WAAW,EAAE,qBAAqB,EAAE;AAC7D;AAgBO,SAAS,eACd,UACA,SACuG;AACvG,QAAM,aAAuC,CAAC;AAI9C,QAAM,oBAAoB,QAAQ,wBAAwB;AAC1D,QAAM,OAAO,oBAAI,IAAuB;AACxC,QAAM,OAAO,QAAQ,QAAQ,CAAC;AAE9B,aAAW,OAAO,UAAU;AAC1B,UAAM,SACJ,KAAK,KAAK,CAAC,MAAM,CAAC,KAAK,IAAI,CAAC,KAAK,EAAE,QAAQ,IAAI,OAAO,EAAE,gBAAgB,IAAI,UAAU,KACtF,KAAK,KAAK,CAAC,MAAM,CAAC,KAAK,IAAI,CAAC,KAAK,EAAE,QAAQ,IAAI,GAAG;AACpD,QAAI,CAAC,QAAQ;AACX,YAAM,mBAAmB,CAAC,IAAI,WAAW,IAAI,WAAW,YAAY;AACpE,UAAI,CAAC,kBAAkB;AACrB,mBAAW,KAAK,EAAE,KAAK,IAAI,KAAK,OAAO,WAAW,UAAU,IAAI,UAAU,QAAQ,KAAK,CAAC;AAAA,MAC1F;AACA;AAAA,IACF;AACA,SAAK,IAAI,MAAM;AACf,QAAI,OAAO,YAAY,IAAI,SAAS;AAClC,iBAAW,KAAK,EAAE,KAAK,IAAI,KAAK,OAAO,WAAW,UAAU,IAAI,SAAS,QAAQ,OAAO,QAAQ,CAAC;AAAA,IACnG;AACA,QAAI,OAAO,qBAAqB,IAAI,kBAAkB;AACpD,iBAAW,KAAK;AAAA,QACd,KAAK,IAAI;AAAA,QACT,OAAO;AAAA,QACP,UAAU,IAAI;AAAA,QACd,QAAQ,OAAO;AAAA,MACjB,CAAC;AAAA,IACH;AACA,QACE,qBACA,IAAI,UAAU,QACd,OAAO,UAAU,QACjB,OAAO,aAAa,IAAI,UACxB;AACA,iBAAW,KAAK,EAAE,KAAK,IAAI,KAAK,OAAO,YAAY,UAAU,IAAI,UAAU,QAAQ,OAAO,SAAS,CAAC;AAAA,IACtG;AACA,UAAM,UAAU,kBAAkB,IAAI,QAAQ;AAC9C,UAAM,UAAU,kBAAkB,OAAO,SAAS;AAClD,QAAI,CAAC,SAAS,SAAS,OAAO,GAAG;AAC/B,iBAAW,KAAK,EAAE,KAAK,IAAI,KAAK,OAAO,aAAa,UAAU,SAAS,QAAQ,QAAQ,CAAC;AAAA,IAC1F;AAAA,EACF;AAEA,SAAO,EAAE,YAAY,YAAY,KAAK,OAAO,CAAC,MAAM,CAAC,KAAK,IAAI,CAAC,CAAC,GAAG,kBAAkB;AACvF;AASO,SAAS,qBAAqB,KAA4C;AAC/E,QAAM,SACJ,IAAI,gBAAgB,sBAAsB,SAAS,IAAI,WAAW,WAAW,aAAa;AAC5F,SAAO;AAAA,IACL,KAAK,IAAI;AAAA,IACT,OAAO,IAAI;AAAA,IACX,YAAY,IAAI;AAAA,IAChB;AAAA,IACA,OAAO;AAAA,IACP,OAAO,IAAI;AAAA,IACX,UAAU,kBAAkB,IAAI,SAAS;AAAA,IACzC,SAAS,IAAI;AAAA,IACb,kBAAkB,IAAI;AAAA,IACtB,UAAU,IAAI;AAAA,IACd,YAAY,EAAE,SAAS,IAAI,WAAW,SAAS,kBAAkB,IAAI,WAAW,iBAAiB;AAAA,IACjG,SAAS,IAAI;AAAA,IACb,QAAQ,cAAc,GAAG;AAAA,EAC3B;AACF;AASA,SAAS,cAAc,KAA2C;AAChE,QAAM,SAA4B,CAAC;AACnC,QAAM,QAAQ,IAAI;AAClB,QAAM,UAAU,IAAI,WAAW;AAC/B,QAAM,YAAY,IAAI,WAAW;AACjC,MAAI,YAAY,UAAW,QAAO,cAAc;AAEhD,QAAM,UAAW,cAAc,UAAU,CAAC,IAAI,WAAa,cAAc,WAAW,IAAI;AACxF,MAAI,YAAY,UAAU,SAAS;AACjC,WAAO,UAAU;AAAA,MACf,UAAU;AAAA,MACV,cAAc,CAAC;AAAA,MACf,kBAAkB,YAAY,SAAS,QAAQ;AAAA,IACjD;AAAA,EACF;AACA,MAAI,YAAY,SAAS;AAGvB,WAAO,QAAQ,EAAE,UAAU,MAAM,kBAAkB,MAAM;AAAA,EAC3D,WAAW,cAAc,SAAS;AAGhC,WAAO,QAAQ,IAAI,UAAU,EAAE,UAAU,KAAK,IAAI,EAAE,UAAU,OAAO,aAAa,KAAK;AAAA,EACzF;AACA,SAAO;AACT;AAKO,SAAS,YAAY,GAAsC;AAChE,MAAI,MAAM,QAAQ,MAAM,UAAa,CAAC,OAAO,SAAS,CAAC,EAAG,QAAO;AACjE,QAAM,MAAM,KAAK,IAAI,CAAC;AACtB,QAAM,OAAO,CAAC,MAAc,EAAE,QAAQ,QAAQ,EAAE;AAChD,MAAI,MAAM,IAAM,QAAO,OAAO,KAAK,MAAM,CAAC,CAAC;AAC3C,MAAI,MAAM,IAAS,QAAO,GAAG,MAAM,IAAI,KAAM,QAAQ,CAAC,CAAC,CAAC;AACxD,MAAI,MAAM,IAAW,QAAO,GAAG,KAAK,MAAM,IAAI,GAAI,CAAC;AACnD,SAAO,GAAG,MAAM,IAAI,KAAW,QAAQ,CAAC,CAAC,CAAC;AAC5C;","names":[]}
@@ -206,6 +206,22 @@ declare function splitContextValue(value: unknown): {
206
206
  declare function serverDefaultMax(value: unknown): number | null;
207
207
  /** Resolve one value into the row the table renders and the wire sends. */
208
208
  declare function resolveContextRow(source: ContextRowSource, savedRows: SavedContextRuleRows | null | undefined, cap?: number): ResolvedContextRow;
209
+ /**
210
+ * Values the server acts on by their top-level shape in `request.context`
211
+ * (aidream `normalize_resource_context` → `parse_resource_reference`, which
212
+ * reads ONLY top-level values):
213
+ *
214
+ * - a resource reference — any object whose `__kind` is `"resource_ref"`
215
+ * (its own promote/exclude/overlay policy rides inside it);
216
+ * - a bare UUID string under a conventional `*_id` key (`file_id`,
217
+ * `attached_file_id`, `current_note_id`, …), which the server promotes when
218
+ * the noun is a registered resource type. The registry lives on the server,
219
+ * so every `*_id` key with a UUID passes through; for an unregistered noun
220
+ * the server reads the bare string exactly as it would read it wrapped.
221
+ *
222
+ * These ship exactly as-is: no label, type, description or limit added.
223
+ */
224
+ declare function isWireDirective(key: string, value: unknown): boolean;
209
225
  /**
210
226
  * Build the request's `context` object from the resolved rows. This is the
211
227
  * ONLY function that may produce it, so what the table shows and what the
@@ -260,4 +276,4 @@ declare function receiptRowToResolved(row: ContextReceiptRow): ResolvedContextRo
260
276
  /** Compact size for a tabular column: `950`, `18.4k`, `184k`, `1.2M`, `—` for unknown. */
261
277
  declare function formatChars(n: number | null | undefined): string;
262
278
 
263
- export { CONTEXT_RULES_FEATURE, type ContextAgentLayer, type ContextDelivery, type ContextLayer, type ContextReceipt, type ContextReceiptMismatch, type ContextReceiptRow, type ContextRowOrigin, type ContextRowSource, type ContextRuleInput, type ContextRuleLayers, type ContextRuleResult, type ContextSurfaceLayer, DEFAULT_INLINE_CAP, DEFAULT_INLINE_MAX_CHARS, DEFAULT_SURFACE_KEY, LAZY_SOURCE_DEFAULT_MAX, RICH_ENVELOPE_KEYS, type ResolvedContextRow, type SavedContextRule, type SavedContextRuleRows, buildContextWire, compareReceipt, contextValueChars, formatChars, isRichEnvelope, lookupSavedRule, receiptRowToResolved, resolveContextRow, resolveContextRule, serverDefaultMax, splitContextValue };
279
+ export { CONTEXT_RULES_FEATURE, type ContextAgentLayer, type ContextDelivery, type ContextLayer, type ContextReceipt, type ContextReceiptMismatch, type ContextReceiptRow, type ContextRowOrigin, type ContextRowSource, type ContextRuleInput, type ContextRuleLayers, type ContextRuleResult, type ContextSurfaceLayer, DEFAULT_INLINE_CAP, DEFAULT_INLINE_MAX_CHARS, DEFAULT_SURFACE_KEY, LAZY_SOURCE_DEFAULT_MAX, RICH_ENVELOPE_KEYS, type ResolvedContextRow, type SavedContextRule, type SavedContextRuleRows, buildContextWire, compareReceipt, contextValueChars, formatChars, isRichEnvelope, isWireDirective, lookupSavedRule, receiptRowToResolved, resolveContextRow, resolveContextRule, serverDefaultMax, splitContextValue };
@@ -206,6 +206,22 @@ declare function splitContextValue(value: unknown): {
206
206
  declare function serverDefaultMax(value: unknown): number | null;
207
207
  /** Resolve one value into the row the table renders and the wire sends. */
208
208
  declare function resolveContextRow(source: ContextRowSource, savedRows: SavedContextRuleRows | null | undefined, cap?: number): ResolvedContextRow;
209
+ /**
210
+ * Values the server acts on by their top-level shape in `request.context`
211
+ * (aidream `normalize_resource_context` → `parse_resource_reference`, which
212
+ * reads ONLY top-level values):
213
+ *
214
+ * - a resource reference — any object whose `__kind` is `"resource_ref"`
215
+ * (its own promote/exclude/overlay policy rides inside it);
216
+ * - a bare UUID string under a conventional `*_id` key (`file_id`,
217
+ * `attached_file_id`, `current_note_id`, …), which the server promotes when
218
+ * the noun is a registered resource type. The registry lives on the server,
219
+ * so every `*_id` key with a UUID passes through; for an unregistered noun
220
+ * the server reads the bare string exactly as it would read it wrapped.
221
+ *
222
+ * These ship exactly as-is: no label, type, description or limit added.
223
+ */
224
+ declare function isWireDirective(key: string, value: unknown): boolean;
209
225
  /**
210
226
  * Build the request's `context` object from the resolved rows. This is the
211
227
  * ONLY function that may produce it, so what the table shows and what the
@@ -260,4 +276,4 @@ declare function receiptRowToResolved(row: ContextReceiptRow): ResolvedContextRo
260
276
  /** Compact size for a tabular column: `950`, `18.4k`, `184k`, `1.2M`, `—` for unknown. */
261
277
  declare function formatChars(n: number | null | undefined): string;
262
278
 
263
- export { CONTEXT_RULES_FEATURE, type ContextAgentLayer, type ContextDelivery, type ContextLayer, type ContextReceipt, type ContextReceiptMismatch, type ContextReceiptRow, type ContextRowOrigin, type ContextRowSource, type ContextRuleInput, type ContextRuleLayers, type ContextRuleResult, type ContextSurfaceLayer, DEFAULT_INLINE_CAP, DEFAULT_INLINE_MAX_CHARS, DEFAULT_SURFACE_KEY, LAZY_SOURCE_DEFAULT_MAX, RICH_ENVELOPE_KEYS, type ResolvedContextRow, type SavedContextRule, type SavedContextRuleRows, buildContextWire, compareReceipt, contextValueChars, formatChars, isRichEnvelope, lookupSavedRule, receiptRowToResolved, resolveContextRow, resolveContextRule, serverDefaultMax, splitContextValue };
279
+ export { CONTEXT_RULES_FEATURE, type ContextAgentLayer, type ContextDelivery, type ContextLayer, type ContextReceipt, type ContextReceiptMismatch, type ContextReceiptRow, type ContextRowOrigin, type ContextRowSource, type ContextRuleInput, type ContextRuleLayers, type ContextRuleResult, type ContextSurfaceLayer, DEFAULT_INLINE_CAP, DEFAULT_INLINE_MAX_CHARS, DEFAULT_SURFACE_KEY, LAZY_SOURCE_DEFAULT_MAX, RICH_ENVELOPE_KEYS, type ResolvedContextRow, type SavedContextRule, type SavedContextRuleRows, buildContextWire, compareReceipt, contextValueChars, formatChars, isRichEnvelope, isWireDirective, lookupSavedRule, receiptRowToResolved, resolveContextRow, resolveContextRule, serverDefaultMax, splitContextValue };
@@ -183,10 +183,19 @@ function unresolvedSize(value) {
183
183
  if (isResourceRef(value)) return true;
184
184
  return (isRichEnvelope(value) || isLazyPointer(value)) && hasLazySource(value) && (value.content === null || value.content === void 0);
185
185
  }
186
+ var KIND_RESOURCE_REF = "resource_ref";
187
+ function isWireDirective(key, value) {
188
+ if (isPlainObject(value)) return value.__kind === KIND_RESOURCE_REF;
189
+ return typeof value === "string" && key.trim().toLowerCase().endsWith("_id") && UUID_RE.test(value.trim());
190
+ }
186
191
  function buildContextWire(rows) {
187
192
  const wire = {};
188
193
  for (const row of rows) {
189
194
  if (!row.include) continue;
195
+ if (isWireDirective(row.key, row.value)) {
196
+ wire[row.key] = row.value;
197
+ continue;
198
+ }
190
199
  const rich = isRichEnvelope(row.value) || isLazyPointer(row.value);
191
200
  const envelope = rich ? { ...row.value } : { content: row.value };
192
201
  delete envelope.max_inline_chars;
@@ -262,9 +271,31 @@ function receiptRowToResolved(row) {
262
271
  max_inline_chars: row.max_inline_chars,
263
272
  delivery: row.delivery,
264
273
  decided_by: { include: row.decided_by.include, max_inline_chars: row.decided_by.max_inline_chars },
265
- clamped: row.clamped
274
+ clamped: row.clamped,
275
+ layers: receiptLayers(row)
266
276
  };
267
277
  }
278
+ function receiptLayers(row) {
279
+ const layers = {};
280
+ const limit = row.max_inline_chars;
281
+ const limitBy = row.decided_by.max_inline_chars;
282
+ const includeBy = row.decided_by.include;
283
+ if (limitBy === "default") layers.default_max = limit;
284
+ const pageOff = includeBy === "page" && !row.include || includeBy === "agent" && row.include;
285
+ if (limitBy === "page" || pageOff) {
286
+ layers.surface = {
287
+ declared: true,
288
+ auto_context: !pageOff,
289
+ max_inline_chars: limitBy === "page" ? limit : null
290
+ };
291
+ }
292
+ if (limitBy === "agent") {
293
+ layers.agent = { declared: true, max_inline_chars: limit };
294
+ } else if (includeBy === "agent") {
295
+ layers.agent = row.include ? { declared: true } : { declared: false, kill_switch: true };
296
+ }
297
+ return layers;
298
+ }
268
299
  function formatChars(n) {
269
300
  if (n === null || n === void 0 || !Number.isFinite(n)) return "\u2014";
270
301
  const abs = Math.abs(n);
@@ -286,6 +317,7 @@ export {
286
317
  contextValueChars,
287
318
  formatChars,
288
319
  isRichEnvelope,
320
+ isWireDirective,
289
321
  lookupSavedRule,
290
322
  receiptRowToResolved,
291
323
  resolveContextRow,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../context/index.ts"],"sourcesContent":["/**\n * @ai-matrx/agents/context — the context value rules, headless.\n *\n * Contract: common-docs/systems/scopes-context/context-delivery/RULES.md.\n * Parity corpus: ./rules-corpus.json (run here by rules.test.ts and by aidream's\n * pytest against the Python twin `resolve_context_rule`). Every function here\n * is pure, zero-React, and safe to import on a server.\n *\n * The one flow a client runs per turn:\n * rows = values.map(v => resolveContextRow(v, savedRows, cap)) // what the table shows\n * context = buildContextWire(rows) // what the request sends\n * …on `context_receipt`: compareReceipt(rows, receipt) // screen vs server\n */\n\n// ── Constants ───────────────────────────────────────────────────────────────\n\n/** `users.user_surface_state.feature` for the person's saved rules. */\nexport const CONTEXT_RULES_FEATURE = \"context_rules\";\n/** Surface key for values no surface emitted (attachments, server-added values). */\nexport const DEFAULT_SURFACE_KEY = \"_default\";\n/** The server's default inline threshold when no layer sets one. */\nexport const DEFAULT_INLINE_MAX_CHARS = 200;\n/** The default ceiling every resolved limit is clamped to (a server knob). */\nexport const DEFAULT_INLINE_CAP = 50000;\n/** A lazy document's server default (RULES.md §2: \"a small lazy document 6000\"). */\nexport const LAZY_SOURCE_DEFAULT_MAX = 6000;\n\n// ── Types ───────────────────────────────────────────────────────────────────\n\n/** Which layer decided a field — lowest to highest. */\nexport type ContextLayer = \"default\" | \"page\" | \"agent\" | \"you\";\nexport type ContextDelivery = \"inline\" | \"on_request\" | \"off\";\n\n/** One saved per-person rule (one key on one surface). Both fields optional. */\nexport interface SavedContextRule {\n include?: boolean;\n max_inline_chars?: number;\n}\n\n/** `{ [surface_key]: { [context key]: SavedContextRule } }` — the person's rows. */\nexport type SavedContextRuleRows = Record<string, Record<string, SavedContextRule>>;\n\n/** The agent layer for one key (RULES.md §2). */\nexport interface ContextAgentLayer {\n /** The agent's Context Policy declares this key (or the value is the agent's own seed). */\n declared?: boolean;\n /** The policy's ceiling; only read when `declared`. */\n max_inline_chars?: number | null;\n /** `auto_context_disabled` — closes every undeclared key. */\n kill_switch?: boolean;\n}\n\n/** The page layer for one key: the surface that emitted the value. */\nexport interface ContextSurfaceLayer {\n /** The surface declares this value (a `ui_surface_value` row / manifest entry). */\n declared?: boolean;\n /** `false` drops the value unless the agent declares it. */\n auto_context?: boolean | null;\n /** `inlineUpTo` / envelope `max_inline_chars` / DB column. */\n max_inline_chars?: number | null;\n}\n\n/** The non-person layers for one value — what the table's layer breakdown shows. */\nexport interface ContextRuleLayers {\n /** The server's per-key default limit; `null`/absent = 200. */\n default_max?: number | null;\n agent?: ContextAgentLayer | null;\n surface?: ContextSurfaceLayer | null;\n}\n\n/**\n * Input to `resolveContextRule`. Snake case on purpose: it is the corpus shape\n * and the Python twin's shape. `user` is typed loosely because invalid saved\n * values (non-boolean include, non-integer or negative limit) must be ignored,\n * not trusted.\n */\nexport interface ContextRuleInput extends ContextRuleLayers {\n /** Size in code points; `null` = an unresolved lazy source. */\n chars: number | null;\n user?: { include?: unknown; max_inline_chars?: unknown } | null;\n /** Ceiling; default `DEFAULT_INLINE_CAP`. */\n cap?: number;\n}\n\nexport interface ContextRuleResult {\n include: boolean;\n /** Effective limit, after the clamp. */\n max_inline_chars: number;\n delivery: ContextDelivery;\n decided_by: { include: ContextLayer; max_inline_chars: ContextLayer };\n clamped: boolean;\n}\n\n/** One receipt row — RULES.md §5, wire fields exactly. */\nexport interface ContextReceiptRow {\n key: string;\n label: string;\n surface_key: string;\n origin: \"client\" | \"server\" | \"surroundings\" | \"rule\";\n chars: number | null;\n include: boolean;\n max_inline_chars: number;\n delivery: ContextDelivery;\n decided_by: { include: ContextLayer; max_inline_chars: ContextLayer };\n user_rule: { include?: boolean; max_inline_chars?: number } | null;\n clamped: boolean;\n /** The CLIENT itself sent a value the person turned off. */\n client_sent_excluded: boolean;\n /** `\"model\"`: nothing reached a model that reads no context. */\n blocked_by: \"model\" | null;\n}\n\n/** The per-turn receipt the server streams as `context_receipt` — RULES.md §5. */\nexport interface ContextReceipt {\n version: 1;\n surface: string | null;\n cap: number;\n /** False: this turn's model cannot read context at all (every row `blocked_by: \"model\"`). */\n model_reads_context: boolean;\n /** The person's saved rules could not be read; the turn ran without them. */\n rules_error: string | null;\n rows: ContextReceiptRow[];\n}\n\nexport type ContextRowOrigin = \"page\" | \"attached\" | \"system\";\n\n/** A client's resolved row: what its table renders and what `buildContextWire` sends. */\nexport interface ResolvedContextRow extends ContextRuleResult {\n key: string;\n label: string;\n surfaceKey: string;\n origin: ContextRowOrigin;\n /** The raw value, or an already-rich envelope (`{ content, type, … }`). */\n value: unknown;\n chars: number | null;\n /** The saved rule that was applied (merged `_default` + surface row), or null. */\n userRule: SavedContextRule | null;\n description?: string;\n /** Context object type for the wire envelope (`text`, `json`, …). */\n type?: string;\n /** The non-person layers the rule was resolved from. Absent = default only. */\n layers?: ContextRuleLayers;\n}\n\n/** What a client knows about one value before resolution. */\nexport interface ContextRowSource {\n key: string;\n label: string;\n surfaceKey?: string;\n origin: ContextRowOrigin;\n value: unknown;\n /** Override the measured size (e.g. `null` for an unresolved lazy source). */\n chars?: number | null;\n description?: string;\n type?: string;\n layers?: ContextRuleLayers;\n}\n\n// ── Helpers ─────────────────────────────────────────────────────────────────\n\nconst isNum = (v: unknown): v is number => typeof v === \"number\" && Number.isFinite(v);\nconst isValidLimit = (v: unknown): v is number =>\n typeof v === \"number\" && Number.isInteger(v) && v >= 0;\n\n// ── resolveContextRule — RULES.md §2, line for line ─────────────────────────\n\nexport function resolveContextRule(input: ContextRuleInput): ContextRuleResult {\n const agent = input.agent ?? null;\n const surface = input.surface ?? null;\n const user = input.user ?? null;\n const cap = isNum(input.cap) ? input.cap : DEFAULT_INLINE_CAP;\n const agentDeclared = agent?.declared === true;\n\n let include = true;\n let includeBy: ContextLayer = \"default\";\n if (surface?.declared === true && surface.auto_context === false) {\n if (agentDeclared) {\n include = true;\n includeBy = \"agent\";\n } else {\n include = false;\n includeBy = \"page\";\n }\n }\n if (agent?.kill_switch === true && !agentDeclared) {\n include = false;\n includeBy = \"agent\";\n }\n if (user && typeof user.include === \"boolean\") {\n include = user.include;\n includeBy = \"you\";\n }\n\n let limit = isNum(input.default_max) ? input.default_max : DEFAULT_INLINE_MAX_CHARS;\n let limitBy: ContextLayer = \"default\";\n const a = agentDeclared && isNum(agent?.max_inline_chars) ? agent.max_inline_chars : null;\n const s = isNum(surface?.max_inline_chars) ? surface.max_inline_chars : null;\n if (a !== null && s !== null) {\n if (s < a) {\n limit = s;\n limitBy = \"page\";\n } else {\n limit = a;\n limitBy = \"agent\";\n }\n } else if (a !== null) {\n limit = a;\n limitBy = \"agent\";\n } else if (s !== null) {\n limit = s;\n limitBy = \"page\";\n }\n if (user && isValidLimit(user.max_inline_chars)) {\n limit = user.max_inline_chars;\n limitBy = \"you\";\n }\n const clamped = limit > cap;\n limit = Math.min(limit, cap);\n\n const chars = input.chars;\n const delivery: ContextDelivery = !include\n ? \"off\"\n : chars === null || chars === undefined || limit === 0 || chars > limit\n ? \"on_request\"\n : \"inline\";\n\n return {\n include,\n max_inline_chars: limit,\n delivery,\n decided_by: { include: includeBy, max_inline_chars: limitBy },\n clamped,\n };\n}\n\n// ── contextValueChars — RULES.md §1 ─────────────────────────────────────────\n\n/** Count Unicode code points (never UTF-16 units: `\"😀\".length` is 2). */\nfunction codePoints(s: string): number {\n let n = 0;\n for (const _ of s) n += 1;\n return n;\n}\n\n/**\n * Size of a value in code points, exactly as the server's `content_as_str()`:\n * a string is itself; anything else is `JSON.stringify(value, null, 2)`\n * (Python `json.dumps(value, ensure_ascii=False, indent=2)`). `undefined` → 0.\n */\nexport function contextValueChars(value: unknown): number {\n if (value === undefined) return 0;\n if (typeof value === \"string\") return codePoints(value);\n let text: string | undefined;\n try {\n text = JSON.stringify(value, null, 2);\n } catch {\n text = String(value);\n }\n return text === undefined ? 0 : codePoints(text);\n}\n\n// ── lookupSavedRule — RULES.md §3 ───────────────────────────────────────────\n\nfunction sanitizeRule(raw: unknown): SavedContextRule {\n const out: SavedContextRule = {};\n if (!raw || typeof raw !== \"object\") return out;\n const r = raw as Record<string, unknown>;\n if (typeof r.include === \"boolean\") out.include = r.include;\n if (isValidLimit(r.max_inline_chars)) out.max_inline_chars = r.max_inline_chars;\n return out;\n}\n\n/**\n * The person's rule for one key on one surface: `_default[key]` merged per\n * field under `rows[surfaceKey][key]`, invalid fields stripped. `null` when\n * nothing valid is saved.\n */\nexport function lookupSavedRule(\n rows: SavedContextRuleRows | null | undefined,\n surfaceKey: string,\n key: string,\n): SavedContextRule | null {\n if (!rows || typeof rows !== \"object\") return null;\n const base = sanitizeRule(rows[DEFAULT_SURFACE_KEY]?.[key]);\n const own = surfaceKey === DEFAULT_SURFACE_KEY ? {} : sanitizeRule(rows[surfaceKey]?.[key]);\n const merged: SavedContextRule = { ...base, ...own };\n return Object.keys(merged).length === 0 ? null : merged;\n}\n\n// ── serverDefaultMax — the server's per-key default, read from the wire value ─\n\n/**\n * The keys that make a dict the rich envelope form — exactly aidream\n * `context_objects._RICH_FORM_KEYS`. A dict with ANY other key is raw content\n * on the server, `content` key or not.\n */\nexport const RICH_ENVELOPE_KEYS: ReadonlySet<string> = new Set([\n \"content\",\n \"mutable\",\n \"persist\",\n \"source\",\n \"type\",\n \"label\",\n \"description\",\n \"max_inline_chars\",\n \"default_max_inline_chars\",\n \"summary_agent_id\",\n]);\n\nconst UUID_RE = /^[0-9a-f]{8}-?[0-9a-f]{4}-?[0-9a-f]{4}-?[0-9a-f]{4}-?[0-9a-f]{12}$/i;\n\nfunction isPlainObject(v: unknown): v is Record<string, unknown> {\n return !!v && typeof v === \"object\" && !Array.isArray(v);\n}\n\n/** A canonical resource reference the server promotes (aidream `parse_resource_reference`). */\nfunction isResourceRef(v: Record<string, unknown>): boolean {\n if (v.__kind !== \"resource_ref\") return false;\n const type = v.resource_type || v.type;\n const id = v.resource_id || v.id;\n return (\n typeof type === \"string\" &&\n type.trim() !== \"\" &&\n typeof id === \"string\" &&\n UUID_RE.test(id.trim())\n );\n}\n\nfunction hasOwn(o: Record<string, unknown>, k: string): boolean {\n return Object.prototype.hasOwnProperty.call(o, k);\n}\n\nfunction onlyRichKeys(o: Record<string, unknown>): boolean {\n return Object.keys(o).every((k) => RICH_ENVELOPE_KEYS.has(k));\n}\n\nfunction hasLazySource(o: Record<string, unknown>): boolean {\n return isPlainObject(o.source) && typeof o.source.kind === \"string\";\n}\n\n/**\n * A rich envelope exactly as the server reads one (aidream\n * `_split_rich_context_value`): a plain object with its own `content` whose\n * every key is in `RICH_ENVELOPE_KEYS`. A record that merely HAS `content`\n * (a note `{ id, title, content }`) is raw content, not an envelope.\n */\nexport function isRichEnvelope(value: unknown): value is Record<string, unknown> {\n return isPlainObject(value) && hasOwn(value, \"content\") && onlyRichKeys(value);\n}\n\n/** The source-only lazy pointer form: rich keys only, no `content`, a `source` with a `kind`. */\nfunction isLazyPointer(value: unknown): value is Record<string, unknown> {\n return isPlainObject(value) && !hasOwn(value, \"content\") && onlyRichKeys(value) && hasLazySource(value);\n}\n\n/**\n * Split one wire value into `{ content, overrides }` exactly as aidream\n * `_split_rich_context_value`: a rich envelope or lazy pointer → its `content`\n * (null when absent) and every other key; anything else → the whole value as\n * content, no overrides.\n */\nexport function splitContextValue(value: unknown): {\n content: unknown;\n overrides: Record<string, unknown>;\n} {\n if (isRichEnvelope(value) || isLazyPointer(value)) {\n const { content = null, ...overrides } = value;\n return { content, overrides };\n }\n return { content: value, overrides: {} };\n}\n\n/**\n * The server's default inline limit for one wire value — the `default` layer\n * the server will use, before page/agent/you. Mirrors aidream\n * `context_rules._server_default_max` over the object `ContextManifest.build`\n * makes from the value (after `normalize_resource_context`):\n *\n * - a rich envelope (every key in the rich-form set, and `content` present or a\n * lazy `source` with a string `kind`) carrying a whole-number, non-negative\n * `default_max_inline_chars` → that;\n * - a top-level resource reference (`__kind: \"resource_ref\"` with a type and a\n * UUID id), or a lazy source whose `extra.resource_type` is set → 0\n * (descriptor-first);\n * - a lazy source (a `source` and null/absent `content`) → 6000;\n * - anything else → null (the server's 200).\n *\n * A resource reference nested as an envelope's `content` is NOT promoted by the\n * server (it reads top-level values only), so it is plain JSON content → null.\n */\nexport function serverDefaultMax(value: unknown): number | null {\n if (!isPlainObject(value)) return null;\n if (isResourceRef(value)) return 0;\n if (!isRichEnvelope(value) && !isLazyPointer(value)) return null;\n if (isValidLimit(value.default_max_inline_chars)) return value.default_max_inline_chars;\n if (hasLazySource(value) && (value.content === null || value.content === undefined)) {\n const src = value.source as Record<string, unknown>;\n const extra = isPlainObject(src.extra) ? src.extra : {};\n return extra.resource_type ? 0 : LAZY_SOURCE_DEFAULT_MAX;\n }\n return null;\n}\n\n// ── resolveContextRow — the glue a client runs per value ────────────────────\n\n/** Resolve one value into the row the table renders and the wire sends. */\nexport function resolveContextRow(\n source: ContextRowSource,\n savedRows: SavedContextRuleRows | null | undefined,\n cap: number = DEFAULT_INLINE_CAP,\n): ResolvedContextRow {\n const surfaceKey = source.surfaceKey ?? DEFAULT_SURFACE_KEY;\n const chars =\n source.chars !== undefined\n ? source.chars\n : unresolvedSize(source.value)\n ? null\n : contextValueChars(splitContextValue(source.value).content);\n const userRule = lookupSavedRule(savedRows, surfaceKey, source.key);\n const layers: ContextRuleLayers = { ...(source.layers ?? {}) };\n if (layers.default_max === undefined || layers.default_max === null) {\n const serverDefault = serverDefaultMax(source.value);\n if (serverDefault !== null) layers.default_max = serverDefault;\n }\n const result = resolveContextRule({ ...layers, chars, user: userRule, cap });\n const row: ResolvedContextRow = {\n ...result,\n key: source.key,\n label: source.label,\n surfaceKey,\n origin: source.origin,\n value: source.value,\n chars,\n userRule,\n };\n if (source.description !== undefined) row.description = source.description;\n if (source.type !== undefined) row.type = source.type;\n if (source.layers !== undefined || layers.default_max !== undefined) row.layers = layers;\n return row;\n}\n\n// ── buildContextWire — the ONLY producer of a request's `context` ──────────\n\n/**\n * Size the server cannot know yet: a lazy pointer (or an envelope with a lazy\n * source and null content), or a resource reference it will promote to one.\n */\nfunction unresolvedSize(value: unknown): boolean {\n if (!isPlainObject(value)) return false;\n if (isResourceRef(value)) return true;\n return (\n (isRichEnvelope(value) || isLazyPointer(value)) &&\n hasLazySource(value) &&\n (value.content === null || value.content === undefined)\n );\n}\n\n/**\n * Build the request's `context` object from the resolved rows. This is the\n * ONLY function that may produce it, so what the table shows and what the\n * request carries cannot drift apart.\n *\n * - A row whose `include` is false is omitted entirely — it never reaches the\n * wire (RULES.md §1: off means off everywhere).\n * - An included row becomes `{ content, type?, label, description?,\n * max_inline_chars? }`. An already-rich envelope (`isRichEnvelope`, e.g. a\n * working document carrying `source`/`mutable`/`persist`, or a lazy pointer)\n * keeps every non-rule field it already has; `label`/`description`/`type`\n * are only filled when missing.\n * - `max_inline_chars` on the envelope is the PAGE layer's value only\n * (`layers.surface.max_inline_chars`, else the envelope's own page value).\n * The server reads the person's saved rules itself every turn (RULES.md §3)\n * and the agent layer from its own policy, so the wire must never carry the\n * person's number or the resolved effective limit dressed up as a page rule\n * — that would make a stale client value outrank the server's fresh read.\n */\nexport function buildContextWire(rows: readonly ResolvedContextRow[]): Record<string, unknown> {\n const wire: Record<string, unknown> = {};\n for (const row of rows) {\n if (!row.include) continue;\n // Rich: an envelope with `content`, or a source-only lazy pointer. Every\n // non-rule field survives — including `default_max_inline_chars`, the\n // server's per-key default (not a person's or a page's number).\n // A record that merely has `content` is NOT rich: it is wrapped whole, or\n // the server would read it as raw content and drop the envelope fields.\n const rich = isRichEnvelope(row.value) || isLazyPointer(row.value);\n const envelope: Record<string, unknown> = rich\n ? { ...(row.value as Record<string, unknown>) }\n : { content: row.value };\n delete envelope.max_inline_chars;\n if (envelope.type === undefined && row.type !== undefined) envelope.type = row.type;\n if (envelope.label === undefined) envelope.label = row.label;\n if (envelope.description === undefined && row.description !== undefined) {\n envelope.description = row.description;\n }\n const pageLimit = row.layers?.surface?.max_inline_chars;\n const ownPageLimit = rich ? (row.value as Record<string, unknown>).max_inline_chars : undefined;\n if (isValidLimit(pageLimit)) envelope.max_inline_chars = pageLimit;\n else if (isValidLimit(ownPageLimit)) envelope.max_inline_chars = ownPageLimit;\n wire[row.key] = envelope;\n }\n return wire;\n}\n\n// ── compareReceipt — RULES.md §6 ────────────────────────────────────────────\n\nexport interface ContextReceiptMismatch {\n key: string;\n field: \"include\" | \"max_inline_chars\" | \"delivery\" | \"user_rule\" | \"missing\";\n expected: unknown;\n actual: unknown;\n}\n\nfunction normalizeUserRule(rule: SavedContextRule | null | undefined): SavedContextRule | null {\n const clean = sanitizeRule(rule);\n return Object.keys(clean).length === 0 ? null : clean;\n}\n\nfunction sameRule(a: SavedContextRule | null, b: SavedContextRule | null): boolean {\n if (a === null || b === null) return a === b;\n return a.include === b.include && a.max_inline_chars === b.max_inline_chars;\n}\n\n/**\n * Compare the rows the client resolved (and rendered, and sent) with the\n * receipt the server emitted for the same turn.\n *\n * For each expected row, the receipt row is matched by key + surface key, then\n * by key alone. Compared: `include`, `max_inline_chars`, `delivery` (only when\n * both sides know the size) and `user_rule`. An expected row absent from the\n * receipt is a `missing` mismatch — except a row the client correctly withheld\n * for a reason other than the person's own rule (page `auto_context` off, the\n * agent's kill switch): the client never sent it and the server lists only\n * the person's saved `include: false` rules as `origin: \"rule\"` rows, so its\n * absence is the truth, not a lie. Receipt rows nobody expected are returned\n * as `systemRows` — shown, never hidden.\n */\nexport function compareReceipt(\n expected: readonly ResolvedContextRow[],\n receipt: ContextReceipt,\n): { mismatches: ContextReceiptMismatch[]; systemRows: ContextReceiptRow[]; modelReadsContext: boolean } {\n const mismatches: ContextReceiptMismatch[] = [];\n // A turn whose model reads no context delivers nothing by design: every row\n // is `delivery: \"off\"`, `blocked_by: \"model\"`. That is not a lie about the\n // person's rules, so delivery is not compared; include / limit / rule still are.\n const modelReadsContext = receipt.model_reads_context !== false;\n const used = new Set<ContextReceiptRow>();\n const rows = receipt.rows ?? [];\n\n for (const exp of expected) {\n const actual =\n rows.find((r) => !used.has(r) && r.key === exp.key && r.surface_key === exp.surfaceKey) ??\n rows.find((r) => !used.has(r) && r.key === exp.key);\n if (!actual) {\n const withheldByOthers = !exp.include && exp.decided_by.include !== \"you\";\n if (!withheldByOthers) {\n mismatches.push({ key: exp.key, field: \"missing\", expected: exp.delivery, actual: null });\n }\n continue;\n }\n used.add(actual);\n if (actual.include !== exp.include) {\n mismatches.push({ key: exp.key, field: \"include\", expected: exp.include, actual: actual.include });\n }\n if (actual.max_inline_chars !== exp.max_inline_chars) {\n mismatches.push({\n key: exp.key,\n field: \"max_inline_chars\",\n expected: exp.max_inline_chars,\n actual: actual.max_inline_chars,\n });\n }\n if (\n modelReadsContext &&\n exp.chars !== null &&\n actual.chars !== null &&\n actual.delivery !== exp.delivery\n ) {\n mismatches.push({ key: exp.key, field: \"delivery\", expected: exp.delivery, actual: actual.delivery });\n }\n const expRule = normalizeUserRule(exp.userRule);\n const actRule = normalizeUserRule(actual.user_rule);\n if (!sameRule(expRule, actRule)) {\n mismatches.push({ key: exp.key, field: \"user_rule\", expected: expRule, actual: actRule });\n }\n }\n\n return { mismatches, systemRows: rows.filter((r) => !used.has(r)), modelReadsContext };\n}\n\n// ── receiptRowToResolved — read-only rows for a sent message ────────────────\n\n/**\n * Turn one receipt row into the row a read-only table renders (a sent\n * message's context, read back from `chat.message.model_context`). The value\n * itself is not in the receipt, so `value` is undefined.\n */\nexport function receiptRowToResolved(row: ContextReceiptRow): ResolvedContextRow {\n const origin: ContextRowOrigin =\n row.surface_key !== DEFAULT_SURFACE_KEY ? \"page\" : row.origin === \"client\" ? \"attached\" : \"system\";\n return {\n key: row.key,\n label: row.label,\n surfaceKey: row.surface_key,\n origin,\n value: undefined,\n chars: row.chars,\n userRule: normalizeUserRule(row.user_rule),\n include: row.include,\n max_inline_chars: row.max_inline_chars,\n delivery: row.delivery,\n decided_by: { include: row.decided_by.include, max_inline_chars: row.decided_by.max_inline_chars },\n clamped: row.clamped,\n };\n}\n\n// ── Display ─────────────────────────────────────────────────────────────────\n\n/** Compact size for a tabular column: `950`, `18.4k`, `184k`, `1.2M`, `—` for unknown. */\nexport function formatChars(n: number | null | undefined): string {\n if (n === null || n === undefined || !Number.isFinite(n)) return \"—\";\n const abs = Math.abs(n);\n const trim = (s: string) => s.replace(/\\.0$/, \"\");\n if (abs < 1000) return String(Math.round(n));\n if (abs < 100_000) return `${trim((n / 1000).toFixed(1))}k`;\n if (abs < 1_000_000) return `${Math.round(n / 1000)}k`;\n return `${trim((n / 1_000_000).toFixed(1))}M`;\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../context/index.ts"],"sourcesContent":["/**\n * @ai-matrx/agents/context — the context value rules, headless.\n *\n * Contract: common-docs/systems/scopes-context/context-delivery/RULES.md.\n * Parity corpus: ./rules-corpus.json (run here by rules.test.ts and by aidream's\n * pytest against the Python twin `resolve_context_rule`). Every function here\n * is pure, zero-React, and safe to import on a server.\n *\n * The one flow a client runs per turn:\n * rows = values.map(v => resolveContextRow(v, savedRows, cap)) // what the table shows\n * context = buildContextWire(rows) // what the request sends\n * …on `context_receipt`: compareReceipt(rows, receipt) // screen vs server\n */\n\n// ── Constants ───────────────────────────────────────────────────────────────\n\n/** `users.user_surface_state.feature` for the person's saved rules. */\nexport const CONTEXT_RULES_FEATURE = \"context_rules\";\n/** Surface key for values no surface emitted (attachments, server-added values). */\nexport const DEFAULT_SURFACE_KEY = \"_default\";\n/** The server's default inline threshold when no layer sets one. */\nexport const DEFAULT_INLINE_MAX_CHARS = 200;\n/** The default ceiling every resolved limit is clamped to (a server knob). */\nexport const DEFAULT_INLINE_CAP = 50000;\n/** A lazy document's server default (RULES.md §2: \"a small lazy document 6000\"). */\nexport const LAZY_SOURCE_DEFAULT_MAX = 6000;\n\n// ── Types ───────────────────────────────────────────────────────────────────\n\n/** Which layer decided a field — lowest to highest. */\nexport type ContextLayer = \"default\" | \"page\" | \"agent\" | \"you\";\nexport type ContextDelivery = \"inline\" | \"on_request\" | \"off\";\n\n/** One saved per-person rule (one key on one surface). Both fields optional. */\nexport interface SavedContextRule {\n include?: boolean;\n max_inline_chars?: number;\n}\n\n/** `{ [surface_key]: { [context key]: SavedContextRule } }` — the person's rows. */\nexport type SavedContextRuleRows = Record<string, Record<string, SavedContextRule>>;\n\n/** The agent layer for one key (RULES.md §2). */\nexport interface ContextAgentLayer {\n /** The agent's Context Policy declares this key (or the value is the agent's own seed). */\n declared?: boolean;\n /** The policy's ceiling; only read when `declared`. */\n max_inline_chars?: number | null;\n /** `auto_context_disabled` — closes every undeclared key. */\n kill_switch?: boolean;\n}\n\n/** The page layer for one key: the surface that emitted the value. */\nexport interface ContextSurfaceLayer {\n /** The surface declares this value (a `ui_surface_value` row / manifest entry). */\n declared?: boolean;\n /** `false` drops the value unless the agent declares it. */\n auto_context?: boolean | null;\n /** `inlineUpTo` / envelope `max_inline_chars` / DB column. */\n max_inline_chars?: number | null;\n}\n\n/** The non-person layers for one value — what the table's layer breakdown shows. */\nexport interface ContextRuleLayers {\n /** The server's per-key default limit; `null`/absent = 200. */\n default_max?: number | null;\n agent?: ContextAgentLayer | null;\n surface?: ContextSurfaceLayer | null;\n}\n\n/**\n * Input to `resolveContextRule`. Snake case on purpose: it is the corpus shape\n * and the Python twin's shape. `user` is typed loosely because invalid saved\n * values (non-boolean include, non-integer or negative limit) must be ignored,\n * not trusted.\n */\nexport interface ContextRuleInput extends ContextRuleLayers {\n /** Size in code points; `null` = an unresolved lazy source. */\n chars: number | null;\n user?: { include?: unknown; max_inline_chars?: unknown } | null;\n /** Ceiling; default `DEFAULT_INLINE_CAP`. */\n cap?: number;\n}\n\nexport interface ContextRuleResult {\n include: boolean;\n /** Effective limit, after the clamp. */\n max_inline_chars: number;\n delivery: ContextDelivery;\n decided_by: { include: ContextLayer; max_inline_chars: ContextLayer };\n clamped: boolean;\n}\n\n/** One receipt row — RULES.md §5, wire fields exactly. */\nexport interface ContextReceiptRow {\n key: string;\n label: string;\n surface_key: string;\n origin: \"client\" | \"server\" | \"surroundings\" | \"rule\";\n chars: number | null;\n include: boolean;\n max_inline_chars: number;\n delivery: ContextDelivery;\n decided_by: { include: ContextLayer; max_inline_chars: ContextLayer };\n user_rule: { include?: boolean; max_inline_chars?: number } | null;\n clamped: boolean;\n /** The CLIENT itself sent a value the person turned off. */\n client_sent_excluded: boolean;\n /** `\"model\"`: nothing reached a model that reads no context. */\n blocked_by: \"model\" | null;\n}\n\n/** The per-turn receipt the server streams as `context_receipt` — RULES.md §5. */\nexport interface ContextReceipt {\n version: 1;\n surface: string | null;\n cap: number;\n /** False: this turn's model cannot read context at all (every row `blocked_by: \"model\"`). */\n model_reads_context: boolean;\n /** The person's saved rules could not be read; the turn ran without them. */\n rules_error: string | null;\n rows: ContextReceiptRow[];\n}\n\nexport type ContextRowOrigin = \"page\" | \"attached\" | \"system\";\n\n/** A client's resolved row: what its table renders and what `buildContextWire` sends. */\nexport interface ResolvedContextRow extends ContextRuleResult {\n key: string;\n label: string;\n surfaceKey: string;\n origin: ContextRowOrigin;\n /** The raw value, or an already-rich envelope (`{ content, type, … }`). */\n value: unknown;\n chars: number | null;\n /** The saved rule that was applied (merged `_default` + surface row), or null. */\n userRule: SavedContextRule | null;\n description?: string;\n /** Context object type for the wire envelope (`text`, `json`, …). */\n type?: string;\n /** The non-person layers the rule was resolved from. Absent = default only. */\n layers?: ContextRuleLayers;\n}\n\n/** What a client knows about one value before resolution. */\nexport interface ContextRowSource {\n key: string;\n label: string;\n surfaceKey?: string;\n origin: ContextRowOrigin;\n value: unknown;\n /** Override the measured size (e.g. `null` for an unresolved lazy source). */\n chars?: number | null;\n description?: string;\n type?: string;\n layers?: ContextRuleLayers;\n}\n\n// ── Helpers ─────────────────────────────────────────────────────────────────\n\nconst isNum = (v: unknown): v is number => typeof v === \"number\" && Number.isFinite(v);\nconst isValidLimit = (v: unknown): v is number =>\n typeof v === \"number\" && Number.isInteger(v) && v >= 0;\n\n// ── resolveContextRule — RULES.md §2, line for line ─────────────────────────\n\nexport function resolveContextRule(input: ContextRuleInput): ContextRuleResult {\n const agent = input.agent ?? null;\n const surface = input.surface ?? null;\n const user = input.user ?? null;\n const cap = isNum(input.cap) ? input.cap : DEFAULT_INLINE_CAP;\n const agentDeclared = agent?.declared === true;\n\n let include = true;\n let includeBy: ContextLayer = \"default\";\n if (surface?.declared === true && surface.auto_context === false) {\n if (agentDeclared) {\n include = true;\n includeBy = \"agent\";\n } else {\n include = false;\n includeBy = \"page\";\n }\n }\n if (agent?.kill_switch === true && !agentDeclared) {\n include = false;\n includeBy = \"agent\";\n }\n if (user && typeof user.include === \"boolean\") {\n include = user.include;\n includeBy = \"you\";\n }\n\n let limit = isNum(input.default_max) ? input.default_max : DEFAULT_INLINE_MAX_CHARS;\n let limitBy: ContextLayer = \"default\";\n const a = agentDeclared && isNum(agent?.max_inline_chars) ? agent.max_inline_chars : null;\n const s = isNum(surface?.max_inline_chars) ? surface.max_inline_chars : null;\n if (a !== null && s !== null) {\n if (s < a) {\n limit = s;\n limitBy = \"page\";\n } else {\n limit = a;\n limitBy = \"agent\";\n }\n } else if (a !== null) {\n limit = a;\n limitBy = \"agent\";\n } else if (s !== null) {\n limit = s;\n limitBy = \"page\";\n }\n if (user && isValidLimit(user.max_inline_chars)) {\n limit = user.max_inline_chars;\n limitBy = \"you\";\n }\n const clamped = limit > cap;\n limit = Math.min(limit, cap);\n\n const chars = input.chars;\n const delivery: ContextDelivery = !include\n ? \"off\"\n : chars === null || chars === undefined || limit === 0 || chars > limit\n ? \"on_request\"\n : \"inline\";\n\n return {\n include,\n max_inline_chars: limit,\n delivery,\n decided_by: { include: includeBy, max_inline_chars: limitBy },\n clamped,\n };\n}\n\n// ── contextValueChars — RULES.md §1 ─────────────────────────────────────────\n\n/** Count Unicode code points (never UTF-16 units: `\"😀\".length` is 2). */\nfunction codePoints(s: string): number {\n let n = 0;\n for (const _ of s) n += 1;\n return n;\n}\n\n/**\n * Size of a value in code points, exactly as the server's `content_as_str()`:\n * a string is itself; anything else is `JSON.stringify(value, null, 2)`\n * (Python `json.dumps(value, ensure_ascii=False, indent=2)`). `undefined` → 0.\n */\nexport function contextValueChars(value: unknown): number {\n if (value === undefined) return 0;\n if (typeof value === \"string\") return codePoints(value);\n let text: string | undefined;\n try {\n text = JSON.stringify(value, null, 2);\n } catch {\n text = String(value);\n }\n return text === undefined ? 0 : codePoints(text);\n}\n\n// ── lookupSavedRule — RULES.md §3 ───────────────────────────────────────────\n\nfunction sanitizeRule(raw: unknown): SavedContextRule {\n const out: SavedContextRule = {};\n if (!raw || typeof raw !== \"object\") return out;\n const r = raw as Record<string, unknown>;\n if (typeof r.include === \"boolean\") out.include = r.include;\n if (isValidLimit(r.max_inline_chars)) out.max_inline_chars = r.max_inline_chars;\n return out;\n}\n\n/**\n * The person's rule for one key on one surface: `_default[key]` merged per\n * field under `rows[surfaceKey][key]`, invalid fields stripped. `null` when\n * nothing valid is saved.\n */\nexport function lookupSavedRule(\n rows: SavedContextRuleRows | null | undefined,\n surfaceKey: string,\n key: string,\n): SavedContextRule | null {\n if (!rows || typeof rows !== \"object\") return null;\n const base = sanitizeRule(rows[DEFAULT_SURFACE_KEY]?.[key]);\n const own = surfaceKey === DEFAULT_SURFACE_KEY ? {} : sanitizeRule(rows[surfaceKey]?.[key]);\n const merged: SavedContextRule = { ...base, ...own };\n return Object.keys(merged).length === 0 ? null : merged;\n}\n\n// ── serverDefaultMax — the server's per-key default, read from the wire value ─\n\n/**\n * The keys that make a dict the rich envelope form — exactly aidream\n * `context_objects._RICH_FORM_KEYS`. A dict with ANY other key is raw content\n * on the server, `content` key or not.\n */\nexport const RICH_ENVELOPE_KEYS: ReadonlySet<string> = new Set([\n \"content\",\n \"mutable\",\n \"persist\",\n \"source\",\n \"type\",\n \"label\",\n \"description\",\n \"max_inline_chars\",\n \"default_max_inline_chars\",\n \"summary_agent_id\",\n]);\n\nconst UUID_RE = /^[0-9a-f]{8}-?[0-9a-f]{4}-?[0-9a-f]{4}-?[0-9a-f]{4}-?[0-9a-f]{12}$/i;\n\nfunction isPlainObject(v: unknown): v is Record<string, unknown> {\n return !!v && typeof v === \"object\" && !Array.isArray(v);\n}\n\n/** A canonical resource reference the server promotes (aidream `parse_resource_reference`). */\nfunction isResourceRef(v: Record<string, unknown>): boolean {\n if (v.__kind !== \"resource_ref\") return false;\n const type = v.resource_type || v.type;\n const id = v.resource_id || v.id;\n return (\n typeof type === \"string\" &&\n type.trim() !== \"\" &&\n typeof id === \"string\" &&\n UUID_RE.test(id.trim())\n );\n}\n\nfunction hasOwn(o: Record<string, unknown>, k: string): boolean {\n return Object.prototype.hasOwnProperty.call(o, k);\n}\n\nfunction onlyRichKeys(o: Record<string, unknown>): boolean {\n return Object.keys(o).every((k) => RICH_ENVELOPE_KEYS.has(k));\n}\n\nfunction hasLazySource(o: Record<string, unknown>): boolean {\n return isPlainObject(o.source) && typeof o.source.kind === \"string\";\n}\n\n/**\n * A rich envelope exactly as the server reads one (aidream\n * `_split_rich_context_value`): a plain object with its own `content` whose\n * every key is in `RICH_ENVELOPE_KEYS`. A record that merely HAS `content`\n * (a note `{ id, title, content }`) is raw content, not an envelope.\n */\nexport function isRichEnvelope(value: unknown): value is Record<string, unknown> {\n return isPlainObject(value) && hasOwn(value, \"content\") && onlyRichKeys(value);\n}\n\n/** The source-only lazy pointer form: rich keys only, no `content`, a `source` with a `kind`. */\nfunction isLazyPointer(value: unknown): value is Record<string, unknown> {\n return isPlainObject(value) && !hasOwn(value, \"content\") && onlyRichKeys(value) && hasLazySource(value);\n}\n\n/**\n * Split one wire value into `{ content, overrides }` exactly as aidream\n * `_split_rich_context_value`: a rich envelope or lazy pointer → its `content`\n * (null when absent) and every other key; anything else → the whole value as\n * content, no overrides.\n */\nexport function splitContextValue(value: unknown): {\n content: unknown;\n overrides: Record<string, unknown>;\n} {\n if (isRichEnvelope(value) || isLazyPointer(value)) {\n const { content = null, ...overrides } = value;\n return { content, overrides };\n }\n return { content: value, overrides: {} };\n}\n\n/**\n * The server's default inline limit for one wire value — the `default` layer\n * the server will use, before page/agent/you. Mirrors aidream\n * `context_rules._server_default_max` over the object `ContextManifest.build`\n * makes from the value (after `normalize_resource_context`):\n *\n * - a rich envelope (every key in the rich-form set, and `content` present or a\n * lazy `source` with a string `kind`) carrying a whole-number, non-negative\n * `default_max_inline_chars` → that;\n * - a top-level resource reference (`__kind: \"resource_ref\"` with a type and a\n * UUID id), or a lazy source whose `extra.resource_type` is set → 0\n * (descriptor-first);\n * - a lazy source (a `source` and null/absent `content`) → 6000;\n * - anything else → null (the server's 200).\n *\n * A resource reference nested as an envelope's `content` is NOT promoted by the\n * server (it reads top-level values only), so it is plain JSON content → null.\n */\nexport function serverDefaultMax(value: unknown): number | null {\n if (!isPlainObject(value)) return null;\n if (isResourceRef(value)) return 0;\n if (!isRichEnvelope(value) && !isLazyPointer(value)) return null;\n if (isValidLimit(value.default_max_inline_chars)) return value.default_max_inline_chars;\n if (hasLazySource(value) && (value.content === null || value.content === undefined)) {\n const src = value.source as Record<string, unknown>;\n const extra = isPlainObject(src.extra) ? src.extra : {};\n return extra.resource_type ? 0 : LAZY_SOURCE_DEFAULT_MAX;\n }\n return null;\n}\n\n// ── resolveContextRow — the glue a client runs per value ────────────────────\n\n/** Resolve one value into the row the table renders and the wire sends. */\nexport function resolveContextRow(\n source: ContextRowSource,\n savedRows: SavedContextRuleRows | null | undefined,\n cap: number = DEFAULT_INLINE_CAP,\n): ResolvedContextRow {\n const surfaceKey = source.surfaceKey ?? DEFAULT_SURFACE_KEY;\n const chars =\n source.chars !== undefined\n ? source.chars\n : unresolvedSize(source.value)\n ? null\n : contextValueChars(splitContextValue(source.value).content);\n const userRule = lookupSavedRule(savedRows, surfaceKey, source.key);\n const layers: ContextRuleLayers = { ...(source.layers ?? {}) };\n if (layers.default_max === undefined || layers.default_max === null) {\n const serverDefault = serverDefaultMax(source.value);\n if (serverDefault !== null) layers.default_max = serverDefault;\n }\n const result = resolveContextRule({ ...layers, chars, user: userRule, cap });\n const row: ResolvedContextRow = {\n ...result,\n key: source.key,\n label: source.label,\n surfaceKey,\n origin: source.origin,\n value: source.value,\n chars,\n userRule,\n };\n if (source.description !== undefined) row.description = source.description;\n if (source.type !== undefined) row.type = source.type;\n if (source.layers !== undefined || layers.default_max !== undefined) row.layers = layers;\n return row;\n}\n\n// ── buildContextWire — the ONLY producer of a request's `context` ──────────\n\n/**\n * Size the server cannot know yet: a lazy pointer (or an envelope with a lazy\n * source and null content), or a resource reference it will promote to one.\n */\nfunction unresolvedSize(value: unknown): boolean {\n if (!isPlainObject(value)) return false;\n if (isResourceRef(value)) return true;\n return (\n (isRichEnvelope(value) || isLazyPointer(value)) &&\n hasLazySource(value) &&\n (value.content === null || value.content === undefined)\n );\n}\n\nconst KIND_RESOURCE_REF = \"resource_ref\";\n\n/**\n * Values the server acts on by their top-level shape in `request.context`\n * (aidream `normalize_resource_context` → `parse_resource_reference`, which\n * reads ONLY top-level values):\n *\n * - a resource reference — any object whose `__kind` is `\"resource_ref\"`\n * (its own promote/exclude/overlay policy rides inside it);\n * - a bare UUID string under a conventional `*_id` key (`file_id`,\n * `attached_file_id`, `current_note_id`, …), which the server promotes when\n * the noun is a registered resource type. The registry lives on the server,\n * so every `*_id` key with a UUID passes through; for an unregistered noun\n * the server reads the bare string exactly as it would read it wrapped.\n *\n * These ship exactly as-is: no label, type, description or limit added.\n */\nexport function isWireDirective(key: string, value: unknown): boolean {\n if (isPlainObject(value)) return value.__kind === KIND_RESOURCE_REF;\n return (\n typeof value === \"string\" &&\n key.trim().toLowerCase().endsWith(\"_id\") &&\n UUID_RE.test(value.trim())\n );\n}\n\n/**\n * Build the request's `context` object from the resolved rows. This is the\n * ONLY function that may produce it, so what the table shows and what the\n * request carries cannot drift apart.\n *\n * - A row whose `include` is false is omitted entirely — it never reaches the\n * wire (RULES.md §1: off means off everywhere).\n * - An included row becomes `{ content, type?, label, description?,\n * max_inline_chars? }`. An already-rich envelope (`isRichEnvelope`, e.g. a\n * working document carrying `source`/`mutable`/`persist`, or a lazy pointer)\n * keeps every non-rule field it already has; `label`/`description`/`type`\n * are only filled when missing.\n * - `max_inline_chars` on the envelope is the PAGE layer's value only\n * (`layers.surface.max_inline_chars`, else the envelope's own page value).\n * The server reads the person's saved rules itself every turn (RULES.md §3)\n * and the agent layer from its own policy, so the wire must never carry the\n * person's number or the resolved effective limit dressed up as a page rule\n * — that would make a stale client value outrank the server's fresh read.\n */\nexport function buildContextWire(rows: readonly ResolvedContextRow[]): Record<string, unknown> {\n const wire: Record<string, unknown> = {};\n for (const row of rows) {\n if (!row.include) continue;\n // Rich: an envelope with `content`, or a source-only lazy pointer. Every\n // non-rule field survives — including `default_max_inline_chars`, the\n // server's per-key default (not a person's or a page's number).\n // A record that merely has `content` is NOT rich: it is wrapped whole, or\n // the server would read it as raw content and drop the envelope fields.\n // A top-level directive the server acts on by SHAPE ships exactly as-is:\n // wrapped, it becomes plain JSON and silently stops resolving.\n if (isWireDirective(row.key, row.value)) {\n wire[row.key] = row.value;\n continue;\n }\n const rich = isRichEnvelope(row.value) || isLazyPointer(row.value);\n const envelope: Record<string, unknown> = rich\n ? { ...(row.value as Record<string, unknown>) }\n : { content: row.value };\n delete envelope.max_inline_chars;\n if (envelope.type === undefined && row.type !== undefined) envelope.type = row.type;\n if (envelope.label === undefined) envelope.label = row.label;\n if (envelope.description === undefined && row.description !== undefined) {\n envelope.description = row.description;\n }\n const pageLimit = row.layers?.surface?.max_inline_chars;\n const ownPageLimit = rich ? (row.value as Record<string, unknown>).max_inline_chars : undefined;\n if (isValidLimit(pageLimit)) envelope.max_inline_chars = pageLimit;\n else if (isValidLimit(ownPageLimit)) envelope.max_inline_chars = ownPageLimit;\n wire[row.key] = envelope;\n }\n return wire;\n}\n\n// ── compareReceipt — RULES.md §6 ────────────────────────────────────────────\n\nexport interface ContextReceiptMismatch {\n key: string;\n field: \"include\" | \"max_inline_chars\" | \"delivery\" | \"user_rule\" | \"missing\";\n expected: unknown;\n actual: unknown;\n}\n\nfunction normalizeUserRule(rule: SavedContextRule | null | undefined): SavedContextRule | null {\n const clean = sanitizeRule(rule);\n return Object.keys(clean).length === 0 ? null : clean;\n}\n\nfunction sameRule(a: SavedContextRule | null, b: SavedContextRule | null): boolean {\n if (a === null || b === null) return a === b;\n return a.include === b.include && a.max_inline_chars === b.max_inline_chars;\n}\n\n/**\n * Compare the rows the client resolved (and rendered, and sent) with the\n * receipt the server emitted for the same turn.\n *\n * For each expected row, the receipt row is matched by key + surface key, then\n * by key alone. Compared: `include`, `max_inline_chars`, `delivery` (only when\n * both sides know the size) and `user_rule`. An expected row absent from the\n * receipt is a `missing` mismatch — except a row the client correctly withheld\n * for a reason other than the person's own rule (page `auto_context` off, the\n * agent's kill switch): the client never sent it and the server lists only\n * the person's saved `include: false` rules as `origin: \"rule\"` rows, so its\n * absence is the truth, not a lie. Receipt rows nobody expected are returned\n * as `systemRows` — shown, never hidden.\n */\nexport function compareReceipt(\n expected: readonly ResolvedContextRow[],\n receipt: ContextReceipt,\n): { mismatches: ContextReceiptMismatch[]; systemRows: ContextReceiptRow[]; modelReadsContext: boolean } {\n const mismatches: ContextReceiptMismatch[] = [];\n // A turn whose model reads no context delivers nothing by design: every row\n // is `delivery: \"off\"`, `blocked_by: \"model\"`. That is not a lie about the\n // person's rules, so delivery is not compared; include / limit / rule still are.\n const modelReadsContext = receipt.model_reads_context !== false;\n const used = new Set<ContextReceiptRow>();\n const rows = receipt.rows ?? [];\n\n for (const exp of expected) {\n const actual =\n rows.find((r) => !used.has(r) && r.key === exp.key && r.surface_key === exp.surfaceKey) ??\n rows.find((r) => !used.has(r) && r.key === exp.key);\n if (!actual) {\n const withheldByOthers = !exp.include && exp.decided_by.include !== \"you\";\n if (!withheldByOthers) {\n mismatches.push({ key: exp.key, field: \"missing\", expected: exp.delivery, actual: null });\n }\n continue;\n }\n used.add(actual);\n if (actual.include !== exp.include) {\n mismatches.push({ key: exp.key, field: \"include\", expected: exp.include, actual: actual.include });\n }\n if (actual.max_inline_chars !== exp.max_inline_chars) {\n mismatches.push({\n key: exp.key,\n field: \"max_inline_chars\",\n expected: exp.max_inline_chars,\n actual: actual.max_inline_chars,\n });\n }\n if (\n modelReadsContext &&\n exp.chars !== null &&\n actual.chars !== null &&\n actual.delivery !== exp.delivery\n ) {\n mismatches.push({ key: exp.key, field: \"delivery\", expected: exp.delivery, actual: actual.delivery });\n }\n const expRule = normalizeUserRule(exp.userRule);\n const actRule = normalizeUserRule(actual.user_rule);\n if (!sameRule(expRule, actRule)) {\n mismatches.push({ key: exp.key, field: \"user_rule\", expected: expRule, actual: actRule });\n }\n }\n\n return { mismatches, systemRows: rows.filter((r) => !used.has(r)), modelReadsContext };\n}\n\n// ── receiptRowToResolved — read-only rows for a sent message ────────────────\n\n/**\n * Turn one receipt row into the row a read-only table renders (a sent\n * message's context, read back from `chat.message.model_context`). The value\n * itself is not in the receipt, so `value` is undefined.\n */\nexport function receiptRowToResolved(row: ContextReceiptRow): ResolvedContextRow {\n const origin: ContextRowOrigin =\n row.surface_key !== DEFAULT_SURFACE_KEY ? \"page\" : row.origin === \"client\" ? \"attached\" : \"system\";\n return {\n key: row.key,\n label: row.label,\n surfaceKey: row.surface_key,\n origin,\n value: undefined,\n chars: row.chars,\n userRule: normalizeUserRule(row.user_rule),\n include: row.include,\n max_inline_chars: row.max_inline_chars,\n delivery: row.delivery,\n decided_by: { include: row.decided_by.include, max_inline_chars: row.decided_by.max_inline_chars },\n clamped: row.clamped,\n layers: receiptLayers(row),\n };\n}\n\n/**\n * Rebuild the non-person layers from what the server applied, so a read-only\n * table shows the applied limit as the inherited value and marks the layer\n * that decided it. The limit goes on the layer named by\n * `decided_by.max_inline_chars` (`you` is carried by `userRule`); include is\n * rebuilt the same way for a page `auto_context` off or the agent's kill switch.\n */\nfunction receiptLayers(row: ContextReceiptRow): ContextRuleLayers {\n const layers: ContextRuleLayers = {};\n const limit = row.max_inline_chars;\n const limitBy = row.decided_by.max_inline_chars;\n const includeBy = row.decided_by.include;\n if (limitBy === \"default\") layers.default_max = limit;\n // Page auto_context off: the page dropped it, or the agent re-admitted it.\n const pageOff = (includeBy === \"page\" && !row.include) || (includeBy === \"agent\" && row.include);\n if (limitBy === \"page\" || pageOff) {\n layers.surface = {\n declared: true,\n auto_context: !pageOff,\n max_inline_chars: limitBy === \"page\" ? limit : null,\n };\n }\n if (limitBy === \"agent\") {\n // An agent limit means the key is declared; a declared key the agent decided to include\n // was re-admitted past a page auto_context off. (The kill switch never closes a declared key.)\n layers.agent = { declared: true, max_inline_chars: limit };\n } else if (includeBy === \"agent\") {\n // Off = the kill switch closing an undeclared key; on = a declaration re-admitting a\n // page auto_context-off value.\n layers.agent = row.include ? { declared: true } : { declared: false, kill_switch: true };\n }\n return layers;\n}\n\n// ── Display ─────────────────────────────────────────────────────────────────\n\n/** Compact size for a tabular column: `950`, `18.4k`, `184k`, `1.2M`, `—` for unknown. */\nexport function formatChars(n: number | null | undefined): string {\n if (n === null || n === undefined || !Number.isFinite(n)) return \"—\";\n const abs = Math.abs(n);\n const trim = (s: string) => s.replace(/\\.0$/, \"\");\n if (abs < 1000) return String(Math.round(n));\n if (abs < 100_000) return `${trim((n / 1000).toFixed(1))}k`;\n if (abs < 1_000_000) return `${Math.round(n / 1000)}k`;\n return `${trim((n / 1_000_000).toFixed(1))}M`;\n}\n"],"mappings":"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","names":[]}