@tangle-network/agent-app 0.44.18 → 0.44.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/.claude/skills/eval-campaign/SKILL.md +58 -95
  2. package/.claude/skills/surface-evolution/SKILL.md +29 -23
  3. package/README.md +8 -7
  4. package/dist/assistant/index.d.ts +2 -2
  5. package/dist/assistant/index.js +3 -3
  6. package/dist/{attachment-validation-Zw8eslhN.d.ts → attachment-validation-Dvc_Livy.d.ts} +1 -1
  7. package/dist/chat-routes/index.d.ts +17 -10
  8. package/dist/chat-routes/index.js +89 -12
  9. package/dist/chat-routes/index.js.map +1 -1
  10. package/dist/chat-store/index.d.ts +2 -2
  11. package/dist/chat-store/index.js +1 -1
  12. package/dist/{chunk-JXIQSGOV.js → chunk-2ZSSOYXP.js} +2 -6
  13. package/dist/chunk-2ZSSOYXP.js.map +1 -0
  14. package/dist/{chunk-VM6VLJH6.js → chunk-BI6NKSO4.js} +2 -2
  15. package/dist/{chunk-DV2FA2PW.js → chunk-C3SRFZGL.js} +18 -9
  16. package/dist/chunk-C3SRFZGL.js.map +1 -0
  17. package/dist/{chunk-34M7AUWO.js → chunk-M3UFMQ7D.js} +1 -1
  18. package/dist/chunk-M3UFMQ7D.js.map +1 -0
  19. package/dist/{chunk-7SMPAAIT.js → chunk-PRKSYTMQ.js} +3 -3
  20. package/dist/eval-campaign/index.d.ts +6 -8
  21. package/dist/eval-campaign/index.js +9 -5
  22. package/dist/eval-campaign/index.js.map +1 -1
  23. package/dist/forms/index.d.ts +333 -0
  24. package/dist/forms/index.js +343 -0
  25. package/dist/forms/index.js.map +1 -0
  26. package/dist/{parts-S1OINlRP.d.ts → parts-fyPPdDdK.d.ts} +3 -4
  27. package/dist/profile/index.d.ts +5 -6
  28. package/dist/profile/index.js +5 -3
  29. package/dist/profile/index.js.map +1 -1
  30. package/dist/sandbox/index.d.ts +3 -7
  31. package/dist/sandbox/index.js +1 -1
  32. package/dist/skills/index.d.ts +4 -3
  33. package/dist/skills/index.js +1 -1
  34. package/dist/skills-placement/index.d.ts +0 -1
  35. package/dist/skills-placement/index.js +1 -1
  36. package/dist/teams-react/index.js +3 -3
  37. package/dist/web-react/index.d.ts +3 -3
  38. package/dist/web-react/index.js +3 -3
  39. package/package.json +29 -18
  40. package/dist/chunk-34M7AUWO.js.map +0 -1
  41. package/dist/chunk-DV2FA2PW.js.map +0 -1
  42. package/dist/chunk-JXIQSGOV.js.map +0 -1
  43. /package/dist/{chunk-VM6VLJH6.js.map → chunk-BI6NKSO4.js.map} +0 -0
  44. /package/dist/{chunk-7SMPAAIT.js.map → chunk-PRKSYTMQ.js.map} +0 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/forms/blank.ts","../../src/forms/registry.ts","../../src/forms/fill.ts","../../src/forms/verify.ts"],"sourcesContent":["/**\n * The blank a form is filled from — embedded, pinned, and never fetched at\n * fill time.\n *\n * A renderer that downloads its own blank works on a developer laptop and\n * fails in both places these products actually run: a Cloudflare Worker has no\n * business making an outbound call mid-request, and a sandbox container's\n * egress proxy refuses the agencies' own hosts (measured on tax-agent:\n * `www.irs.gov` CONNECT tunnel 403 while pypi and npm returned 200). A\n * form-filler that cannot reach its blank does not fail loudly — it degrades\n * into an agent describing the form in prose, which is the exact behaviour\n * this module exists to end.\n *\n * Pinning the bytes by digest also pins the artifact: a filing made against\n * the 2025 revision is reproducible from the committed bytes, and an agency\n * revision shows up as a diff of the recorded checksum instead of silently\n * changing under a live URL.\n */\n\n/** A blank form's bytes, base64-encoded, with the provenance to check them. */\nexport interface FormBlank {\n /** Base64 of the PDF exactly as the agency published it. */\n base64: string\n /** SHA-256 of the decoded bytes, lowercase hex. */\n sha256: string\n /** Where the bytes came from. Provenance for a reviewer, not a fetch target. */\n sourceUrl: string\n /** Decoded length in bytes. A cheap first check that the base64 is intact. */\n byteLength: number\n}\n\nconst decoded = new WeakMap<FormBlank, Uint8Array>()\n\n/**\n * Decode a blank once per isolate.\n *\n * Keyed on the blank OBJECT rather than a module-level singleton, because a\n * product carries several forms and a single cached slot would serve one\n * form's bytes for another's fill — a failure that produces a plausible PDF\n * and no error at all.\n */\nexport function decodeFormBlank(blank: FormBlank): Uint8Array {\n const cached = decoded.get(blank)\n if (cached) return cached\n const binary = atob(blank.base64)\n const bytes = new Uint8Array(binary.length)\n for (let index = 0; index < binary.length; index += 1) bytes[index] = binary.charCodeAt(index)\n if (bytes.length !== blank.byteLength) {\n throw new Error(\n `blank form is ${bytes.length} bytes but declares ${blank.byteLength} — the embedded base64 is truncated`,\n )\n }\n decoded.set(blank, bytes)\n return bytes\n}\n\n/** Lowercase-hex SHA-256 of a byte range. */\nexport async function sha256Hex(bytes: Uint8Array): Promise<string> {\n const view = bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength)\n const digest = await crypto.subtle.digest('SHA-256', view as ArrayBuffer)\n return Array.from(new Uint8Array(digest))\n .map((byte) => byte.toString(16).padStart(2, '0'))\n .join('')\n}\n\n/**\n * Prove the embedded bytes are the file the registry was derived from.\n *\n * Run this in a test, not on the fill path: a registry is only meaningful\n * against the exact revision it was derived from, and a blank swapped for a\n * newer revision moves every widget without changing a single field name.\n */\nexport async function assertFormBlankIntegrity(blank: FormBlank): Promise<void> {\n const bytes = decodeFormBlank(blank)\n const digest = await sha256Hex(bytes)\n if (digest !== blank.sha256) {\n throw new Error(\n `blank form digest is ${digest} but the registry was derived against ${blank.sha256} (${blank.sourceUrl})`,\n )\n }\n}\n","/**\n * The slot → widget registry: the one place a form filler can be wrong\n * invisibly, and the one place a language model is never allowed to write.\n *\n * WHY THE MODEL NEVER NAMES A WIDGET\n *\n * An agency PDF's widget names carry no meaning a reader can check —\n * `topmostSubform[0].Page2[0].f2_06[0]` on an IRS form, `registered2` on a\n * Texas SOS form. A model asked for one produces a plausible string, and the\n * natural audit — read the field back by the name you just wrote — PASSES\n * REGARDLESS, because it re-reads the invention rather than checking it.\n * Measured on tax-agent: three values written to model-chosen widgets landed\n * in \"Combat zone\" and the date boxes, and the audit reported 3 passed /\n * 0 failed.\n *\n * So the model supplies SEMANTIC SLOTS — a form line, a named box — and the\n * platform owns the slot → widget mapping. A misplaced figure stops being an\n * invention that survives review and becomes something that cannot be\n * expressed at all.\n *\n * WHY A SLOT CARRIES A LABEL, AND WHY THE LABEL DECLARES ITS BASIS\n *\n * A registry is still just a table, and a hand-typed table drifts (tax-agent's\n * form catalog carried wrong page counts for 7 of 17 forms). So every slot\n * states the label it believes it is aiming at, and states where that belief\n * came from:\n *\n * - `labelBasis: 'widget'` — the label must match the widget's OWN `/TU`\n * accessibility text inside the PDF. `checkRegistryAgainstBlank` re-checks\n * it against the bytes, so a mis-aimed slot fails a test rather than\n * quietly printing in the wrong box. Every one of Texas Form 205's 64\n * widgets carries `/TU`, so its whole registry can be checked this way.\n * - `labelBasis: 'derived'` — the label came from somewhere else (an XFA\n * template, a published instruction sheet, a human reading the form) and\n * CANNOT be re-checked against the widget. It is evidence for a reviewer,\n * never proof. Measured on IRS f1040: 0 of 199 widgets carry `/TU` or\n * `/Alt`, so its labels can only ever be derived.\n *\n * Declaring the basis is the point. A guess presented as truth is how a second\n * self-confirming artifact gets built on top of the first one.\n */\n\nimport type { PDFDocument, PDFForm } from 'pdf-lib'\n\n/** What kind of widget a slot writes to. */\nexport type FormSlotKind = 'text' | 'checkbox'\n\n/** How a slot's text value is rendered into the box. */\nexport type FormSlotFormat = 'text' | 'currency'\n\n/** Where a slot's `label` came from, and therefore what it can prove. */\nexport type FormLabelBasis = 'widget' | 'derived'\n\n/** One semantic slot: what a caller supplies, and where the platform puts it. */\nexport interface FormSlot {\n /** The name a caller (and a model) uses: `15`, `entity_name`, `agent_is_org`. */\n slot: string\n /**\n * The AcroForm widget path(s) this slot writes.\n *\n * A LIST because one semantic value legitimately occupies several boxes: IRS\n * Form 1040 states adjusted gross income twice, at the foot of page 1 and\n * the head of page 2, so page 2's arithmetic stands alone. Writing only one\n * of them leaves \"subtract line 14 from line 11b\" pointing at an empty box.\n */\n fields: readonly string[]\n kind: FormSlotKind\n /** How the value is rendered. Ignored for `checkbox`. Default `'text'`. */\n format?: FormSlotFormat\n /** What this slot is, in the form's own words. */\n label: string\n /** Whether `label` is checkable against the PDF, or merely recorded. */\n labelBasis: FormLabelBasis\n}\n\n/** A form's complete slot table, pinned to the revision it was derived from. */\nexport interface FormRegistry {\n /** Stable id for the form: `us-irs-1040`, `us-tx-sos-205`. */\n form: string\n /** The agency's own revision marker: `2025`, `Rev. 12-21`. */\n revision: string\n slots: readonly FormSlot[]\n}\n\n/** Why a registry does not match the blank it claims to describe. */\nexport type RegistryProblemCode =\n | 'no_slots'\n | 'no_fields'\n | 'missing_field'\n | 'wrong_kind'\n | 'label_mismatch'\n | 'duplicate_field'\n | 'duplicate_slot'\n\nexport interface RegistryProblem {\n slot: string\n field?: string\n code: RegistryProblemCode\n detail: string\n}\n\nexport interface RegistryCheckResult {\n ok: boolean\n /** Widgets actually compared against the PDF. Zero means nothing was proven. */\n checked: number\n /** Slots whose label was compared against the widget's own `/TU`. */\n labelsChecked: number\n problems: RegistryProblem[]\n}\n\n/** The `/TU` accessibility text a widget carries, if any. */\nexport async function widgetLabel(form: PDFForm, field: string): Promise<string | undefined> {\n const { PDFName, PDFHexString, PDFString } = await import('pdf-lib')\n const target = form.getFieldMaybe(field)\n if (!target) return undefined\n const tooltip = target.acroField.dict.get(PDFName.of('TU'))\n if (tooltip instanceof PDFString || tooltip instanceof PDFHexString) return tooltip.decodeText()\n return undefined\n}\n\n/** Normalize whitespace so a label comparison survives the agency's own typing. */\nfunction normalizeLabel(value: string): string {\n return value.replace(/\\s+/gu, ' ').trim().toLowerCase()\n}\n\n/**\n * Check a registry against the blank it claims to describe.\n *\n * This is the placement check. It runs in a test or in CI, never on the fill\n * path, and it is the ONLY thing that can catch a slot aimed at the wrong box\n * — reading a value back after writing it cannot, because it re-reads the same\n * name it wrote.\n *\n * It fails on ABSENCE, deliberately. tax-agent shipped an audit that silently\n * skipped an expected field the PDF did not expose, so a fill against entirely\n * wrong paths reported `passed=0 failed=0`. Here an empty registry, a slot\n * with no fields, and a field the PDF does not expose are each a problem with\n * a name, and `checked` is reported so a caller can see how much was actually\n * proven rather than trusting a bare `ok: true`.\n */\nexport async function checkRegistryAgainstBlank(args: {\n /** The blank's bytes. Decode a `FormBlank` with `decodeFormBlank` first. */\n pdf: Uint8Array\n registry: FormRegistry\n}): Promise<RegistryCheckResult> {\n const { PDFDocument } = await import('pdf-lib')\n const document: PDFDocument = await PDFDocument.load(args.pdf, { updateMetadata: false })\n const form = document.getForm()\n\n // The PDF's OWN field list, enumerated independently of anything the\n // registry claims. Every lookup below resolves against this, so a registry\n // naming a field that does not exist cannot pass by being skipped.\n const exposed = new Map<string, string>()\n for (const field of form.getFields()) exposed.set(field.getName(), field.constructor.name)\n\n const problems: RegistryProblem[] = []\n const claimedBy = new Map<string, string>()\n const seenSlots = new Set<string>()\n let checked = 0\n let labelsChecked = 0\n\n if (args.registry.slots.length === 0) {\n problems.push({\n slot: '(registry)',\n code: 'no_slots',\n detail: `registry ${args.registry.form} declares no slots — it can prove nothing and fill nothing`,\n })\n }\n\n for (const slot of args.registry.slots) {\n if (seenSlots.has(slot.slot)) {\n problems.push({\n slot: slot.slot,\n code: 'duplicate_slot',\n detail: `slot ${slot.slot} is declared twice; the later entry silently wins at fill time`,\n })\n }\n seenSlots.add(slot.slot)\n\n if (slot.fields.length === 0) {\n problems.push({\n slot: slot.slot,\n code: 'no_fields',\n detail: `slot ${slot.slot} names no widget, so a value supplied for it goes nowhere`,\n })\n continue\n }\n\n for (const field of slot.fields) {\n const previous = claimedBy.get(field)\n if (previous !== undefined && previous !== slot.slot) {\n problems.push({\n slot: slot.slot,\n field,\n code: 'duplicate_field',\n detail: `${field} is claimed by both slot ${previous} and slot ${slot.slot}`,\n })\n }\n claimedBy.set(field, slot.slot)\n\n const actualKind = exposed.get(field)\n if (actualKind === undefined) {\n problems.push({\n slot: slot.slot,\n field,\n code: 'missing_field',\n detail: `${args.registry.form} has no widget named ${field}`,\n })\n continue\n }\n checked += 1\n\n const expectedKind = slot.kind === 'checkbox' ? 'PDFCheckBox' : 'PDFTextField'\n if (actualKind !== expectedKind) {\n problems.push({\n slot: slot.slot,\n field,\n code: 'wrong_kind',\n detail: `${field} is a ${actualKind}, but slot ${slot.slot} declares ${slot.kind}`,\n })\n continue\n }\n\n if (slot.labelBasis !== 'widget') continue\n const onWidget = await widgetLabel(form, field)\n if (onWidget === undefined) {\n problems.push({\n slot: slot.slot,\n field,\n code: 'label_mismatch',\n detail: `slot ${slot.slot} claims labelBasis 'widget' but ${field} carries no /TU text to check it against — the label is derived, not checked`,\n })\n continue\n }\n labelsChecked += 1\n if (normalizeLabel(onWidget) !== normalizeLabel(slot.label)) {\n problems.push({\n slot: slot.slot,\n field,\n code: 'label_mismatch',\n detail: `${field} is labelled ${JSON.stringify(onWidget)} but slot ${slot.slot} claims ${JSON.stringify(slot.label)}`,\n })\n }\n }\n }\n\n return { ok: problems.length === 0, checked, labelsChecked, problems }\n}\n","/**\n * Fill a real agency PDF from a slot → value map.\n *\n * Mechanically this is `pdf-lib` writing an AcroForm: TypeScript, no\n * container, no Python, no network. That matters because it is the only shape\n * that runs everywhere these products run — a Cloudflare Worker (where 100% of\n * tax-agent's production work products were produced, with no sandbox at all)\n * and a sandbox container whose egress proxy refuses the agency's own host.\n *\n * The invariant it enforces is in `registry.ts`: the caller supplies SEMANTIC\n * SLOTS and never a widget name. Two consequences show up here.\n *\n * NOTHING IS SILENT. Every slot ends in `filled` or in `unfilled` with a code\n * and a reason. A value the caller supplied that never reached the page means\n * the document and the data disagree, which is the one thing a reviewer must\n * not have to discover by eye. That includes a registry naming a widget the\n * PDF does not expose: it is reported per-slot rather than crashing the render\n * or — worse — being skipped, which is how tax-agent once audited a fill\n * against entirely wrong paths as `passed=0 failed=0`.\n *\n * NO ON-STATE IS EVER AUTHORED. A checkbox's \"on\" name is a property of the\n * PDF and frequently a hex-escaped sentence: Texas Form 205's seven boxes are\n * `is#20an#20organization`, `initially#20has#20#20managers` (note the double\n * space), and four more like them. A caller that types one of those strings is\n * authoring something it cannot check, so a checkbox slot takes a BOOLEAN and\n * the widget's own on-value is read out of the file. The escaped spelling is\n * not handled — it is unrepresentable.\n */\n\nimport type { PDFCheckBox, PDFDocument, PDFTextField } from 'pdf-lib'\n\nimport type { FormRegistry, FormSlot } from './registry'\n\n/** One widget the fill actually wrote. */\nexport interface FilledWidget {\n slot: string\n field: string\n kind: FormSlot['kind']\n /** The value as the caller supplied it, before formatting. */\n value: unknown\n /** Exactly the text placed in the box. Absent for a checkbox. */\n text?: string\n /** Whether a checkbox was ticked. Absent for a text field. */\n checked?: boolean\n /** The widget's OWN on-state, decoded — read from the PDF, never authored. */\n onState?: string\n}\n\n/** Why a supplied value did not reach the page. */\nexport type UnfilledCode =\n | 'unknown_slot'\n | 'missing_field'\n | 'wrong_kind'\n | 'not_a_number'\n | 'not_a_boolean'\n | 'unformattable'\n\nexport interface UnfilledSlot {\n slot: string\n field?: string\n value: unknown\n code: UnfilledCode\n reason: string\n}\n\nexport interface FillFormResult {\n bytes: Uint8Array\n filled: FilledWidget[]\n unfilled: UnfilledSlot[]\n form: string\n revision: string\n}\n\nexport interface FillFormOptions {\n /** The blank's bytes. Decode a `FormBlank` with `decodeFormBlank` first. */\n pdf: Uint8Array\n registry: FormRegistry\n /** slot name → value. Keys the registry does not know are reported, not dropped. */\n values: Record<string, unknown>\n /**\n * Override how a text value becomes box text. Return `undefined` to fall\n * through to the built-in formatting for the slot's `format`.\n */\n formatText?: (value: unknown, slot: FormSlot) => string | undefined\n}\n\n/**\n * Format a figure the way a US agency form prints it: grouped thousands, two\n * decimals, negatives in parentheses.\n *\n * Two decimals rather than whole dollars because the caller's data carries\n * cents and a reviewer compares the document against that data — rounding here\n * would manufacture a disagreement between the two on every line with cents.\n */\nexport function formatFormCurrency(value: number): string {\n const magnitude = Math.abs(value).toLocaleString('en-US', {\n minimumFractionDigits: 2,\n maximumFractionDigits: 2,\n })\n return value < 0 ? `(${magnitude})` : magnitude\n}\n\n/** A number, or a number written the way a form prints one (`$141,318.74`). */\nexport function parseFormAmount(value: unknown): number | undefined {\n if (typeof value === 'number' && Number.isFinite(value)) return value\n if (typeof value !== 'string') return undefined\n const normalized = value.replace(/[$,\\s]/gu, '')\n if (!/^-?\\d+(?:\\.\\d+)?$/u.test(normalized)) return undefined\n const parsed = Number(normalized)\n return Number.isFinite(parsed) ? parsed : undefined\n}\n\n/**\n * A checkbox takes a boolean and nothing else.\n *\n * Strict on purpose. Accepting a truthy string would accept the widget's own\n * escaped on-state (`'is#20an#20organization'`) as \"true\", which is exactly\n * the authored-on-state this module refuses — and it would accept `'no'` as\n * true, silently ticking a box the caller meant to leave clear.\n */\nexport function parseFormBoolean(value: unknown): boolean | undefined {\n if (typeof value === 'boolean') return value\n if (typeof value !== 'string') return undefined\n const normalized = value.trim().toLowerCase()\n if (normalized === 'true' || normalized === 'yes') return true\n if (normalized === 'false' || normalized === 'no') return false\n return undefined\n}\n\nfunction textFor(\n value: unknown,\n slot: FormSlot,\n override: FillFormOptions['formatText'],\n): { text: string } | { code: UnfilledCode; reason: string } {\n const custom = override?.(value, slot)\n if (custom !== undefined) return { text: custom }\n if ((slot.format ?? 'text') === 'currency') {\n const amount = parseFormAmount(value)\n if (amount === undefined) return { code: 'not_a_number', reason: 'not a numeric amount' }\n return { text: formatFormCurrency(amount) }\n }\n if (typeof value === 'string') return { text: value }\n if (typeof value === 'number' && Number.isFinite(value)) return { text: String(value) }\n return { code: 'unformattable', reason: `cannot render a ${typeof value} into a text box` }\n}\n\n/**\n * Write a value map onto a blank, returning the bytes plus the exact\n * widget-by-widget account of what was and was not placed.\n */\nexport async function fillPdfForm(options: FillFormOptions): Promise<FillFormResult> {\n const { PDFCheckBox: CheckBox, PDFDocument, PDFName, PDFTextField: TextField } = await import('pdf-lib')\n\n // `updateMetadata: false` keeps the render deterministic — pdf-lib otherwise\n // stamps a ModDate, which would give the same values a different checksum on\n // every call and defeat content-addressed storage. It also keeps our\n // timestamps off the agency's document.\n const document: PDFDocument = await PDFDocument.load(options.pdf, { updateMetadata: false })\n const form = document.getForm()\n\n // Agency forms ship as AcroForm/XFA hybrids, and a reader that prefers the\n // XFA layer would draw the ORIGINAL empty form and show none of these\n // values. `getForm()` drops the XFA packet (pdf-lib does not support it), so\n // this asserts the property rather than trusting it to stay incidental.\n if (document.catalog.getOrCreateAcroForm().dict.has(PDFName.of('XFA'))) {\n throw new Error('filled form still carries an XFA layer — a viewer would draw the blank instead')\n }\n\n const bySlot = new Map(options.registry.slots.map((slot) => [slot.slot, slot]))\n const filled: FilledWidget[] = []\n const unfilled: UnfilledSlot[] = []\n\n for (const [name, value] of Object.entries(options.values)) {\n const slot = bySlot.get(name)\n if (!slot) {\n unfilled.push({\n slot: name,\n value,\n code: 'unknown_slot',\n reason: `${options.registry.form} has no slot named ${name}`,\n })\n continue\n }\n\n if (slot.kind === 'checkbox') {\n const checked = parseFormBoolean(value)\n if (checked === undefined) {\n unfilled.push({\n slot: name,\n value,\n code: 'not_a_boolean',\n reason: 'a checkbox takes true or false; a checkbox on-state is read from the PDF and is never supplied',\n })\n continue\n }\n for (const field of slot.fields) {\n const widget = form.getFieldMaybe(field)\n if (!widget) {\n unfilled.push({ slot: name, field, value, code: 'missing_field', reason: `${options.registry.form} has no widget named ${field}` })\n continue\n }\n if (!(widget instanceof CheckBox)) {\n unfilled.push({ slot: name, field, value, code: 'wrong_kind', reason: `${field} is a ${widget.constructor.name}, not a checkbox` })\n continue\n }\n const box = widget as PDFCheckBox\n // The widget's OWN on-state. Read, never authored: Texas Form 205's\n // are hex-escaped sentences, and a caller that typed one would be\n // asserting a fact about the file it cannot check.\n const onState = box.acroField.getOnValue()?.decodeText()\n if (checked) box.check()\n else box.uncheck()\n filled.push({ slot: name, field, kind: 'checkbox', value, checked, onState })\n }\n continue\n }\n\n const rendered = textFor(value, slot, options.formatText)\n if ('code' in rendered) {\n unfilled.push({ slot: name, value, code: rendered.code, reason: rendered.reason })\n continue\n }\n for (const field of slot.fields) {\n const widget = form.getFieldMaybe(field)\n if (!widget) {\n unfilled.push({ slot: name, field, value, code: 'missing_field', reason: `${options.registry.form} has no widget named ${field}` })\n continue\n }\n if (!(widget instanceof TextField)) {\n unfilled.push({ slot: name, field, value, code: 'wrong_kind', reason: `${field} is a ${widget.constructor.name}, not a text field` })\n continue\n }\n ;(widget as PDFTextField).setText(rendered.text)\n filled.push({ slot: name, field, kind: 'text', value, text: rendered.text })\n }\n }\n\n return {\n bytes: await document.save(),\n filled,\n unfilled,\n form: options.registry.form,\n revision: options.registry.revision,\n }\n}\n\n/**\n * What to tell the agent after a fill.\n *\n * Names the values that did NOT reach the page. Those are exactly the cases\n * where the document and the data disagree, and the agent is the only party\n * that can resolve it — returning a bare \"done\" hands a reviewer a form\n * missing values the data claims are on it.\n */\nexport function describeFormFill(result: FillFormResult): string {\n const count = result.filled.length\n const base = `Filled ${result.form} (${result.revision}) with ${count} ${count === 1 ? 'value' : 'values'}.`\n if (result.unfilled.length === 0) return base\n const detail = result.unfilled.map((entry) => `${entry.slot} (${entry.reason})`).join('; ')\n return `${base} NOT placed on the form: ${detail}. Those values are in the data but not on the document a reviewer opens.`\n}\n","/**\n * Read a filled form back and say, slot by slot, whether the document agrees\n * with the data it was built from.\n *\n * WHAT THIS PROVES, AND WHAT IT CANNOT\n *\n * This proves the WRITE LANDED: the value reached a real widget, that widget\n * exists in the produced file, and it holds the text or tick the data claims.\n * It does NOT prove PLACEMENT — that the widget is the right box on the page —\n * because it resolves the same field names the fill used. Reading a value back\n * by the name you just wrote passes even when the name was invented; measured\n * on tax-agent, an audit of that shape reported 3 passed / 0 failed for three\n * figures sitting in \"Combat zone\" and the date boxes.\n *\n * Placement is proven by `checkRegistryAgainstBlank`, which compares each\n * slot's claimed label against the widget's own `/TU` text inside the PDF.\n * The two checks are complements, and a product that runs only this one has\n * the audit that already failed once. That is stated here rather than in a\n * commit message because a future caller will otherwise reach for the\n * convenient half.\n *\n * FAILS ON ABSENCE. A registry field the produced PDF does not expose is\n * `missing_field`, and an empty `expected` map is `ok: false` — the\n * `passed=0 failed=0` verdict is the exact shape of the bug this replaces.\n */\n\nimport type { PDFCheckBox, PDFDocument, PDFTextField } from 'pdf-lib'\n\nimport { parseFormBoolean, formatFormCurrency, parseFormAmount } from './fill'\nimport { widgetLabel, type FormRegistry } from './registry'\n\nexport type SlotVerdict = 'ok' | 'unknown_slot' | 'missing_field' | 'wrong_kind' | 'not_written' | 'mismatch'\n\n/** What the slot's `label` is worth, checked against the produced file. */\nexport type LabelVerdict = 'matches_widget' | 'label_mismatch' | 'derived_unchecked' | 'no_widget_label'\n\nexport interface SlotVerification {\n slot: string\n field?: string\n verdict: SlotVerdict\n /** What the data says the box should hold. */\n expected?: string\n /** What the box actually holds, read out of the produced bytes. */\n actual?: string\n labelVerdict?: LabelVerdict\n label?: string\n}\n\nexport interface VerifyFormResult {\n ok: boolean\n /** Widgets actually read back. Zero means nothing was proven. */\n verified: number\n slots: SlotVerification[]\n}\n\nfunction expectedText(value: unknown, format: string | undefined): string {\n if (format === 'currency') {\n const amount = parseFormAmount(value)\n return amount === undefined ? String(value) : formatFormCurrency(amount)\n }\n return typeof value === 'number' ? String(value) : String(value)\n}\n\n/**\n * Verify a filled form against the value map it was filled from.\n *\n * `expected` is the SAME shape passed to `fillPdfForm` — deliberately, so a\n * caller cannot verify against a convenient restatement of what it wrote.\n */\nexport async function verifyFilledForm(args: {\n /** The PRODUCED bytes, not the blank. */\n pdf: Uint8Array\n registry: FormRegistry\n expected: Record<string, unknown>\n /** Slots the caller knowingly left out of `expected`; anything else is checked. */\n}): Promise<VerifyFormResult> {\n const { PDFCheckBox: CheckBox, PDFDocument, PDFTextField: TextField } = await import('pdf-lib')\n const document: PDFDocument = await PDFDocument.load(args.pdf, { updateMetadata: false })\n const form = document.getForm()\n\n // The produced file's OWN field list, enumerated before anything the\n // registry claims is consulted. A registry field absent from this set is a\n // failure, never a skip.\n const exposed = new Set(form.getFields().map((field) => field.getName()))\n\n const bySlot = new Map(args.registry.slots.map((slot) => [slot.slot, slot]))\n const slots: SlotVerification[] = []\n let verified = 0\n let ok = true\n\n for (const [name, value] of Object.entries(args.expected)) {\n const slot = bySlot.get(name)\n if (!slot) {\n slots.push({ slot: name, verdict: 'unknown_slot' })\n ok = false\n continue\n }\n for (const field of slot.fields) {\n if (!exposed.has(field)) {\n slots.push({ slot: name, field, verdict: 'missing_field' })\n ok = false\n continue\n }\n const widget = form.getField(field)\n const label = await widgetLabel(form, field)\n const labelVerdict: LabelVerdict =\n slot.labelBasis === 'derived'\n ? 'derived_unchecked'\n : label === undefined\n ? 'no_widget_label'\n : label.replace(/\\s+/gu, ' ').trim().toLowerCase() ===\n slot.label.replace(/\\s+/gu, ' ').trim().toLowerCase()\n ? 'matches_widget'\n : 'label_mismatch'\n if (labelVerdict === 'label_mismatch' || labelVerdict === 'no_widget_label') ok = false\n\n if (slot.kind === 'checkbox') {\n if (!(widget instanceof CheckBox)) {\n slots.push({ slot: name, field, verdict: 'wrong_kind', label, labelVerdict })\n ok = false\n continue\n }\n const want = parseFormBoolean(value)\n const got = (widget as PDFCheckBox).isChecked()\n const verdict: SlotVerdict = want === undefined ? 'mismatch' : got === want ? 'ok' : 'mismatch'\n if (verdict !== 'ok') ok = false\n else verified += 1\n slots.push({\n slot: name,\n field,\n verdict,\n expected: String(want),\n actual: String(got),\n label,\n labelVerdict,\n })\n continue\n }\n\n if (!(widget instanceof TextField)) {\n slots.push({ slot: name, field, verdict: 'wrong_kind', label, labelVerdict })\n ok = false\n continue\n }\n const want = expectedText(value, slot.format)\n const got = (widget as PDFTextField).getText() ?? ''\n const verdict: SlotVerdict = got === '' && want !== '' ? 'not_written' : got === want ? 'ok' : 'mismatch'\n if (verdict !== 'ok') ok = false\n else verified += 1\n slots.push({ slot: name, field, verdict, expected: want, actual: got, label, labelVerdict })\n }\n }\n\n // An audit that checked nothing must never report success. This is the\n // `passed=0 failed=0` verdict, refused by name.\n if (verified === 0) ok = false\n\n return { ok, verified, slots }\n}\n"],"mappings":"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","names":["want","got","verdict"]}
@@ -9,9 +9,9 @@ import { ChatPlanPersistedPart } from './plans/index.js';
9
9
  * `/web-react` re-exports these types into browser bundles, so nothing here may
10
10
  * reach a Node builtin or an engine package.
11
11
  *
12
- * The part shape mirrors the sandbox SDK's `PromptInputPart` structurally
13
- * (text | image | file with filename/mediaType/url/path/content) derived
14
- * here, not imported, so the client bundle never touches the SDK.
12
+ * The client part shape permits an absolute file path until the server converts
13
+ * it to the URL required by the sandbox SDK. It is derived here, not imported,
14
+ * so the client bundle never touches the SDK.
15
15
  */
16
16
  interface ChatTurnTextPartInput {
17
17
  type: 'text';
@@ -27,7 +27,6 @@ interface ChatTurnFilePartInput {
27
27
  mediaType?: string;
28
28
  url?: string;
29
29
  path?: string;
30
- content?: string;
31
30
  }
32
31
  /** Resolve input as either a text part or a file part of a chat turn */
33
32
  type ChatTurnPartInput = ChatTurnTextPartInput | ChatTurnFilePartInput;
@@ -1,11 +1,10 @@
1
- import { AgentProfileFileMount, AgentProfileResourceRef, AgentProfileMcpServer, AgentProfile } from '@tangle-network/sandbox';
1
+ import { AgentProfileFileMount, AgentProfileResourceRef, AgentProfileMcpServer, AgentProfile } from '@tangle-network/agent-interface';
2
2
  import { profile } from '@tangle-network/agent-eval';
3
3
  export { profile } from '@tangle-network/agent-eval';
4
4
  import { SkillEntry } from '../skills/index.js';
5
5
  export { ComposeShellResourcesInput, ComposedSkills, CorpusEntry, CorpusLoadResult, GlobModules, LoadCorpusOptions, ParsedSkill, SkillDeliveryMode, SkillFrontmatter, assertSkillDeliveryDisjoint, composeShellResources, composeSkills, corpusSkills, loadMarkdownCorpus, mergeComposedSkills, parseCorpusSkills, parseSkillFrontmatter, registrySkills, renderInlineSkills, renderSkillIndex, skillEntryFromMarkdown, skillMountPath, skillRefs } from '../skills/index.js';
6
6
  import { C as ComposeProfileBudget } from '../budget-BOucfcb_.js';
7
7
  export { D as DEFAULT_MAX_SYSTEM_PROMPT_BYTES, P as ProfileDrift, a as ProfileDriftEntry, b as ProfileFingerprint, c as ProfileFingerprintContext, d as assertProfilePromptWithinBudget, e as assertSystemPromptWithinBudget, f as diffProfileFingerprints, g as fingerprintAgentProfile, h as formatProfileDrift, l as largestPromptSections } from '../budget-BOucfcb_.js';
8
- import '@tangle-network/agent-interface';
9
8
 
10
9
  /**
11
10
  * Profile composer + evolvable-section seam for agent products.
@@ -24,10 +23,10 @@ import '@tangle-network/agent-interface';
24
23
  * exactly like the registry's free tier
25
24
  *
26
25
  * plus an optional MCP overlay (delegation + per-turn app-tool side channel), a
27
- * per-turn `systemPrompt` override, and a `name` override. The merge is the SDK
28
- * `mergeAgentProfiles`: `mcp` is last-wins per key (base -> overlay), `resources`
29
- * arrays are concatenated (base ++ overlay), `prompt` is shallow-merged so an
30
- * overlay carrying only `systemPrompt` overrides it while keeping base
26
+ * per-turn `systemPrompt` override, and a `name` override. The canonical
27
+ * `mergeAgentProfiles` contract makes `mcp` last-wins per key (base -> overlay),
28
+ * concatenates `resources` arrays (base ++ overlay), and shallow-merges `prompt`
29
+ * so an overlay carrying only `systemPrompt` overrides it while keeping base
31
30
  * instructions. The compose algebra is DATA — the product injects the base
32
31
  * profile, the channel mounts (built with the `skills` subpath primitives), the
33
32
  * delegation/app-tool MCP map, and the override strings; nothing here reaches
@@ -13,7 +13,7 @@ import {
13
13
  skillEntryFromMarkdown,
14
14
  skillMountPath,
15
15
  skillRefs
16
- } from "../chunk-34M7AUWO.js";
16
+ } from "../chunk-M3UFMQ7D.js";
17
17
  import {
18
18
  DEFAULT_MAX_SYSTEM_PROMPT_BYTES,
19
19
  assertProfilePromptWithinBudget,
@@ -25,7 +25,7 @@ import {
25
25
  } from "../chunk-LWSJK546.js";
26
26
 
27
27
  // src/profile/index.ts
28
- import { mergeAgentProfiles } from "@tangle-network/sandbox";
28
+ import { mergeAgentProfiles } from "@tangle-network/agent-interface";
29
29
  import { profile } from "@tangle-network/agent-eval";
30
30
  function userSkillMounts(userSkills) {
31
31
  return userSkills.map(
@@ -69,7 +69,9 @@ function composeAgentProfile(base, channels = {}, overlay = {}, budget = {}) {
69
69
  function pruneEmptyResourceChannels(profile2) {
70
70
  if (!profile2.resources) return profile2;
71
71
  const kept = Object.fromEntries(
72
- Object.entries(profile2.resources).filter(([, value]) => !(Array.isArray(value) && value.length === 0))
72
+ Object.entries(profile2.resources).filter(
73
+ ([, value]) => value !== void 0 && !(Array.isArray(value) && value.length === 0)
74
+ )
73
75
  );
74
76
  const out = { ...profile2, resources: kept };
75
77
  if (kept && Object.keys(kept).length === 0) delete out.resources;
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/profile/index.ts"],"sourcesContent":["/**\n * Profile composer + evolvable-section seam for agent products.\n *\n * The standard \"load a deployable AgentProfile, including skills, plus the\n * skills the end user added to their own instance\" entry point. A product holds\n * a canonical base `AgentProfile` (role/environment/tool-conventions rendered\n * into `prompt.systemPrompt`, baseline skills, baseline MCP). At deploy/turn\n * time it layers four file-mount channels onto `resources.files` —\n *\n * 1. skills — the always-mounted product skill corpus\n * 2. knowledge — a second always-mounted corpus (domain knowledge pack)\n * 3. registry — the tier-gated installable registry (free -> boot-mounted)\n * 4. userSkills — per-user / per-workspace skills the END USER adds to their\n * own instance, mounted at `~/.claude/skills/<id>/SKILL.md`\n * exactly like the registry's free tier\n *\n * plus an optional MCP overlay (delegation + per-turn app-tool side channel), a\n * per-turn `systemPrompt` override, and a `name` override. The merge is the SDK\n * `mergeAgentProfiles`: `mcp` is last-wins per key (base -> overlay), `resources`\n * arrays are concatenated (base ++ overlay), `prompt` is shallow-merged so an\n * overlay carrying only `systemPrompt` overrides it while keeping base\n * instructions. The compose algebra is DATA — the product injects the base\n * profile, the channel mounts (built with the `skills` subpath primitives), the\n * delegation/app-tool MCP map, and the override strings; nothing here reaches\n * for env, a glob, or a specific product's profile.\n *\n * The evolvable-section seam is the loader closure. A product's single\n * self-improvable domain section (the one `applyDomainPatch` targets) loads its\n * body from a deployed markdown override, falling back to an in-tree baseline.\n * The `import.meta.glob('<lit>', ...)` literal must stay at the CONSUMER call\n * site (Vite static-analyzes it), so `makeEvolvableSection` takes the loader as\n * a closure and a REQUIRED `baseline` — it never constructs a glob and never\n * defaults the baseline, so a product can't render an empty learned-guidance\n * section. `stripComments` is the shared \"is this addendum really empty?\" test.\n */\n\nimport type {\n AgentProfile,\n AgentProfileFileMount,\n AgentProfileMcpServer,\n AgentProfileResourceRef,\n} from '@tangle-network/sandbox'\nimport { mergeAgentProfiles } from '@tangle-network/sandbox'\nimport { profile } from '@tangle-network/agent-eval'\nimport {\n composeShellResources,\n registrySkills,\n skillMountPath,\n type ComposeShellResourcesInput,\n type SkillEntry,\n} from '../skills/index'\nimport { assertSystemPromptWithinBudget, type ComposeProfileBudget } from './budget'\n\n/** The prompt byte budget lives in `./budget` (import-free) so `/sandbox` can\n * run the same gate without pulling agent-eval through this module. Re-exported\n * here so the published `/profile` surface is unchanged. */\nexport {\n assertProfilePromptWithinBudget,\n assertSystemPromptWithinBudget,\n DEFAULT_MAX_SYSTEM_PROMPT_BYTES,\n largestPromptSections,\n type ComposeProfileBudget,\n} from './budget'\n\n/** Re-expose the agent-eval section/render substrate so a product wires the\n * evolvable surface through ONE subpath: `makeEvolvableSection` builds the\n * section, `profile.renderProfile` renders it, `profile.applyDomainPatch` lets\n * the loop patch it by id. The rendering/patching engine stays in agent-eval;\n * reach it through this namespace (re-exporting the bare fns would leak\n * agent-eval's un-nameable AgentProfile type into our generated d.ts). */\nexport { profile }\n\n/** The file-mount channels layered onto `resources.files`. The first three\n * mirror {@link ComposeShellResourcesInput}; `userSkills` is the per-user /\n * per-workspace channel — skills the END USER added to their own instance,\n * mounted at the harness skill-discovery path like the registry's free tier. */\nexport interface ProfileChannels {\n /** Always-mounted skill corpus (pass `corpusSkills(...)`). */\n skills?: AgentProfileFileMount[]\n /** Always-mounted knowledge corpus (pass `corpusSkills(...)` for the pack). */\n knowledge?: AgentProfileFileMount[]\n /** Single-file evolvable / learned-guidance corpora, if mounted as files. */\n evolvable?: AgentProfileFileMount[]\n /** Tier-gated installable registry (pass the registry array; free tier is\n * mounted, paid is install-on-demand). Gated through {@link registrySkills}. */\n registry?: SkillEntry[]\n /** Per-user / per-workspace skills the end user adds to their own instance.\n * Mounted at `~/.claude/skills/<id>/SKILL.md`, the same harness path the\n * registry uses, so a user skill and a registry skill with the same id\n * collide deterministically (the user skill, appended last, wins). */\n userSkills?: UserSkill[]\n /** Final skip filter applied to the composed mount list by mount `path`. */\n filesPredicate?: (mount: AgentProfileFileMount) => boolean\n /** Typed `resources.skills` channel — refs the platform materializer places\n * at the harness-native skill dir (see {@link skillRefs} and\n * `@tangle-network/agent-app/skills-placement`'s `composeSkillsForHarness`).\n * The successor to path-baked mounts: `registry`/`userSkills` above mount\n * files at the hardcoded claude-code path via {@link skillMountPath};\n * `skillRefs` instead rides the provider-neutral `resources.skills` field\n * the platform resolves per harness. */\n skillRefs?: AgentProfileResourceRef[]\n /** Tier passed to {@link registrySkills} for the `registry` channel.\n * Previously hardcoded `'free'`; default unchanged. */\n registryTier?: string\n}\n\n/** A per-user / per-workspace skill: an id and an inline `SKILL.md` body. The\n * user-facing analogue of a registry {@link SkillEntry} with no tier gate —\n * every user skill is mounted (the user opted in by adding it). */\nexport interface UserSkill {\n id: string\n /** Inline `SKILL.md` body mounted at {@link skillMountPath}. */\n skillMd: string\n}\n\n/** Overlay overrides applied on top of the channel mounts. */\nexport interface ProfileOverlay {\n /** Extra MCP servers merged into the profile `mcp` map (last-wins per key over\n * the base servers). The product builds this from its delegation MCP entry\n * and any per-turn app-tool side-channel servers. An absent/`undefined` entry\n * is dropped — pass only the servers that resolved (fail-closed at the seam,\n * not here). */\n mcp?: Record<string, AgentProfileMcpServer>\n /** Per-turn system-prompt override. When set, replaces the base\n * `prompt.systemPrompt` while keeping base `prompt.instructions`. When unset,\n * the base prompt passes through unchanged. */\n systemPrompt?: string\n /** Extra instruction lines merged onto the active prompt (e.g. a per-turn\n * domain/integration directive). Appended to base `prompt.instructions` by\n * the SDK merge. */\n instructions?: string[]\n /** Profile `name` override. When unset, the base name is kept. */\n name?: string\n}\n\n/** Project per-user skills onto SDK file mounts at the harness skill-discovery\n * path. No tier gate — a user skill is mounted because the user added it.\n * Sorted by path for determinism (matches {@link registrySkills}). */\nexport function userSkillMounts(userSkills: UserSkill[]): AgentProfileFileMount[] {\n return userSkills\n .map(\n (s) =>\n ({\n path: skillMountPath(s.id),\n resource: { kind: 'inline', name: s.id, content: s.skillMd },\n }) satisfies AgentProfileFileMount,\n )\n .sort((a, b) => a.path.localeCompare(b.path))\n}\n\n/**\n * Compose a deployable `AgentProfile` from a canonical base plus the four\n * file-mount channels and the overlay overrides.\n *\n * Files: base `resources.files` come first; the four channels follow in\n * `skills -> knowledge -> evolvable -> registry -> userSkills` order (so a\n * userSkill that mounts at the same path as a registry skill is the last write\n * and wins). MCP: base servers first, the overlay `mcp` last (last-wins per\n * key). Prompt: the overlay `systemPrompt`, when set, replaces the base one;\n * base instructions are preserved. Name: the overlay `name`, when set, wins.\n *\n * The merge delegates to the SDK `mergeAgentProfiles` (overlay-wins on records,\n * arrays concatenated) — the deterministic algebra is the overlay we hand it,\n * not a hand-rolled spread. `mergeAgentProfiles(base, overlay)` returns\n * `undefined` only when BOTH are `undefined`; `base` is always defined here, so\n * the result is non-`undefined` by construction and we assert that to the caller.\n *\n * The composed `prompt.systemPrompt` is byte-budgeted here — the single point\n * where the FINAL prompt exists ({@link assertSystemPromptWithinBudget};\n * default {@link DEFAULT_MAX_SYSTEM_PROMPT_BYTES}, `warnOnly` escape hatch).\n */\nexport function composeAgentProfile(\n base: AgentProfile,\n channels: ProfileChannels = {},\n overlay: ProfileOverlay = {},\n budget: ComposeProfileBudget = {},\n): AgentProfile {\n const shellInput: ComposeShellResourcesInput = {\n skills: channels.skills,\n knowledge: channels.knowledge,\n evolvable: channels.evolvable,\n registry: channels.registry\n ? registrySkills(channels.registry, channels.registryTier ?? 'free')\n : undefined,\n predicate: channels.filesPredicate,\n }\n const channelFiles = composeShellResources(shellInput)\n const userFiles = channels.userSkills ? userSkillMounts(channels.userSkills) : []\n const overlayFiles = channels.filesPredicate\n ? userFiles.filter(channels.filesPredicate)\n : userFiles\n const files = [...channelFiles, ...overlayFiles]\n\n const promptOverlay: { systemPrompt?: string; instructions?: string[] } = {}\n if (overlay.systemPrompt) promptOverlay.systemPrompt = overlay.systemPrompt\n if (overlay.instructions && overlay.instructions.length > 0) promptOverlay.instructions = overlay.instructions\n\n const overlayProfile: AgentProfile = {\n ...(overlay.name ? { name: overlay.name } : {}),\n ...(Object.keys(promptOverlay).length > 0 ? { prompt: promptOverlay } : {}),\n ...(overlay.mcp ? { mcp: overlay.mcp } : {}),\n resources: {\n files,\n ...(channels.skillRefs && channels.skillRefs.length > 0 ? { skills: channels.skillRefs } : {}),\n },\n }\n\n const merged = mergeAgentProfiles(base, overlayProfile)\n if (!merged)\n throw new Error('composeAgentProfile: mergeAgentProfiles returned undefined for a defined base')\n // Byte-budget gate on the FINAL composed systemPrompt — this is the single\n // point where every channel and overlay has been merged in.\n const systemPrompt = merged.prompt?.systemPrompt\n if (typeof systemPrompt === 'string') assertSystemPromptWithinBudget(systemPrompt, budget)\n return pruneEmptyResourceChannels(merged)\n}\n\n/** Drop empty resource channels the SDK merge normalizes in (`tools`/`skills`/\n * `agents`/`commands`: `[]`), so the composed profile's wire payload carries\n * only the channels that actually have content — one canonical shape every app\n * emits, instead of a sidecar payload full of empty arrays. */\nfunction pruneEmptyResourceChannels(profile: AgentProfile): AgentProfile {\n if (!profile.resources) return profile\n const kept = Object.fromEntries(\n Object.entries(profile.resources).filter(([, value]) => !(Array.isArray(value) && value.length === 0)),\n ) as AgentProfile['resources']\n const out: AgentProfile = { ...profile, resources: kept }\n if (kept && Object.keys(kept).length === 0) delete out.resources\n return out\n}\n\n/** True body of an addendum file with HTML comments stripped — an all-comment\n * placeholder counts as empty, so the loader falls back to the baseline. */\nexport function stripComments(raw: string): string {\n return raw.replace(/<!--[\\s\\S]*?-->/g, '').trim()\n}\n\n/** Inputs to {@link makeEvolvableSection}. */\nexport interface EvolvableSectionInput {\n /** Section id the self-improvement loop targets with `applyDomainPatch`. */\n id: string\n /** Section title rendered as `### <title>`. */\n title: string\n /**\n * Load the deployed section body. The CONSUMER supplies this closure and runs\n * its own `import.meta.glob('<lit>', { eager: true, query: '?raw', import:\n * 'default' })` inside it — the literal must stay at the call site so Vite can\n * static-analyze it; a glob constructed here would not resolve the product's\n * files. Return the raw markdown (comments and all); `makeEvolvableSection`\n * applies {@link stripComments} to decide whether it is really populated.\n */\n load: () => string\n /**\n * The in-tree fallback body, used when `load()` returns an\n * all-comments/empty placeholder. REQUIRED — no internal default — so a\n * product can never accidentally render an empty evolvable section.\n */\n baseline: string\n}\n\n/**\n * Build the one evolvable (`evolvable: true`) domain section whose body comes\n * from the product's loader, falling back to the required baseline when the\n * loaded body is empty after stripping comments. Returns the agent-eval\n * `AgentProfileSection` shape — drop it straight into `prodProfile`'s shipped\n * sections. The loader is the only seam; the empty-vs-populated rule and the\n * baseline fallback are the lifted algebra.\n */\nexport function makeEvolvableSection(input: EvolvableSectionInput): profile.AgentProfileSection {\n const loaded = input.load()\n const body = stripComments(loaded) ? loaded.trim() : input.baseline\n return { id: input.id, title: input.title, body, evolvable: true }\n}\n\nexport {\n assertSkillDeliveryDisjoint,\n composeShellResources,\n composeSkills,\n corpusSkills,\n loadMarkdownCorpus,\n mergeComposedSkills,\n parseCorpusSkills,\n parseSkillFrontmatter,\n registrySkills,\n renderInlineSkills,\n renderSkillIndex,\n skillEntryFromMarkdown,\n skillMountPath,\n skillRefs,\n} from '../skills/index'\nexport type {\n ComposedSkills,\n ComposeShellResourcesInput,\n CorpusEntry,\n CorpusLoadResult,\n GlobModules,\n LoadCorpusOptions,\n ParsedSkill,\n SkillDeliveryMode,\n SkillEntry,\n SkillFrontmatter,\n} from '../skills/index'\nexport {\n diffProfileFingerprints,\n fingerprintAgentProfile,\n formatProfileDrift,\n} from './fingerprint'\nexport type {\n ProfileDrift,\n ProfileDriftEntry,\n ProfileFingerprint,\n ProfileFingerprintContext,\n} from './fingerprint'\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;AA0CA,SAAS,0BAA0B;AACnC,SAAS,eAAe;AA+FjB,SAAS,gBAAgB,YAAkD;AAChF,SAAO,WACJ;AAAA,IACC,CAAC,OACE;AAAA,MACC,MAAM,eAAe,EAAE,EAAE;AAAA,MACzB,UAAU,EAAE,MAAM,UAAU,MAAM,EAAE,IAAI,SAAS,EAAE,QAAQ;AAAA,IAC7D;AAAA,EACJ,EACC,KAAK,CAAC,GAAG,MAAM,EAAE,KAAK,cAAc,EAAE,IAAI,CAAC;AAChD;AAuBO,SAAS,oBACd,MACA,WAA4B,CAAC,GAC7B,UAA0B,CAAC,GAC3B,SAA+B,CAAC,GAClB;AACd,QAAM,aAAyC;AAAA,IAC7C,QAAQ,SAAS;AAAA,IACjB,WAAW,SAAS;AAAA,IACpB,WAAW,SAAS;AAAA,IACpB,UAAU,SAAS,WACf,eAAe,SAAS,UAAU,SAAS,gBAAgB,MAAM,IACjE;AAAA,IACJ,WAAW,SAAS;AAAA,EACtB;AACA,QAAM,eAAe,sBAAsB,UAAU;AACrD,QAAM,YAAY,SAAS,aAAa,gBAAgB,SAAS,UAAU,IAAI,CAAC;AAChF,QAAM,eAAe,SAAS,iBAC1B,UAAU,OAAO,SAAS,cAAc,IACxC;AACJ,QAAM,QAAQ,CAAC,GAAG,cAAc,GAAG,YAAY;AAE/C,QAAM,gBAAoE,CAAC;AAC3E,MAAI,QAAQ,aAAc,eAAc,eAAe,QAAQ;AAC/D,MAAI,QAAQ,gBAAgB,QAAQ,aAAa,SAAS,EAAG,eAAc,eAAe,QAAQ;AAElG,QAAM,iBAA+B;AAAA,IACnC,GAAI,QAAQ,OAAO,EAAE,MAAM,QAAQ,KAAK,IAAI,CAAC;AAAA,IAC7C,GAAI,OAAO,KAAK,aAAa,EAAE,SAAS,IAAI,EAAE,QAAQ,cAAc,IAAI,CAAC;AAAA,IACzE,GAAI,QAAQ,MAAM,EAAE,KAAK,QAAQ,IAAI,IAAI,CAAC;AAAA,IAC1C,WAAW;AAAA,MACT;AAAA,MACA,GAAI,SAAS,aAAa,SAAS,UAAU,SAAS,IAAI,EAAE,QAAQ,SAAS,UAAU,IAAI,CAAC;AAAA,IAC9F;AAAA,EACF;AAEA,QAAM,SAAS,mBAAmB,MAAM,cAAc;AACtD,MAAI,CAAC;AACH,UAAM,IAAI,MAAM,+EAA+E;AAGjG,QAAM,eAAe,OAAO,QAAQ;AACpC,MAAI,OAAO,iBAAiB,SAAU,gCAA+B,cAAc,MAAM;AACzF,SAAO,2BAA2B,MAAM;AAC1C;AAMA,SAAS,2BAA2BA,UAAqC;AACvE,MAAI,CAACA,SAAQ,UAAW,QAAOA;AAC/B,QAAM,OAAO,OAAO;AAAA,IAClB,OAAO,QAAQA,SAAQ,SAAS,EAAE,OAAO,CAAC,CAAC,EAAE,KAAK,MAAM,EAAE,MAAM,QAAQ,KAAK,KAAK,MAAM,WAAW,EAAE;AAAA,EACvG;AACA,QAAM,MAAoB,EAAE,GAAGA,UAAS,WAAW,KAAK;AACxD,MAAI,QAAQ,OAAO,KAAK,IAAI,EAAE,WAAW,EAAG,QAAO,IAAI;AACvD,SAAO;AACT;AAIO,SAAS,cAAc,KAAqB;AACjD,SAAO,IAAI,QAAQ,oBAAoB,EAAE,EAAE,KAAK;AAClD;AAiCO,SAAS,qBAAqB,OAA2D;AAC9F,QAAM,SAAS,MAAM,KAAK;AAC1B,QAAM,OAAO,cAAc,MAAM,IAAI,OAAO,KAAK,IAAI,MAAM;AAC3D,SAAO,EAAE,IAAI,MAAM,IAAI,OAAO,MAAM,OAAO,MAAM,WAAW,KAAK;AACnE;","names":["profile"]}
1
+ {"version":3,"sources":["../../src/profile/index.ts"],"sourcesContent":["/**\n * Profile composer + evolvable-section seam for agent products.\n *\n * The standard \"load a deployable AgentProfile, including skills, plus the\n * skills the end user added to their own instance\" entry point. A product holds\n * a canonical base `AgentProfile` (role/environment/tool-conventions rendered\n * into `prompt.systemPrompt`, baseline skills, baseline MCP). At deploy/turn\n * time it layers four file-mount channels onto `resources.files` —\n *\n * 1. skills — the always-mounted product skill corpus\n * 2. knowledge — a second always-mounted corpus (domain knowledge pack)\n * 3. registry — the tier-gated installable registry (free -> boot-mounted)\n * 4. userSkills — per-user / per-workspace skills the END USER adds to their\n * own instance, mounted at `~/.claude/skills/<id>/SKILL.md`\n * exactly like the registry's free tier\n *\n * plus an optional MCP overlay (delegation + per-turn app-tool side channel), a\n * per-turn `systemPrompt` override, and a `name` override. The canonical\n * `mergeAgentProfiles` contract makes `mcp` last-wins per key (base -> overlay),\n * concatenates `resources` arrays (base ++ overlay), and shallow-merges `prompt`\n * so an overlay carrying only `systemPrompt` overrides it while keeping base\n * instructions. The compose algebra is DATA — the product injects the base\n * profile, the channel mounts (built with the `skills` subpath primitives), the\n * delegation/app-tool MCP map, and the override strings; nothing here reaches\n * for env, a glob, or a specific product's profile.\n *\n * The evolvable-section seam is the loader closure. A product's single\n * self-improvable domain section (the one `applyDomainPatch` targets) loads its\n * body from a deployed markdown override, falling back to an in-tree baseline.\n * The `import.meta.glob('<lit>', ...)` literal must stay at the CONSUMER call\n * site (Vite static-analyzes it), so `makeEvolvableSection` takes the loader as\n * a closure and a REQUIRED `baseline` — it never constructs a glob and never\n * defaults the baseline, so a product can't render an empty learned-guidance\n * section. `stripComments` is the shared \"is this addendum really empty?\" test.\n */\n\nimport type {\n AgentProfile,\n AgentProfileFileMount,\n AgentProfileMcpServer,\n AgentProfileResourceRef,\n} from '@tangle-network/agent-interface'\nimport { mergeAgentProfiles } from '@tangle-network/agent-interface'\nimport { profile } from '@tangle-network/agent-eval'\nimport {\n composeShellResources,\n registrySkills,\n skillMountPath,\n type ComposeShellResourcesInput,\n type SkillEntry,\n} from '../skills/index'\nimport { assertSystemPromptWithinBudget, type ComposeProfileBudget } from './budget'\n\n/** The prompt byte budget lives in `./budget` (import-free) so `/sandbox` can\n * run the same gate without pulling agent-eval through this module. Re-exported\n * here so the published `/profile` surface is unchanged. */\nexport {\n assertProfilePromptWithinBudget,\n assertSystemPromptWithinBudget,\n DEFAULT_MAX_SYSTEM_PROMPT_BYTES,\n largestPromptSections,\n type ComposeProfileBudget,\n} from './budget'\n\n/** Re-expose the agent-eval section/render substrate so a product wires the\n * evolvable surface through ONE subpath: `makeEvolvableSection` builds the\n * section, `profile.renderProfile` renders it, `profile.applyDomainPatch` lets\n * the loop patch it by id. The rendering/patching engine stays in agent-eval;\n * reach it through this namespace (re-exporting the bare fns would leak\n * agent-eval's un-nameable AgentProfile type into our generated d.ts). */\nexport { profile }\n\n/** The file-mount channels layered onto `resources.files`. The first three\n * mirror {@link ComposeShellResourcesInput}; `userSkills` is the per-user /\n * per-workspace channel — skills the END USER added to their own instance,\n * mounted at the harness skill-discovery path like the registry's free tier. */\nexport interface ProfileChannels {\n /** Always-mounted skill corpus (pass `corpusSkills(...)`). */\n skills?: AgentProfileFileMount[]\n /** Always-mounted knowledge corpus (pass `corpusSkills(...)` for the pack). */\n knowledge?: AgentProfileFileMount[]\n /** Single-file evolvable / learned-guidance corpora, if mounted as files. */\n evolvable?: AgentProfileFileMount[]\n /** Tier-gated installable registry (pass the registry array; free tier is\n * mounted, paid is install-on-demand). Gated through {@link registrySkills}. */\n registry?: SkillEntry[]\n /** Per-user / per-workspace skills the end user adds to their own instance.\n * Mounted at `~/.claude/skills/<id>/SKILL.md`, the same harness path the\n * registry uses, so a user skill and a registry skill with the same id\n * collide deterministically (the user skill, appended last, wins). */\n userSkills?: UserSkill[]\n /** Final skip filter applied to the composed mount list by mount `path`. */\n filesPredicate?: (mount: AgentProfileFileMount) => boolean\n /** Typed `resources.skills` channel — refs the platform materializer places\n * at the harness-native skill dir (see {@link skillRefs} and\n * `@tangle-network/agent-app/skills-placement`'s `composeSkillsForHarness`).\n * The successor to path-baked mounts: `registry`/`userSkills` above mount\n * files at the hardcoded claude-code path via {@link skillMountPath};\n * `skillRefs` instead rides the provider-neutral `resources.skills` field\n * the platform resolves per harness. */\n skillRefs?: AgentProfileResourceRef[]\n /** Tier passed to {@link registrySkills} for the `registry` channel.\n * Previously hardcoded `'free'`; default unchanged. */\n registryTier?: string\n}\n\n/** A per-user / per-workspace skill: an id and an inline `SKILL.md` body. The\n * user-facing analogue of a registry {@link SkillEntry} with no tier gate —\n * every user skill is mounted (the user opted in by adding it). */\nexport interface UserSkill {\n id: string\n /** Inline `SKILL.md` body mounted at {@link skillMountPath}. */\n skillMd: string\n}\n\n/** Overlay overrides applied on top of the channel mounts. */\nexport interface ProfileOverlay {\n /** Extra MCP servers merged into the profile `mcp` map (last-wins per key over\n * the base servers). The product builds this from its delegation MCP entry\n * and any per-turn app-tool side-channel servers. An absent/`undefined` entry\n * is dropped — pass only the servers that resolved (fail-closed at the seam,\n * not here). */\n mcp?: Record<string, AgentProfileMcpServer>\n /** Per-turn system-prompt override. When set, replaces the base\n * `prompt.systemPrompt` while keeping base `prompt.instructions`. When unset,\n * the base prompt passes through unchanged. */\n systemPrompt?: string\n /** Extra instruction lines merged onto the active prompt (e.g. a per-turn\n * domain/integration directive). Appended to base `prompt.instructions` by\n * the SDK merge. */\n instructions?: string[]\n /** Profile `name` override. When unset, the base name is kept. */\n name?: string\n}\n\n/** Project per-user skills onto SDK file mounts at the harness skill-discovery\n * path. No tier gate — a user skill is mounted because the user added it.\n * Sorted by path for determinism (matches {@link registrySkills}). */\nexport function userSkillMounts(userSkills: UserSkill[]): AgentProfileFileMount[] {\n return userSkills\n .map(\n (s) =>\n ({\n path: skillMountPath(s.id),\n resource: { kind: 'inline', name: s.id, content: s.skillMd },\n }) satisfies AgentProfileFileMount,\n )\n .sort((a, b) => a.path.localeCompare(b.path))\n}\n\n/**\n * Compose a deployable `AgentProfile` from a canonical base plus the four\n * file-mount channels and the overlay overrides.\n *\n * Files: base `resources.files` come first; the four channels follow in\n * `skills -> knowledge -> evolvable -> registry -> userSkills` order (so a\n * userSkill that mounts at the same path as a registry skill is the last write\n * and wins). MCP: base servers first, the overlay `mcp` last (last-wins per\n * key). Prompt: the overlay `systemPrompt`, when set, replaces the base one;\n * base instructions are preserved. Name: the overlay `name`, when set, wins.\n *\n * The merge delegates to the SDK `mergeAgentProfiles` (overlay-wins on records,\n * arrays concatenated) — the deterministic algebra is the overlay we hand it,\n * not a hand-rolled spread. `mergeAgentProfiles(base, overlay)` returns\n * `undefined` only when BOTH are `undefined`; `base` is always defined here, so\n * the result is non-`undefined` by construction and we assert that to the caller.\n *\n * The composed `prompt.systemPrompt` is byte-budgeted here — the single point\n * where the FINAL prompt exists ({@link assertSystemPromptWithinBudget};\n * default {@link DEFAULT_MAX_SYSTEM_PROMPT_BYTES}, `warnOnly` escape hatch).\n */\nexport function composeAgentProfile(\n base: AgentProfile,\n channels: ProfileChannels = {},\n overlay: ProfileOverlay = {},\n budget: ComposeProfileBudget = {},\n): AgentProfile {\n const shellInput: ComposeShellResourcesInput = {\n skills: channels.skills,\n knowledge: channels.knowledge,\n evolvable: channels.evolvable,\n registry: channels.registry\n ? registrySkills(channels.registry, channels.registryTier ?? 'free')\n : undefined,\n predicate: channels.filesPredicate,\n }\n const channelFiles = composeShellResources(shellInput)\n const userFiles = channels.userSkills ? userSkillMounts(channels.userSkills) : []\n const overlayFiles = channels.filesPredicate\n ? userFiles.filter(channels.filesPredicate)\n : userFiles\n const files = [...channelFiles, ...overlayFiles]\n\n const promptOverlay: { systemPrompt?: string; instructions?: string[] } = {}\n if (overlay.systemPrompt) promptOverlay.systemPrompt = overlay.systemPrompt\n if (overlay.instructions && overlay.instructions.length > 0) promptOverlay.instructions = overlay.instructions\n\n const overlayProfile: AgentProfile = {\n ...(overlay.name ? { name: overlay.name } : {}),\n ...(Object.keys(promptOverlay).length > 0 ? { prompt: promptOverlay } : {}),\n ...(overlay.mcp ? { mcp: overlay.mcp } : {}),\n resources: {\n files,\n ...(channels.skillRefs && channels.skillRefs.length > 0 ? { skills: channels.skillRefs } : {}),\n },\n }\n\n const merged = mergeAgentProfiles(base, overlayProfile)\n if (!merged)\n throw new Error('composeAgentProfile: mergeAgentProfiles returned undefined for a defined base')\n // Byte-budget gate on the FINAL composed systemPrompt — this is the single\n // point where every channel and overlay has been merged in.\n const systemPrompt = merged.prompt?.systemPrompt\n if (typeof systemPrompt === 'string') assertSystemPromptWithinBudget(systemPrompt, budget)\n return pruneEmptyResourceChannels(merged)\n}\n\n/** Drop absent and empty resource channels the canonical merge normalizes in,\n * so the composed profile's wire payload carries\n * only the channels that actually have content — one canonical shape every app\n * emits, instead of a sidecar payload full of empty arrays. */\nfunction pruneEmptyResourceChannels(profile: AgentProfile): AgentProfile {\n if (!profile.resources) return profile\n const kept = Object.fromEntries(\n Object.entries(profile.resources).filter(([, value]) =>\n value !== undefined && !(Array.isArray(value) && value.length === 0),\n ),\n ) as AgentProfile['resources']\n const out: AgentProfile = { ...profile, resources: kept }\n if (kept && Object.keys(kept).length === 0) delete out.resources\n return out\n}\n\n/** True body of an addendum file with HTML comments stripped — an all-comment\n * placeholder counts as empty, so the loader falls back to the baseline. */\nexport function stripComments(raw: string): string {\n return raw.replace(/<!--[\\s\\S]*?-->/g, '').trim()\n}\n\n/** Inputs to {@link makeEvolvableSection}. */\nexport interface EvolvableSectionInput {\n /** Section id the self-improvement loop targets with `applyDomainPatch`. */\n id: string\n /** Section title rendered as `### <title>`. */\n title: string\n /**\n * Load the deployed section body. The CONSUMER supplies this closure and runs\n * its own `import.meta.glob('<lit>', { eager: true, query: '?raw', import:\n * 'default' })` inside it — the literal must stay at the call site so Vite can\n * static-analyze it; a glob constructed here would not resolve the product's\n * files. Return the raw markdown (comments and all); `makeEvolvableSection`\n * applies {@link stripComments} to decide whether it is really populated.\n */\n load: () => string\n /**\n * The in-tree fallback body, used when `load()` returns an\n * all-comments/empty placeholder. REQUIRED — no internal default — so a\n * product can never accidentally render an empty evolvable section.\n */\n baseline: string\n}\n\n/**\n * Build the one evolvable (`evolvable: true`) domain section whose body comes\n * from the product's loader, falling back to the required baseline when the\n * loaded body is empty after stripping comments. Returns the agent-eval\n * `AgentProfileSection` shape — drop it straight into `prodProfile`'s shipped\n * sections. The loader is the only seam; the empty-vs-populated rule and the\n * baseline fallback are the lifted algebra.\n */\nexport function makeEvolvableSection(input: EvolvableSectionInput): profile.AgentProfileSection {\n const loaded = input.load()\n const body = stripComments(loaded) ? loaded.trim() : input.baseline\n return { id: input.id, title: input.title, body, evolvable: true }\n}\n\nexport {\n assertSkillDeliveryDisjoint,\n composeShellResources,\n composeSkills,\n corpusSkills,\n loadMarkdownCorpus,\n mergeComposedSkills,\n parseCorpusSkills,\n parseSkillFrontmatter,\n registrySkills,\n renderInlineSkills,\n renderSkillIndex,\n skillEntryFromMarkdown,\n skillMountPath,\n skillRefs,\n} from '../skills/index'\nexport type {\n ComposedSkills,\n ComposeShellResourcesInput,\n CorpusEntry,\n CorpusLoadResult,\n GlobModules,\n LoadCorpusOptions,\n ParsedSkill,\n SkillDeliveryMode,\n SkillEntry,\n SkillFrontmatter,\n} from '../skills/index'\nexport {\n diffProfileFingerprints,\n fingerprintAgentProfile,\n formatProfileDrift,\n} from './fingerprint'\nexport type {\n ProfileDrift,\n ProfileDriftEntry,\n ProfileFingerprint,\n ProfileFingerprintContext,\n} from './fingerprint'\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;AA0CA,SAAS,0BAA0B;AACnC,SAAS,eAAe;AA+FjB,SAAS,gBAAgB,YAAkD;AAChF,SAAO,WACJ;AAAA,IACC,CAAC,OACE;AAAA,MACC,MAAM,eAAe,EAAE,EAAE;AAAA,MACzB,UAAU,EAAE,MAAM,UAAU,MAAM,EAAE,IAAI,SAAS,EAAE,QAAQ;AAAA,IAC7D;AAAA,EACJ,EACC,KAAK,CAAC,GAAG,MAAM,EAAE,KAAK,cAAc,EAAE,IAAI,CAAC;AAChD;AAuBO,SAAS,oBACd,MACA,WAA4B,CAAC,GAC7B,UAA0B,CAAC,GAC3B,SAA+B,CAAC,GAClB;AACd,QAAM,aAAyC;AAAA,IAC7C,QAAQ,SAAS;AAAA,IACjB,WAAW,SAAS;AAAA,IACpB,WAAW,SAAS;AAAA,IACpB,UAAU,SAAS,WACf,eAAe,SAAS,UAAU,SAAS,gBAAgB,MAAM,IACjE;AAAA,IACJ,WAAW,SAAS;AAAA,EACtB;AACA,QAAM,eAAe,sBAAsB,UAAU;AACrD,QAAM,YAAY,SAAS,aAAa,gBAAgB,SAAS,UAAU,IAAI,CAAC;AAChF,QAAM,eAAe,SAAS,iBAC1B,UAAU,OAAO,SAAS,cAAc,IACxC;AACJ,QAAM,QAAQ,CAAC,GAAG,cAAc,GAAG,YAAY;AAE/C,QAAM,gBAAoE,CAAC;AAC3E,MAAI,QAAQ,aAAc,eAAc,eAAe,QAAQ;AAC/D,MAAI,QAAQ,gBAAgB,QAAQ,aAAa,SAAS,EAAG,eAAc,eAAe,QAAQ;AAElG,QAAM,iBAA+B;AAAA,IACnC,GAAI,QAAQ,OAAO,EAAE,MAAM,QAAQ,KAAK,IAAI,CAAC;AAAA,IAC7C,GAAI,OAAO,KAAK,aAAa,EAAE,SAAS,IAAI,EAAE,QAAQ,cAAc,IAAI,CAAC;AAAA,IACzE,GAAI,QAAQ,MAAM,EAAE,KAAK,QAAQ,IAAI,IAAI,CAAC;AAAA,IAC1C,WAAW;AAAA,MACT;AAAA,MACA,GAAI,SAAS,aAAa,SAAS,UAAU,SAAS,IAAI,EAAE,QAAQ,SAAS,UAAU,IAAI,CAAC;AAAA,IAC9F;AAAA,EACF;AAEA,QAAM,SAAS,mBAAmB,MAAM,cAAc;AACtD,MAAI,CAAC;AACH,UAAM,IAAI,MAAM,+EAA+E;AAGjG,QAAM,eAAe,OAAO,QAAQ;AACpC,MAAI,OAAO,iBAAiB,SAAU,gCAA+B,cAAc,MAAM;AACzF,SAAO,2BAA2B,MAAM;AAC1C;AAMA,SAAS,2BAA2BA,UAAqC;AACvE,MAAI,CAACA,SAAQ,UAAW,QAAOA;AAC/B,QAAM,OAAO,OAAO;AAAA,IAClB,OAAO,QAAQA,SAAQ,SAAS,EAAE;AAAA,MAAO,CAAC,CAAC,EAAE,KAAK,MAChD,UAAU,UAAa,EAAE,MAAM,QAAQ,KAAK,KAAK,MAAM,WAAW;AAAA,IACpE;AAAA,EACF;AACA,QAAM,MAAoB,EAAE,GAAGA,UAAS,WAAW,KAAK;AACxD,MAAI,QAAQ,OAAO,KAAK,IAAI,EAAE,WAAW,EAAG,QAAO,IAAI;AACvD,SAAO;AACT;AAIO,SAAS,cAAc,KAAqB;AACjD,SAAO,IAAI,QAAQ,oBAAoB,EAAE,EAAE,KAAK;AAClD;AAiCO,SAAS,qBAAqB,OAA2D;AAC9F,QAAM,SAAS,MAAM,KAAK;AAC1B,QAAM,OAAO,cAAc,MAAM,IAAI,OAAO,KAAK,IAAI,MAAM;AAC3D,SAAO,EAAE,IAAI,MAAM,IAAI,OAAO,MAAM,OAAO,MAAM,WAAW,KAAK;AACnE;","names":["profile"]}
@@ -1,11 +1,11 @@
1
- import { SandboxInstance, AgentProfileMcpServer, ProvisionEvent, AgentProfileFileMount, AgentProfile, StorageConfig, ScopedTokenScope, TurnDriveResult, Sandbox } from '@tangle-network/sandbox';
1
+ import { SandboxInstance, ProvisionEvent, StorageConfig, ScopedTokenScope, TurnDriveResult, Sandbox, MintScopedTokenOptions } from '@tangle-network/sandbox';
2
2
  export { StorageConfig } from '@tangle-network/sandbox';
3
+ import { AgentProfileMcpServer, AgentProfileFileMount, AgentProfile } from '@tangle-network/agent-interface';
3
4
  import { T as ToolHeaderNames } from '../auth-anc7mv2W.js';
4
5
  import { b as AppToolName, a as AppToolContext } from '../types-BCxK0wyS.js';
5
6
  import { Harness } from '../harness/index.js';
6
7
  import { a as TangleExecutionEnvironment } from '../model-CdCDfBA9.js';
7
8
  import { b as ProfileFingerprint, C as ComposeProfileBudget } from '../budget-BOucfcb_.js';
8
- import '@tangle-network/agent-interface';
9
9
 
10
10
  /** Represent success or failure of an operation with corresponding value or error information */
11
11
  type Outcome<T> = {
@@ -769,11 +769,7 @@ interface ScopedTokenResult {
769
769
  * proxy a narrowed credential). Uses the SDK's native `box.mintScopedToken`,
770
770
  * which normalizes `expiresAt` to a Date — no hand-rolled wire call.
771
771
  */
772
- declare function mintSandboxScopedToken(box: SandboxInstance, options: {
773
- scope: ScopedTokenScope;
774
- sessionId?: string;
775
- ttlMinutes?: number;
776
- }): Promise<Outcome<ScopedTokenResult>>;
772
+ declare function mintSandboxScopedToken(box: SandboxInstance, options: MintScopedTokenOptions): Promise<Outcome<ScopedTokenResult>>;
777
773
  /** Define options to manage deterministic session resumption and turn idempotency in sandboxed drive turns */
778
774
  interface DriveSandboxTurnOptions extends StreamSandboxPromptOptions {
779
775
  /** Deterministic resume key — required. Every tick for the same logical turn
@@ -59,7 +59,7 @@ import {
59
59
  verifySandboxTerminalToken,
60
60
  verifyTerminalProxyToken,
61
61
  writeProfileFilesToBox
62
- } from "../chunk-JXIQSGOV.js";
62
+ } from "../chunk-2ZSSOYXP.js";
63
63
  import "../chunk-LWSJK546.js";
64
64
  import "../chunk-CQZSAR77.js";
65
65
  import "../chunk-ICOHEZK6.js";
@@ -1,4 +1,4 @@
1
- import { AgentProfileFileMount, AgentProfileResourceRef } from '@tangle-network/sandbox';
1
+ import { AgentProfileFileMount, AgentProfileResourceRef } from '@tangle-network/agent-interface';
2
2
 
3
3
  /**
4
4
  * Unified skill + corpus mounter for agent products.
@@ -34,10 +34,11 @@ import { AgentProfileFileMount, AgentProfileResourceRef } from '@tangle-network/
34
34
  * (see `@tangle-network/agent-app/skills-placement`) and deliberately kept out
35
35
  * of this substrate-free module.
36
36
  *
37
- * Substrate-free over storage, exact over the SDK boundary: the only inbound
37
+ * Storage-independent and exact over the profile boundary: the only inbound
38
38
  * seam is the glob-result map the consumer passes in (its call site keeps the
39
39
  * literal `import.meta.glob` Vite must static-analyze); the only outbound seam
40
- * is `@tangle-network/sandbox`'s `AgentProfileFileMount[]`/`AgentProfileResourceRef[]`,
40
+ * is `@tangle-network/agent-interface`'s
41
+ * `AgentProfileFileMount[]`/`AgentProfileResourceRef[]`,
41
42
  * the exact shapes `resources.files`/`resources.skills` consume. Node builtins
42
43
  * are resolved lazily via `process.getBuiltinModule` so a static `node:*`
43
44
  * import never reaches the Vite SSR bundle.
@@ -13,7 +13,7 @@ import {
13
13
  skillEntryFromMarkdown,
14
14
  skillMountPath,
15
15
  skillRefs
16
- } from "../chunk-34M7AUWO.js";
16
+ } from "../chunk-M3UFMQ7D.js";
17
17
  export {
18
18
  assertSkillDeliveryDisjoint,
19
19
  composeShellResources,
@@ -1,7 +1,6 @@
1
1
  import { Harness } from '../harness/index.js';
2
2
  import { SkillEntry, ComposedSkills } from '../skills/index.js';
3
3
  import '@tangle-network/agent-interface';
4
- import '@tangle-network/sandbox';
5
4
 
6
5
  /**
7
6
  * Harness-native skill directory resolution — the one place agent-app binds
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  composeSkills,
3
3
  renderInlineSkills
4
- } from "../chunk-34M7AUWO.js";
4
+ } from "../chunk-M3UFMQ7D.js";
5
5
  import {
6
6
  KNOWN_HARNESSES
7
7
  } from "../chunk-CQZSAR77.js";
@@ -1,12 +1,12 @@
1
+ import {
2
+ InviteAcceptPage
3
+ } from "../chunk-VCPZ3HTN.js";
1
4
  import {
2
5
  MembersPanel
3
6
  } from "../chunk-S564OFTL.js";
4
7
  import {
5
8
  InvitationsPanel
6
9
  } from "../chunk-5SXS3YAB.js";
7
- import {
8
- InviteAcceptPage
9
- } from "../chunk-VCPZ3HTN.js";
10
10
  import "../chunk-6XIAPIW6.js";
11
11
  export {
12
12
  InvitationsPanel,
@@ -5,8 +5,8 @@ export { f as ChatFreeTextField, g as ComposerAnswerDelivery, h as INTERACTION_C
5
5
  import { ChatPlan } from '../plans/index.js';
6
6
  import { InteractionData } from '@tangle-network/agent-interface';
7
7
  export { InteractionData, InteractionOutcome, InteractionRequest } from '@tangle-network/agent-interface';
8
- import { M as FileMention, g as ChatMentionPart, b as ChatAttachmentPart, a as ChatAttachmentKind, Q as ChatAttachmentInput } from '../parts-S1OINlRP.js';
9
- export { f as ChatMentionKind, P as ChatTurnFilePartInput, O as ChatTurnPartInput, N as ChatTurnRequestPayload, U as DISPATCH_MAX_MEDIA_PARTS, V as DISPATCH_MAX_PARTS, W as DISPATCH_REQUEST_MAX_BYTES, X as DISPATCH_STRUCTURAL_RESERVE_BYTES, $ as ProducerErrorEvent, a0 as ProducerNoticeEvent, a1 as ProducerPassthroughEvent, a2 as ProducerPassthroughEventType, a3 as ProducerReasoningEvent, a4 as ProducerTextEvent, a5 as ProducerToolCallEvent, a6 as ProducerToolResultEvent, a7 as ProducerUsageEvent, a8 as ProducerWireEvent, u as attachmentInputToPart, v as attachmentKindForMime, w as attachmentPartsFromMessageParts, ab as base64WireLen, ac as buildMentionPromptBlock, ad as chatTurnRequestInit, ae as fileMentionsToParts, z as isChatAttachmentPart, ag as mediaTypeForMentionPath, J as mentionInputToPart, ah as mentionKindForPath, K as mentionPartsFromMessageParts } from '../parts-S1OINlRP.js';
8
+ import { M as FileMention, g as ChatMentionPart, b as ChatAttachmentPart, a as ChatAttachmentKind, Q as ChatAttachmentInput } from '../parts-fyPPdDdK.js';
9
+ export { f as ChatMentionKind, P as ChatTurnFilePartInput, O as ChatTurnPartInput, N as ChatTurnRequestPayload, U as DISPATCH_MAX_MEDIA_PARTS, V as DISPATCH_MAX_PARTS, W as DISPATCH_REQUEST_MAX_BYTES, X as DISPATCH_STRUCTURAL_RESERVE_BYTES, $ as ProducerErrorEvent, a0 as ProducerNoticeEvent, a1 as ProducerPassthroughEvent, a2 as ProducerPassthroughEventType, a3 as ProducerReasoningEvent, a4 as ProducerTextEvent, a5 as ProducerToolCallEvent, a6 as ProducerToolResultEvent, a7 as ProducerUsageEvent, a8 as ProducerWireEvent, u as attachmentInputToPart, v as attachmentKindForMime, w as attachmentPartsFromMessageParts, ab as base64WireLen, ac as buildMentionPromptBlock, ad as chatTurnRequestInit, ae as fileMentionsToParts, z as isChatAttachmentPart, ag as mediaTypeForMentionPath, J as mentionInputToPart, ah as mentionKindForPath, K as mentionPartsFromMessageParts } from '../parts-fyPPdDdK.js';
10
10
  import { E as EvidenceEntry, g as ExceptionEntry, a as WorkProductProvenance, P as ProfileBacktestSummary, Q as QualityCheck, c as WorkProductPersistedPart, h as WorkProductStatus } from '../types-DB82fktc.js';
11
11
  import { S as StepAgentActivity } from '../agent-activity-C8ZG0F0M.js';
12
12
  import { F as FlowTrace } from '../flow-types-CJxEmaRy.js';
@@ -15,7 +15,7 @@ export { p as parseReviewQueueItem } from '../queue-vRI0Qx3X.js';
15
15
  export { S as SandboxTerminalConnection, a as SandboxTerminalConnectionResponse, U as UseSandboxTerminalConnectionOptions, b as UseSandboxTerminalConnectionResult, t as tabTerminalConnectionId, u as useSandboxTerminalConnection } from '../sandbox-terminal-ChNEdHF8.js';
16
16
  import { CatalogModel } from '../catalog/index.js';
17
17
  import { Harness } from '../harness/index.js';
18
- export { a as ATTACHMENT_ACCEPT, e as FileIndexReadyResponse, f as FileIndexResponse, g as FileIndexWarmingResponse } from '../attachment-validation-Zw8eslhN.js';
18
+ export { a as ATTACHMENT_ACCEPT, e as FileIndexReadyResponse, f as FileIndexResponse, g as FileIndexWarmingResponse } from '../attachment-validation-Dvc_Livy.js';
19
19
  export { a as attachmentPartKey } from '../stream-normalizer-DnuqkZvw.js';
20
20
 
21
21
  /** Represent durable plan decisions as either approved or rejected */
@@ -74,7 +74,7 @@ import {
74
74
  useSmoothText,
75
75
  useThinkingSeconds,
76
76
  waterfallLayout
77
- } from "../chunk-7SMPAAIT.js";
77
+ } from "../chunk-PRKSYTMQ.js";
78
78
  import "../chunk-FBVLEGEG.js";
79
79
  import {
80
80
  EvidenceLineageTable,
@@ -97,7 +97,7 @@ import {
97
97
  } from "../chunk-HCOROIRT.js";
98
98
  import {
99
99
  ATTACHMENT_ACCEPT
100
- } from "../chunk-VM6VLJH6.js";
100
+ } from "../chunk-BI6NKSO4.js";
101
101
  import {
102
102
  DISPATCH_MAX_MEDIA_PARTS,
103
103
  DISPATCH_MAX_PARTS,
@@ -115,7 +115,7 @@ import {
115
115
  mentionInputToPart,
116
116
  mentionKindForPath,
117
117
  mentionPartsFromMessageParts
118
- } from "../chunk-DV2FA2PW.js";
118
+ } from "../chunk-C3SRFZGL.js";
119
119
  import "../chunk-ZVEEWGDK.js";
120
120
  import {
121
121
  attachmentPartKey
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tangle-network/agent-app",
3
- "version": "0.44.18",
3
+ "version": "0.44.21",
4
4
  "packageManager": "pnpm@10.33.4",
5
5
  "description": "Application-shell framework for Tangle agent products: a bounded tool loop, the structured agent→app tool side channel, integration-hub client, per-workspace billing, and crypto — composed over the Tangle agent substrate through typed seams.",
6
6
  "keywords": [
@@ -147,6 +147,11 @@
147
147
  "import": "./dist/preflight/index.js",
148
148
  "default": "./dist/preflight/index.js"
149
149
  },
150
+ "./forms": {
151
+ "types": "./dist/forms/index.d.ts",
152
+ "import": "./dist/forms/index.js",
153
+ "default": "./dist/forms/index.js"
154
+ },
150
155
  "./object-store": {
151
156
  "types": "./dist/object-store/index.d.ts",
152
157
  "import": "./dist/object-store/index.js",
@@ -415,21 +420,22 @@
415
420
  "@cloudflare/workers-types": "^4.20250620.0",
416
421
  "@radix-ui/react-dialog": "^1.1.15",
417
422
  "@tangle-network/agent-docs": "0.2.0",
418
- "@tangle-network/agent-eval": "^0.100.0",
423
+ "@tangle-network/agent-eval": "0.133.2",
419
424
  "@tangle-network/agent-integrations": "^0.44.0",
420
- "@tangle-network/agent-interface": "^0.15.0",
421
- "@tangle-network/agent-knowledge": "^1.7.0",
422
- "@tangle-network/agent-profile-materialize": "^0.6.0",
423
- "@tangle-network/agent-runtime": "^0.79.3",
424
- "@tangle-network/brand": "^1.0.0",
425
- "@tangle-network/sandbox": "^0.10.5",
426
- "@tangle-network/sandbox-ui": "^0.72.0",
425
+ "@tangle-network/agent-interface": "0.36.0",
426
+ "@tangle-network/agent-knowledge": "6.1.4",
427
+ "@tangle-network/agent-profile-materialize": "0.9.0",
428
+ "@tangle-network/agent-runtime": "0.107.4",
429
+ "@tangle-network/brand": "1.1.0",
430
+ "@tangle-network/sandbox": "0.15.1",
431
+ "@tangle-network/sandbox-ui": "0.90.1",
427
432
  "@tangle-network/ui": "^11.0.0",
428
433
  "@testing-library/dom": "^10.4.1",
429
434
  "@testing-library/react": "^16.3.2",
430
435
  "@types/better-sqlite3": "^7.6.13",
431
436
  "@types/node": "^25.6.0",
432
437
  "@types/react": "^19.0.0",
438
+ "@types/react-dom": "19.2.3",
433
439
  "@xterm/addon-fit": "^0.11.0",
434
440
  "@xterm/addon-web-links": "^0.12.0",
435
441
  "@xterm/addon-webgl": "^0.19.0",
@@ -443,6 +449,7 @@
443
449
  "knip": "^5.46.0",
444
450
  "konva": "^10.3.0",
445
451
  "lucide-react": "^1.16.0",
452
+ "pdf-lib": "^1.17.1",
446
453
  "react": "^19.0.0",
447
454
  "react-dom": "^19.2.7",
448
455
  "react-konva": "^19.2.5",
@@ -455,15 +462,15 @@
455
462
  "peerDependencies": {
456
463
  "@huggingface/transformers": ">=3",
457
464
  "@radix-ui/react-dialog": ">=1.1",
458
- "@tangle-network/agent-eval": ">=0.100.0",
465
+ "@tangle-network/agent-eval": "0.133.2",
459
466
  "@tangle-network/agent-integrations": ">=0.44.0",
460
- "@tangle-network/agent-interface": ">=0.15.0",
461
- "@tangle-network/agent-knowledge": ">=1.7.0",
462
- "@tangle-network/agent-profile-materialize": ">=0.6.0",
463
- "@tangle-network/agent-runtime": ">=0.79.3",
464
- "@tangle-network/brand": ">=1.0.0",
465
- "@tangle-network/sandbox": ">=0.9.7",
466
- "@tangle-network/sandbox-ui": ">=0.72.0",
467
+ "@tangle-network/agent-interface": "0.36.0",
468
+ "@tangle-network/agent-knowledge": "6.1.4",
469
+ "@tangle-network/agent-profile-materialize": "0.9.0",
470
+ "@tangle-network/agent-runtime": "0.107.4",
471
+ "@tangle-network/brand": "1.1.0",
472
+ "@tangle-network/sandbox": "0.15.1",
473
+ "@tangle-network/sandbox-ui": "0.90.1",
467
474
  "@xyflow/react": ">=12.0.0",
468
475
  "better-auth": ">=1.6.16",
469
476
  "drizzle-orm": ">=0.36",
@@ -472,7 +479,8 @@
472
479
  "react": ">=18",
473
480
  "react-konva": ">=18",
474
481
  "react-router": ">=7",
475
- "resend": ">=6"
482
+ "resend": ">=6",
483
+ "pdf-lib": ">=1.17"
476
484
  },
477
485
  "peerDependenciesMeta": {
478
486
  "@huggingface/transformers": {
@@ -525,6 +533,9 @@
525
533
  },
526
534
  "resend": {
527
535
  "optional": true
536
+ },
537
+ "pdf-lib": {
538
+ "optional": true
528
539
  }
529
540
  },
530
541
  "dependencies": {