{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "ultra-chatbot-agent",
  "title": "Ultra Chatbot Agent",
  "description": "A complete AI SDK application-shape agent workspace with persistent chat, resumable streams, documents, uploads, RAG, MCP, research reports, and gated sandbox tools.",
  "dependencies": [
    "@ai-sdk/mcp",
    "@ai-sdk/openai",
    "@ai-sdk/react",
    "@base-ui/react",
    "@modelcontextprotocol/sdk",
    "@neondatabase/serverless",
    "@phosphor-icons/react",
    "@vercel/blob",
    "@vercel/sandbox",
    "ai",
    "class-variance-authority",
    "drizzle-orm",
    "ioredis",
    "lucide-react",
    "pdfjs-dist",
    "resumable-stream",
    "server-only",
    "zod"
  ],
  "registryDependencies": [
    "utils",
    "alert-dialog",
    "badge",
    "breadcrumb",
    "button",
    "card",
    "dialog",
    "dropdown-menu",
    "separator",
    "skeleton",
    "spinner",
    "textarea",
    "tooltip",
    "https://elements.ai-sdk.dev/api/registry/attachments.json",
    "https://elements.ai-sdk.dev/api/registry/code-block.json",
    "https://elements.ai-sdk.dev/api/registry/confirmation.json",
    "https://elements.ai-sdk.dev/api/registry/conversation.json",
    "https://elements.ai-sdk.dev/api/registry/message.json",
    "https://elements.ai-sdk.dev/api/registry/model-selector.json",
    "https://elements.ai-sdk.dev/api/registry/reasoning.json",
    "https://elements.ai-sdk.dev/api/registry/shimmer.json",
    "https://elements.ai-sdk.dev/api/registry/sources.json",
    "https://elements.ai-sdk.dev/api/registry/suggestion.json",
    "https://elements.ai-sdk.dev/api/registry/tool.json"
  ],
  "files": [
    {
      "path": "registry/ultra-chatbot-agent/.agents/skills/grill-with-docs/ADR-FORMAT.md",
      "content": "# ADR Format\n\nADRs live in `docs/adr/` and use sequential numbering: `0001-slug.md`, `0002-slug.md`, etc.\n\nCreate the `docs/adr/` directory lazily — only when the first ADR is needed.\n\n## Template\n\n```md\n# {Short title of the decision}\n\n{1-3 sentences: what's the context, what did we decide, and why.}\n```\n\nThat's it. An ADR can be a single paragraph. The value is in recording *that* a decision was made and *why* — not in filling out sections.\n\n## Optional sections\n\nOnly include these when they add genuine value. Most ADRs won't need them.\n\n- **Status** frontmatter (`proposed | accepted | deprecated | superseded by ADR-NNNN`) — useful when decisions are revisited\n- **Considered Options** — only when the rejected alternatives are worth remembering\n- **Consequences** — only when non-obvious downstream effects need to be called out\n\n## Numbering\n\nScan `docs/adr/` for the highest existing number and increment by one.\n\n## When to offer an ADR\n\nAll three of these must be true:\n\n1. **Hard to reverse** — the cost of changing your mind later is meaningful\n2. **Surprising without context** — a future reader will look at the code and wonder \"why on earth did they do it this way?\"\n3. **The result of a real trade-off** — there were genuine alternatives and you picked one for specific reasons\n\nIf a decision is easy to reverse, skip it — you'll just reverse it. If it's not surprising, nobody will wonder why. If there was no real alternative, there's nothing to record beyond \"we did the obvious thing.\"\n\n### What qualifies\n\n- **Architectural shape.** \"We're using a monorepo.\" \"The write model is event-sourced, the read model is projected into Postgres.\"\n- **Integration patterns between contexts.** \"Ordering and Billing communicate via domain events, not synchronous HTTP.\"\n- **Technology choices that carry lock-in.** Database, message bus, auth provider, deployment target. Not every library — just the ones that would take a quarter to swap out.\n- **Boundary and scope decisions.** \"Customer data is owned by the Customer context; other contexts reference it by ID only.\" The explicit no-s are as valuable as the yes-s.\n- **Deliberate deviations from the obvious path.** \"We're using manual SQL instead of an ORM because X.\" Anything where a reasonable reader would assume the opposite. These stop the next engineer from \"fixing\" something that was deliberate.\n- **Constraints not visible in the code.** \"We can't use AWS because of compliance requirements.\" \"Response times must be under 200ms because of the partner API contract.\"\n- **Rejected alternatives when the rejection is non-obvious.** If you considered GraphQL and picked REST for subtle reasons, record it — otherwise someone will suggest GraphQL again in six months.\n",
      "type": "registry:file",
      "target": ".agents/skills/grill-with-docs/ADR-FORMAT.md"
    },
    {
      "path": "registry/ultra-chatbot-agent/.agents/skills/grill-with-docs/CONTEXT-FORMAT.md",
      "content": "# CONTEXT.md Format\n\n## Structure\n\n```md\n# {Context Name}\n\n{One or two sentence description of what this context is and why it exists.}\n\n## Language\n\n**Order**:\n{A one or two sentence description of the term}\n_Avoid_: Purchase, transaction\n\n**Invoice**:\nA request for payment sent to a customer after delivery.\n_Avoid_: Bill, payment request\n\n**Customer**:\nA person or organization that places orders.\n_Avoid_: Client, buyer, account\n```\n\n## Rules\n\n- **Be opinionated.** When multiple words exist for the same concept, pick the best one and list the others as aliases to avoid.\n- **Flag conflicts explicitly.** If a term is used ambiguously, call it out in \"Flagged ambiguities\" with a clear resolution.\n- **Keep definitions tight.** One or two sentences max. Define what it IS, not what it does.\n- **Show relationships.** Use bold term names and express cardinality where obvious.\n- **Only include terms specific to this project's context.** General programming concepts (timeouts, error types, utility patterns) don't belong even if the project uses them extensively. Before adding a term, ask: is this a concept unique to this context, or a general programming concept? Only the former belongs.\n- **Group terms under subheadings** when natural clusters emerge. If all terms belong to a single cohesive area, a flat list is fine.\n- **Write an example dialogue.** A conversation between a dev and a domain expert that demonstrates how the terms interact naturally and clarifies boundaries between related concepts.\n\n## Single vs multi-context repos\n\n**Single context (most repos):** One `CONTEXT.md` at the repo root.\n\n**Multiple contexts:** A `CONTEXT-MAP.md` at the repo root lists the contexts, where they live, and how they relate to each other:\n\n```md\n# Context Map\n\n## Contexts\n\n- [Ordering](./src/ordering/CONTEXT.md) — receives and tracks customer orders\n- [Billing](./src/billing/CONTEXT.md) — generates invoices and processes payments\n- [Fulfillment](./src/fulfillment/CONTEXT.md) — manages warehouse picking and shipping\n\n## Relationships\n\n- **Ordering → Fulfillment**: Ordering emits `OrderPlaced` events; Fulfillment consumes them to start picking\n- **Fulfillment → Billing**: Fulfillment emits `ShipmentDispatched` events; Billing consumes them to generate invoices\n- **Ordering ↔ Billing**: Shared types for `CustomerId` and `Money`\n```\n\nThe skill infers which structure applies:\n\n- If `CONTEXT-MAP.md` exists, read it to find contexts\n- If only a root `CONTEXT.md` exists, single context\n- If neither exists, create a root `CONTEXT.md` lazily when the first term is resolved\n\nWhen multiple contexts exist, infer which one the current topic relates to. If unclear, ask.\n",
      "type": "registry:file",
      "target": ".agents/skills/grill-with-docs/CONTEXT-FORMAT.md"
    },
    {
      "path": "registry/ultra-chatbot-agent/.agents/skills/grill-with-docs/SKILL.md",
      "content": "---\nname: grill-with-docs\ndescription: Grilling session that challenges your plan against the existing domain model, sharpens terminology, and updates documentation (CONTEXT.md, ADRs) inline as decisions crystallise. Use when user wants to stress-test a plan against their project's language and documented decisions.\n---\n\n<what-to-do>\n\nInterview me relentlessly about every aspect of this plan until we reach a shared understanding. Walk down each branch of the design tree, resolving dependencies between decisions one-by-one. For each question, provide your recommended answer.\n\nAsk the questions one at a time, waiting for feedback on each question before continuing.\n\nIf a question can be answered by exploring the codebase, explore the codebase instead.\n\n</what-to-do>\n\n<supporting-info>\n\n## Domain awareness\n\nDuring codebase exploration, also look for existing documentation:\n\n### File structure\n\nMost repos have a single context:\n\n```\n/\n├── CONTEXT.md\n├── docs/\n│   └── adr/\n│       ├── 0001-event-sourced-orders.md\n│       └── 0002-postgres-for-write-model.md\n└── src/\n```\n\nIf a `CONTEXT-MAP.md` exists at the root, the repo has multiple contexts. The map points to where each one lives:\n\n```\n/\n├── CONTEXT-MAP.md\n├── docs/\n│   └── adr/                          ← system-wide decisions\n├── src/\n│   ├── ordering/\n│   │   ├── CONTEXT.md\n│   │   └── docs/adr/                 ← context-specific decisions\n│   └── billing/\n│       ├── CONTEXT.md\n│       └── docs/adr/\n```\n\nCreate files lazily — only when you have something to write. If no `CONTEXT.md` exists, create one when the first term is resolved. If no `docs/adr/` exists, create it when the first ADR is needed.\n\n## During the session\n\n### Challenge against the glossary\n\nWhen the user uses a term that conflicts with the existing language in `CONTEXT.md`, call it out immediately. \"Your glossary defines 'cancellation' as X, but you seem to mean Y — which is it?\"\n\n### Sharpen fuzzy language\n\nWhen the user uses vague or overloaded terms, propose a precise canonical term. \"You're saying 'account' — do you mean the Customer or the User? Those are different things.\"\n\n### Discuss concrete scenarios\n\nWhen domain relationships are being discussed, stress-test them with specific scenarios. Invent scenarios that probe edge cases and force the user to be precise about the boundaries between concepts.\n\n### Cross-reference with code\n\nWhen the user states how something works, check whether the code agrees. If you find a contradiction, surface it: \"Your code cancels entire Orders, but you just said partial cancellation is possible — which is right?\"\n\n### Update CONTEXT.md inline\n\nWhen a term is resolved, update `CONTEXT.md` right there. Don't batch these up — capture them as they happen. Use the format in [CONTEXT-FORMAT.md](./CONTEXT-FORMAT.md).\n\n`CONTEXT.md` should be totally devoid of implementation details. Do not treat `CONTEXT.md` as a spec, a scratch pad, or a repository for implementation decisions. It is a glossary and nothing else.\n\n### Offer ADRs sparingly\n\nOnly offer to create an ADR when all three are true:\n\n1. **Hard to reverse** — the cost of changing your mind later is meaningful\n2. **Surprising without context** — a future reader will wonder \"why did they do it this way?\"\n3. **The result of a real trade-off** — there were genuine alternatives and you picked one for specific reasons\n\nIf any of the three is missing, skip the ADR. Use the format in [ADR-FORMAT.md](./ADR-FORMAT.md).\n\n</supporting-info>\n",
      "type": "registry:file",
      "target": ".agents/skills/grill-with-docs/SKILL.md"
    },
    {
      "path": "registry/ultra-chatbot-agent/.agents/skills/skill-creator/agents/openai.yaml",
      "content": "interface:\n  display_name: \"Skill Creator\"\n  short_description: \"Create or update a skill\"\n  icon_small: \"./assets/skill-creator-small.svg\"\n  icon_large: \"./assets/skill-creator.png\"\n",
      "type": "registry:file",
      "target": ".agents/skills/skill-creator/agents/openai.yaml"
    },
    {
      "path": "registry/ultra-chatbot-agent/.agents/skills/skill-creator/assets/skill-creator-small.svg",
      "content": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"20\" height=\"20\" fill=\"currentColor\" viewBox=\"0 0 20 20\">\n  <path fill=\"#0D0D0D\" d=\"M12.03 4.113a3.612 3.612 0 0 1 5.108 5.108l-6.292 6.29c-.324.324-.56.561-.791.752l-.235.176c-.205.14-.422.261-.65.36l-.229.093a4.136 4.136 0 0 1-.586.16l-.764.134-2.394.4c-.142.024-.294.05-.423.06-.098.007-.232.01-.378-.026l-.149-.05a1.081 1.081 0 0 1-.521-.474l-.046-.093a1.104 1.104 0 0 1-.075-.527c.01-.129.035-.28.06-.422l.398-2.394c.1-.602.162-.987.295-1.35l.093-.23c.1-.228.22-.445.36-.65l.176-.235c.19-.232.428-.467.751-.79l6.292-6.292Zm-5.35 7.232c-.35.35-.534.535-.66.688l-.11.147a2.67 2.67 0 0 0-.24.433l-.062.154c-.08.22-.124.462-.232 1.112l-.398 2.394-.001.001h.003l2.393-.399.717-.126a2.63 2.63 0 0 0 .394-.105l.154-.063a2.65 2.65 0 0 0 .433-.24l.147-.11c.153-.126.339-.31.688-.66l4.988-4.988-3.227-3.226-4.987 4.988Zm9.517-6.291a2.281 2.281 0 0 0-3.225 0l-.364.362 3.226 3.227.363-.364c.89-.89.89-2.334 0-3.225ZM4.583 1.783a.3.3 0 0 1 .294.241c.117.585.347 1.092.707 1.48.357.385.859.668 1.549.783a.3.3 0 0 1 0 .592c-.69.115-1.192.398-1.549.783-.315.34-.53.77-.657 1.265l-.05.215a.3.3 0 0 1-.588 0c-.117-.585-.347-1.092-.707-1.48-.357-.384-.859-.668-1.549-.783a.3.3 0 0 1 0-.592c.69-.115 1.192-.398 1.549-.783.36-.388.59-.895.707-1.48l.015-.05a.3.3 0 0 1 .279-.19Z\"/>\n</svg>\n",
      "type": "registry:file",
      "target": ".agents/skills/skill-creator/assets/skill-creator-small.svg"
    },
    {
      "path": "registry/ultra-chatbot-agent/.agents/skills/skill-creator/assets/skill-creator.png",
      "content": "�PNG\r\n\u001a\n\u0000\u0000\u0000\rIHDR\u0000\u0000\u0000d\u0000\u0000\u0000d\b\u0006\u0000\u0000\u0000p�T\u0000\u0000\u0000\tpHYs\u0000\u0000\u000b\u0013\u0000\u0000\u000b\u0013\u0001\u0000��\u0018\u0000\u0000\u0000\u0001sRGB\u0000��\u001c�\u0000\u0000\u0000\u0004gAMA\u0000\u0000��\u000b�a\u0005\u0000\u0000\u0005�IDATx\u0001��]k\u001cU\u001c��ߙ�m�6i֦mBhd��\"\u00161\"B�M\u0013�P!�\u0005o�3�h\u0011�0x�M\u0013�\n\u0016�\u000f�\"Eھ\u0000I|\u0001%A/�\b�Z%\u0017>d�56Zj�4�fw3�9�\u001c�y�9�3�/��f��~�Ϝ��$\u0000EQ\u0014EQ\u0014EQ\u0014EQ\u0014EQ�B\f�����fPJu��t�\u001bu�\u0000�M&�\u001c�\u001b�E\b%\u001e�|�\u000b���(6d�s�M��/�Ь\t�q��>`\"�\u0012\u000b�?y+\u000b�c\u0018��\fܦ�Kе@`\u0012\u0007�/\u000e�qn\u0014��]�\u0007�� ;��\u0010|,Q ��p\u0006Ek��*\u0003���\u0019Ñ\u000b}�{֗}���\u0014\b�(ߜ�ϧG�S�\u0000\t\fC�?��G޿\b\u001f�=���d�����[)#� �x/��ܛ�X��!\u0002C�4{2��9w\u0007m�|\u0005�XD��\"�?�e�\u0015�\t��\u0010�\u0017\u001b\u001a��3�`�R\b�4��3�P,'d-Fe�Lʒ�\u0010\u001bP{e\u001f�\t�\nC\u0014ʤh%�}I�@�Ð\u0005����P,6�,>5�)���\u001b��\u0012�8�M��/��V,&D`�\u001b�;p/�fL�����\u0005;)�\f\u0014�y\u0010�!_���<\"���\u001dP��A�b�\"��P͂l�!�:\n��Y�n�!�.JB@�bȪ�R2rP��@�b��Gaf�_��b�BF��b5\u0001�\u0015C\u0016\u001aʮ��P,� ~a�\u0002Ga�`��L@�H���!+�,�\u0007�4����P�\u001c��\"\u000b\u0012\u0014F����\u0012��1\u001c����(\u001c\u001f��KJ�+Y$A����\u0017�S6�(̄�\u000f�c:\"VX\u00182ݚC}~\u001c�ꞃ�5l{\u001fK;�0��\u0000�ޜ\u0006�����\u0018KZ��Խ\t�E\n$l\f�7\u0014.1L�Pd@��!SAY��d6^��7\fQ$ޠ�6FeE�\u00157�j��u����eu�����OH�0D\u000e'%\u0010��㣊E\rC�\rJ`\u0018��F��*�l\u0013�@1ʏ�*\u0014u\f�\u001a��1D���k\u0005����9u���1D���\"���4t�\u0010\n\r�0�\u0015\n\ba8/p\u0010�pW� ���@\bC�@@\bC=�A\b�[��cȿ;I\u0018\u001e��-��\u0013���-�@�ԕa��\u000e`'�,I\u0018\"_�!��+�`|���E�^Z>�z�\t�\u0010�3!\f\u0015�\u0002�\u0013��$\u0011C�\u0019�<\u001d��\u001b�P��!�>!���25�$c�<���WU�P��!�6!����Wr�B\u0018�y\u0003ћ\u001d\u001e �5\na<�\u001b\b��C7F!��)�,�?�vw��(��>�\t)�\\bȖQ��\u001f\bc��?\u0006�+�}��֓{v���0��6`�k�rv$��j\u001aC�\f������k�vv>�(�P�z�ݝ~��<�H}\u001f���߾��G>�8U�h\u0006k9[>�X,0D� ���\n+\u0002(��\u0010)m���lڲ�Ay~��\"�^�\rU�|�\nC�4!�Ri��*LJ�0DJ rs��\u0010Qb�!R\u0005�lvY\b(��\u0010)��\u0015�V�\u0007�\u0012k\f�����\u0002@�=���*k�\nk��Z}���9��\u0010w\f��\t�h��U�'Eo��C\u0017b?\u00192� ����\u000f\u0015�P�,,>�\u001e;��\u000ew��\u0007��Ph���9J\u0019b\b�v�\u001dy�\u0003$,ן�\u0012�\u0010]��*\u0014\nuP�྽8�Ƌ�ӕ��,\u0018�Y�[�kzӘ�0�\u0004��A��������3.of��r�����g[��N\u001d͡�#��͑��?����3h�O6B1���ۗ��i\u0012�?�\u001c\u001b\u001b�'�A�>J*6_r�e\u0018�x�鉧(��(��(��(�����\u001fAI�ҟ�j�\u0000\u0000\u0000\u0000IEND�B`�",
      "type": "registry:file",
      "target": ".agents/skills/skill-creator/assets/skill-creator.png"
    },
    {
      "path": "registry/ultra-chatbot-agent/.agents/skills/skill-creator/license.txt",
      "content": "\n                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"[]\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright [yyyy] [name of copyright owner]\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n",
      "type": "registry:file",
      "target": ".agents/skills/skill-creator/license.txt"
    },
    {
      "path": "registry/ultra-chatbot-agent/.agents/skills/skill-creator/references/openai_yaml.md",
      "content": "# openai.yaml fields (full example + descriptions)\n\n`agents/openai.yaml` is an extended, product-specific config intended for the machine/harness to read, not the agent. Other product-specific config can also live in the `agents/` folder.\n\n## Full example\n\n```yaml\ninterface:\n  display_name: \"Optional user-facing name\"\n  short_description: \"Optional user-facing description\"\n  icon_small: \"./assets/small-400px.png\"\n  icon_large: \"./assets/large-logo.svg\"\n  brand_color: \"#3B82F6\"\n  default_prompt: \"Optional surrounding prompt to use the skill with\"\n\ndependencies:\n  tools:\n    - type: \"mcp\"\n      value: \"github\"\n      description: \"GitHub MCP server\"\n      transport: \"streamable_http\"\n      url: \"https://api.githubcopilot.com/mcp/\"\n\npolicy:\n  allow_implicit_invocation: true\n```\n\n## Field descriptions and constraints\n\nTop-level constraints:\n\n- Quote all string values.\n- Keep keys unquoted.\n- For `interface.default_prompt`: generate a helpful, short (typically 1 sentence) example starting prompt based on the skill. It must explicitly mention the skill as `$skill-name` (e.g., \"Use $skill-name-here to draft a concise weekly status update.\").\n\n- `interface.display_name`: Human-facing title shown in UI skill lists and chips.\n- `interface.short_description`: Human-facing short UI blurb (25–64 chars) for quick scanning.\n- `interface.icon_small`: Path to a small icon asset (relative to skill dir). Default to `./assets/` and place icons in the skill's `assets/` folder.\n- `interface.icon_large`: Path to a larger logo asset (relative to skill dir). Default to `./assets/` and place icons in the skill's `assets/` folder.\n- `interface.brand_color`: Hex color used for UI accents (e.g., badges).\n- `interface.default_prompt`: Default prompt snippet inserted when invoking the skill.\n- `dependencies.tools[].type`: Dependency category. Only `mcp` is supported for now.\n- `dependencies.tools[].value`: Identifier of the tool or dependency.\n- `dependencies.tools[].description`: Human-readable explanation of the dependency.\n- `dependencies.tools[].transport`: Connection type when `type` is `mcp`.\n- `dependencies.tools[].url`: MCP server URL when `type` is `mcp`.\n- `policy.allow_implicit_invocation`: When false, the skill is not injected into\n  the model context by default, but can still be invoked explicitly via `$skill`.\n  Defaults to true.\n",
      "type": "registry:file",
      "target": ".agents/skills/skill-creator/references/openai_yaml.md"
    },
    {
      "path": "registry/ultra-chatbot-agent/.agents/skills/skill-creator/scripts/generate_openai_yaml.py",
      "content": "#!/usr/bin/env python3\n\"\"\"\nOpenAI YAML Generator - Creates agents/openai.yaml for a skill folder.\n\nUsage:\n    generate_openai_yaml.py <skill_dir> [--name <skill_name>] [--interface key=value]\n\"\"\"\n\nimport argparse\nimport re\nimport sys\nfrom pathlib import Path\n\nACRONYMS = {\n    \"GH\",\n    \"MCP\",\n    \"API\",\n    \"CI\",\n    \"CLI\",\n    \"LLM\",\n    \"PDF\",\n    \"PR\",\n    \"UI\",\n    \"URL\",\n    \"SQL\",\n}\n\nBRANDS = {\n    \"openai\": \"OpenAI\",\n    \"openapi\": \"OpenAPI\",\n    \"github\": \"GitHub\",\n    \"pagerduty\": \"PagerDuty\",\n    \"datadog\": \"DataDog\",\n    \"sqlite\": \"SQLite\",\n    \"fastapi\": \"FastAPI\",\n}\n\nSMALL_WORDS = {\"and\", \"or\", \"to\", \"up\", \"with\"}\n\nALLOWED_INTERFACE_KEYS = {\n    \"display_name\",\n    \"short_description\",\n    \"icon_small\",\n    \"icon_large\",\n    \"brand_color\",\n    \"default_prompt\",\n}\n\n\ndef yaml_quote(value):\n    escaped = value.replace(\"\\\\\", \"\\\\\\\\\").replace('\"', '\\\\\"').replace(\"\\n\", \"\\\\n\")\n    return f'\"{escaped}\"'\n\n\ndef format_display_name(skill_name):\n    words = [word for word in skill_name.split(\"-\") if word]\n    formatted = []\n    for index, word in enumerate(words):\n        lower = word.lower()\n        upper = word.upper()\n        if upper in ACRONYMS:\n            formatted.append(upper)\n            continue\n        if lower in BRANDS:\n            formatted.append(BRANDS[lower])\n            continue\n        if index > 0 and lower in SMALL_WORDS:\n            formatted.append(lower)\n            continue\n        formatted.append(word.capitalize())\n    return \" \".join(formatted)\n\n\ndef generate_short_description(display_name):\n    description = f\"Help with {display_name} tasks\"\n\n    if len(description) < 25:\n        description = f\"Help with {display_name} tasks and workflows\"\n    if len(description) < 25:\n        description = f\"Help with {display_name} tasks with guidance\"\n\n    if len(description) > 64:\n        description = f\"Help with {display_name}\"\n    if len(description) > 64:\n        description = f\"{display_name} helper\"\n    if len(description) > 64:\n        description = f\"{display_name} tools\"\n    if len(description) > 64:\n        suffix = \" helper\"\n        max_name_length = 64 - len(suffix)\n        trimmed = display_name[:max_name_length].rstrip()\n        description = f\"{trimmed}{suffix}\"\n    if len(description) > 64:\n        description = description[:64].rstrip()\n\n    if len(description) < 25:\n        description = f\"{description} workflows\"\n        if len(description) > 64:\n            description = description[:64].rstrip()\n\n    return description\n\n\ndef read_frontmatter_name(skill_dir):\n    skill_md = Path(skill_dir) / \"SKILL.md\"\n    if not skill_md.exists():\n        print(f\"[ERROR] SKILL.md not found in {skill_dir}\")\n        return None\n    content = skill_md.read_text()\n    match = re.match(r\"^---\\n(.*?)\\n---\", content, re.DOTALL)\n    if not match:\n        print(\"[ERROR] Invalid SKILL.md frontmatter format.\")\n        return None\n    frontmatter_text = match.group(1)\n\n    import yaml\n\n    try:\n        frontmatter = yaml.safe_load(frontmatter_text)\n    except yaml.YAMLError as exc:\n        print(f\"[ERROR] Invalid YAML frontmatter: {exc}\")\n        return None\n    if not isinstance(frontmatter, dict):\n        print(\"[ERROR] Frontmatter must be a YAML dictionary.\")\n        return None\n    name = frontmatter.get(\"name\", \"\")\n    if not isinstance(name, str) or not name.strip():\n        print(\"[ERROR] Frontmatter 'name' is missing or invalid.\")\n        return None\n    return name.strip()\n\n\ndef parse_interface_overrides(raw_overrides):\n    overrides = {}\n    optional_order = []\n    for item in raw_overrides:\n        if \"=\" not in item:\n            print(f\"[ERROR] Invalid interface override '{item}'. Use key=value.\")\n            return None, None\n        key, value = item.split(\"=\", 1)\n        key = key.strip()\n        value = value.strip()\n        if not key:\n            print(f\"[ERROR] Invalid interface override '{item}'. Key is empty.\")\n            return None, None\n        if key not in ALLOWED_INTERFACE_KEYS:\n            allowed = \", \".join(sorted(ALLOWED_INTERFACE_KEYS))\n            print(f\"[ERROR] Unknown interface field '{key}'. Allowed: {allowed}\")\n            return None, None\n        overrides[key] = value\n        if key not in (\"display_name\", \"short_description\") and key not in optional_order:\n            optional_order.append(key)\n    return overrides, optional_order\n\n\ndef write_openai_yaml(skill_dir, skill_name, raw_overrides):\n    overrides, optional_order = parse_interface_overrides(raw_overrides)\n    if overrides is None:\n        return None\n\n    display_name = overrides.get(\"display_name\") or format_display_name(skill_name)\n    short_description = overrides.get(\"short_description\") or generate_short_description(display_name)\n\n    if not (25 <= len(short_description) <= 64):\n        print(\n            \"[ERROR] short_description must be 25-64 characters \"\n            f\"(got {len(short_description)}).\"\n        )\n        return None\n\n    interface_lines = [\n        \"interface:\",\n        f\"  display_name: {yaml_quote(display_name)}\",\n        f\"  short_description: {yaml_quote(short_description)}\",\n    ]\n\n    for key in optional_order:\n        value = overrides.get(key)\n        if value is not None:\n            interface_lines.append(f\"  {key}: {yaml_quote(value)}\")\n\n    agents_dir = Path(skill_dir) / \"agents\"\n    agents_dir.mkdir(parents=True, exist_ok=True)\n    output_path = agents_dir / \"openai.yaml\"\n    output_path.write_text(\"\\n\".join(interface_lines) + \"\\n\")\n    print(f\"[OK] Created agents/openai.yaml\")\n    return output_path\n\n\ndef main():\n    parser = argparse.ArgumentParser(\n        description=\"Create agents/openai.yaml for a skill directory.\",\n    )\n    parser.add_argument(\"skill_dir\", help=\"Path to the skill directory\")\n    parser.add_argument(\n        \"--name\",\n        help=\"Skill name override (defaults to SKILL.md frontmatter)\",\n    )\n    parser.add_argument(\n        \"--interface\",\n        action=\"append\",\n        default=[],\n        help=\"Interface override in key=value format (repeatable)\",\n    )\n    args = parser.parse_args()\n\n    skill_dir = Path(args.skill_dir).resolve()\n    if not skill_dir.exists():\n        print(f\"[ERROR] Skill directory not found: {skill_dir}\")\n        sys.exit(1)\n    if not skill_dir.is_dir():\n        print(f\"[ERROR] Path is not a directory: {skill_dir}\")\n        sys.exit(1)\n\n    skill_name = args.name or read_frontmatter_name(skill_dir)\n    if not skill_name:\n        sys.exit(1)\n\n    result = write_openai_yaml(skill_dir, skill_name, args.interface)\n    if result:\n        sys.exit(0)\n    sys.exit(1)\n\n\nif __name__ == \"__main__\":\n    main()\n",
      "type": "registry:file",
      "target": ".agents/skills/skill-creator/scripts/generate_openai_yaml.py"
    },
    {
      "path": "registry/ultra-chatbot-agent/.agents/skills/skill-creator/scripts/init_skill.py",
      "content": "#!/usr/bin/env python3\n\"\"\"\nSkill Initializer - Creates a new skill from template\n\nUsage:\n    init_skill.py <skill-name> --path <path> [--resources scripts,references,assets] [--examples] [--interface key=value]\n\nExamples:\n    init_skill.py my-new-skill --path skills/public\n    init_skill.py my-new-skill --path skills/public --resources scripts,references\n    init_skill.py my-api-helper --path skills/private --resources scripts --examples\n    init_skill.py custom-skill --path /custom/location\n    init_skill.py my-skill --path skills/public --interface short_description=\"Short UI label\"\n\"\"\"\n\nimport argparse\nimport re\nimport sys\nfrom pathlib import Path\n\nfrom generate_openai_yaml import write_openai_yaml\n\nMAX_SKILL_NAME_LENGTH = 64\nALLOWED_RESOURCES = {\"scripts\", \"references\", \"assets\"}\n\nSKILL_TEMPLATE = \"\"\"---\nname: {skill_name}\ndescription: \"TODO: Complete and informative explanation of what the skill does and when to use it. Include WHEN to use this skill - specific scenarios, file types, or tasks that trigger it.\"\n---\n\n# {skill_title}\n\n## Overview\n\n[TODO: 1-2 sentences explaining what this skill enables]\n\n## Structuring This Skill\n\n[TODO: Choose the structure that best fits this skill's purpose. Common patterns:\n\n**1. Workflow-Based** (best for sequential processes)\n- Works well when there are clear step-by-step procedures\n- Example: DOCX skill with \"Workflow Decision Tree\" -> \"Reading\" -> \"Creating\" -> \"Editing\"\n- Structure: ## Overview -> ## Workflow Decision Tree -> ## Step 1 -> ## Step 2...\n\n**2. Task-Based** (best for tool collections)\n- Works well when the skill offers different operations/capabilities\n- Example: PDF skill with \"Quick Start\" -> \"Merge PDFs\" -> \"Split PDFs\" -> \"Extract Text\"\n- Structure: ## Overview -> ## Quick Start -> ## Task Category 1 -> ## Task Category 2...\n\n**3. Reference/Guidelines** (best for standards or specifications)\n- Works well for brand guidelines, coding standards, or requirements\n- Example: Brand styling with \"Brand Guidelines\" -> \"Colors\" -> \"Typography\" -> \"Features\"\n- Structure: ## Overview -> ## Guidelines -> ## Specifications -> ## Usage...\n\n**4. Capabilities-Based** (best for integrated systems)\n- Works well when the skill provides multiple interrelated features\n- Example: Product Management with \"Core Capabilities\" -> numbered capability list\n- Structure: ## Overview -> ## Core Capabilities -> ### 1. Feature -> ### 2. Feature...\n\nPatterns can be mixed and matched as needed. Most skills combine patterns (e.g., start with task-based, add workflow for complex operations).\n\nDelete this entire \"Structuring This Skill\" section when done - it's just guidance.]\n\n## [TODO: Replace with the first main section based on chosen structure]\n\n[TODO: Add content here. See examples in existing skills:\n- Code samples for technical skills\n- Decision trees for complex workflows\n- Concrete examples with realistic user requests\n- References to scripts/templates/references as needed]\n\n## Resources (optional)\n\nCreate only the resource directories this skill actually needs. Delete this section if no resources are required.\n\n### scripts/\nExecutable code (Python/Bash/etc.) that can be run directly to perform specific operations.\n\n**Examples from other skills:**\n- PDF skill: `fill_fillable_fields.py`, `extract_form_field_info.py` - utilities for PDF manipulation\n- DOCX skill: `document.py`, `utilities.py` - Python modules for document processing\n\n**Appropriate for:** Python scripts, shell scripts, or any executable code that performs automation, data processing, or specific operations.\n\n**Note:** Scripts may be executed without loading into context, but can still be read by Codex for patching or environment adjustments.\n\n### references/\nDocumentation and reference material intended to be loaded into context to inform Codex's process and thinking.\n\n**Examples from other skills:**\n- Product management: `communication.md`, `context_building.md` - detailed workflow guides\n- BigQuery: API reference documentation and query examples\n- Finance: Schema documentation, company policies\n\n**Appropriate for:** In-depth documentation, API references, database schemas, comprehensive guides, or any detailed information that Codex should reference while working.\n\n### assets/\nFiles not intended to be loaded into context, but rather used within the output Codex produces.\n\n**Examples from other skills:**\n- Brand styling: PowerPoint template files (.pptx), logo files\n- Frontend builder: HTML/React boilerplate project directories\n- Typography: Font files (.ttf, .woff2)\n\n**Appropriate for:** Templates, boilerplate code, document templates, images, icons, fonts, or any files meant to be copied or used in the final output.\n\n---\n\n**Not every skill requires all three types of resources.**\n\"\"\"\n\nEXAMPLE_SCRIPT = '''#!/usr/bin/env python3\n\"\"\"\nExample helper script for {skill_name}\n\nThis is a placeholder script that can be executed directly.\nReplace with actual implementation or delete if not needed.\n\nExample real scripts from other skills:\n- pdf/scripts/fill_fillable_fields.py - Fills PDF form fields\n- pdf/scripts/convert_pdf_to_images.py - Converts PDF pages to images\n\"\"\"\n\ndef main():\n    print(\"This is an example script for {skill_name}\")\n    # TODO: Add actual script logic here\n    # This could be data processing, file conversion, API calls, etc.\n\nif __name__ == \"__main__\":\n    main()\n'''\n\nEXAMPLE_REFERENCE = \"\"\"# Reference Documentation for {skill_title}\n\nThis is a placeholder for detailed reference documentation.\nReplace with actual reference content or delete if not needed.\n\nExample real reference docs from other skills:\n- product-management/references/communication.md - Comprehensive guide for status updates\n- product-management/references/context_building.md - Deep-dive on gathering context\n- bigquery/references/ - API references and query examples\n\n## When Reference Docs Are Useful\n\nReference docs are ideal for:\n- Comprehensive API documentation\n- Detailed workflow guides\n- Complex multi-step processes\n- Information too lengthy for main SKILL.md\n- Content that's only needed for specific use cases\n\n## Structure Suggestions\n\n### API Reference Example\n- Overview\n- Authentication\n- Endpoints with examples\n- Error codes\n- Rate limits\n\n### Workflow Guide Example\n- Prerequisites\n- Step-by-step instructions\n- Common patterns\n- Troubleshooting\n- Best practices\n\"\"\"\n\nEXAMPLE_ASSET = \"\"\"# Example Asset File\n\nThis placeholder represents where asset files would be stored.\nReplace with actual asset files (templates, images, fonts, etc.) or delete if not needed.\n\nAsset files are NOT intended to be loaded into context, but rather used within\nthe output Codex produces.\n\nExample asset files from other skills:\n- Brand guidelines: logo.png, slides_template.pptx\n- Frontend builder: hello-world/ directory with HTML/React boilerplate\n- Typography: custom-font.ttf, font-family.woff2\n- Data: sample_data.csv, test_dataset.json\n\n## Common Asset Types\n\n- Templates: .pptx, .docx, boilerplate directories\n- Images: .png, .jpg, .svg, .gif\n- Fonts: .ttf, .otf, .woff, .woff2\n- Boilerplate code: Project directories, starter files\n- Icons: .ico, .svg\n- Data files: .csv, .json, .xml, .yaml\n\nNote: This is a text placeholder. Actual assets can be any file type.\n\"\"\"\n\n\ndef normalize_skill_name(skill_name):\n    \"\"\"Normalize a skill name to lowercase hyphen-case.\"\"\"\n    normalized = skill_name.strip().lower()\n    normalized = re.sub(r\"[^a-z0-9]+\", \"-\", normalized)\n    normalized = normalized.strip(\"-\")\n    normalized = re.sub(r\"-{2,}\", \"-\", normalized)\n    return normalized\n\n\ndef title_case_skill_name(skill_name):\n    \"\"\"Convert hyphenated skill name to Title Case for display.\"\"\"\n    return \" \".join(word.capitalize() for word in skill_name.split(\"-\"))\n\n\ndef parse_resources(raw_resources):\n    if not raw_resources:\n        return []\n    resources = [item.strip() for item in raw_resources.split(\",\") if item.strip()]\n    invalid = sorted({item for item in resources if item not in ALLOWED_RESOURCES})\n    if invalid:\n        allowed = \", \".join(sorted(ALLOWED_RESOURCES))\n        print(f\"[ERROR] Unknown resource type(s): {', '.join(invalid)}\")\n        print(f\"   Allowed: {allowed}\")\n        sys.exit(1)\n    deduped = []\n    seen = set()\n    for resource in resources:\n        if resource not in seen:\n            deduped.append(resource)\n            seen.add(resource)\n    return deduped\n\n\ndef create_resource_dirs(skill_dir, skill_name, skill_title, resources, include_examples):\n    for resource in resources:\n        resource_dir = skill_dir / resource\n        resource_dir.mkdir(exist_ok=True)\n        if resource == \"scripts\":\n            if include_examples:\n                example_script = resource_dir / \"example.py\"\n                example_script.write_text(EXAMPLE_SCRIPT.format(skill_name=skill_name))\n                example_script.chmod(0o755)\n                print(\"[OK] Created scripts/example.py\")\n            else:\n                print(\"[OK] Created scripts/\")\n        elif resource == \"references\":\n            if include_examples:\n                example_reference = resource_dir / \"api_reference.md\"\n                example_reference.write_text(EXAMPLE_REFERENCE.format(skill_title=skill_title))\n                print(\"[OK] Created references/api_reference.md\")\n            else:\n                print(\"[OK] Created references/\")\n        elif resource == \"assets\":\n            if include_examples:\n                example_asset = resource_dir / \"example_asset.txt\"\n                example_asset.write_text(EXAMPLE_ASSET)\n                print(\"[OK] Created assets/example_asset.txt\")\n            else:\n                print(\"[OK] Created assets/\")\n\n\ndef init_skill(skill_name, path, resources, include_examples, interface_overrides):\n    \"\"\"\n    Initialize a new skill directory with template SKILL.md.\n\n    Args:\n        skill_name: Name of the skill\n        path: Path where the skill directory should be created\n        resources: Resource directories to create\n        include_examples: Whether to create example files in resource directories\n\n    Returns:\n        Path to created skill directory, or None if error\n    \"\"\"\n    # Determine skill directory path\n    skill_dir = Path(path).resolve() / skill_name\n\n    # Check if directory already exists\n    if skill_dir.exists():\n        print(f\"[ERROR] Skill directory already exists: {skill_dir}\")\n        return None\n\n    # Create skill directory\n    try:\n        skill_dir.mkdir(parents=True, exist_ok=False)\n        print(f\"[OK] Created skill directory: {skill_dir}\")\n    except Exception as e:\n        print(f\"[ERROR] Error creating directory: {e}\")\n        return None\n\n    # Create SKILL.md from template\n    skill_title = title_case_skill_name(skill_name)\n    skill_content = SKILL_TEMPLATE.format(skill_name=skill_name, skill_title=skill_title)\n\n    skill_md_path = skill_dir / \"SKILL.md\"\n    try:\n        skill_md_path.write_text(skill_content)\n        print(\"[OK] Created SKILL.md\")\n    except Exception as e:\n        print(f\"[ERROR] Error creating SKILL.md: {e}\")\n        return None\n\n    # Create agents/openai.yaml\n    try:\n        result = write_openai_yaml(skill_dir, skill_name, interface_overrides)\n        if not result:\n            return None\n    except Exception as e:\n        print(f\"[ERROR] Error creating agents/openai.yaml: {e}\")\n        return None\n\n    # Create resource directories if requested\n    if resources:\n        try:\n            create_resource_dirs(skill_dir, skill_name, skill_title, resources, include_examples)\n        except Exception as e:\n            print(f\"[ERROR] Error creating resource directories: {e}\")\n            return None\n\n    # Print next steps\n    print(f\"\\n[OK] Skill '{skill_name}' initialized successfully at {skill_dir}\")\n    print(\"\\nNext steps:\")\n    print(\"1. Edit SKILL.md to complete the TODO items and update the description\")\n    if resources:\n        if include_examples:\n            print(\"2. Customize or delete the example files in scripts/, references/, and assets/\")\n        else:\n            print(\"2. Add resources to scripts/, references/, and assets/ as needed\")\n    else:\n        print(\"2. Create resource directories only if needed (scripts/, references/, assets/)\")\n    print(\"3. Update agents/openai.yaml if the UI metadata should differ\")\n    print(\"4. Run the validator when ready to check the skill structure\")\n    print(\n        \"5. Forward-test complex skills with realistic user requests to ensure they work as intended\"\n    )\n\n    return skill_dir\n\n\ndef main():\n    parser = argparse.ArgumentParser(\n        description=\"Create a new skill directory with a SKILL.md template.\",\n    )\n    parser.add_argument(\"skill_name\", help=\"Skill name (normalized to hyphen-case)\")\n    parser.add_argument(\"--path\", required=True, help=\"Output directory for the skill\")\n    parser.add_argument(\n        \"--resources\",\n        default=\"\",\n        help=\"Comma-separated list: scripts,references,assets\",\n    )\n    parser.add_argument(\n        \"--examples\",\n        action=\"store_true\",\n        help=\"Create example files inside the selected resource directories\",\n    )\n    parser.add_argument(\n        \"--interface\",\n        action=\"append\",\n        default=[],\n        help=\"Interface override in key=value format (repeatable)\",\n    )\n    args = parser.parse_args()\n\n    raw_skill_name = args.skill_name\n    skill_name = normalize_skill_name(raw_skill_name)\n    if not skill_name:\n        print(\"[ERROR] Skill name must include at least one letter or digit.\")\n        sys.exit(1)\n    if len(skill_name) > MAX_SKILL_NAME_LENGTH:\n        print(\n            f\"[ERROR] Skill name '{skill_name}' is too long ({len(skill_name)} characters). \"\n            f\"Maximum is {MAX_SKILL_NAME_LENGTH} characters.\"\n        )\n        sys.exit(1)\n    if skill_name != raw_skill_name:\n        print(f\"Note: Normalized skill name from '{raw_skill_name}' to '{skill_name}'.\")\n\n    resources = parse_resources(args.resources)\n    if args.examples and not resources:\n        print(\"[ERROR] --examples requires --resources to be set.\")\n        sys.exit(1)\n\n    path = args.path\n\n    print(f\"Initializing skill: {skill_name}\")\n    print(f\"   Location: {path}\")\n    if resources:\n        print(f\"   Resources: {', '.join(resources)}\")\n        if args.examples:\n            print(\"   Examples: enabled\")\n    else:\n        print(\"   Resources: none (create as needed)\")\n    print()\n\n    result = init_skill(skill_name, path, resources, args.examples, args.interface)\n\n    if result:\n        sys.exit(0)\n    else:\n        sys.exit(1)\n\n\nif __name__ == \"__main__\":\n    main()\n",
      "type": "registry:file",
      "target": ".agents/skills/skill-creator/scripts/init_skill.py"
    },
    {
      "path": "registry/ultra-chatbot-agent/.agents/skills/skill-creator/scripts/quick_validate.py",
      "content": "#!/usr/bin/env python3\n\"\"\"\nQuick validation script for skills - minimal version\n\"\"\"\n\nimport re\nimport sys\nfrom pathlib import Path\n\nimport yaml\n\nMAX_SKILL_NAME_LENGTH = 64\n\n\ndef validate_skill(skill_path):\n    \"\"\"Basic validation of a skill\"\"\"\n    skill_path = Path(skill_path)\n\n    skill_md = skill_path / \"SKILL.md\"\n    if not skill_md.exists():\n        return False, \"SKILL.md not found\"\n\n    content = skill_md.read_text()\n    if not content.startswith(\"---\"):\n        return False, \"No YAML frontmatter found\"\n\n    match = re.match(r\"^---\\n(.*?)\\n---\", content, re.DOTALL)\n    if not match:\n        return False, \"Invalid frontmatter format\"\n\n    frontmatter_text = match.group(1)\n\n    try:\n        frontmatter = yaml.safe_load(frontmatter_text)\n        if not isinstance(frontmatter, dict):\n            return False, \"Frontmatter must be a YAML dictionary\"\n    except yaml.YAMLError as e:\n        return False, f\"Invalid YAML in frontmatter: {e}\"\n\n    allowed_properties = {\"name\", \"description\", \"license\", \"allowed-tools\", \"metadata\"}\n\n    unexpected_keys = set(frontmatter.keys()) - allowed_properties\n    if unexpected_keys:\n        allowed = \", \".join(sorted(allowed_properties))\n        unexpected = \", \".join(sorted(unexpected_keys))\n        return (\n            False,\n            f\"Unexpected key(s) in SKILL.md frontmatter: {unexpected}. Allowed properties are: {allowed}\",\n        )\n\n    if \"name\" not in frontmatter:\n        return False, \"Missing 'name' in frontmatter\"\n    if \"description\" not in frontmatter:\n        return False, \"Missing 'description' in frontmatter\"\n\n    name = frontmatter.get(\"name\", \"\")\n    if not isinstance(name, str):\n        return False, f\"Name must be a string, got {type(name).__name__}\"\n    name = name.strip()\n    if name:\n        if not re.match(r\"^[a-z0-9-]+$\", name):\n            return (\n                False,\n                f\"Name '{name}' should be hyphen-case (lowercase letters, digits, and hyphens only)\",\n            )\n        if name.startswith(\"-\") or name.endswith(\"-\") or \"--\" in name:\n            return (\n                False,\n                f\"Name '{name}' cannot start/end with hyphen or contain consecutive hyphens\",\n            )\n        if len(name) > MAX_SKILL_NAME_LENGTH:\n            return (\n                False,\n                f\"Name is too long ({len(name)} characters). \"\n                f\"Maximum is {MAX_SKILL_NAME_LENGTH} characters.\",\n            )\n\n    description = frontmatter.get(\"description\", \"\")\n    if not isinstance(description, str):\n        return False, f\"Description must be a string, got {type(description).__name__}\"\n    description = description.strip()\n    if description:\n        if \"<\" in description or \">\" in description:\n            return False, \"Description cannot contain angle brackets (< or >)\"\n        if len(description) > 1024:\n            return (\n                False,\n                f\"Description is too long ({len(description)} characters). Maximum is 1024 characters.\",\n            )\n\n    return True, \"Skill is valid!\"\n\n\nif __name__ == \"__main__\":\n    if len(sys.argv) != 2:\n        print(\"Usage: python quick_validate.py <skill_directory>\")\n        sys.exit(1)\n\n    valid, message = validate_skill(sys.argv[1])\n    print(message)\n    sys.exit(0 if valid else 1)\n",
      "type": "registry:file",
      "target": ".agents/skills/skill-creator/scripts/quick_validate.py"
    },
    {
      "path": "registry/ultra-chatbot-agent/.agents/skills/skill-creator/SKILL.md",
      "content": "---\nname: skill-creator\ndescription: Guide for creating effective skills. This skill should be used when users want to create a new skill (or update an existing skill) that extends Codex's capabilities with specialized knowledge, workflows, or tool integrations.\nmetadata:\n  short-description: Create or update a skill\n---\n\n# Skill Creator\n\nThis skill provides guidance for creating effective skills.\n\n## About Skills\n\nSkills are modular, self-contained folders that extend Codex's capabilities by providing\nspecialized knowledge, workflows, and tools. Think of them as \"onboarding guides\" for specific\ndomains or tasks—they transform Codex from a general-purpose agent into a specialized agent\nequipped with procedural knowledge that no model can fully possess.\n\n### What Skills Provide\n\n1. Specialized workflows - Multi-step procedures for specific domains\n2. Tool integrations - Instructions for working with specific file formats or APIs\n3. Domain expertise - Company-specific knowledge, schemas, business logic\n4. Bundled resources - Scripts, references, and assets for complex and repetitive tasks\n\n## Core Principles\n\n### Concise is Key\n\nThe context window is a public good. Skills share the context window with everything else Codex needs: system prompt, conversation history, other Skills' metadata, and the actual user request.\n\n**Default assumption: Codex is already very smart.** Only add context Codex doesn't already have. Challenge each piece of information: \"Does Codex really need this explanation?\" and \"Does this paragraph justify its token cost?\"\n\nPrefer concise examples over verbose explanations.\n\n### Set Appropriate Degrees of Freedom\n\nMatch the level of specificity to the task's fragility and variability:\n\n**High freedom (text-based instructions)**: Use when multiple approaches are valid, decisions depend on context, or heuristics guide the approach.\n\n**Medium freedom (pseudocode or scripts with parameters)**: Use when a preferred pattern exists, some variation is acceptable, or configuration affects behavior.\n\n**Low freedom (specific scripts, few parameters)**: Use when operations are fragile and error-prone, consistency is critical, or a specific sequence must be followed.\n\nThink of Codex as exploring a path: a narrow bridge with cliffs needs specific guardrails (low freedom), while an open field allows many routes (high freedom).\n\n### Protect Validation Integrity\n\nYou may use subagents during iteration to validate whether a skill works on realistic tasks or whether a suspected problem is real. This is most useful when you want an independent pass on the skill's behavior, outputs, or failure modes after a revision.  Only do this when it is possible to start new subagents.\n\nWhen using subagents for validation, treat that as an evaluation surface. The goal is to learn whether the skill generalizes, not whether another agent can reconstruct the answer from leaked context.\n\nPrefer raw artifacts such as example prompts, outputs, diffs, logs, or traces. Give the minimum task-local context needed to perform the validation. Avoid passing the intended answer, suspected bug, intended fix, or your prior conclusions unless the validation explicitly requires them.\n\n### Anatomy of a Skill\n\nEvery skill consists of a required SKILL.md file and optional bundled resources:\n\n```\nskill-name/\n├── SKILL.md (required)\n│   ├── YAML frontmatter metadata (required)\n│   │   ├── name: (required)\n│   │   └── description: (required)\n│   └── Markdown instructions (required)\n├── agents/ (recommended)\n│   └── openai.yaml - UI metadata for skill lists and chips\n└── Bundled Resources (optional)\n    ├── scripts/          - Executable code (Python/Bash/etc.)\n    ├── references/       - Documentation intended to be loaded into context as needed\n    └── assets/           - Files used in output (templates, icons, fonts, etc.)\n```\n\n#### SKILL.md (required)\n\nEvery SKILL.md consists of:\n\n- **Frontmatter** (YAML): Contains `name` and `description` fields. These are the only fields that Codex reads to determine when the skill gets used, thus it is very important to be clear and comprehensive in describing what the skill is, and when it should be used.\n- **Body** (Markdown): Instructions and guidance for using the skill. Only loaded AFTER the skill triggers (if at all).\n\n#### Agents metadata (recommended)\n\n- UI-facing metadata for skill lists and chips\n- Read references/openai_yaml.md before generating values and follow its descriptions and constraints\n- Create: human-facing `display_name`, `short_description`, and `default_prompt` by reading the skill\n- Generate deterministically by passing the values as `--interface key=value` to `scripts/generate_openai_yaml.py` or `scripts/init_skill.py`\n- On updates: validate `agents/openai.yaml` still matches SKILL.md; regenerate if stale\n- Only include other optional interface fields (icons, brand color) if explicitly provided\n- See references/openai_yaml.md for field definitions and examples\n\n#### Bundled Resources (optional)\n\n##### Scripts (`scripts/`)\n\nExecutable code (Python/Bash/etc.) for tasks that require deterministic reliability or are repeatedly rewritten.\n\n- **When to include**: When the same code is being rewritten repeatedly or deterministic reliability is needed\n- **Example**: `scripts/rotate_pdf.py` for PDF rotation tasks\n- **Benefits**: Token efficient, deterministic, may be executed without loading into context\n- **Note**: Scripts may still need to be read by Codex for patching or environment-specific adjustments\n\n##### References (`references/`)\n\nDocumentation and reference material intended to be loaded as needed into context to inform Codex's process and thinking.\n\n- **When to include**: For documentation that Codex should reference while working\n- **Examples**: `references/finance.md` for financial schemas, `references/mnda.md` for company NDA template, `references/policies.md` for company policies, `references/api_docs.md` for API specifications\n- **Use cases**: Database schemas, API documentation, domain knowledge, company policies, detailed workflow guides\n- **Benefits**: Keeps SKILL.md lean, loaded only when Codex determines it's needed\n- **Best practice**: If files are large (>10k words), include grep search patterns in SKILL.md\n- **Avoid duplication**: Information should live in either SKILL.md or references files, not both. Prefer references files for detailed information unless it's truly core to the skill—this keeps SKILL.md lean while making information discoverable without hogging the context window. Keep only essential procedural instructions and workflow guidance in SKILL.md; move detailed reference material, schemas, and examples to references files.\n\n##### Assets (`assets/`)\n\nFiles not intended to be loaded into context, but rather used within the output Codex produces.\n\n- **When to include**: When the skill needs files that will be used in the final output\n- **Examples**: `assets/logo.png` for brand assets, `assets/slides.pptx` for PowerPoint templates, `assets/frontend-template/` for HTML/React boilerplate, `assets/font.ttf` for typography\n- **Use cases**: Templates, images, icons, boilerplate code, fonts, sample documents that get copied or modified\n- **Benefits**: Separates output resources from documentation, enables Codex to use files without loading them into context\n\n#### What to Not Include in a Skill\n\nA skill should only contain essential files that directly support its functionality. Do NOT create extraneous documentation or auxiliary files, including:\n\n- README.md\n- INSTALLATION_GUIDE.md\n- QUICK_REFERENCE.md\n- CHANGELOG.md\n- etc.\n\nThe skill should only contain the information needed for an AI agent to do the job at hand. It should not contain auxiliary context about the process that went into creating it, setup and testing procedures, user-facing documentation, etc. Creating additional documentation files just adds clutter and confusion.\n\n### Progressive Disclosure Design Principle\n\nSkills use a three-level loading system to manage context efficiently:\n\n1. **Metadata (name + description)** - Always in context (~100 words)\n2. **SKILL.md body** - When skill triggers (<5k words)\n3. **Bundled resources** - As needed by Codex (Unlimited because scripts can be executed without reading into context window)\n\n#### Progressive Disclosure Patterns\n\nKeep SKILL.md body to the essentials and under 500 lines to minimize context bloat. Split content into separate files when approaching this limit. When splitting out content into other files, it is very important to reference them from SKILL.md and describe clearly when to read them, to ensure the reader of the skill knows they exist and when to use them.\n\n**Key principle:** When a skill supports multiple variations, frameworks, or options, keep only the core workflow and selection guidance in SKILL.md. Move variant-specific details (patterns, examples, configuration) into separate reference files.\n\n**Pattern 1: High-level guide with references**\n\n```markdown\n# PDF Processing\n\n## Quick start\n\nExtract text with pdfplumber:\n[code example]\n\n## Advanced features\n\n- **Form filling**: See [FORMS.md](FORMS.md) for complete guide\n- **API reference**: See [REFERENCE.md](REFERENCE.md) for all methods\n- **Examples**: See [EXAMPLES.md](EXAMPLES.md) for common patterns\n```\n\nCodex loads FORMS.md, REFERENCE.md, or EXAMPLES.md only when needed.\n\n**Pattern 2: Domain-specific organization**\n\nFor Skills with multiple domains, organize content by domain to avoid loading irrelevant context:\n\n```\nbigquery-skill/\n├── SKILL.md (overview and navigation)\n└── reference/\n    ├── finance.md (revenue, billing metrics)\n    ├── sales.md (opportunities, pipeline)\n    ├── product.md (API usage, features)\n    └── marketing.md (campaigns, attribution)\n```\n\nWhen a user asks about sales metrics, Codex only reads sales.md.\n\nSimilarly, for skills supporting multiple frameworks or variants, organize by variant:\n\n```\ncloud-deploy/\n├── SKILL.md (workflow + provider selection)\n└── references/\n    ├── aws.md (AWS deployment patterns)\n    ├── gcp.md (GCP deployment patterns)\n    └── azure.md (Azure deployment patterns)\n```\n\nWhen the user chooses AWS, Codex only reads aws.md.\n\n**Pattern 3: Conditional details**\n\nShow basic content, link to advanced content:\n\n```markdown\n# DOCX Processing\n\n## Creating documents\n\nUse docx-js for new documents. See [DOCX-JS.md](DOCX-JS.md).\n\n## Editing documents\n\nFor simple edits, modify the XML directly.\n\n**For tracked changes**: See [REDLINING.md](REDLINING.md)\n**For OOXML details**: See [OOXML.md](OOXML.md)\n```\n\nCodex reads REDLINING.md or OOXML.md only when the user needs those features.\n\n**Important guidelines:**\n\n- **Avoid deeply nested references** - Keep references one level deep from SKILL.md. All reference files should link directly from SKILL.md.\n- **Structure longer reference files** - For files longer than 100 lines, include a table of contents at the top so Codex can see the full scope when previewing.\n\n## Skill Creation Process\n\nSkill creation involves these steps:\n\n1. Understand the skill with concrete examples\n2. Plan reusable skill contents (scripts, references, assets)\n3. Initialize the skill (run init_skill.py)\n4. Edit the skill (implement resources and write SKILL.md)\n5. Validate the skill (run quick_validate.py)\n6. Iterate based on real usage and forward-test complex skills.\n\nFollow these steps in order, skipping only if there is a clear reason why they are not applicable.\n\n### Skill Naming\n\n- Use lowercase letters, digits, and hyphens only; normalize user-provided titles to hyphen-case (e.g., \"Plan Mode\" -> `plan-mode`).\n- When generating names, generate a name under 64 characters (letters, digits, hyphens).\n- Prefer short, verb-led phrases that describe the action.\n- Namespace by tool when it improves clarity or triggering (e.g., `gh-address-comments`, `linear-address-issue`).\n- Name the skill folder exactly after the skill name.\n\n### Step 1: Understanding the Skill with Concrete Examples\n\nSkip this step only when the skill's usage patterns are already clearly understood. It remains valuable even when working with an existing skill.\n\nTo create an effective skill, clearly understand concrete examples of how the skill will be used. This understanding can come from either direct user examples or generated examples that are validated with user feedback.\n\nFor example, when building an image-editor skill, relevant questions include:\n\n- \"What functionality should the image-editor skill support? Editing, rotating, anything else?\"\n- \"Can you give some examples of how this skill would be used?\"\n- \"I can imagine users asking for things like 'Remove the red-eye from this image' or 'Rotate this image'. Are there other ways you imagine this skill being used?\"\n- \"What would a user say that should trigger this skill?\"\n- \"Where should I create this skill? If you do not have a preference, I will place it in `$CODEX_HOME/skills` (or `~/.codex/skills` when `CODEX_HOME` is unset) so Codex can discover it automatically.\"\n\nTo avoid overwhelming users, avoid asking too many questions in a single message. Start with the most important questions and follow up as needed for better effectiveness.\n\nConclude this step when there is a clear sense of the functionality the skill should support.\n\n### Step 2: Planning the Reusable Skill Contents\n\nTo turn concrete examples into an effective skill, analyze each example by:\n\n1. Considering how to execute on the example from scratch\n2. Identifying what scripts, references, and assets would be helpful when executing these workflows repeatedly\n\nExample: When building a `pdf-editor` skill to handle queries like \"Help me rotate this PDF,\" the analysis shows:\n\n1. Rotating a PDF requires re-writing the same code each time\n2. A `scripts/rotate_pdf.py` script would be helpful to store in the skill\n\nExample: When designing a `frontend-webapp-builder` skill for queries like \"Build me a todo app\" or \"Build me a dashboard to track my steps,\" the analysis shows:\n\n1. Writing a frontend webapp requires the same boilerplate HTML/React each time\n2. An `assets/hello-world/` template containing the boilerplate HTML/React project files would be helpful to store in the skill\n\nExample: When building a `big-query` skill to handle queries like \"How many users have logged in today?\" the analysis shows:\n\n1. Querying BigQuery requires re-discovering the table schemas and relationships each time\n2. A `references/schema.md` file documenting the table schemas would be helpful to store in the skill\n\nTo establish the skill's contents, analyze each concrete example to create a list of the reusable resources to include: scripts, references, and assets.\n\n### Step 3: Initializing the Skill\n\nAt this point, it is time to actually create the skill.\n\nSkip this step only if the skill being developed already exists. In this case, continue to the next step.\n\nBefore running `init_skill.py`, ask where the user wants the skill created. If they do not specify a location, default to `$CODEX_HOME/skills`; when `CODEX_HOME` is unset, fall back to `~/.codex/skills` so the skill is auto-discovered.\n\nWhen creating a new skill from scratch, always run the `init_skill.py` script. The script conveniently generates a new template skill directory that automatically includes everything a skill requires, making the skill creation process much more efficient and reliable.\n\nUsage:\n\n```bash\nscripts/init_skill.py <skill-name> --path <output-directory> [--resources scripts,references,assets] [--examples]\n```\n\nExamples:\n\n```bash\nscripts/init_skill.py my-skill --path \"${CODEX_HOME:-$HOME/.codex}/skills\"\nscripts/init_skill.py my-skill --path \"${CODEX_HOME:-$HOME/.codex}/skills\" --resources scripts,references\nscripts/init_skill.py my-skill --path ~/work/skills --resources scripts --examples\n```\n\nThe script:\n\n- Creates the skill directory at the specified path\n- Generates a SKILL.md template with proper frontmatter and TODO placeholders\n- Creates `agents/openai.yaml` using agent-generated `display_name`, `short_description`, and `default_prompt` passed via `--interface key=value`\n- Optionally creates resource directories based on `--resources`\n- Optionally adds example files when `--examples` is set\n\nAfter initialization, customize the SKILL.md and add resources as needed. If you used `--examples`, replace or delete placeholder files.\n\nGenerate `display_name`, `short_description`, and `default_prompt` by reading the skill, then pass them as `--interface key=value` to `init_skill.py` or regenerate with:\n\n```bash\nscripts/generate_openai_yaml.py <path/to/skill-folder> --interface key=value\n```\n\nOnly include other optional interface fields when the user explicitly provides them. For full field descriptions and examples, see references/openai_yaml.md.\n\n### Step 4: Edit the Skill\n\nWhen editing the (newly-generated or existing) skill, remember that the skill is being created for another instance of Codex to use. Include information that would be beneficial and non-obvious to Codex. Consider what procedural knowledge, domain-specific details, or reusable assets would help another Codex instance execute these tasks more effectively.\n\nAfter substantial revisions, or if the skill is particularly tricky, you should use subagents to forward-test the skill on realistic tasks or artifacts. When doing so, pass the artifact under validation rather than your diagnosis of what is wrong, and keep the prompt generic enough that success depends on transferable reasoning rather than hidden ground truth.\n\n#### Start with Reusable Skill Contents\n\nTo begin implementation, start with the reusable resources identified above: `scripts/`, `references/`, and `assets/` files. Note that this step may require user input. For example, when implementing a `brand-guidelines` skill, the user may need to provide brand assets or templates to store in `assets/`, or documentation to store in `references/`.\n\nAdded scripts must be tested by actually running them to ensure there are no bugs and that the output matches what is expected. If there are many similar scripts, only a representative sample needs to be tested to ensure confidence that they all work while balancing time to completion.\n\nIf you used `--examples`, delete any placeholder files that are not needed for the skill. Only create resource directories that are actually required.\n\n#### Update SKILL.md\n\n**Writing Guidelines:** Always use imperative/infinitive form.\n\n##### Frontmatter\n\nWrite the YAML frontmatter with `name` and `description`:\n\n- `name`: The skill name\n- `description`: This is the primary triggering mechanism for your skill, and helps Codex understand when to use the skill.\n  - Include both what the Skill does and specific triggers/contexts for when to use it.\n  - Include all \"when to use\" information here - Not in the body. The body is only loaded after triggering, so \"When to Use This Skill\" sections in the body are not helpful to Codex.\n  - Example description for a `docx` skill: \"Comprehensive document creation, editing, and analysis with support for tracked changes, comments, formatting preservation, and text extraction. Use when Codex needs to work with professional documents (.docx files) for: (1) Creating new documents, (2) Modifying or editing content, (3) Working with tracked changes, (4) Adding comments, or any other document tasks\"\n\nDo not include any other fields in YAML frontmatter.\n\n##### Body\n\nWrite instructions for using the skill and its bundled resources.\n\n### Step 5: Validate the Skill\n\nOnce development of the skill is complete, validate the skill folder to catch basic issues early:\n\n```bash\nscripts/quick_validate.py <path/to/skill-folder>\n```\n\nThe validation script checks YAML frontmatter format, required fields, and naming rules. If validation fails, fix the reported issues and run the command again.\n\n### Step 6: Iterate\n\nAfter testing the skill, you may detect the skill is complex enough that it requires forward-testing; or users may request improvements.\n\nUser testing often this happens right after using the skill, with fresh context of how the skill performed.\n\n**Forward-testing and iteration workflow:**\n\n1. Use the skill on real tasks\n2. Notice struggles or inefficiencies\n3. Identify how SKILL.md or bundled resources should be updated\n4. Implement changes and test again\n5. Forward-test if it is reasonable and appropriate\n\n## Forward-testing\n\nTo forward-test, launch subagents as a way to stress test the skill with minimal context.\nSubagents should *not* know that they are being asked to test the skill.  They should be treated as\nan agent asked to perform a task by the user.  Prompts to subagents should look like:\n  `Use $skill-x at /path/to/skill-x to solve problem y`\nNot:\n  `Review the skill at /path/to/skill-x; pretend a user asks you to...`\n\nDecision rule for forward-testing:\n  - Err on the side of forward-testing\n  - Ask for approval if you think there's a risk that forward-testing would:\n    * take a long time,\n    * require additional approvals from the user, or\n    * modify live production systems\n\n  In these cases, show the user your proposed prompt and request (1) a yes/no decision, and\n  (2) any suggested modifictions.\n\nConsiderations when forward-testing:\n   - use fresh threads for independent passes\n   - pass the skill, and a request in a similar way the user would.\n   - pass raw artifacts, not your conclusions\n   - avoid showing expected answers or intended fixes\n   - rebuild context from source artifacts after each iteration\n   - review the subagent's output and reasoning and emitted artifacts\n   - avoid leaving artifacts the agent can find on disk between iterations;\n     clean up subagents' artifacts to avoid additional contamination.\n\nIf forward-testing only succeeds when subagents see leaked context, tighten the skill or the\nforward-testing setup before trusting the result.\n",
      "type": "registry:file",
      "target": ".agents/skills/skill-creator/SKILL.md"
    },
    {
      "path": "registry/ultra-chatbot-agent/app/api/cron/ultra-chatbot-agent-cleanup/route.ts",
      "content": "import { getUltraChatbotAgentAppEnv } from \"@/lib/ultra-chatbot-agent/server/env\";\n\nconst appEnv = getUltraChatbotAgentAppEnv();\nimport {\n  getCronSecret,\n  getCronSecretError,\n} from \"@/lib/ultra-chatbot-agent/server/env\";\nimport {\n  cleanupExpiredUltraChatbotAgentDemoData,\n  ultraChatbotAgentCleanupCronScheduleUtc,\n} from \"@/lib/ultra-chatbot-agent/server/cleanup\";\n\nfunction isAuthorizedCronRequest(request: Request, cronSecret: string) {\n  const authorizationHeader = request.headers.get(\"authorization\");\n\n  return authorizationHeader === `Bearer ${cronSecret}`;\n}\n\nexport async function GET(request: Request) {\n  let cronSecret: string;\n\n  try {\n    cronSecret = getCronSecret(appEnv);\n  } catch (error) {\n    return Response.json(\n      {\n        error: error instanceof Error ? error.message : getCronSecretError(),\n      },\n      { status: 500 }\n    );\n  }\n\n  if (!isAuthorizedCronRequest(request, cronSecret)) {\n    return Response.json(\n      {\n        error: \"Unauthorized ultra-chatbot-agent cleanup request.\",\n      },\n      { status: 401 }\n    );\n  }\n\n  try {\n    const result = await cleanupExpiredUltraChatbotAgentDemoData();\n\n    return Response.json({\n      ...result,\n      scheduleUtc: ultraChatbotAgentCleanupCronScheduleUtc,\n    });\n  } catch (error) {\n    return Response.json(\n      {\n        error:\n          error instanceof Error\n            ? error.message\n            : \"Ultra-chatbot-agent cleanup failed.\",\n      },\n      { status: 500 }\n    );\n  }\n}\n",
      "type": "registry:page",
      "target": "app/api/cron/ultra-chatbot-agent-cleanup/route.ts"
    },
    {
      "path": "registry/ultra-chatbot-agent/app/api/demos/ultra-chatbot-agent/[id]/capabilities/route.ts",
      "content": "import { handleUltraChatbotAgentCapabilitySettingsPatchRequest } from \"@/lib/ultra-chatbot-agent/server/capability-settings\";\nimport { handleUltraChatbotAgentVisitorRequest } from \"@/lib/ultra-chatbot-agent/server/viewer-context\";\n\ninterface RouteContext {\n  params: Promise<{\n    id: string;\n  }>;\n}\n\nexport async function PATCH(request: Request, context: RouteContext) {\n  const { id } = await context.params;\n  return handleUltraChatbotAgentVisitorRequest(\n    request,\n    async (_request, visitor) =>\n      handleUltraChatbotAgentCapabilitySettingsPatchRequest(request, {\n        chatId: id,\n        visitorId: visitor.visitorId,\n      })\n  );\n}\n",
      "type": "registry:page",
      "target": "app/api/demos/ultra-chatbot-agent/[id]/capabilities/route.ts"
    },
    {
      "path": "registry/ultra-chatbot-agent/app/api/demos/ultra-chatbot-agent/[id]/messages/route.ts",
      "content": "import { handleUltraChatbotAgentMessageEditRequest } from \"@/lib/ultra-chatbot-agent/server/message-edit\";\nimport { handleUltraChatbotAgentVisitorRequest } from \"@/lib/ultra-chatbot-agent/server/viewer-context\";\n\ninterface RouteContext {\n  params: Promise<{\n    id: string;\n  }>;\n}\n\nexport async function PATCH(request: Request, context: RouteContext) {\n  const { id } = await context.params;\n  return handleUltraChatbotAgentVisitorRequest(\n    request,\n    async (_request, visitor) =>\n      handleUltraChatbotAgentMessageEditRequest(request, {\n        chatId: id,\n        visitorId: visitor.visitorId,\n      })\n  );\n}\n",
      "type": "registry:page",
      "target": "app/api/demos/ultra-chatbot-agent/[id]/messages/route.ts"
    },
    {
      "path": "registry/ultra-chatbot-agent/app/api/demos/ultra-chatbot-agent/[id]/route.ts",
      "content": "import { handleUltraChatbotAgentDeleteChatRequest } from \"@/lib/ultra-chatbot-agent/server/history\";\nimport { handleUltraChatbotAgentVisitorRequest } from \"@/lib/ultra-chatbot-agent/server/viewer-context\";\n\ninterface RouteContext {\n  params: Promise<{\n    id: string;\n  }>;\n}\n\nexport async function DELETE(request: Request, context: RouteContext) {\n  const { id } = await context.params;\n  return handleUltraChatbotAgentVisitorRequest(\n    request,\n    async (_request, visitor) =>\n      handleUltraChatbotAgentDeleteChatRequest(id, {\n        visitorId: visitor.visitorId,\n      })\n  );\n}\n",
      "type": "registry:page",
      "target": "app/api/demos/ultra-chatbot-agent/[id]/route.ts"
    },
    {
      "path": "registry/ultra-chatbot-agent/app/api/demos/ultra-chatbot-agent/[id]/session/route.ts",
      "content": "import { handleUltraChatbotAgentSessionRequest } from \"@/lib/ultra-chatbot-agent/server/runtime\";\nimport { handleUltraChatbotAgentVisitorRequest } from \"@/lib/ultra-chatbot-agent/server/viewer-context\";\n\nexport async function GET(\n  request: Request,\n  { params }: { params: Promise<{ id: string }> }\n) {\n  const { id } = await params;\n  return handleUltraChatbotAgentVisitorRequest(\n    request,\n    async (_request, visitor) =>\n      handleUltraChatbotAgentSessionRequest(id, {\n        visitorId: visitor.visitorId,\n      })\n  );\n}\n",
      "type": "registry:page",
      "target": "app/api/demos/ultra-chatbot-agent/[id]/session/route.ts"
    },
    {
      "path": "registry/ultra-chatbot-agent/app/api/demos/ultra-chatbot-agent/[id]/stream/route.ts",
      "content": "import { handleUltraChatbotAgentStreamResumeRequest } from \"@/lib/ultra-chatbot-agent/server/runtime\";\nimport { handleUltraChatbotAgentVisitorRequest } from \"@/lib/ultra-chatbot-agent/server/viewer-context\";\n\nexport async function GET(\n  request: Request,\n  { params }: { params: Promise<{ id: string }> }\n) {\n  const { id } = await params;\n  return handleUltraChatbotAgentVisitorRequest(\n    request,\n    async (_request, visitor) =>\n      handleUltraChatbotAgentStreamResumeRequest(id, {\n        visitorId: visitor.visitorId,\n      })\n  );\n}\n",
      "type": "registry:page",
      "target": "app/api/demos/ultra-chatbot-agent/[id]/stream/route.ts"
    },
    {
      "path": "registry/ultra-chatbot-agent/app/api/demos/ultra-chatbot-agent/[id]/visibility/route.ts",
      "content": "import { handleUltraChatbotAgentVisitorRequest } from \"@/lib/ultra-chatbot-agent/server/viewer-context\";\nimport { handleUltraChatbotAgentVisibilityPatchRequest } from \"@/lib/ultra-chatbot-agent/server/visibility\";\n\ninterface RouteContext {\n  params: Promise<{\n    id: string;\n  }>;\n}\n\nexport async function PATCH(request: Request, context: RouteContext) {\n  const { id } = await context.params;\n  return handleUltraChatbotAgentVisitorRequest(\n    request,\n    async (_request, visitor) =>\n      handleUltraChatbotAgentVisibilityPatchRequest(request, {\n        chatId: id,\n        visitorId: visitor.visitorId,\n      })\n  );\n}\n",
      "type": "registry:page",
      "target": "app/api/demos/ultra-chatbot-agent/[id]/visibility/route.ts"
    },
    {
      "path": "registry/ultra-chatbot-agent/app/api/demos/ultra-chatbot-agent/document/route.ts",
      "content": "import { handleUltraChatbotAgentDocumentRequest } from \"@/lib/ultra-chatbot-agent/server/documents\";\nimport { handleUltraChatbotAgentVisitorRequest } from \"@/lib/ultra-chatbot-agent/server/viewer-context\";\n\nexport async function GET(request: Request) {\n  return handleUltraChatbotAgentVisitorRequest(\n    request,\n    async (_request, visitor) =>\n      handleUltraChatbotAgentDocumentRequest(request, {\n        visitorId: visitor.visitorId,\n      })\n  );\n}\n\nexport async function POST(request: Request) {\n  return handleUltraChatbotAgentVisitorRequest(\n    request,\n    async (_request, visitor) =>\n      handleUltraChatbotAgentDocumentRequest(request, {\n        visitorId: visitor.visitorId,\n      })\n  );\n}\n\nexport async function DELETE(request: Request) {\n  return handleUltraChatbotAgentVisitorRequest(\n    request,\n    async (_request, visitor) =>\n      handleUltraChatbotAgentDocumentRequest(request, {\n        visitorId: visitor.visitorId,\n      })\n  );\n}\n",
      "type": "registry:page",
      "target": "app/api/demos/ultra-chatbot-agent/document/route.ts"
    },
    {
      "path": "registry/ultra-chatbot-agent/app/api/demos/ultra-chatbot-agent/files/upload/route.ts",
      "content": "import { handleUltraChatbotAgentFileUploadRequest } from \"@/lib/ultra-chatbot-agent/server/upload\";\nimport { handleUltraChatbotAgentVisitorRequest } from \"@/lib/ultra-chatbot-agent/server/viewer-context\";\n\nexport async function POST(request: Request) {\n  return handleUltraChatbotAgentVisitorRequest(\n    request,\n    async (ownedRequest, viewer) =>\n      handleUltraChatbotAgentFileUploadRequest(ownedRequest, {\n        visitorId: viewer.visitorId,\n      })\n  );\n}\n",
      "type": "registry:page",
      "target": "app/api/demos/ultra-chatbot-agent/files/upload/route.ts"
    },
    {
      "path": "registry/ultra-chatbot-agent/app/api/demos/ultra-chatbot-agent/history/route.ts",
      "content": "import {\n  handleUltraChatbotAgentDeleteHistoryRequest,\n  handleUltraChatbotAgentHistoryRequest,\n} from \"@/lib/ultra-chatbot-agent/server/history\";\nimport { handleUltraChatbotAgentVisitorRequest } from \"@/lib/ultra-chatbot-agent/server/viewer-context\";\n\nexport async function GET(request: Request) {\n  return handleUltraChatbotAgentVisitorRequest(\n    request,\n    async (_request, visitor) =>\n      handleUltraChatbotAgentHistoryRequest(request, {\n        visitorId: visitor.visitorId,\n      })\n  );\n}\n\nexport async function DELETE(request: Request) {\n  return handleUltraChatbotAgentVisitorRequest(\n    request,\n    async (_request, visitor) =>\n      handleUltraChatbotAgentDeleteHistoryRequest({\n        visitorId: visitor.visitorId,\n      })\n  );\n}\n",
      "type": "registry:page",
      "target": "app/api/demos/ultra-chatbot-agent/history/route.ts"
    },
    {
      "path": "registry/ultra-chatbot-agent/app/api/demos/ultra-chatbot-agent/mcp/project-docs/route.ts",
      "content": "import { handleProjectDocsMcpRequest } from \"@/lib/ultra-chatbot-agent/project-docs-mcp/project-mcp-server\";\n\nexport const runtime = \"nodejs\";\n\nexport function DELETE(request: Request) {\n  return handleProjectDocsMcpRequest(request);\n}\n\nexport function GET(request: Request) {\n  return handleProjectDocsMcpRequest(request);\n}\n\nexport function POST(request: Request) {\n  return handleProjectDocsMcpRequest(request);\n}\n",
      "type": "registry:page",
      "target": "app/api/demos/ultra-chatbot-agent/mcp/project-docs/route.ts"
    },
    {
      "path": "registry/ultra-chatbot-agent/app/api/demos/ultra-chatbot-agent/models/route.ts",
      "content": "import { getUltraChatbotAgentModelCatalog } from \"@/lib/ultra-chatbot-agent/server/models\";\n\nexport async function GET() {\n  return Response.json(\n    {\n      models: getUltraChatbotAgentModelCatalog(),\n    },\n    {\n      headers: {\n        \"Cache-Control\": \"public, max-age=86400, s-maxage=86400\",\n      },\n    }\n  );\n}\n",
      "type": "registry:page",
      "target": "app/api/demos/ultra-chatbot-agent/models/route.ts"
    },
    {
      "path": "registry/ultra-chatbot-agent/app/api/demos/ultra-chatbot-agent/route.ts",
      "content": "import { handleUltraChatbotAgentChatRequest } from \"@/lib/ultra-chatbot-agent/server/runtime\";\nimport { handleUltraChatbotAgentVisitorRequest } from \"@/lib/ultra-chatbot-agent/server/viewer-context\";\n\nexport async function POST(request: Request) {\n  return handleUltraChatbotAgentVisitorRequest(\n    request,\n    async (_request, visitor) =>\n    handleUltraChatbotAgentChatRequest(request, {\n      visitorId: visitor.visitorId,\n      })\n  );\n}\n",
      "type": "registry:page",
      "target": "app/api/demos/ultra-chatbot-agent/route.ts"
    },
    {
      "path": "registry/ultra-chatbot-agent/app/api/demos/ultra-chatbot-agent/suggestions/route.ts",
      "content": "import { handleUltraChatbotAgentSuggestionsRequest } from \"@/lib/ultra-chatbot-agent/server/suggestions\";\nimport { handleUltraChatbotAgentVisitorRequest } from \"@/lib/ultra-chatbot-agent/server/viewer-context\";\n\nexport async function GET(request: Request) {\n  return handleUltraChatbotAgentVisitorRequest(\n    request,\n    async (_request, visitor) =>\n      handleUltraChatbotAgentSuggestionsRequest(request, {\n        visitorId: visitor.visitorId,\n      })\n  );\n}\n",
      "type": "registry:page",
      "target": "app/api/demos/ultra-chatbot-agent/suggestions/route.ts"
    },
    {
      "path": "registry/ultra-chatbot-agent/app/api/demos/ultra-chatbot-agent/vote/route.ts",
      "content": "import { handleUltraChatbotAgentVisitorRequest } from \"@/lib/ultra-chatbot-agent/server/viewer-context\";\nimport {\n  handleUltraChatbotAgentVoteListRequest,\n  handleUltraChatbotAgentVotePatchRequest,\n} from \"@/lib/ultra-chatbot-agent/server/votes\";\n\nexport async function GET(request: Request) {\n  return handleUltraChatbotAgentVisitorRequest(\n    request,\n    async (_request, visitor) =>\n      handleUltraChatbotAgentVoteListRequest(request, {\n        visitorId: visitor.visitorId,\n      })\n  );\n}\n\nexport async function PATCH(request: Request) {\n  return handleUltraChatbotAgentVisitorRequest(\n    request,\n    async (_request, visitor) =>\n      handleUltraChatbotAgentVotePatchRequest(request, {\n        visitorId: visitor.visitorId,\n      })\n  );\n}\n",
      "type": "registry:page",
      "target": "app/api/demos/ultra-chatbot-agent/vote/route.ts"
    },
    {
      "path": "registry/ultra-chatbot-agent/app/demos/ultra-chatbot-agent/[id]/page.tsx",
      "content": "import { notFound } from \"next/navigation\";\n\nimport { loadUltraChatbotAgentScreenData } from \"@/lib/ultra-chatbot-agent/server/session\";\nimport { UltraChatbotAgentScreen } from \"@/components/ultra-chatbot-agent/ultra-chatbot-agent-screen\";\n\nexport default async function UltraChatbotAgentConversationPage({\n  params,\n}: {\n  params: Promise<{ id: string }>;\n}) {\n  const { id } = await params;\n  const screenData = await loadUltraChatbotAgentScreenData({\n    chatId: id,\n  });\n\n  if (!screenData.initialSession) {\n    notFound();\n  }\n\n  return <UltraChatbotAgentScreen {...screenData} />;\n}\n",
      "type": "registry:page",
      "target": "app/demos/ultra-chatbot-agent/[id]/page.tsx"
    },
    {
      "path": "registry/ultra-chatbot-agent/app/demos/ultra-chatbot-agent/page.tsx",
      "content": "import { loadUltraChatbotAgentScreenData } from \"@/lib/ultra-chatbot-agent/server/session\";\nimport { UltraChatbotAgentScreen } from \"@/components/ultra-chatbot-agent/ultra-chatbot-agent-screen\";\n\nexport const dynamic = \"force-dynamic\";\n\nexport default async function UltraChatbotAgentPage() {\n  const screenData = await loadUltraChatbotAgentScreenData({\n    chatId: null,\n  });\n\n  return <UltraChatbotAgentScreen {...screenData} />;\n}\n",
      "type": "registry:page",
      "target": "app/demos/ultra-chatbot-agent/page.tsx"
    },
    {
      "path": "registry/ultra-chatbot-agent/components/ai-elements/code-block.tsx",
      "content": "\"use client\";\n\n/* eslint-disable react-hooks/refs */\n\nimport { Button } from \"@/components/ui/button\";\nimport {\n  Select,\n  SelectContent,\n  SelectItem,\n  SelectTrigger,\n  SelectValue,\n} from \"@/components/ui/select\";\nimport { cn } from \"@/lib/utils\";\nimport { CheckIcon, CopyIcon } from \"lucide-react\";\nimport type { ComponentProps, CSSProperties, HTMLAttributes } from \"react\";\nimport {\n  createContext,\n  memo,\n  useCallback,\n  useContext,\n  useEffect,\n  useMemo,\n  useRef,\n  useState,\n} from \"react\";\nimport type {\n  BundledLanguage,\n  BundledTheme,\n  HighlighterGeneric,\n  ThemedToken,\n} from \"shiki\";\nimport { createHighlighter } from \"shiki\";\n\n// Shiki uses bitflags for font styles: 1=italic, 2=bold, 4=underline\n// oxlint-disable-next-line eslint(no-bitwise)\nconst isItalic = (fontStyle: number | undefined) => fontStyle && fontStyle & 1;\n// oxlint-disable-next-line eslint(no-bitwise)\nconst isBold = (fontStyle: number | undefined) => fontStyle && fontStyle & 2;\nconst isUnderline = (fontStyle: number | undefined) =>\n  // oxlint-disable-next-line eslint(no-bitwise)\n  fontStyle && fontStyle & 4;\n\n// Transform tokens to include pre-computed keys to avoid noArrayIndexKey lint\ninterface KeyedToken {\n  token: ThemedToken;\n  key: string;\n}\ninterface KeyedLine {\n  tokens: KeyedToken[];\n  key: string;\n}\n\nconst addKeysToTokens = (lines: ThemedToken[][]): KeyedLine[] =>\n  lines.map((line, lineIdx) => ({\n    key: `line-${lineIdx}`,\n    tokens: line.map((token, tokenIdx) => ({\n      key: `line-${lineIdx}-${tokenIdx}`,\n      token,\n    })),\n  }));\n\n// Token rendering component\nconst TokenSpan = ({ token }: { token: ThemedToken }) => (\n  <span\n    className=\"dark:!bg-[var(--shiki-dark-bg)] dark:!text-[var(--shiki-dark)]\"\n    style={\n      {\n        backgroundColor: token.bgColor,\n        color: token.color,\n        fontStyle: isItalic(token.fontStyle) ? \"italic\" : undefined,\n        fontWeight: isBold(token.fontStyle) ? \"bold\" : undefined,\n        textDecoration: isUnderline(token.fontStyle) ? \"underline\" : undefined,\n        ...token.htmlStyle,\n      } as CSSProperties\n    }\n  >\n    {token.content}\n  </span>\n);\n\n// Line number styles using CSS counters\nconst LINE_NUMBER_CLASSES = cn(\n  \"block\",\n  \"before:content-[counter(line)]\",\n  \"before:inline-block\",\n  \"before:[counter-increment:line]\",\n  \"before:w-8\",\n  \"before:mr-4\",\n  \"before:text-right\",\n  \"before:text-muted-foreground/50\",\n  \"before:font-mono\",\n  \"before:select-none\"\n);\n\n// Line rendering component\nconst LineSpan = ({\n  keyedLine,\n  showLineNumbers,\n}: {\n  keyedLine: KeyedLine;\n  showLineNumbers: boolean;\n}) => (\n  <span className={showLineNumbers ? LINE_NUMBER_CLASSES : \"block\"}>\n    {keyedLine.tokens.length === 0\n      ? \"\\n\"\n      : keyedLine.tokens.map(({ token, key }) => (\n          <TokenSpan key={key} token={token} />\n        ))}\n  </span>\n);\n\n// Types\ntype CodeBlockProps = HTMLAttributes<HTMLDivElement> & {\n  code: string;\n  language: BundledLanguage;\n  showLineNumbers?: boolean;\n};\n\ninterface TokenizedCode {\n  tokens: ThemedToken[][];\n  fg: string;\n  bg: string;\n}\n\ninterface CodeBlockContextType {\n  code: string;\n}\n\n// Context\nconst CodeBlockContext = createContext<CodeBlockContextType>({\n  code: \"\",\n});\n\n// Highlighter cache (singleton per language)\nconst highlighterCache = new Map<\n  string,\n  Promise<HighlighterGeneric<BundledLanguage, BundledTheme>>\n>();\n\n// Token cache\nconst tokensCache = new Map<string, TokenizedCode>();\n\n// Subscribers for async token updates\nconst subscribers = new Map<string, Set<(result: TokenizedCode) => void>>();\n\nconst getTokensCacheKey = (code: string, language: BundledLanguage) => {\n  const start = code.slice(0, 100);\n  const end = code.length > 100 ? code.slice(-100) : \"\";\n  return `${language}:${code.length}:${start}:${end}`;\n};\n\nconst getHighlighter = (\n  language: BundledLanguage\n): Promise<HighlighterGeneric<BundledLanguage, BundledTheme>> => {\n  const cached = highlighterCache.get(language);\n  if (cached) {\n    return cached;\n  }\n\n  const highlighterPromise = createHighlighter({\n    langs: [language],\n    themes: [\"github-light\", \"github-dark\"],\n  });\n\n  highlighterCache.set(language, highlighterPromise);\n  return highlighterPromise;\n};\n\n// Create raw tokens for immediate display while highlighting loads\nconst createRawTokens = (code: string): TokenizedCode => ({\n  bg: \"transparent\",\n  fg: \"inherit\",\n  tokens: code.split(\"\\n\").map((line) =>\n    line === \"\"\n      ? []\n      : [\n          {\n            color: \"inherit\",\n            content: line,\n          } as ThemedToken,\n        ]\n  ),\n});\n\n// Synchronous highlight with callback for async results\nexport const highlightCode = (\n  code: string,\n  language: BundledLanguage,\n  // oxlint-disable-next-line eslint-plugin-promise(prefer-await-to-callbacks)\n  callback?: (result: TokenizedCode) => void\n): TokenizedCode | null => {\n  const tokensCacheKey = getTokensCacheKey(code, language);\n\n  // Return cached result if available\n  const cached = tokensCache.get(tokensCacheKey);\n  if (cached) {\n    return cached;\n  }\n\n  // Subscribe callback if provided\n  if (callback) {\n    if (!subscribers.has(tokensCacheKey)) {\n      subscribers.set(tokensCacheKey, new Set());\n    }\n    subscribers.get(tokensCacheKey)?.add(callback);\n  }\n\n  // Start highlighting in background - fire-and-forget async pattern\n  getHighlighter(language)\n    // oxlint-disable-next-line eslint-plugin-promise(prefer-await-to-then)\n    .then((highlighter) => {\n      const availableLangs = highlighter.getLoadedLanguages();\n      const langToUse = availableLangs.includes(language) ? language : \"text\";\n\n      const result = highlighter.codeToTokens(code, {\n        lang: langToUse,\n        themes: {\n          dark: \"github-dark\",\n          light: \"github-light\",\n        },\n      });\n\n      const tokenized: TokenizedCode = {\n        bg: result.bg ?? \"transparent\",\n        fg: result.fg ?? \"inherit\",\n        tokens: result.tokens,\n      };\n\n      // Cache the result\n      tokensCache.set(tokensCacheKey, tokenized);\n\n      // Notify all subscribers\n      const subs = subscribers.get(tokensCacheKey);\n      if (subs) {\n        for (const sub of subs) {\n          sub(tokenized);\n        }\n        subscribers.delete(tokensCacheKey);\n      }\n    })\n    // oxlint-disable-next-line eslint-plugin-promise(prefer-await-to-then), eslint-plugin-promise(prefer-await-to-callbacks)\n    .catch((error) => {\n      console.error(\"Failed to highlight code:\", error);\n      subscribers.delete(tokensCacheKey);\n    });\n\n  return null;\n};\n\nconst CodeBlockBody = memo(\n  ({\n    tokenized,\n    showLineNumbers,\n    className,\n  }: {\n    tokenized: TokenizedCode;\n    showLineNumbers: boolean;\n    className?: string;\n  }) => {\n    const preStyle = useMemo(\n      () => ({\n        backgroundColor: tokenized.bg,\n        color: tokenized.fg,\n      }),\n      [tokenized.bg, tokenized.fg]\n    );\n\n    const keyedLines = useMemo(\n      () => addKeysToTokens(tokenized.tokens),\n      [tokenized.tokens]\n    );\n\n    return (\n      <pre\n        className={cn(\n          \"dark:!bg-[var(--shiki-dark-bg)] dark:!text-[var(--shiki-dark)] m-0 p-4 text-sm\",\n          className\n        )}\n        style={preStyle}\n      >\n        <code\n          className={cn(\n            \"font-mono text-sm\",\n            showLineNumbers && \"[counter-increment:line_0] [counter-reset:line]\"\n          )}\n        >\n          {keyedLines.map((keyedLine) => (\n            <LineSpan\n              key={keyedLine.key}\n              keyedLine={keyedLine}\n              showLineNumbers={showLineNumbers}\n            />\n          ))}\n        </code>\n      </pre>\n    );\n  },\n  (prevProps, nextProps) =>\n    prevProps.tokenized === nextProps.tokenized &&\n    prevProps.showLineNumbers === nextProps.showLineNumbers &&\n    prevProps.className === nextProps.className\n);\n\nCodeBlockBody.displayName = \"CodeBlockBody\";\n\nexport const CodeBlockContainer = ({\n  className,\n  language,\n  style,\n  ...props\n}: HTMLAttributes<HTMLDivElement> & { language: string }) => (\n  <div\n    className={cn(\n      \"group relative w-full overflow-hidden rounded-md border bg-background text-foreground\",\n      className\n    )}\n    data-language={language}\n    style={{\n      containIntrinsicSize: \"auto 200px\",\n      contentVisibility: \"auto\",\n      ...style,\n    }}\n    {...props}\n  />\n);\n\nexport const CodeBlockHeader = ({\n  children,\n  className,\n  ...props\n}: HTMLAttributes<HTMLDivElement>) => (\n  <div\n    className={cn(\n      \"flex items-center justify-between border-b bg-muted/80 px-3 py-2 text-muted-foreground text-xs\",\n      className\n    )}\n    {...props}\n  >\n    {children}\n  </div>\n);\n\nexport const CodeBlockTitle = ({\n  children,\n  className,\n  ...props\n}: HTMLAttributes<HTMLDivElement>) => (\n  <div className={cn(\"flex items-center gap-2\", className)} {...props}>\n    {children}\n  </div>\n);\n\nexport const CodeBlockFilename = ({\n  children,\n  className,\n  ...props\n}: HTMLAttributes<HTMLSpanElement>) => (\n  <span className={cn(\"font-mono\", className)} {...props}>\n    {children}\n  </span>\n);\n\nexport const CodeBlockActions = ({\n  children,\n  className,\n  ...props\n}: HTMLAttributes<HTMLDivElement>) => (\n  <div\n    className={cn(\"-my-1 -mr-1 flex items-center gap-2\", className)}\n    {...props}\n  >\n    {children}\n  </div>\n);\n\nexport const CodeBlockContent = ({\n  code,\n  language,\n  showLineNumbers = false,\n}: {\n  code: string;\n  language: BundledLanguage;\n  showLineNumbers?: boolean;\n}) => {\n  // Memoized raw tokens for immediate display\n  const rawTokens = useMemo(() => createRawTokens(code), [code]);\n\n  // Synchronous cache lookup — avoids setState in effect for cached results\n  const syncTokens = useMemo(\n    () => highlightCode(code, language) ?? rawTokens,\n    [code, language, rawTokens]\n  );\n\n  // Async highlighting result (populated after shiki loads)\n  const [asyncTokens, setAsyncTokens] = useState<TokenizedCode | null>(null);\n  const asyncKeyRef = useRef({ code, language });\n\n  // Invalidate stale async tokens synchronously during render\n  if (\n    asyncKeyRef.current.code !== code ||\n    asyncKeyRef.current.language !== language\n  ) {\n    asyncKeyRef.current = { code, language };\n    setAsyncTokens(null);\n  }\n\n  useEffect(() => {\n    let cancelled = false;\n\n    highlightCode(code, language, (result) => {\n      if (!cancelled) {\n        setAsyncTokens(result);\n      }\n    });\n\n    return () => {\n      cancelled = true;\n    };\n  }, [code, language]);\n\n  const tokenized = asyncTokens ?? syncTokens;\n\n  return (\n    <div className=\"relative overflow-auto\">\n      <CodeBlockBody showLineNumbers={showLineNumbers} tokenized={tokenized} />\n    </div>\n  );\n};\n\nexport const CodeBlock = ({\n  code,\n  language,\n  showLineNumbers = false,\n  className,\n  children,\n  ...props\n}: CodeBlockProps) => {\n  const contextValue = useMemo(() => ({ code }), [code]);\n\n  return (\n    <CodeBlockContext.Provider value={contextValue}>\n      <CodeBlockContainer className={className} language={language} {...props}>\n        {children}\n        <CodeBlockContent\n          code={code}\n          language={language}\n          showLineNumbers={showLineNumbers}\n        />\n      </CodeBlockContainer>\n    </CodeBlockContext.Provider>\n  );\n};\n\nexport type CodeBlockCopyButtonProps = ComponentProps<typeof Button> & {\n  onCopy?: () => void;\n  onError?: (error: Error) => void;\n  timeout?: number;\n};\n\nexport const CodeBlockCopyButton = ({\n  onCopy,\n  onError,\n  timeout = 2000,\n  children,\n  className,\n  ...props\n}: CodeBlockCopyButtonProps) => {\n  const [isCopied, setIsCopied] = useState(false);\n  const timeoutRef = useRef<number>(0);\n  const { code } = useContext(CodeBlockContext);\n\n  const copyToClipboard = useCallback(async () => {\n    if (typeof window === \"undefined\" || !navigator?.clipboard?.writeText) {\n      onError?.(new Error(\"Clipboard API not available\"));\n      return;\n    }\n\n    try {\n      if (!isCopied) {\n        await navigator.clipboard.writeText(code);\n        setIsCopied(true);\n        onCopy?.();\n        timeoutRef.current = window.setTimeout(\n          () => setIsCopied(false),\n          timeout\n        );\n      }\n    } catch (error) {\n      onError?.(error as Error);\n    }\n  }, [code, onCopy, onError, timeout, isCopied]);\n\n  useEffect(\n    () => () => {\n      window.clearTimeout(timeoutRef.current);\n    },\n    []\n  );\n\n  const Icon = isCopied ? CheckIcon : CopyIcon;\n\n  return (\n    <Button\n      className={cn(\"shrink-0\", className)}\n      onClick={copyToClipboard}\n      size=\"icon\"\n      variant=\"ghost\"\n      {...props}\n    >\n      {children ?? <Icon size={14} />}\n    </Button>\n  );\n};\n\nexport type CodeBlockLanguageSelectorProps = ComponentProps<typeof Select>;\n\nexport const CodeBlockLanguageSelector = (\n  props: CodeBlockLanguageSelectorProps\n) => <Select {...props} />;\n\nexport type CodeBlockLanguageSelectorTriggerProps = ComponentProps<\n  typeof SelectTrigger\n>;\n\nexport const CodeBlockLanguageSelectorTrigger = ({\n  className,\n  ...props\n}: CodeBlockLanguageSelectorTriggerProps) => (\n  <SelectTrigger\n    className={cn(\n      \"h-7 border-none bg-transparent px-2 text-xs shadow-none\",\n      className\n    )}\n    size=\"sm\"\n    {...props}\n  />\n);\n\nexport type CodeBlockLanguageSelectorValueProps = ComponentProps<\n  typeof SelectValue\n>;\n\nexport const CodeBlockLanguageSelectorValue = (\n  props: CodeBlockLanguageSelectorValueProps\n) => <SelectValue {...props} />;\n\nexport type CodeBlockLanguageSelectorContentProps = ComponentProps<\n  typeof SelectContent\n>;\n\nexport const CodeBlockLanguageSelectorContent = ({\n  align = \"end\",\n  ...props\n}: CodeBlockLanguageSelectorContentProps) => (\n  <SelectContent align={align} {...props} />\n);\n\nexport type CodeBlockLanguageSelectorItemProps = ComponentProps<\n  typeof SelectItem\n>;\n\nexport const CodeBlockLanguageSelectorItem = (\n  props: CodeBlockLanguageSelectorItemProps\n) => <SelectItem {...props} />;\n",
      "type": "registry:component",
      "target": "@components/ai-elements/code-block.tsx"
    },
    {
      "path": "registry/ultra-chatbot-agent/components/ai-elements/prompt-input.tsx",
      "content": "\"use client\";\n\nimport { CornerDownLeftIcon, LoaderCircleIcon, SquareIcon } from \"lucide-react\";\nimport type {\n  ComponentProps,\n  FormEvent,\n  HTMLAttributes,\n  ReactNode,\n  TextareaHTMLAttributes,\n} from \"react\";\nimport {\n  createContext,\n  useCallback,\n  useContext,\n  useMemo,\n  useState,\n} from \"react\";\nimport { Button } from \"@/components/ui/button\";\nimport { Textarea } from \"@/components/ui/textarea\";\nimport { cn } from \"@/lib/utils\";\n\ntype PromptStatus = \"error\" | \"ready\" | \"streaming\" | \"submitted\";\n\ninterface PromptInputContextValue {\n  setText: (text: string) => void;\n  text: string;\n}\n\ninterface PromptInputMessage {\n  text: string;\n}\n\ninterface PromptInputProps\n  extends Omit<HTMLAttributes<HTMLFormElement>, \"onSubmit\"> {\n  children: ReactNode;\n  onSubmit: (\n    message: PromptInputMessage,\n    event: FormEvent<HTMLFormElement>\n  ) => void | Promise<void>;\n}\n\ninterface PromptInputSubmitProps\n  extends Omit<ComponentProps<typeof Button>, \"children\" | \"type\"> {\n  onStop?: () => void;\n  status?: PromptStatus;\n}\n\ntype PromptInputClickEvent = Parameters<\n  Exclude<ComponentProps<typeof Button>[\"onClick\"], undefined>\n>[0];\n\nconst PromptInputContext = createContext<PromptInputContextValue | null>(null);\n\nfunction usePromptInputContext() {\n  const context = useContext(PromptInputContext);\n\n  if (!context) {\n    throw new Error(\n      \"PromptInput components must be used inside <PromptInput>.\"\n    );\n  }\n\n  return context;\n}\n\nexport function PromptInput({\n  children,\n  className,\n  onSubmit,\n  ...props\n}: PromptInputProps) {\n  const [text, setText] = useState(\"\");\n\n  const contextValue = useMemo(\n    () => ({\n      setText,\n      text,\n    }),\n    [text]\n  );\n\n  const handleSubmit = useCallback(\n    async (event: FormEvent<HTMLFormElement>) => {\n      event.preventDefault();\n\n      const nextText = text.trim();\n      if (!nextText) {\n        return;\n      }\n\n      const result = onSubmit({ text: nextText }, event);\n\n      try {\n        await result;\n        setText(\"\");\n      } catch {\n        // Keep the text for retry after a failed submit.\n      }\n    },\n    [onSubmit, text]\n  );\n\n  return (\n    <PromptInputContext.Provider value={contextValue}>\n      <form\n        className={cn(\"w-full\", className)}\n        onSubmit={handleSubmit}\n        {...props}\n      >\n        {children}\n      </form>\n    </PromptInputContext.Provider>\n  );\n}\n\nexport function PromptInputBody({\n  className,\n  ...props\n}: HTMLAttributes<HTMLDivElement>) {\n  return <div className={cn(\"grid gap-3\", className)} {...props} />;\n}\n\nexport function PromptInputFooter({\n  className,\n  ...props\n}: HTMLAttributes<HTMLDivElement>) {\n  return <div className={cn(className)} {...props} />;\n}\n\nexport function PromptInputTextarea({\n  className,\n  disabled,\n  onChange,\n  ...props\n}: TextareaHTMLAttributes<HTMLTextAreaElement>) {\n  const context = usePromptInputContext();\n\n  return (\n    <Textarea\n      className={cn(\"min-h-16 resize-none\", className)}\n      disabled={disabled}\n      onChange={(event) => {\n        context.setText(event.currentTarget.value);\n        onChange?.(event);\n      }}\n      value={context.text}\n      {...props}\n    />\n  );\n}\n\nexport function PromptInputSubmit({\n  className,\n  disabled,\n  onClick,\n  onStop,\n  size = \"icon\",\n  status = \"ready\",\n  variant = \"default\",\n  ...props\n}: PromptInputSubmitProps) {\n  const { text } = usePromptInputContext();\n  const isBusy = status === \"submitted\" || status === \"streaming\";\n  const isDisabled = Boolean(disabled) || (!isBusy && text.trim().length === 0);\n\n  let icon = <CornerDownLeftIcon className=\"size-4\" />;\n\n  if (status === \"submitted\") {\n    icon = <LoaderCircleIcon className=\"size-4 animate-spin\" />;\n  } else if (status === \"streaming\") {\n    icon = <SquareIcon className=\"size-4\" />;\n  }\n\n  return (\n    <Button\n      aria-label={isBusy ? \"Stop\" : \"Submit\"}\n      className={cn(className)}\n      disabled={isDisabled}\n      onClick={(event: PromptInputClickEvent) => {\n        if (isBusy && onStop) {\n          event.preventDefault();\n          onStop();\n          return;\n        }\n\n        onClick?.(event);\n      }}\n      size={size}\n      type={isBusy && onStop ? \"button\" : \"submit\"}\n      variant={variant}\n      {...props}\n    >\n      {icon}\n    </Button>\n  );\n}\n",
      "type": "registry:component",
      "target": "@components/ai-elements/prompt-input.tsx"
    },
    {
      "path": "registry/ultra-chatbot-agent/components/ai-elements/shimmer.tsx",
      "content": "\"use client\";\n\n/* eslint-disable react-hooks/static-components */\n\nimport { cn } from \"@/lib/utils\";\nimport type { MotionProps } from \"motion/react\";\nimport { motion } from \"motion/react\";\nimport type { CSSProperties, ElementType, JSX } from \"react\";\nimport { memo, useMemo } from \"react\";\n\ntype MotionHTMLProps = MotionProps & Record<string, unknown>;\n\n// Cache motion components at module level to avoid creating during render\nconst motionComponentCache = new Map<\n  keyof JSX.IntrinsicElements,\n  React.ComponentType<MotionHTMLProps>\n>();\n\nconst getMotionComponent = (element: keyof JSX.IntrinsicElements) => {\n  let component = motionComponentCache.get(element);\n  if (!component) {\n    component = motion.create(element);\n    motionComponentCache.set(element, component);\n  }\n  return component;\n};\n\nexport interface TextShimmerProps {\n  children: string;\n  as?: ElementType;\n  className?: string;\n  duration?: number;\n  spread?: number;\n}\n\nconst ShimmerComponent = ({\n  children,\n  as: Component = \"p\",\n  className,\n  duration = 2,\n  spread = 2,\n}: TextShimmerProps) => {\n  const MotionComponent = getMotionComponent(\n    Component as keyof JSX.IntrinsicElements\n  );\n\n  const dynamicSpread = useMemo(\n    () => (children?.length ?? 0) * spread,\n    [children, spread]\n  );\n\n  return (\n    <MotionComponent\n      animate={{ backgroundPosition: \"0% center\" }}\n      className={cn(\n        \"relative inline-block bg-[length:250%_100%,auto] bg-clip-text text-transparent\",\n        \"[--bg:linear-gradient(90deg,#0000_calc(50%-var(--spread)),var(--color-background),#0000_calc(50%+var(--spread)))] [background-repeat:no-repeat,padding-box]\",\n        className\n      )}\n      initial={{ backgroundPosition: \"100% center\" }}\n      style={\n        {\n          \"--spread\": `${dynamicSpread}px`,\n          backgroundImage:\n            \"var(--bg), linear-gradient(var(--color-muted-foreground), var(--color-muted-foreground))\",\n        } as CSSProperties\n      }\n      transition={{\n        duration,\n        ease: \"linear\",\n        repeat: Number.POSITIVE_INFINITY,\n      }}\n    >\n      {children}\n    </MotionComponent>\n  );\n};\n\nexport const Shimmer = memo(ShimmerComponent);\n",
      "type": "registry:component",
      "target": "@components/ai-elements/shimmer.tsx"
    },
    {
      "path": "registry/ultra-chatbot-agent/components/demo-breadcrumb.tsx",
      "content": "import {\n  Breadcrumb,\n  BreadcrumbItem,\n  BreadcrumbLink,\n  BreadcrumbList,\n  BreadcrumbPage,\n  BreadcrumbSeparator,\n} from \"@/components/ui/breadcrumb\";\nimport { cn } from \"@/lib/utils\";\nimport { ArrowLeft } from \"lucide-react\";\n\ninterface DemoBreadcrumbProps {\n  className?: string;\n  title: string;\n}\n\nexport function DemoBreadcrumb({ className, title }: DemoBreadcrumbProps) {\n  return (\n    <Breadcrumb>\n      <BreadcrumbList\n        className={cn(\n          \"text-[11px] text-muted-foreground uppercase tracking-[0.2em]\",\n          className\n        )}\n      >\n        <BreadcrumbItem>\n          <BreadcrumbLink\n            aria-label=\"Back to demos\"\n            className=\"-ml-1 inline-flex items-center gap-1.5 text-muted-foreground transition-colors hover:text-foreground\"\n            href=\"/\"\n          >\n            <ArrowLeft aria-hidden=\"true\" className=\"size-3.5 shrink-0\" />\n            <span>Demo</span>\n          </BreadcrumbLink>\n        </BreadcrumbItem>\n        <BreadcrumbSeparator className=\"text-muted-foreground\">\n          /\n        </BreadcrumbSeparator>\n        <BreadcrumbItem>\n          <BreadcrumbPage className=\"font-normal text-muted-foreground\">\n            {title}\n          </BreadcrumbPage>\n        </BreadcrumbItem>\n      </BreadcrumbList>\n    </Breadcrumb>\n  );\n}\n",
      "type": "registry:component",
      "target": "@components/demo-breadcrumb.tsx"
    },
    {
      "path": "registry/ultra-chatbot-agent/components/demo-chat/conversation-error-message.tsx",
      "content": "\"use client\";\n\nimport {\n  Message,\n  MessageContent,\n} from \"@/components/ai-elements/message\";\nimport { Button } from \"@/components/ui/button\";\nimport { cn } from \"@/lib/utils\";\nimport { RefreshCcwIcon } from \"lucide-react\";\nimport { useCallback } from \"react\";\n\ninterface ConversationErrorMessageProps {\n  className?: string;\n  error: Error | string;\n  isRetryDisabled?: boolean;\n  onRetry?: () => Promise<void> | void;\n  retryLabel?: string;\n  title?: string;\n}\n\ninterface UseConversationErrorRetryInput {\n  clearError: () => void;\n  regenerate: () => Promise<void>;\n}\n\nexport function useConversationErrorRetry({\n  clearError,\n  regenerate,\n}: UseConversationErrorRetryInput) {\n  return useCallback(async () => {\n    clearError();\n    await regenerate();\n  }, [clearError, regenerate]);\n}\n\nexport function ConversationErrorMessage({\n  className,\n  error,\n  isRetryDisabled = false,\n  retryLabel = \"Retry\",\n  onRetry,\n  title = \"Assistant response failed\",\n}: ConversationErrorMessageProps) {\n  const errorMessage = typeof error === \"string\" ? error : error.message;\n\n  return (\n    <Message from=\"assistant\">\n      <MessageContent\n        className={cn(\n          \"max-w-3xl border border-destructive/25 bg-destructive/5 px-4 py-3 text-destructive\",\n          className\n        )}\n      >\n        <div className=\"flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between\">\n          <div className=\"min-w-0\">\n            <p className=\"font-medium text-sm\">{title}</p>\n            <p className=\"mt-1 break-words text-xs/relaxed opacity-90\">\n              {errorMessage}\n            </p>\n          </div>\n          {onRetry ? (\n            <Button\n              className=\"self-start\"\n              disabled={isRetryDisabled}\n              onClick={() => {\n                void onRetry();\n              }}\n              size=\"sm\"\n              type=\"button\"\n              variant=\"destructive\"\n            >\n              <RefreshCcwIcon className=\"size-3.5\" />\n              {retryLabel}\n            </Button>\n          ) : null}\n        </div>\n      </MessageContent>\n    </Message>\n  );\n}\n",
      "type": "registry:component",
      "target": "@components/demo-chat/conversation-error-message.tsx"
    },
    {
      "path": "registry/ultra-chatbot-agent/components/demo-workspace-shell.tsx",
      "content": "import { Badge } from \"@/components/ui/badge\";\nimport { Card } from \"@/components/ui/card\";\nimport { cn } from \"@/lib/utils\";\nimport type { ReactNode } from \"react\";\n\nimport { DemoBreadcrumb } from \"@/components/demo-breadcrumb\";\n\ntype DemoWorkspaceHeaderFrame = \"header\" | \"card\";\n\ninterface DemoWorkspaceShellProps {\n  badges?: readonly ReactNode[];\n  breadcrumbClassName?: string;\n  breadcrumbTitle?: string;\n  children: ReactNode;\n  contentClassName?: string;\n  headerClassName?: string;\n  headerFrame?: DemoWorkspaceHeaderFrame;\n  maxWidthClassName?: string;\n  summary: ReactNode;\n  summaryClassName?: string;\n  title: string;\n  titleClassName?: string;\n  workspaceClassName?: string | null;\n}\n\nconst defaultHeaderClassName =\n  \"grid gap-4 border border-foreground/10 bg-background px-4 py-5 md:grid-cols-[minmax(0,1fr)_auto] md:items-end\";\n\nconst cardHeaderClassName =\n  \"grid gap-4 bg-background px-4 py-5 text-base text-foreground leading-normal md:grid-cols-[minmax(0,1fr)_auto] md:items-end\";\n\nexport function DemoWorkspaceShell({\n  badges = [],\n  breadcrumbClassName,\n  breadcrumbTitle,\n  children,\n  contentClassName,\n  headerClassName,\n  headerFrame = \"header\",\n  maxWidthClassName = \"max-w-7xl\",\n  summary,\n  summaryClassName = \"max-w-3xl\",\n  title,\n  titleClassName = \"max-w-3xl\",\n  workspaceClassName = \"lg:h-svh\",\n}: DemoWorkspaceShellProps) {\n  const headerContent = (\n    <>\n      <div className=\"space-y-2\">\n        <DemoBreadcrumb\n          className={breadcrumbClassName}\n          title={breadcrumbTitle ?? title}\n        />\n        <h1\n          className={cn(\"font-medium text-2xl tracking-tight\", titleClassName)}\n        >\n          {title}\n        </h1>\n        <p\n          className={cn(\n            \"text-muted-foreground text-sm/relaxed\",\n            summaryClassName\n          )}\n        >\n          {summary}\n        </p>\n      </div>\n\n      {badges.length > 0 ? (\n        <div className=\"flex flex-wrap items-center gap-2\">\n          {badges.map((badge, index) => (\n            <Badge key={String(index)} variant=\"outline\">\n              {badge}\n            </Badge>\n          ))}\n        </div>\n      ) : null}\n    </>\n  );\n\n  return (\n    <main className=\"min-h-svh bg-background text-foreground\">\n      <div\n        className={cn(\n          \"mx-auto flex w-full flex-col gap-6 px-4 py-6 md:px-6\",\n          maxWidthClassName,\n          contentClassName\n        )}\n      >\n        {headerFrame === \"card\" ? (\n          <Card className={cn(cardHeaderClassName, headerClassName)}>\n            {headerContent}\n          </Card>\n        ) : (\n          <header className={cn(defaultHeaderClassName, headerClassName)}>\n            {headerContent}\n          </header>\n        )}\n\n        {workspaceClassName ? (\n          <div className={workspaceClassName}>{children}</div>\n        ) : (\n          children\n        )}\n      </div>\n    </main>\n  );\n}\n",
      "type": "registry:component",
      "target": "@components/demo-workspace-shell.tsx"
    },
    {
      "path": "registry/ultra-chatbot-agent/components/ultra-chatbot-agent/resume-pending-state.ts",
      "content": "import type { UIMessage } from \"ai\";\n\nimport type { UltraChatbotAgentChatSession } from \"@/lib/ultra-chatbot-agent/server/chat-store\";\n\nexport function shouldShowUltraChatbotAgentResumeThinking(input: {\n  initialSession: UltraChatbotAgentChatSession | null;\n  messages: UIMessage[];\n}) {\n  const { initialSession, messages } = input;\n\n  if (!initialSession?.chat.activeStreamId) {\n    return false;\n  }\n\n  return messages.at(-1)?.role === \"user\";\n}\n",
      "type": "registry:component",
      "target": "@components/ultra-chatbot-agent/resume-pending-state.ts"
    },
    {
      "path": "registry/ultra-chatbot-agent/components/ultra-chatbot-agent/ultra-chatbot-agent-artifact-actions.tsx",
      "content": "\"use client\";\n\nimport {\n  ArrowCounterClockwiseIcon,\n  FloppyDiskIcon,\n  PlusIcon,\n} from \"@phosphor-icons/react\";\nimport { Button } from \"@/components/ui/button\";\n\nexport function UltraChatbotAgentArtifactActions({\n  canResetToLatest,\n  disabled,\n  hasSelectedDocument,\n  isCreating,\n  isLatestVersion,\n  isSaving,\n  onCreateScratchDocument,\n  onResetToLatest,\n  onSaveVersion,\n}: {\n  canResetToLatest: boolean;\n  disabled: boolean;\n  hasSelectedDocument: boolean;\n  isCreating: boolean;\n  isLatestVersion: boolean;\n  isSaving: boolean;\n  onCreateScratchDocument: () => Promise<void> | void;\n  onResetToLatest: () => void;\n  onSaveVersion: () => Promise<void> | void;\n}) {\n  return (\n    <div className=\"flex flex-wrap items-center gap-2\">\n      <Button\n        disabled={disabled || isCreating}\n        onClick={() => void onCreateScratchDocument()}\n        size=\"sm\"\n        type=\"button\"\n        variant=\"outline\"\n      >\n        <PlusIcon className=\"size-3.5\" />\n        Scratch doc\n      </Button>\n\n      {hasSelectedDocument ? (\n        <Button\n          disabled={disabled || isSaving || !isLatestVersion}\n          onClick={() => void onSaveVersion()}\n          size=\"sm\"\n          type=\"button\"\n          variant=\"outline\"\n        >\n          <FloppyDiskIcon className=\"size-3.5\" />\n          Save version\n        </Button>\n      ) : null}\n\n      {canResetToLatest ? (\n        <Button\n          disabled={disabled}\n          onClick={onResetToLatest}\n          size=\"sm\"\n          type=\"button\"\n          variant=\"ghost\"\n        >\n          <ArrowCounterClockwiseIcon className=\"size-3.5\" />\n          Latest\n        </Button>\n      ) : null}\n    </div>\n  );\n}\n",
      "type": "registry:component",
      "target": "@components/ultra-chatbot-agent/ultra-chatbot-agent-artifact-actions.tsx"
    },
    {
      "path": "registry/ultra-chatbot-agent/components/ultra-chatbot-agent/ultra-chatbot-agent-artifact-state.ts",
      "content": "export type UltraChatbotAgentArtifactMode = \"edit\" | \"diff\";\n\nexport interface UltraChatbotAgentArtifactState {\n  mode: UltraChatbotAgentArtifactMode;\n  refreshToken: number;\n  selectedDocumentId: string | null;\n}\n\nexport function createInitialUltraChatbotAgentArtifactState(): UltraChatbotAgentArtifactState {\n  return {\n    mode: \"edit\",\n    refreshToken: 0,\n    selectedDocumentId: null,\n  };\n}\n\nexport function openUltraChatbotAgentArtifact(\n  state: UltraChatbotAgentArtifactState,\n  documentId: string\n): UltraChatbotAgentArtifactState {\n  return {\n    ...state,\n    mode: \"edit\",\n    refreshToken: state.refreshToken + 1,\n    selectedDocumentId: documentId,\n  };\n}\n\nexport function closeUltraChatbotAgentArtifact(\n  state: UltraChatbotAgentArtifactState\n): UltraChatbotAgentArtifactState {\n  return {\n    ...state,\n    mode: \"edit\",\n    selectedDocumentId: null,\n  };\n}\n\nexport function refreshUltraChatbotAgentArtifact(\n  state: UltraChatbotAgentArtifactState\n): UltraChatbotAgentArtifactState {\n  return {\n    ...state,\n    refreshToken: state.refreshToken + 1,\n  };\n}\n\nexport function setUltraChatbotAgentArtifactMode(\n  state: UltraChatbotAgentArtifactState,\n  mode: UltraChatbotAgentArtifactMode\n): UltraChatbotAgentArtifactState {\n  return {\n    ...state,\n    mode,\n  };\n}\n\nexport function setUltraChatbotAgentArtifactSelection(\n  state: UltraChatbotAgentArtifactState,\n  documentId: string | null\n): UltraChatbotAgentArtifactState {\n  return {\n    ...state,\n    mode: \"edit\",\n    selectedDocumentId: documentId,\n  };\n}\n",
      "type": "registry:component",
      "target": "@components/ultra-chatbot-agent/ultra-chatbot-agent-artifact-state.ts"
    },
    {
      "path": "registry/ultra-chatbot-agent/components/ultra-chatbot-agent/ultra-chatbot-agent-artifact.tsx",
      "content": "\"use client\";\n\nimport { FileTextIcon } from \"@phosphor-icons/react\";\nimport { Badge } from \"@/components/ui/badge\";\n\nimport type { UltraChatbotAgentArtifactMode } from \"./ultra-chatbot-agent-artifact-state\";\nimport { UltraChatbotAgentDocumentBrowser } from \"./ultra-chatbot-agent-document-browser\";\nimport { UltraChatbotAgentDocumentDialog } from \"./ultra-chatbot-agent-document-dialog\";\n\nexport function UltraChatbotAgentArtifact({\n  chatId,\n  disabled,\n  mode,\n  onClose,\n  onModeChange,\n  onOpen,\n  onRefresh,\n  refreshToken,\n  selectedDocumentId,\n}: {\n  chatId: string;\n  disabled: boolean;\n  mode: UltraChatbotAgentArtifactMode;\n  onClose: () => void;\n  onModeChange: (mode: UltraChatbotAgentArtifactMode) => void;\n  onOpen: (documentId: string) => void;\n  onRefresh: () => void;\n  refreshToken: number;\n  selectedDocumentId: string | null;\n}) {\n  return (\n    <>\n      <section className=\"space-y-4 border border-foreground/10 p-3\">\n        <div>\n          <div className=\"flex items-center gap-2\">\n            <FileTextIcon className=\"size-4 text-muted-foreground\" />\n            <p className=\"text-[11px] text-muted-foreground uppercase tracking-[0.2em]\">\n              Artifact\n            </p>\n            <Badge variant=\"outline\">text</Badge>\n          </div>\n          <p className=\"mt-1 text-sm\">\n            Companion browser for the Ultra artifact port. Open any document\n            into a dedicated detail dialog.\n          </p>\n        </div>\n        <UltraChatbotAgentDocumentBrowser\n          chatId={chatId}\n          disabled={disabled}\n          onOpen={onOpen}\n          refreshToken={refreshToken}\n          selectedDocumentId={selectedDocumentId}\n        />\n      </section>\n\n      <UltraChatbotAgentDocumentDialog\n        chatId={chatId}\n        disabled={disabled}\n        documentId={selectedDocumentId}\n        mode={mode}\n        onClose={onClose}\n        onModeChange={onModeChange}\n        onRefreshArtifact={onRefresh}\n        refreshToken={refreshToken}\n      />\n    </>\n  );\n}\n",
      "type": "registry:component",
      "target": "@components/ultra-chatbot-agent/ultra-chatbot-agent-artifact.tsx"
    },
    {
      "path": "registry/ultra-chatbot-agent/components/ultra-chatbot-agent/ultra-chatbot-agent-diff-view.tsx",
      "content": "\"use client\";\n\nimport { cn } from \"@/lib/utils\";\n\ninterface DiffLine {\n  after: string;\n  before: string;\n  kind: \"added\" | \"changed\" | \"removed\" | \"unchanged\";\n}\n\nfunction buildDiffLines(before: string, after: string) {\n  const beforeLines = before.split(\"\\n\");\n  const afterLines = after.split(\"\\n\");\n  const length = Math.max(beforeLines.length, afterLines.length);\n  const lines: DiffLine[] = [];\n\n  for (let index = 0; index < length; index += 1) {\n    const previousLine = beforeLines[index] ?? \"\";\n    const nextLine = afterLines[index] ?? \"\";\n\n    if (previousLine === nextLine) {\n      lines.push({\n        after: nextLine,\n        before: previousLine,\n        kind: \"unchanged\",\n      });\n      continue;\n    }\n\n    if (previousLine.length === 0 && nextLine.length > 0) {\n      lines.push({\n        after: nextLine,\n        before: previousLine,\n        kind: \"added\",\n      });\n      continue;\n    }\n\n    if (previousLine.length > 0 && nextLine.length === 0) {\n      lines.push({\n        after: nextLine,\n        before: previousLine,\n        kind: \"removed\",\n      });\n      continue;\n    }\n\n    lines.push({\n      after: nextLine,\n      before: previousLine,\n      kind: \"changed\",\n    });\n  }\n\n  return lines;\n}\n\nexport function UltraChatbotAgentDiffView({\n  after,\n  before,\n}: {\n  after: string;\n  before: string;\n}) {\n  const diffLines = buildDiffLines(before, after);\n\n  return (\n    <div className=\"max-h-96 overflow-auto border border-foreground/10 bg-muted/20\">\n      <div className=\"border-foreground/10 border-b px-3 py-2 text-[11px] text-muted-foreground uppercase tracking-[0.2em]\">\n        Diff preview\n      </div>\n      <div className=\"font-mono text-xs\">\n        {diffLines.map((line, index) => (\n          <div\n            className={cn(\n              \"grid grid-cols-[3rem_1fr] gap-3 border-foreground/5 border-b px-3 py-2 last:border-b-0\",\n              line.kind === \"added\" && \"bg-emerald-500/10\",\n              line.kind === \"changed\" && \"bg-amber-500/10\",\n              line.kind === \"removed\" && \"bg-rose-500/10\"\n            )}\n            key={`${index}-${line.kind}`}\n          >\n            <span className=\"text-muted-foreground\">{index + 1}</span>\n            <div className=\"space-y-1 whitespace-pre-wrap break-words\">\n              {line.kind === \"changed\" || line.kind === \"removed\" ? (\n                <div className=\"text-muted-foreground/80 line-through\">\n                  {line.before || \" \"}\n                </div>\n              ) : null}\n              <div>{line.after || \" \"}</div>\n            </div>\n          </div>\n        ))}\n      </div>\n    </div>\n  );\n}\n",
      "type": "registry:component",
      "target": "@components/ultra-chatbot-agent/ultra-chatbot-agent-diff-view.tsx"
    },
    {
      "path": "registry/ultra-chatbot-agent/components/ultra-chatbot-agent/ultra-chatbot-agent-document-browser.tsx",
      "content": "\"use client\";\n\n/* eslint-disable react-hooks/set-state-in-effect */\n\nimport { FileCodeIcon, FileTextIcon } from \"@phosphor-icons/react\";\nimport { Shimmer } from \"@/components/ai-elements/shimmer\";\nimport { Badge } from \"@/components/ui/badge\";\nimport { type ReactNode, useCallback, useEffect, useState } from \"react\";\n\nimport type { UltraChatbotAgentDocumentRecord } from \"@/lib/ultra-chatbot-agent/server/document-store\";\nimport { UltraChatbotAgentArtifactActions } from \"./ultra-chatbot-agent-artifact-actions\";\nimport {\n  createUltraChatbotAgentScratchDocument,\n  formatUltraChatbotAgentDocumentTimestamp,\n  loadUltraChatbotAgentDocuments,\n} from \"./ultra-chatbot-agent-document-client\";\n\nfunction noopArtifactAction() {\n  return;\n}\n\nexport function UltraChatbotAgentDocumentBrowser({\n  chatId,\n  disabled,\n  onOpen,\n  refreshToken,\n  selectedDocumentId,\n}: {\n  chatId: string;\n  disabled: boolean;\n  onOpen: (documentId: string) => void;\n  refreshToken: number;\n  selectedDocumentId: string | null;\n}) {\n  const [documents, setDocuments] = useState<UltraChatbotAgentDocumentRecord[]>(\n    []\n  );\n  const [documentError, setDocumentError] = useState<string | null>(null);\n  const [isCreating, setIsCreating] = useState(false);\n  const [isDocumentsLoading, setIsDocumentsLoading] = useState(true);\n\n  const refreshDocuments = useCallback(async () => {\n    setIsDocumentsLoading(true);\n\n    try {\n      setDocuments(await loadUltraChatbotAgentDocuments(chatId));\n      setDocumentError(null);\n    } catch (error) {\n      setDocumentError(\n        error instanceof Error\n          ? error.message\n          : \"Failed to load thread documents.\"\n      );\n    } finally {\n      setIsDocumentsLoading(false);\n    }\n  }, [chatId]);\n\n  useEffect(() => {\n    if (refreshToken < 0) {\n      return;\n    }\n\n    refreshDocuments().catch(() => undefined);\n  }, [refreshDocuments, refreshToken]);\n\n  async function handleCreateScratchDocument() {\n    setIsCreating(true);\n\n    try {\n      const document = await createUltraChatbotAgentScratchDocument(chatId);\n      await refreshDocuments();\n      onOpen(document.id);\n    } catch (error) {\n      setDocumentError(\n        error instanceof Error\n          ? error.message\n          : \"Failed to create the scratch document.\"\n      );\n    } finally {\n      setIsCreating(false);\n    }\n  }\n\n  const documentGroups = [\n    {\n      documents: documents.filter((document) => document.kind === \"code\"),\n      icon: FileCodeIcon,\n      title: \"Code\",\n    },\n    {\n      documents: documents.filter((document) => document.kind !== \"code\"),\n      icon: FileTextIcon,\n      title: \"Documents\",\n    },\n  ].filter((group) => group.documents.length > 0);\n\n  let documentBrowserContent: ReactNode;\n\n  if (isDocumentsLoading) {\n    documentBrowserContent = (\n      <Shimmer className=\"text-sm\">Loading documents...</Shimmer>\n    );\n  } else if (documents.length === 0) {\n    documentBrowserContent = (\n      <div className=\"border border-foreground/10 border-dashed px-3 py-4 text-muted-foreground text-xs/relaxed\">\n        Create a scratch doc to exercise the versioned document route and the\n        artifact dialog.\n      </div>\n    );\n  } else {\n    documentBrowserContent = (\n      <div className=\"space-y-4\">\n        {documentGroups.map((group) => {\n          const Icon = group.icon;\n\n          return (\n            <div className=\"space-y-2\" key={group.title}>\n              <p className=\"text-[11px] text-muted-foreground uppercase tracking-[0.2em]\">\n                {group.title}\n              </p>\n              {group.documents.map((document) => (\n                <button\n                  className=\"w-full border border-foreground/10 px-3 py-2 text-left transition-colors hover:border-foreground/30\"\n                  key={document.id}\n                  onClick={() => onOpen(document.id)}\n                  type=\"button\"\n                >\n                  <div className=\"flex items-center justify-between gap-2\">\n                    <div className=\"flex min-w-0 items-center gap-2\">\n                      <Icon className=\"size-3.5 text-muted-foreground\" />\n                      <span className=\"truncate text-sm\">{document.title}</span>\n                    </div>\n                    {selectedDocumentId === document.id ? (\n                      <Badge variant=\"secondary\">Open</Badge>\n                    ) : null}\n                  </div>\n                  <p className=\"mt-1 text-muted-foreground text-xs\">\n                    {formatUltraChatbotAgentDocumentTimestamp(\n                      document.createdAt\n                    )}\n                  </p>\n                </button>\n              ))}\n            </div>\n          );\n        })}\n      </div>\n    );\n  }\n\n  return (\n    <div className=\"space-y-4\">\n      <div className=\"space-y-3\">\n        <div>\n          <p className=\"text-[11px] text-muted-foreground uppercase tracking-[0.2em]\">\n            Artifacts\n          </p>\n          <p className=\"mt-1 text-sm\">\n            Browse code and document artifacts created in this thread. Open one\n            to inspect or edit it in a dedicated detail dialog.\n          </p>\n        </div>\n        <UltraChatbotAgentArtifactActions\n          canResetToLatest={false}\n          disabled={disabled}\n          hasSelectedDocument={false}\n          isCreating={isCreating}\n          isLatestVersion\n          isSaving={false}\n          onCreateScratchDocument={handleCreateScratchDocument}\n          onResetToLatest={noopArtifactAction}\n          onSaveVersion={noopArtifactAction}\n        />\n      </div>\n\n      {documentError ? (\n        <div className=\"text-destructive text-xs/relaxed\">{documentError}</div>\n      ) : null}\n\n      {documentBrowserContent}\n    </div>\n  );\n}\n",
      "type": "registry:component",
      "target": "@components/ultra-chatbot-agent/ultra-chatbot-agent-document-browser.tsx"
    },
    {
      "path": "registry/ultra-chatbot-agent/components/ultra-chatbot-agent/ultra-chatbot-agent-document-client.ts",
      "content": "\"use client\";\n\nimport type { UltraChatbotAgentDocumentRecord } from \"@/lib/ultra-chatbot-agent/server/document-store\";\nimport type { UltraChatbotAgentSuggestionRecord } from \"@/lib/ultra-chatbot-agent/server/suggestion-store\";\n\nexport function formatUltraChatbotAgentDocumentTimestamp(value: string) {\n  const date = new Date(value);\n\n  if (Number.isNaN(date.valueOf())) {\n    return value;\n  }\n\n  return new Intl.DateTimeFormat(\"en\", {\n    day: \"2-digit\",\n    hour: \"2-digit\",\n    minute: \"2-digit\",\n    month: \"short\",\n  }).format(date);\n}\n\nfunction buildUltraChatbotAgentDocumentSearchParams(input: {\n  chatId: string;\n  documentId?: string;\n  timestamp?: string;\n}) {\n  const searchParams = new URLSearchParams({\n    chatId: input.chatId,\n  });\n\n  if (input.documentId) {\n    searchParams.set(\"id\", input.documentId);\n  }\n\n  if (input.timestamp) {\n    searchParams.set(\"timestamp\", input.timestamp);\n  }\n\n  return searchParams;\n}\n\nexport async function loadUltraChatbotAgentDocuments(chatId: string) {\n  const response = await fetch(\n    `/api/demos/ultra-chatbot-agent/document?${buildUltraChatbotAgentDocumentSearchParams(\n      {\n        chatId,\n      }\n    ).toString()}`,\n    {\n      credentials: \"include\",\n    }\n  );\n\n  if (!response.ok) {\n    throw new Error(\"Failed to load thread documents.\");\n  }\n\n  return (await response.json()) as UltraChatbotAgentDocumentRecord[];\n}\n\nexport async function loadUltraChatbotAgentLatestDocument(\n  chatId: string,\n  documentId: string\n) {\n  const documents = await loadUltraChatbotAgentDocumentVersions(\n    chatId,\n    documentId\n  );\n\n  return documents[0] ?? null;\n}\n\nexport async function loadUltraChatbotAgentDocumentVersions(\n  chatId: string,\n  documentId: string\n) {\n  const response = await fetch(\n    `/api/demos/ultra-chatbot-agent/document?${buildUltraChatbotAgentDocumentSearchParams(\n      {\n        chatId,\n        documentId,\n      }\n    ).toString()}`,\n    {\n      credentials: \"include\",\n    }\n  );\n\n  if (!response.ok) {\n    throw new Error(\"Failed to load document versions.\");\n  }\n\n  return (await response.json()) as UltraChatbotAgentDocumentRecord[];\n}\n\nexport async function loadUltraChatbotAgentDocumentSuggestions(\n  chatId: string,\n  documentId: string\n) {\n  const response = await fetch(\n    `/api/demos/ultra-chatbot-agent/suggestions?${buildUltraChatbotAgentDocumentSearchParams(\n      {\n        chatId,\n        documentId,\n      }\n    ).toString()}`,\n    {\n      credentials: \"include\",\n    }\n  );\n\n  if (!response.ok) {\n    throw new Error(\"Failed to load document suggestions.\");\n  }\n\n  return (await response.json()) as UltraChatbotAgentSuggestionRecord[];\n}\n\nexport async function createUltraChatbotAgentScratchDocument(chatId: string) {\n  const documentId = crypto.randomUUID();\n  const response = await fetch(\n    `/api/demos/ultra-chatbot-agent/document?${buildUltraChatbotAgentDocumentSearchParams(\n      {\n        chatId,\n        documentId,\n      }\n    ).toString()}`,\n    {\n      body: JSON.stringify({\n        content: \"\",\n        kind: \"text\",\n        title: \"Scratch note\",\n      }),\n      credentials: \"include\",\n      headers: {\n        \"content-type\": \"application/json\",\n      },\n      method: \"POST\",\n    }\n  );\n\n  if (!response.ok) {\n    throw new Error(\"Failed to create the scratch document.\");\n  }\n\n  return (await response.json()) as UltraChatbotAgentDocumentRecord;\n}\n\nexport async function saveUltraChatbotAgentDocumentDraft(input: {\n  chatId: string;\n  content: string;\n  documentId: string;\n  title: string;\n}) {\n  const response = await fetch(\n    `/api/demos/ultra-chatbot-agent/document?${buildUltraChatbotAgentDocumentSearchParams(\n      {\n        chatId: input.chatId,\n        documentId: input.documentId,\n      }\n    ).toString()}`,\n    {\n      body: JSON.stringify({\n        content: input.content,\n        isManualEdit: true,\n        kind: \"text\",\n        title: input.title,\n      }),\n      credentials: \"include\",\n      headers: {\n        \"content-type\": \"application/json\",\n      },\n      method: \"POST\",\n    }\n  );\n\n  if (!response.ok) {\n    throw new Error(\"Failed to save the document draft.\");\n  }\n\n  return (await response.json()) as UltraChatbotAgentDocumentRecord;\n}\n\nexport async function restoreUltraChatbotAgentDocumentVersion(input: {\n  chatId: string;\n  createdAt: string;\n  documentId: string;\n}) {\n  const response = await fetch(\n    `/api/demos/ultra-chatbot-agent/document?${buildUltraChatbotAgentDocumentSearchParams(\n      {\n        chatId: input.chatId,\n        documentId: input.documentId,\n        timestamp: input.createdAt,\n      }\n    ).toString()}`,\n    {\n      credentials: \"include\",\n      method: \"DELETE\",\n    }\n  );\n\n  if (!response.ok) {\n    throw new Error(\"Failed to restore the selected document version.\");\n  }\n}\n",
      "type": "registry:component",
      "target": "@components/ultra-chatbot-agent/ultra-chatbot-agent-document-client.ts"
    },
    {
      "path": "registry/ultra-chatbot-agent/components/ultra-chatbot-agent/ultra-chatbot-agent-document-detail.tsx",
      "content": "\"use client\";\n\n/* eslint-disable react-hooks/set-state-in-effect */\n\nimport {\n  CodeBlock,\n  CodeBlockActions,\n  CodeBlockCopyButton,\n  CodeBlockFilename,\n  CodeBlockHeader,\n  CodeBlockTitle,\n} from \"@/components/ai-elements/code-block\";\nimport { Shimmer } from \"@/components/ai-elements/shimmer\";\nimport { Badge } from \"@/components/ui/badge\";\nimport { Textarea } from \"@/components/ui/textarea\";\nimport {\n  type ComponentProps,\n  type ReactNode,\n  useCallback,\n  useEffect,\n  useMemo,\n  useState,\n} from \"react\";\n\nimport type { UltraChatbotAgentDocumentRecord } from \"@/lib/ultra-chatbot-agent/server/document-store\";\nimport type { UltraChatbotAgentSuggestionRecord } from \"@/lib/ultra-chatbot-agent/server/suggestion-store\";\nimport type { UltraChatbotAgentArtifactMode } from \"./ultra-chatbot-agent-artifact-state\";\nimport { UltraChatbotAgentDiffView } from \"./ultra-chatbot-agent-diff-view\";\nimport {\n  formatUltraChatbotAgentDocumentTimestamp,\n  loadUltraChatbotAgentDocumentSuggestions,\n  loadUltraChatbotAgentDocumentVersions,\n  restoreUltraChatbotAgentDocumentVersion,\n  saveUltraChatbotAgentDocumentDraft,\n} from \"./ultra-chatbot-agent-document-client\";\nimport { UltraChatbotAgentDocumentSuggestions } from \"./ultra-chatbot-agent-document-suggestions\";\nimport { UltraChatbotAgentMessageResponse } from \"./ultra-chatbot-agent-message-response\";\nimport { UltraChatbotAgentVersionFooter } from \"./ultra-chatbot-agent-version-footer\";\n\ntype UltraChatbotAgentCodeLanguage = ComponentProps<\n  typeof CodeBlock\n>[\"language\"];\n\nfunction UltraChatbotAgentCodeDocumentPreview({\n  content,\n  onStartEdit,\n  title,\n}: {\n  content: string;\n  onStartEdit: () => void;\n  title: string;\n}) {\n  const language = inferCodeLanguage(title, content);\n\n  return (\n    <CodeBlock\n      className=\"min-h-[24rem] rounded-xl border-foreground/10 [&_pre]:min-h-[20rem]\"\n      code={content}\n      language={language}\n      onDoubleClick={onStartEdit}\n      onKeyDown={(event) => {\n        if (event.key === \"Enter\") {\n          onStartEdit();\n        }\n      }}\n      role=\"button\"\n      showLineNumbers\n      tabIndex={0}\n    >\n      <CodeBlockHeader className=\"gap-3\">\n        <CodeBlockTitle className=\"min-w-0\">\n          <CodeBlockFilename className=\"truncate\">{title}</CodeBlockFilename>\n        </CodeBlockTitle>\n        <CodeBlockActions>\n          <CodeBlockCopyButton />\n        </CodeBlockActions>\n      </CodeBlockHeader>\n    </CodeBlock>\n  );\n}\n\nfunction inferCodeLanguage(\n  title: string,\n  content: string\n): UltraChatbotAgentCodeLanguage {\n  const lowerTitle = title.toLowerCase();\n\n  if (lowerTitle.endsWith(\".tsx\")) {\n    return \"tsx\";\n  }\n\n  if (lowerTitle.endsWith(\".ts\")) {\n    return \"typescript\";\n  }\n\n  if (lowerTitle.endsWith(\".jsx\")) {\n    return \"jsx\";\n  }\n\n  if (lowerTitle.endsWith(\".js\")) {\n    return \"javascript\";\n  }\n\n  if (lowerTitle.endsWith(\".py\")) {\n    return \"python\";\n  }\n\n  if (lowerTitle.endsWith(\".json\")) {\n    return \"json\";\n  }\n\n  if (lowerTitle.endsWith(\".css\")) {\n    return \"css\";\n  }\n\n  if (lowerTitle.endsWith(\".html\")) {\n    return \"html\";\n  }\n\n  if (lowerTitle.endsWith(\".md\") || lowerTitle.endsWith(\".mdx\")) {\n    return \"markdown\";\n  }\n\n  const trimmedContent = content.trim();\n\n  if (trimmedContent.startsWith(\"{\") || trimmedContent.startsWith(\"[\")) {\n    return \"json\";\n  }\n\n  return \"typescript\";\n}\n\n// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: document detail coordinates artifact loading, preview, editing, diffing, and version controls for one focused dialog.\n// biome-ignore lint/complexity/noExcessiveLinesPerFunction: splitting this while the artifact contract is still moving would add indirection without reducing the bug surface.\nexport function UltraChatbotAgentDocumentDetail({\n  chatId,\n  disabled,\n  documentId,\n  mode,\n  onModeChange,\n  onRefreshArtifact,\n  refreshToken,\n}: {\n  chatId: string;\n  disabled: boolean;\n  documentId: string;\n  mode: UltraChatbotAgentArtifactMode;\n  onModeChange: (mode: UltraChatbotAgentArtifactMode) => void;\n  onRefreshArtifact: () => void;\n  refreshToken: number;\n}) {\n  const [detailError, setDetailError] = useState<string | null>(null);\n  const [draftContent, setDraftContent] = useState(\"\");\n  const [isEditing, setIsEditing] = useState(false);\n  const [isSaving, setIsSaving] = useState(false);\n  const [isSuggestionsLoading, setIsSuggestionsLoading] = useState(false);\n  const [isVersionsLoading, setIsVersionsLoading] = useState(true);\n  const [selectedDocumentVersions, setSelectedDocumentVersions] = useState<\n    UltraChatbotAgentDocumentRecord[]\n  >([]);\n  const [selectedSuggestions, setSelectedSuggestions] = useState<\n    UltraChatbotAgentSuggestionRecord[]\n  >([]);\n  const [selectedVersionIndex, setSelectedVersionIndex] = useState(0);\n\n  const refreshSelectedDocument = useCallback(async () => {\n    setIsVersionsLoading(true);\n\n    try {\n      const versions = await loadUltraChatbotAgentDocumentVersions(\n        chatId,\n        documentId\n      );\n      setSelectedDocumentVersions(versions);\n      setDraftContent(versions[0]?.content ?? \"\");\n      setDetailError(null);\n    } catch (error) {\n      setDetailError(\n        error instanceof Error\n          ? error.message\n          : \"Failed to load document versions.\"\n      );\n    } finally {\n      setIsVersionsLoading(false);\n    }\n  }, [chatId, documentId]);\n\n  const refreshSelectedSuggestions = useCallback(async () => {\n    setIsSuggestionsLoading(true);\n\n    try {\n      const suggestions = await loadUltraChatbotAgentDocumentSuggestions(\n        chatId,\n        documentId\n      );\n      setSelectedSuggestions(suggestions);\n      setDetailError(null);\n    } catch (error) {\n      setDetailError(\n        error instanceof Error\n          ? error.message\n          : \"Failed to load document suggestions.\"\n      );\n    } finally {\n      setIsSuggestionsLoading(false);\n    }\n  }, [chatId, documentId]);\n\n  useEffect(() => {\n    if (refreshToken < 0) {\n      return;\n    }\n\n    refreshSelectedDocument().catch(() => undefined);\n    refreshSelectedSuggestions().catch(() => undefined);\n    setIsEditing(false);\n    setSelectedVersionIndex(0);\n    onModeChange(\"edit\");\n  }, [\n    onModeChange,\n    refreshSelectedDocument,\n    refreshSelectedSuggestions,\n    refreshToken,\n  ]);\n\n  const selectedLatestDocument = selectedDocumentVersions[0] ?? null;\n  const selectedDocument =\n    selectedDocumentVersions[selectedVersionIndex] ?? selectedLatestDocument;\n  const comparisonDocument = useMemo(() => {\n    if (selectedVersionIndex === 0) {\n      return selectedDocumentVersions[1] ?? null;\n    }\n\n    return selectedDocumentVersions[selectedVersionIndex - 1] ?? null;\n  }, [selectedDocumentVersions, selectedVersionIndex]);\n  const isLatestVersion = selectedVersionIndex === 0;\n  const previewContent = isLatestVersion\n    ? draftContent\n    : (selectedDocument?.content ?? \"\");\n\n  useEffect(() => {\n    if (!selectedDocument) {\n      setDraftContent(\"\");\n      return;\n    }\n\n    if (isLatestVersion) {\n      setDraftContent(selectedLatestDocument?.content ?? \"\");\n      return;\n    }\n\n    setDraftContent(selectedDocument.content ?? \"\");\n  }, [isLatestVersion, selectedDocument, selectedLatestDocument]);\n\n  async function handleSaveVersion() {\n    if (!selectedLatestDocument) {\n      return;\n    }\n\n    setIsSaving(true);\n\n    try {\n      await saveUltraChatbotAgentDocumentDraft({\n        chatId,\n        content: draftContent,\n        documentId: selectedLatestDocument.id,\n        title: selectedLatestDocument.title,\n      });\n      setDetailError(null);\n      setIsEditing(false);\n      onRefreshArtifact();\n      setSelectedVersionIndex(0);\n      onModeChange(\"edit\");\n    } catch (error) {\n      setDetailError(\n        error instanceof Error\n          ? error.message\n          : \"Failed to save the document draft.\"\n      );\n    } finally {\n      setIsSaving(false);\n    }\n  }\n\n  async function handleRestoreVersion() {\n    if (!selectedDocument) {\n      return;\n    }\n\n    try {\n      await restoreUltraChatbotAgentDocumentVersion({\n        chatId,\n        createdAt: selectedDocument.createdAt,\n        documentId: selectedDocument.id,\n      });\n      setDetailError(null);\n      setIsEditing(false);\n      onRefreshArtifact();\n      setSelectedVersionIndex(0);\n      onModeChange(\"edit\");\n    } catch (error) {\n      setDetailError(\n        error instanceof Error\n          ? error.message\n          : \"Failed to restore the selected document version.\"\n      );\n    }\n  }\n\n  function handleStartEdit() {\n    if (!isLatestVersion || mode === \"diff\") {\n      return;\n    }\n\n    setIsEditing(true);\n  }\n\n  function handleCancelEdit() {\n    setDraftContent(selectedLatestDocument?.content ?? \"\");\n    setIsEditing(false);\n  }\n\n  if (detailError) {\n    return (\n      <div className=\"text-destructive text-xs/relaxed\">{detailError}</div>\n    );\n  }\n\n  if (isVersionsLoading) {\n    return (\n      <div className=\"space-y-4\">\n        <Shimmer className=\"text-sm\">Loading document...</Shimmer>\n        <div className=\"space-y-3\">\n          <div className=\"h-6 w-48 rounded-md bg-muted/50\" />\n          <div className=\"flex flex-wrap gap-2\">\n            <div className=\"h-7 w-20 rounded-md bg-muted/50\" />\n            <div className=\"h-7 w-28 rounded-md bg-muted/50\" />\n            <div className=\"h-7 w-16 rounded-md bg-muted/50\" />\n          </div>\n        </div>\n        <div className=\"space-y-2 rounded-xl border border-foreground/10 bg-muted/20 px-4 py-4\">\n          <div className=\"h-4 w-5/6 rounded-md bg-muted/50\" />\n          <div className=\"h-4 w-full rounded-md bg-muted/50\" />\n          <div className=\"h-4 w-4/5 rounded-md bg-muted/50\" />\n          <div className=\"h-4 w-3/4 rounded-md bg-muted/50\" />\n          <div className=\"h-4 w-[88%] rounded-md bg-muted/50\" />\n          <div className=\"h-4 w-2/3 rounded-md bg-muted/50\" />\n          <div className=\"h-4 w-[92%] rounded-md bg-muted/50\" />\n          <div className=\"h-4 w-3/5 rounded-md bg-muted/50\" />\n        </div>\n      </div>\n    );\n  }\n\n  if (!selectedDocument) {\n    return (\n      <div className=\"border border-foreground/10 border-dashed px-3 py-4 text-muted-foreground text-xs/relaxed\">\n        This document is no longer available.\n      </div>\n    );\n  }\n\n  let documentBody: ReactNode;\n\n  if (mode === \"diff\") {\n    documentBody = (\n      <UltraChatbotAgentDiffView\n        after={selectedDocument.content ?? \"\"}\n        before={comparisonDocument?.content ?? \"\"}\n      />\n    );\n  } else if (isLatestVersion && isEditing) {\n    documentBody = (\n      <Textarea\n        className=\"min-h-[24rem]\"\n        onChange={(event) => setDraftContent(event.target.value)}\n        value={draftContent}\n      />\n    );\n  } else if (selectedDocument.kind === \"code\") {\n    documentBody = (\n      <UltraChatbotAgentCodeDocumentPreview\n        content={previewContent}\n        onStartEdit={() => {\n          if (isLatestVersion) {\n            setIsEditing(true);\n          }\n        }}\n        title={selectedDocument.title}\n      />\n    );\n  } else {\n    documentBody = (\n      // biome-ignore lint/a11y/useSemanticElements: the rich markdown preview may contain nested interactive content, so it cannot be wrapped in a semantic button.\n      <div\n        className=\"min-h-[24rem] rounded-xl border border-foreground/10 px-4 py-4 text-sm\"\n        onDoubleClick={() => {\n          if (isLatestVersion) {\n            setIsEditing(true);\n          }\n        }}\n        onKeyDown={(event) => {\n          if (event.key === \"Enter\" && isLatestVersion) {\n            setIsEditing(true);\n          }\n        }}\n        role=\"button\"\n        tabIndex={0}\n      >\n        <UltraChatbotAgentMessageResponse>\n          {previewContent}\n        </UltraChatbotAgentMessageResponse>\n      </div>\n    );\n  }\n\n  let versionHelpText =\n    \"History view is read-only until you restore or jump to latest.\";\n\n  if (mode === \"diff\") {\n    versionHelpText =\n      \"Diff view compares the selected version against its nearest neighbor.\";\n  } else if (isLatestVersion && isEditing) {\n    versionHelpText = \"Editing the latest artifact revision.\";\n  } else if (isLatestVersion) {\n    versionHelpText =\n      \"Preview mode renders markdown. Use Edit or double-click the body to revise.\";\n  }\n\n  return (\n    <div className=\"space-y-4\">\n      <div className=\"space-y-3 border-foreground/10 border-b pb-4\">\n        <div>\n          <p className=\"font-medium text-sm\">{selectedDocument.title}</p>\n          <div className=\"mt-2 flex flex-wrap gap-2\">\n            <Badge variant=\"outline\">{selectedDocument.kind}</Badge>\n            <Badge variant=\"outline\">\n              {selectedDocumentVersions.length} versions\n            </Badge>\n            {isLatestVersion ? (\n              <Badge variant=\"secondary\">Latest</Badge>\n            ) : (\n              <Badge variant=\"outline\">History</Badge>\n            )}\n          </div>\n        </div>\n        <p className=\"max-w-2xl text-muted-foreground text-xs/relaxed\">\n          Review, revise, diff, and restore this artifact without leaving the\n          current chat route.\n        </p>\n      </div>\n\n      {documentBody}\n\n      <div className=\"flex items-center justify-between gap-3\">\n        <p className=\"text-muted-foreground text-xs\">\n          Version{\" \"}\n          {formatUltraChatbotAgentDocumentTimestamp(selectedDocument.createdAt)}\n        </p>\n        <p className=\"text-muted-foreground text-xs\">{versionHelpText}</p>\n      </div>\n\n      <UltraChatbotAgentVersionFooter\n        currentVersionIndex={selectedVersionIndex}\n        isEditing={isEditing}\n        isLatestVersionView={isLatestVersion}\n        mode={mode}\n        onCancelEdit={handleCancelEdit}\n        onChangeVersion={(direction) => {\n          if (direction === \"latest\") {\n            setIsEditing(false);\n            setSelectedVersionIndex(0);\n            onModeChange(\"edit\");\n            return;\n          }\n\n          if (direction === \"newer\") {\n            setIsEditing(false);\n            setSelectedVersionIndex((current) => Math.max(0, current - 1));\n            return;\n          }\n\n          setIsEditing(false);\n          setSelectedVersionIndex((current) =>\n            Math.min(selectedDocumentVersions.length - 1, current + 1)\n          );\n        }}\n        onRestoreVersion={handleRestoreVersion}\n        onSaveVersion={handleSaveVersion}\n        onSetMode={onModeChange}\n        onStartEdit={handleStartEdit}\n        saveDisabled={disabled || isSaving || !isLatestVersion}\n        totalVersions={selectedDocumentVersions.length}\n      />\n\n      <div className=\"space-y-2 border-foreground/10 border-t pt-4\">\n        <div className=\"flex items-center justify-between gap-3\">\n          <p className=\"font-medium text-sm\">Suggestions</p>\n          <Badge variant=\"outline\">{selectedSuggestions.length}</Badge>\n        </div>\n        {isSuggestionsLoading ? (\n          <Shimmer className=\"text-sm\">Loading suggestions...</Shimmer>\n        ) : (\n          <UltraChatbotAgentDocumentSuggestions\n            suggestions={selectedSuggestions}\n          />\n        )}\n      </div>\n    </div>\n  );\n}\n",
      "type": "registry:component",
      "target": "@components/ultra-chatbot-agent/ultra-chatbot-agent-document-detail.tsx"
    },
    {
      "path": "registry/ultra-chatbot-agent/components/ultra-chatbot-agent/ultra-chatbot-agent-document-dialog.tsx",
      "content": "\"use client\";\n\nimport {\n  Dialog,\n  DialogContent,\n  DialogHeader,\n  DialogTitle,\n} from \"@/components/ui/dialog\";\n\nimport type { UltraChatbotAgentArtifactMode } from \"./ultra-chatbot-agent-artifact-state\";\nimport { UltraChatbotAgentDocumentDetail } from \"./ultra-chatbot-agent-document-detail\";\n\nexport function UltraChatbotAgentDocumentDialog({\n  chatId,\n  disabled,\n  documentId,\n  mode,\n  onClose,\n  onModeChange,\n  onRefreshArtifact,\n  refreshToken,\n}: {\n  chatId: string;\n  disabled: boolean;\n  documentId: string | null;\n  mode: UltraChatbotAgentArtifactMode;\n  onClose: () => void;\n  onModeChange: (mode: UltraChatbotAgentArtifactMode) => void;\n  onRefreshArtifact: () => void;\n  refreshToken: number;\n}) {\n  return (\n    <Dialog\n      onOpenChange={(open: boolean) => {\n        if (!open) {\n          onClose();\n        }\n      }}\n      open={documentId != null}\n    >\n      <DialogContent className=\"grid h-[calc(100svh-3rem)] max-h-[calc(100svh-3rem)] max-w-[min(1100px,calc(100%-2rem))] grid-rows-[auto,minmax(0,1fr)] gap-0 overflow-hidden p-0 sm:max-w-[min(1100px,calc(100%-2rem))]\">\n        <DialogHeader className=\"border-foreground/10 border-b px-5 py-4\">\n          <DialogTitle>Document detail</DialogTitle>\n        </DialogHeader>\n\n        <div className=\"min-h-0 overflow-y-auto px-5 py-4\">\n          {documentId ? (\n            <UltraChatbotAgentDocumentDetail\n              chatId={chatId}\n              disabled={disabled}\n              documentId={documentId}\n              mode={mode}\n              onModeChange={onModeChange}\n              onRefreshArtifact={onRefreshArtifact}\n              refreshToken={refreshToken}\n            />\n          ) : null}\n        </div>\n      </DialogContent>\n    </Dialog>\n  );\n}\n",
      "type": "registry:component",
      "target": "@components/ultra-chatbot-agent/ultra-chatbot-agent-document-dialog.tsx"
    },
    {
      "path": "registry/ultra-chatbot-agent/components/ultra-chatbot-agent/ultra-chatbot-agent-document-preview.tsx",
      "content": "\"use client\";\n\n/* eslint-disable react-hooks/set-state-in-effect, react-hooks/static-components */\n\nimport {\n  ArrowsOutSimpleIcon,\n  FileCodeIcon,\n  FileImageIcon,\n  FileTextIcon,\n  RowsIcon,\n  SpinnerGapIcon,\n} from \"@phosphor-icons/react\";\nimport { Skeleton } from \"@/components/ui/skeleton\";\nimport { cn } from \"@/lib/utils\";\nimport { type ReactNode, useEffect, useMemo, useState } from \"react\";\n\nimport type { UltraChatbotAgentDocumentRecord } from \"@/lib/ultra-chatbot-agent/server/document-store\";\nimport { loadUltraChatbotAgentLatestDocument } from \"./ultra-chatbot-agent-document-client\";\nimport type { UltraChatbotAgentDocumentToolResult } from \"./ultra-chatbot-agent-message-parts\";\n\nfunction getDocumentKindLabel(\n  kind: UltraChatbotAgentDocumentToolResult[\"kind\"]\n) {\n  switch (kind) {\n    case \"code\":\n      return \"Code\";\n    case \"image\":\n      return \"Image\";\n    case \"sheet\":\n      return \"Sheet\";\n    default:\n      return \"Document\";\n  }\n}\n\nfunction getDocumentKindIcon(\n  kind: UltraChatbotAgentDocumentToolResult[\"kind\"]\n) {\n  switch (kind) {\n    case \"code\":\n      return FileCodeIcon;\n    case \"image\":\n      return FileImageIcon;\n    case \"sheet\":\n      return RowsIcon;\n    default:\n      return FileTextIcon;\n  }\n}\n\nexport function UltraChatbotAgentDocumentPreview({\n  chatId,\n  onOpen,\n  result,\n}: {\n  chatId: string;\n  onOpen: (documentId: string) => void;\n  result: UltraChatbotAgentDocumentToolResult;\n}) {\n  const [document, setDocument] =\n    useState<UltraChatbotAgentDocumentRecord | null>(null);\n  const [error, setError] = useState<string | null>(null);\n  const [isLoading, setIsLoading] = useState(true);\n\n  useEffect(() => {\n    let isCancelled = false;\n\n    setIsLoading(true);\n    setError(null);\n    setDocument(null);\n\n    loadUltraChatbotAgentLatestDocument(chatId, result.id)\n      .then((nextDocument) => {\n        if (isCancelled) {\n          return;\n        }\n\n        setDocument(nextDocument);\n      })\n      .catch((loadError) => {\n        if (isCancelled) {\n          return;\n        }\n\n        setError(\n          loadError instanceof Error\n            ? loadError.message\n            : \"Failed to load the inline document preview.\"\n        );\n      })\n      .finally(() => {\n        if (isCancelled) {\n          return;\n        }\n\n        setIsLoading(false);\n      });\n\n    return () => {\n      isCancelled = true;\n    };\n  }, [chatId, result.id]);\n\n  const previewDocument = document ?? {\n    chatId,\n    content: null,\n    createdAt: \"\",\n    id: result.id,\n    kind: result.kind as UltraChatbotAgentDocumentRecord[\"kind\"],\n    title: result.title,\n    visitorId: \"\",\n  };\n  const Icon = useMemo(\n    () => getDocumentKindIcon(previewDocument.kind),\n    [previewDocument.kind]\n  );\n  const previewText =\n    previewDocument.content?.trim().replace(/\\s+/g, \" \").slice(0, 220) ?? \"\";\n  let previewBody: ReactNode;\n\n  if (isLoading) {\n    previewBody = (\n      <div className=\"space-y-2\">\n        <Skeleton className=\"h-4 w-3/4\" />\n        <Skeleton className=\"h-4 w-full\" />\n        <Skeleton className=\"h-4 w-5/6\" />\n        <Skeleton className=\"h-4 w-2/3\" />\n        <Skeleton className=\"h-4 w-[88%]\" />\n      </div>\n    );\n  } else if (previewText) {\n    previewBody = (\n      <p className=\"whitespace-pre-wrap break-words text-sm leading-6 [overflow-wrap:anywhere]\">\n        {previewText}\n      </p>\n    );\n  } else {\n    previewBody = (\n      <p className=\"text-muted-foreground text-sm\">\n        Open this artifact to keep editing it in the detail dialog.\n      </p>\n    );\n  }\n\n  if (error) {\n    return (\n      <div className=\"w-full rounded-xl border border-destructive/30 px-4 py-3 text-destructive text-sm\">\n        {error}\n      </div>\n    );\n  }\n\n  return (\n    <button\n      className={cn(\n        \"w-full min-w-0 max-w-full overflow-hidden rounded-2xl border border-foreground/10 text-left transition-colors hover:border-foreground/30\",\n        \"focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-foreground/30\"\n      )}\n      onClick={() => onOpen(result.id)}\n      type=\"button\"\n    >\n      <div className=\"flex items-center justify-between gap-3 border-foreground/10 border-b px-4 py-3\">\n        <div className=\"flex min-w-0 flex-1 items-center gap-2.5\">\n          <span className=\"text-muted-foreground\">\n            {isLoading ? (\n              <SpinnerGapIcon className=\"size-4 animate-spin\" />\n            ) : (\n              <Icon className=\"size-4\" />\n            )}\n          </span>\n          <div className=\"min-w-0 flex-1\">\n            <p className=\"break-words font-medium text-sm leading-6 [overflow-wrap:anywhere]\">\n              {previewDocument.title}\n            </p>\n            <p className=\"mt-0.5 text-[11px] text-muted-foreground uppercase tracking-[0.18em]\">\n              {getDocumentKindLabel(previewDocument.kind)}\n            </p>\n          </div>\n        </div>\n        <ArrowsOutSimpleIcon className=\"size-4 shrink-0 text-muted-foreground\" />\n      </div>\n\n      <div className=\"min-h-36 bg-muted/30 px-4 py-4\">{previewBody}</div>\n    </button>\n  );\n}\n",
      "type": "registry:component",
      "target": "@components/ultra-chatbot-agent/ultra-chatbot-agent-document-preview.tsx"
    },
    {
      "path": "registry/ultra-chatbot-agent/components/ultra-chatbot-agent/ultra-chatbot-agent-document-suggestions.tsx",
      "content": "import { SparkleIcon } from \"@phosphor-icons/react\";\nimport { Badge } from \"@/components/ui/badge\";\n\nimport type { UltraChatbotAgentSuggestionRecord } from \"@/lib/ultra-chatbot-agent/server/suggestion-store\";\n\nexport function UltraChatbotAgentDocumentSuggestions({\n  suggestions,\n}: {\n  suggestions: UltraChatbotAgentSuggestionRecord[];\n}) {\n  if (suggestions.length === 0) {\n    return (\n      <div className=\"border border-foreground/10 border-dashed px-3 py-4 text-muted-foreground text-xs/relaxed\">\n        Ask the agent to review this document when you want sentence-level\n        improvement suggestions.\n      </div>\n    );\n  }\n\n  return (\n    <div className=\"space-y-2\">\n      {suggestions.map((suggestion) => (\n        <div\n          className=\"space-y-2 border border-foreground/10 px-3 py-3\"\n          key={suggestion.id}\n        >\n          <div className=\"flex items-center gap-2\">\n            <SparkleIcon className=\"size-3.5 text-muted-foreground\" />\n            <Badge variant=\"secondary\">Suggestion</Badge>\n          </div>\n          <div className=\"space-y-1 text-sm\">\n            <p className=\"text-muted-foreground text-xs uppercase tracking-[0.2em]\">\n              Original\n            </p>\n            <p>{suggestion.originalText}</p>\n          </div>\n          <div className=\"space-y-1 text-sm\">\n            <p className=\"text-muted-foreground text-xs uppercase tracking-[0.2em]\">\n              Suggested\n            </p>\n            <p>{suggestion.suggestedText}</p>\n          </div>\n          {suggestion.description ? (\n            <p className=\"text-muted-foreground text-xs/relaxed\">\n              {suggestion.description}\n            </p>\n          ) : null}\n        </div>\n      ))}\n    </div>\n  );\n}\n",
      "type": "registry:component",
      "target": "@components/ultra-chatbot-agent/ultra-chatbot-agent-document-suggestions.tsx"
    },
    {
      "path": "registry/ultra-chatbot-agent/components/ultra-chatbot-agent/ultra-chatbot-agent-history-item.tsx",
      "content": "\"use client\";\n\nimport { LinkIcon, TrashIcon } from \"@phosphor-icons/react\";\nimport {\n  AlertDialog,\n  AlertDialogAction,\n  AlertDialogCancel,\n  AlertDialogContent,\n  AlertDialogDescription,\n  AlertDialogFooter,\n  AlertDialogHeader,\n  AlertDialogTitle,\n} from \"@/components/ui/alert-dialog\";\nimport { Button } from \"@/components/ui/button\";\nimport { cn } from \"@/lib/utils\";\nimport Link from \"next/link\";\nimport { useState } from \"react\";\n\nimport type { UltraChatbotAgentChatRecord } from \"@/lib/ultra-chatbot-agent/server/chat-store\";\n\nfunction formatDateTime(value: string) {\n  const date = new Date(value);\n\n  if (Number.isNaN(date.valueOf())) {\n    return value;\n  }\n\n  return new Intl.DateTimeFormat(\"en\", {\n    day: \"2-digit\",\n    hour: \"2-digit\",\n    minute: \"2-digit\",\n    month: \"short\",\n  }).format(date);\n}\n\nfunction toConversationPath(chatId: string) {\n  return `/demos/ultra-chatbot-agent/${chatId}`;\n}\n\ninterface UltraChatbotAgentHistoryItemProps {\n  chat: UltraChatbotAgentChatRecord;\n  isActive: boolean;\n  isDeleting?: boolean;\n  onDelete?: (chat: UltraChatbotAgentChatRecord) => void;\n}\n\nexport function UltraChatbotAgentHistoryItem({\n  chat,\n  isActive,\n  isDeleting = false,\n  onDelete,\n}: UltraChatbotAgentHistoryItemProps) {\n  const [isConfirmOpen, setIsConfirmOpen] = useState(false);\n\n  return (\n    <div\n      className={cn(\n        \"flex items-start gap-2 border text-sm transition-colors\",\n        isActive\n          ? \"border-foreground bg-foreground text-background\"\n          : \"border-foreground/10 hover:border-foreground/30\"\n      )}\n    >\n      <Link\n        className=\"min-w-0 flex-1 px-3 py-2 text-left\"\n        href={toConversationPath(chat.id)}\n      >\n        <div className=\"flex items-start justify-between gap-3\">\n          <span className=\"line-clamp-2\">{chat.title}</span>\n          <LinkIcon className=\"mt-0.5 size-3.5 shrink-0\" />\n        </div>\n        <p\n          className={cn(\n            \"mt-2 text-[11px]\",\n            isActive ? \"text-background/80\" : \"text-muted-foreground\"\n          )}\n        >\n          {formatDateTime(chat.updatedAt)}\n        </p>\n      </Link>\n      {onDelete ? (\n        <AlertDialog onOpenChange={setIsConfirmOpen} open={isConfirmOpen}>\n          <Button\n            aria-label={`Delete ${chat.title}`}\n            className={cn(\n              \"m-1.5 shrink-0\",\n              isActive &&\n                \"border-background/30 bg-transparent text-background hover:bg-background hover:text-foreground\"\n            )}\n            disabled={isDeleting}\n            onClick={() => setIsConfirmOpen(true)}\n            size=\"icon-xs\"\n            type=\"button\"\n            variant=\"outline\"\n          >\n            <TrashIcon className=\"size-3\" />\n          </Button>\n          <AlertDialogContent>\n            <AlertDialogHeader>\n              <AlertDialogTitle>Delete this chat?</AlertDialogTitle>\n              <AlertDialogDescription>\n                This removes the conversation, votes, and thread artifacts for\n                this visitor. The action cannot be undone.\n              </AlertDialogDescription>\n            </AlertDialogHeader>\n            <AlertDialogFooter>\n              <AlertDialogCancel>Cancel</AlertDialogCancel>\n              <AlertDialogAction\n                disabled={isDeleting}\n                onClick={() => {\n                  setIsConfirmOpen(false);\n                  onDelete(chat);\n                }}\n                variant=\"destructive\"\n              >\n                Delete\n              </AlertDialogAction>\n            </AlertDialogFooter>\n          </AlertDialogContent>\n        </AlertDialog>\n      ) : null}\n    </div>\n  );\n}\n",
      "type": "registry:component",
      "target": "@components/ultra-chatbot-agent/ultra-chatbot-agent-history-item.tsx"
    },
    {
      "path": "registry/ultra-chatbot-agent/components/ultra-chatbot-agent/ultra-chatbot-agent-history-sidebar.tsx",
      "content": "\"use client\";\n\n/* eslint-disable react-hooks/set-state-in-effect */\n\nimport { ArrowClockwiseIcon, TrashIcon } from \"@phosphor-icons/react\";\nimport { Badge } from \"@/components/ui/badge\";\nimport { Button, buttonVariants } from \"@/components/ui/button\";\nimport { cn } from \"@/lib/utils\";\nimport Link from \"next/link\";\nimport { useRouter } from \"next/navigation\";\nimport { useEffect, useState } from \"react\";\n\nimport type {\n  UltraChatbotAgentChatRecord,\n  UltraChatbotAgentHistoryPage,\n} from \"@/lib/ultra-chatbot-agent/server/chat-store\";\nimport { UltraChatbotAgentHistoryItem } from \"./ultra-chatbot-agent-history-item\";\nimport { mergeUltraChatbotAgentChatIntoHistory } from \"./ultra-chatbot-agent-history-state\";\n\nfunction toRootPath() {\n  return \"/demos/ultra-chatbot-agent\";\n}\n\nasync function loadHistoryPage(input: {\n  endingBefore: string | null;\n  limit?: number;\n}) {\n  const searchParams = new URLSearchParams();\n\n  if (input.limit) {\n    searchParams.set(\"limit\", String(input.limit));\n  }\n\n  if (input.endingBefore) {\n    searchParams.set(\"ending_before\", input.endingBefore);\n  }\n\n  const response = await fetch(\n    `/api/demos/ultra-chatbot-agent/history?${searchParams.toString()}`,\n    {\n      credentials: \"include\",\n    }\n  );\n\n  if (!response.ok) {\n    throw new Error(\"Failed to load chat history.\");\n  }\n\n  return (await response.json()) as UltraChatbotAgentHistoryPage;\n}\n\nasync function deleteAllHistory() {\n  const response = await fetch(\"/api/demos/ultra-chatbot-agent/history\", {\n    credentials: \"include\",\n    method: \"DELETE\",\n  });\n\n  if (!response.ok) {\n    throw new Error(\"Failed to delete chat history.\");\n  }\n}\n\nasync function deleteHistoryChat(chatId: string) {\n  const response = await fetch(`/api/demos/ultra-chatbot-agent/${chatId}`, {\n    credentials: \"include\",\n    method: \"DELETE\",\n  });\n\n  if (!response.ok) {\n    throw new Error(\"Failed to delete this chat.\");\n  }\n}\n\ninterface UltraChatbotAgentHistorySidebarProps {\n  currentChatId: string;\n  currentChatRecordHint: UltraChatbotAgentChatRecord | null;\n  initialHistoryPage: UltraChatbotAgentHistoryPage;\n}\n\nexport function UltraChatbotAgentHistorySidebar({\n  currentChatId,\n  currentChatRecordHint,\n  initialHistoryPage,\n}: UltraChatbotAgentHistorySidebarProps) {\n  const router = useRouter();\n  const [historyPage, setHistoryPage] = useState(initialHistoryPage);\n  const [deletingChatId, setDeletingChatId] = useState<string | null>(null);\n  const [isDeleting, setIsDeleting] = useState(false);\n  const [isLoadingMore, setIsLoadingMore] = useState(false);\n  const [historyError, setHistoryError] = useState<string | null>(null);\n\n  useEffect(() => {\n    if (!currentChatRecordHint) {\n      return;\n    }\n\n    setHistoryPage((current) => {\n      const chats = mergeUltraChatbotAgentChatIntoHistory(\n        current.chats,\n        currentChatRecordHint\n      );\n\n      return chats === current.chats\n        ? current\n        : {\n            ...current,\n            chats,\n          };\n    });\n  }, [currentChatRecordHint]);\n\n  async function handleLoadMore() {\n    const lastChat = historyPage.chats.at(-1);\n\n    if (!lastChat) {\n      return;\n    }\n\n    setHistoryError(null);\n    setIsLoadingMore(true);\n\n    try {\n      const nextPage = await loadHistoryPage({\n        endingBefore: lastChat.id,\n        limit: 10,\n      });\n\n      setHistoryPage((current) => ({\n        chats: [...current.chats, ...nextPage.chats],\n        hasMore: nextPage.hasMore,\n      }));\n    } catch (error) {\n      setHistoryError(\n        error instanceof Error ? error.message : \"Failed to load chat history.\"\n      );\n    } finally {\n      setIsLoadingMore(false);\n    }\n  }\n\n  async function handleDeleteAll() {\n    setHistoryError(null);\n    setIsDeleting(true);\n\n    try {\n      await deleteAllHistory();\n      setHistoryPage({\n        chats: [],\n        hasMore: false,\n      });\n      window.location.replace(toRootPath());\n    } catch (error) {\n      setHistoryError(\n        error instanceof Error\n          ? error.message\n          : \"Failed to delete chat history.\"\n      );\n    } finally {\n      setIsDeleting(false);\n    }\n  }\n\n  async function handleDeleteChat(chat: UltraChatbotAgentChatRecord) {\n    setHistoryError(null);\n    setDeletingChatId(chat.id);\n\n    try {\n      await deleteHistoryChat(chat.id);\n      setHistoryPage((current) => ({\n        ...current,\n        chats: current.chats.filter(\n          (currentChat) => currentChat.id !== chat.id\n        ),\n      }));\n\n      if (chat.id === currentChatId) {\n        window.location.replace(toRootPath());\n      } else {\n        router.refresh();\n      }\n    } catch (error) {\n      setHistoryError(\n        error instanceof Error ? error.message : \"Failed to delete this chat.\"\n      );\n    } finally {\n      setDeletingChatId(null);\n    }\n  }\n\n  return (\n    <aside className=\"border border-foreground/10 bg-background p-4 lg:min-h-0 lg:overflow-y-auto\">\n      <div className=\"space-y-4\">\n        <div className=\"flex items-center justify-between gap-2\">\n          <div>\n            <p className=\"text-[11px] text-muted-foreground uppercase tracking-[0.2em]\">\n              History\n            </p>\n            <p className=\"mt-1 font-medium text-sm\">Visitor-owned chats</p>\n          </div>\n          <Badge variant=\"outline\">{historyPage.chats.length}</Badge>\n        </div>\n\n        <div className=\"flex gap-2\">\n          <Link\n            className={cn(buttonVariants({ size: \"sm\", variant: \"outline\" }))}\n            href={toRootPath()}\n          >\n            New chat\n          </Link>\n          <Button\n            disabled={isDeleting || historyPage.chats.length === 0}\n            onClick={handleDeleteAll}\n            size=\"sm\"\n            type=\"button\"\n            variant=\"outline\"\n          >\n            <TrashIcon className=\"size-3.5\" />\n            Clear all\n          </Button>\n        </div>\n\n        {historyError ? (\n          <p className=\"text-destructive text-xs/relaxed\">{historyError}</p>\n        ) : null}\n\n        <div className=\"grid gap-2\">\n          {historyPage.chats.length > 0 ? (\n            historyPage.chats.map((chat) => (\n              <UltraChatbotAgentHistoryItem\n                chat={chat}\n                isActive={chat.id === currentChatId}\n                isDeleting={deletingChatId === chat.id}\n                key={chat.id}\n                onDelete={handleDeleteChat}\n              />\n            ))\n          ) : (\n            <p className=\"text-muted-foreground text-sm\">\n              No persisted chats for this visitor yet.\n            </p>\n          )}\n        </div>\n\n        {historyPage.hasMore ? (\n          <Button\n            disabled={isLoadingMore}\n            onClick={handleLoadMore}\n            size=\"sm\"\n            type=\"button\"\n            variant=\"outline\"\n          >\n            <ArrowClockwiseIcon className=\"size-3.5\" />\n            {isLoadingMore ? \"Loading...\" : \"Load more\"}\n          </Button>\n        ) : null}\n      </div>\n    </aside>\n  );\n}\n",
      "type": "registry:component",
      "target": "@components/ultra-chatbot-agent/ultra-chatbot-agent-history-sidebar.tsx"
    },
    {
      "path": "registry/ultra-chatbot-agent/components/ultra-chatbot-agent/ultra-chatbot-agent-history-state.ts",
      "content": "import type { UltraChatbotAgentChatRecord } from \"@/lib/ultra-chatbot-agent/server/chat-store\";\n\nfunction areChatCapabilitiesEqual(\n  left: UltraChatbotAgentChatRecord[\"capabilities\"],\n  right: UltraChatbotAgentChatRecord[\"capabilities\"]\n) {\n  return left.sandboxEnabled === right.sandboxEnabled;\n}\n\nexport function areUltraChatbotAgentChatRecordsEqual(\n  left: UltraChatbotAgentChatRecord,\n  right: UltraChatbotAgentChatRecord\n) {\n  return (\n    left.activeStreamId === right.activeStreamId &&\n    left.createdAt === right.createdAt &&\n    left.id === right.id &&\n    left.selectedChatModel === right.selectedChatModel &&\n    left.title === right.title &&\n    left.updatedAt === right.updatedAt &&\n    left.visibility === right.visibility &&\n    left.visitorId === right.visitorId &&\n    areChatCapabilitiesEqual(left.capabilities, right.capabilities)\n  );\n}\n\nexport function mergeUltraChatbotAgentChatIntoHistory(\n  chats: UltraChatbotAgentChatRecord[],\n  incoming: UltraChatbotAgentChatRecord\n) {\n  const currentIndex = chats.findIndex((chat) => chat.id === incoming.id);\n  const firstChat = chats[0];\n\n  if (\n    currentIndex === 0 &&\n    firstChat &&\n    areUltraChatbotAgentChatRecordsEqual(firstChat, incoming)\n  ) {\n    return chats;\n  }\n\n  const next = chats.filter((chat) => chat.id !== incoming.id);\n  return [incoming, ...next];\n}\n",
      "type": "registry:component",
      "target": "@components/ultra-chatbot-agent/ultra-chatbot-agent-history-state.ts"
    },
    {
      "path": "registry/ultra-chatbot-agent/components/ultra-chatbot-agent/ultra-chatbot-agent-knowledge-base-result.tsx",
      "content": "\"use client\";\n\nimport { Badge } from \"@/components/ui/badge\";\n\nimport type { UltraChatbotAgentKnowledgeBaseResult } from \"./ultra-chatbot-agent-message-parts\";\n\nexport function UltraChatbotAgentKnowledgeBaseResultCard({\n  result,\n}: {\n  result: UltraChatbotAgentKnowledgeBaseResult;\n}) {\n  const seenSourceKeys = new Map<string, number>();\n  const seenSnippetKeys = new Map<string, number>();\n  const keyedSnippets = result.snippets.slice(0, 3).map((snippet) => {\n    const baseKey = `${snippet.documentUrl}-${snippet.citationLabel}-${snippet.content}`;\n    const nextCount = (seenSnippetKeys.get(baseKey) ?? 0) + 1;\n    seenSnippetKeys.set(baseKey, nextCount);\n\n    return {\n      key: nextCount === 1 ? baseKey : `${baseKey}-${nextCount}`,\n      snippet,\n    };\n  });\n  const keyedSources = result.sources.map((source) => {\n    const baseKey = `${source.url}-${source.title}`;\n    const nextCount = (seenSourceKeys.get(baseKey) ?? 0) + 1;\n    seenSourceKeys.set(baseKey, nextCount);\n\n    return {\n      key: nextCount === 1 ? baseKey : `${baseKey}-${nextCount}`,\n      source,\n    };\n  });\n\n  return (\n    <article className=\"w-full space-y-4 border border-foreground/15 bg-background px-5 py-5\">\n      <div className=\"space-y-3\">\n        <div className=\"flex flex-wrap items-center gap-2\">\n          <Badge variant=\"outline\">Knowledge base</Badge>\n          <Badge variant={result.answerable ? \"secondary\" : \"outline\"}>\n            {result.answerable ? \"Grounded\" : \"Low confidence\"}\n          </Badge>\n          <Badge variant=\"secondary\">{result.sources.length} sources</Badge>\n        </div>\n        <div>\n          <p className=\"text-muted-foreground text-xs uppercase tracking-[0.24em]\">\n            Query\n          </p>\n          <h3 className=\"mt-2 text-balance font-medium text-lg\">\n            {result.query}\n          </h3>\n        </div>\n        <p className=\"text-muted-foreground text-sm leading-relaxed\">\n          {result.message}\n        </p>\n      </div>\n\n      {result.snippets.length > 0 ? (\n        <div className=\"space-y-3\">\n          <p className=\"font-medium text-muted-foreground text-xs uppercase tracking-[0.24em]\">\n            Retrieved snippets\n          </p>\n          <div className=\"grid gap-3\">\n            {keyedSnippets.map(({ key, snippet }) => (\n              <div\n                className=\"space-y-2 border border-foreground/10 bg-muted/20 px-3 py-3\"\n                key={key}\n              >\n                <div className=\"flex flex-wrap items-center gap-2\">\n                  <Badge variant=\"outline\">{snippet.citationLabel}</Badge>\n                  {snippet.sectionTitle ? (\n                    <Badge variant=\"secondary\">{snippet.sectionTitle}</Badge>\n                  ) : null}\n                </div>\n                <p className=\"line-clamp-4 text-sm leading-relaxed\">\n                  {snippet.content}\n                </p>\n              </div>\n            ))}\n          </div>\n        </div>\n      ) : null}\n\n      {result.sources.length > 0 ? (\n        <div className=\"space-y-2 border-foreground/10 border-t pt-4\">\n          <p className=\"font-medium text-muted-foreground text-xs uppercase tracking-[0.24em]\">\n            Resources\n          </p>\n          <div className=\"flex flex-wrap gap-2\">\n            {keyedSources.map(({ key, source }) => (\n              <a\n                className=\"border border-foreground/15 px-2.5 py-1 text-xs transition-colors hover:border-foreground hover:bg-foreground hover:text-background\"\n                href={source.url}\n                key={key}\n                rel=\"noreferrer\"\n                target=\"_blank\"\n              >\n                {source.title}\n              </a>\n            ))}\n          </div>\n        </div>\n      ) : null}\n    </article>\n  );\n}\n",
      "type": "registry:component",
      "target": "@components/ultra-chatbot-agent/ultra-chatbot-agent-knowledge-base-result.tsx"
    },
    {
      "path": "registry/ultra-chatbot-agent/components/ultra-chatbot-agent/ultra-chatbot-agent-message-parts.ts",
      "content": "import {\n  type DynamicToolUIPart,\n  type FileUIPart,\n  isReasoningUIPart,\n  type SourceUrlUIPart,\n  type ToolUIPart,\n  type UIMessage,\n} from \"ai\";\n\nexport type UltraChatbotAgentToolPart = DynamicToolUIPart | ToolUIPart;\n\nexport interface UltraChatbotAgentDocumentToolResult {\n  id: string;\n  kind: string;\n  title: string;\n}\n\ninterface UltraChatbotAgentToolSourcesResult {\n  sources: Array<\n    | {\n        title: string;\n        url: string;\n      }\n    | {\n        citationLabel: string;\n        documentUrl: string;\n      }\n  >;\n}\n\nexport interface UltraChatbotAgentKnowledgeBaseResult {\n  answerable: boolean;\n  knowledgeSource?: {\n    description?: string;\n    title: string;\n  };\n  message: string;\n  query: string;\n  retrievalQueries?: string[];\n  snippets: Array<{\n    citationLabel: string;\n    content: string;\n    documentUrl: string;\n    pageLabel?: string;\n    sectionTitle?: string;\n    similarity?: number;\n  }>;\n  sources: Array<{\n    title: string;\n    url: string;\n  }>;\n}\n\nexport interface UltraChatbotAgentSourcePart {\n  sourceId: string;\n  title: string;\n  url: string;\n}\n\nexport interface UltraChatbotAgentResearchReportResult {\n  executiveSummary: string;\n  keyFindings: string[];\n  kind: \"research-report\";\n  recommendations: string[];\n  risks: string[];\n  sources: Array<{\n    title: string;\n    url: string;\n  }>;\n  title: string;\n  topic: string;\n}\n\nexport interface UltraChatbotAgentProjectDocsSearchResult {\n  kind: \"search\";\n  matches: Array<{\n    line: number;\n    path: string;\n    text: string;\n  }>;\n  query: string;\n}\n\nexport type UltraChatbotAgentProjectDocsMcpResult =\n  UltraChatbotAgentProjectDocsSearchResult;\n\nconst markdownLinkPattern = /\\[([^\\]]+)\\]\\((https?:\\/\\/[^\\s)]+)\\)/g;\nconst explicitUrlPattern = /\\bhttps?:\\/\\/[^\\s)]+/g;\nconst domainCitationPattern =\n  /\\b(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\\.)+[a-z]{2,}(?:\\/[^\\s),\\]]*)?/gi;\n\nfunction normalizeCitationUrl(value: string) {\n  const trimmed = value.trim().replace(/[),.;]+$/g, \"\");\n\n  if (trimmed.startsWith(\"http://\") || trimmed.startsWith(\"https://\")) {\n    return trimmed;\n  }\n\n  return `https://${trimmed}`;\n}\n\nfunction collectTextCitationSources(message: UIMessage) {\n  const text = message.parts\n    .filter(\n      (part): part is Extract<UIMessage[\"parts\"][number], { type: \"text\" }> =>\n        part.type === \"text\" && part.text.trim().length > 0\n    )\n    .map((part) => part.text)\n    .join(\"\\n\");\n\n  if (text.length === 0) {\n    return [] as UltraChatbotAgentSourcePart[];\n  }\n\n  const sources = new Map<string, UltraChatbotAgentSourcePart>();\n\n  for (const match of text.matchAll(markdownLinkPattern)) {\n    const [, label, rawUrl] = match;\n    if (typeof rawUrl !== \"string\") {\n      continue;\n    }\n\n    const url = normalizeCitationUrl(rawUrl);\n    const title = typeof label === \"string\" ? label.trim() : \"\";\n\n    sources.set(url, {\n      sourceId: url,\n      title: title || url,\n      url,\n    });\n  }\n\n  for (const match of text.matchAll(explicitUrlPattern)) {\n    const url = normalizeCitationUrl(match[0]);\n\n    sources.set(url, {\n      sourceId: url,\n      title: url.replace(/^https?:\\/\\//, \"\"),\n      url,\n    });\n  }\n\n  for (const match of text.matchAll(domainCitationPattern)) {\n    const url = normalizeCitationUrl(match[0]);\n\n    if (sources.has(url)) {\n      continue;\n    }\n\n    sources.set(url, {\n      sourceId: url,\n      title: url.replace(/^https?:\\/\\//, \"\"),\n      url,\n    });\n  }\n\n  return [...sources.values()].sort((left, right) => {\n    const leftIndex = text.indexOf(left.title);\n    const rightIndex = text.indexOf(right.title);\n\n    return leftIndex - rightIndex;\n  });\n}\n\nfunction hasUltraChatbotAgentToolSources(\n  output: UltraChatbotAgentToolPart[\"output\"]\n): output is UltraChatbotAgentToolSourcesResult {\n  if (!output || typeof output !== \"object\" || !(\"sources\" in output)) {\n    return false;\n  }\n\n  return Array.isArray(output.sources);\n}\n\nfunction normalizeToolSource(\n  source: UltraChatbotAgentToolSourcesResult[\"sources\"][number]\n) {\n  if (\n    \"url\" in source &&\n    typeof source.url === \"string\" &&\n    typeof source.title === \"string\"\n  ) {\n    const title = source.title.trim() || source.url;\n    const url = source.url.trim();\n\n    return url.length === 0\n      ? null\n      : {\n          sourceId: `${url}#${title}`,\n          title,\n          url,\n        };\n  }\n\n  if (\n    \"documentUrl\" in source &&\n    typeof source.documentUrl === \"string\" &&\n    typeof source.citationLabel === \"string\"\n  ) {\n    const title = source.citationLabel.trim() || source.documentUrl;\n    const url = source.documentUrl.trim();\n\n    return url.length === 0\n      ? null\n      : {\n          sourceId: `${url}#${title}`,\n          title,\n          url,\n        };\n  }\n\n  return null;\n}\n\nexport function getUltraChatbotAgentReasoningText(message: UIMessage) {\n  return message.parts\n    .filter(isReasoningUIPart)\n    .map((part) => part.text)\n    .join(\"\\n\\n\");\n}\n\nexport function getUltraChatbotAgentTextContent(message: UIMessage) {\n  return message.parts\n    .filter((part) => part.type === \"text\")\n    .map((part) => part.text)\n    .join(\"\\n\");\n}\n\nexport function getUltraChatbotAgentFileParts(message: UIMessage) {\n  return message.parts.filter(\n    (part): part is FileUIPart =>\n      part.type === \"file\" && typeof part.url === \"string\"\n  );\n}\n\nexport function getUltraChatbotAgentSourceParts(message: UIMessage) {\n  const explicitSources = message.parts\n    .filter(\n      (part): part is SourceUrlUIPart =>\n        part.type === \"source-url\" &&\n        typeof part.url === \"string\" &&\n        typeof part.sourceId === \"string\"\n    )\n    .map((part) => ({\n      sourceId: part.sourceId,\n      title: part.title?.trim() || part.url,\n      url: part.url,\n    }));\n\n  if (explicitSources.length > 0) {\n    return explicitSources;\n  }\n\n  const toolSources = message.parts\n    .filter(isUltraChatbotAgentToolPart)\n    .flatMap((part) =>\n      part.state === \"output-available\" &&\n      hasUltraChatbotAgentToolSources(part.output)\n        ? part.output.sources.map(normalizeToolSource).filter(\n            (\n              source\n            ): source is {\n              sourceId: string;\n              title: string;\n              url: string;\n            } => source !== null\n          )\n        : []\n    );\n\n  if (toolSources.length > 0) {\n    return toolSources;\n  }\n\n  return collectTextCitationSources(message);\n}\n\nexport function isUltraChatbotAgentToolPart(\n  part: UIMessage[\"parts\"][number]\n): part is UltraChatbotAgentToolPart {\n  return part.type === \"dynamic-tool\" || part.type.startsWith(\"tool-\");\n}\n\nexport function getUltraChatbotAgentToolParts(message: UIMessage) {\n  return message.parts.filter(isUltraChatbotAgentToolPart);\n}\n\nexport function hasUltraChatbotAgentVisibleMessageContent(message: UIMessage) {\n  return message.parts.some(\n    (part) =>\n      (part.type === \"text\" && part.text.trim().length > 0) ||\n      (isReasoningUIPart(part) && part.text.trim().length > 0) ||\n      isUltraChatbotAgentToolPart(part)\n  );\n}\n\nexport function isUltraChatbotAgentDocumentResult(\n  output: UltraChatbotAgentToolPart[\"output\"]\n): output is UltraChatbotAgentDocumentToolResult {\n  if (!output || typeof output !== \"object\") {\n    return false;\n  }\n\n  return (\n    \"id\" in output &&\n    typeof output.id === \"string\" &&\n    \"title\" in output &&\n    typeof output.title === \"string\" &&\n    \"kind\" in output &&\n    typeof output.kind === \"string\"\n  );\n}\n\nfunction isStringArray(value: unknown) {\n  return (\n    Array.isArray(value) && value.every((item) => typeof item === \"string\")\n  );\n}\n\nfunction isResearchReportSources(value: unknown) {\n  return (\n    Array.isArray(value) &&\n    value.every(\n      (item) =>\n        item &&\n        typeof item === \"object\" &&\n        \"title\" in item &&\n        typeof item.title === \"string\" &&\n        \"url\" in item &&\n        typeof item.url === \"string\"\n    )\n  );\n}\n\nfunction isKnowledgeBaseSnippet(value: unknown) {\n  return (\n    value &&\n    typeof value === \"object\" &&\n    \"citationLabel\" in value &&\n    typeof value.citationLabel === \"string\" &&\n    \"content\" in value &&\n    typeof value.content === \"string\" &&\n    \"documentUrl\" in value &&\n    typeof value.documentUrl === \"string\"\n  );\n}\n\nfunction readMcpJsonText(output: UltraChatbotAgentToolPart[\"output\"]) {\n  if (!output || typeof output !== \"object\" || !(\"content\" in output)) {\n    return null;\n  }\n\n  const { content } = output;\n\n  if (!Array.isArray(content)) {\n    return null;\n  }\n\n  const firstTextPart = content.find(\n    (part) =>\n      part &&\n      typeof part === \"object\" &&\n      \"type\" in part &&\n      part.type === \"text\" &&\n      \"text\" in part &&\n      typeof part.text === \"string\"\n  );\n\n  if (!(firstTextPart && \"text\" in firstTextPart)) {\n    return null;\n  }\n\n  try {\n    return JSON.parse(firstTextPart.text as string) as unknown;\n  } catch {\n    return null;\n  }\n}\n\nfunction isProjectDocsSearchPayload(\n  value: unknown\n): value is Omit<UltraChatbotAgentProjectDocsSearchResult, \"kind\"> {\n  return (\n    value !== null &&\n    typeof value === \"object\" &&\n    \"query\" in value &&\n    typeof value.query === \"string\" &&\n    \"matches\" in value &&\n    Array.isArray(value.matches) &&\n    value.matches.every(\n      (match) =>\n        match &&\n        typeof match === \"object\" &&\n        \"path\" in match &&\n        typeof match.path === \"string\" &&\n        \"line\" in match &&\n        typeof match.line === \"number\" &&\n        \"text\" in match &&\n        typeof match.text === \"string\"\n    )\n  );\n}\n\nexport function isUltraChatbotAgentResearchReportResult(\n  output: UltraChatbotAgentToolPart[\"output\"]\n): output is UltraChatbotAgentResearchReportResult {\n  if (!output || typeof output !== \"object\") {\n    return false;\n  }\n\n  return (\n    \"kind\" in output &&\n    output.kind === \"research-report\" &&\n    \"title\" in output &&\n    typeof output.title === \"string\" &&\n    \"topic\" in output &&\n    typeof output.topic === \"string\" &&\n    \"executiveSummary\" in output &&\n    typeof output.executiveSummary === \"string\" &&\n    \"keyFindings\" in output &&\n    isStringArray(output.keyFindings) &&\n    \"recommendations\" in output &&\n    isStringArray(output.recommendations) &&\n    \"risks\" in output &&\n    isStringArray(output.risks) &&\n    \"sources\" in output &&\n    isResearchReportSources(output.sources)\n  );\n}\n\nexport function isUltraChatbotAgentKnowledgeBaseResult(\n  output: UltraChatbotAgentToolPart[\"output\"]\n): output is UltraChatbotAgentKnowledgeBaseResult {\n  if (!output || typeof output !== \"object\") {\n    return false;\n  }\n\n  return (\n    \"answerable\" in output &&\n    typeof output.answerable === \"boolean\" &&\n    \"message\" in output &&\n    typeof output.message === \"string\" &&\n    \"query\" in output &&\n    typeof output.query === \"string\" &&\n    \"snippets\" in output &&\n    Array.isArray(output.snippets) &&\n    output.snippets.every(isKnowledgeBaseSnippet) &&\n    \"sources\" in output &&\n    isResearchReportSources(output.sources)\n  );\n}\n\nexport function getUltraChatbotAgentProjectDocsMcpResult(\n  part: UltraChatbotAgentToolPart\n): UltraChatbotAgentProjectDocsMcpResult | null {\n  if (\n    part.state !== \"output-available\" ||\n    part.type !== \"tool-project__search_project_docs\"\n  ) {\n    return null;\n  }\n\n  const parsed = readMcpJsonText(part.output);\n\n  if (!isProjectDocsSearchPayload(parsed)) {\n    return null;\n  }\n\n  return {\n    kind: \"search\",\n    matches: parsed.matches,\n    query: parsed.query,\n  };\n}\n",
      "type": "registry:component",
      "target": "@components/ultra-chatbot-agent/ultra-chatbot-agent-message-parts.ts"
    },
    {
      "path": "registry/ultra-chatbot-agent/components/ultra-chatbot-agent/ultra-chatbot-agent-message-reasoning.tsx",
      "content": "\"use client\";\n\n/* eslint-disable react-hooks/set-state-in-effect */\n\nimport {\n  Reasoning,\n  ReasoningContent,\n  ReasoningTrigger,\n} from \"@/components/ai-elements/reasoning\";\nimport { useEffect, useState } from \"react\";\n\nexport function UltraChatbotAgentMessageReasoning({\n  isLoading,\n  reasoning,\n}: {\n  isLoading: boolean;\n  reasoning: string;\n}) {\n  const [hasBeenStreaming, setHasBeenStreaming] = useState(isLoading);\n\n  useEffect(() => {\n    if (isLoading) {\n      setHasBeenStreaming(true);\n    }\n  }, [isLoading]);\n\n  return (\n    <Reasoning\n      className=\"w-full\"\n      defaultOpen={hasBeenStreaming}\n      isStreaming={isLoading}\n    >\n      <ReasoningTrigger />\n      <ReasoningContent>{reasoning}</ReasoningContent>\n    </Reasoning>\n  );\n}\n",
      "type": "registry:component",
      "target": "@components/ultra-chatbot-agent/ultra-chatbot-agent-message-reasoning.tsx"
    },
    {
      "path": "registry/ultra-chatbot-agent/components/ultra-chatbot-agent/ultra-chatbot-agent-message-rendering-model.ts",
      "content": "import type { UIMessage } from \"ai\";\n\nimport {\n  getUltraChatbotAgentFileParts,\n  getUltraChatbotAgentReasoningText,\n  getUltraChatbotAgentSourceParts,\n  getUltraChatbotAgentTextContent,\n  getUltraChatbotAgentToolParts,\n} from \"./ultra-chatbot-agent-message-parts\";\n\nexport type UltraChatbotAgentMessageBodyKind =\n  | \"files\"\n  | \"source-only\"\n  | \"text\"\n  | \"thinking\"\n  | \"tool-only\"\n  | \"waiting\";\n\nexport interface UltraChatbotAgentPendingVote {\n  messageId: string;\n  target: \"down\" | \"up\";\n}\n\nexport interface UltraChatbotAgentMessageRenderPlan {\n  bodyKind: UltraChatbotAgentMessageBodyKind;\n  currentVote: boolean | undefined;\n  files: ReturnType<typeof getUltraChatbotAgentFileParts>;\n  isEditing: boolean;\n  isHelpfulPending: boolean;\n  isNeedsWorkPending: boolean;\n  isReasoningStreaming: boolean;\n  isVotePending: boolean;\n  reasoningText: string;\n  showEditButton: boolean;\n  showFeedbackButtons: boolean;\n  sources: ReturnType<typeof getUltraChatbotAgentSourceParts>;\n  text: string;\n  toolParts: ReturnType<typeof getUltraChatbotAgentToolParts>;\n}\n\nexport function buildUltraChatbotAgentMessageRenderPlan(input: {\n  currentVote: boolean | undefined;\n  editingMessageId: string | null;\n  isBusy: boolean;\n  isLastMessage: boolean;\n  message: UIMessage;\n  pendingVote: UltraChatbotAgentPendingVote | null;\n  showThinking: boolean;\n  status: string;\n}): UltraChatbotAgentMessageRenderPlan {\n  const {\n    currentVote,\n    editingMessageId,\n    isBusy,\n    isLastMessage,\n    message,\n    pendingVote,\n    showThinking,\n    status,\n  } = input;\n  const text = getUltraChatbotAgentTextContent(message);\n  const files = getUltraChatbotAgentFileParts(message);\n  const sources =\n    message.role === \"assistant\"\n      ? getUltraChatbotAgentSourceParts(message)\n      : [];\n  const toolParts = getUltraChatbotAgentToolParts(message);\n  const reasoningText =\n    message.role === \"assistant\"\n      ? getUltraChatbotAgentReasoningText(message)\n      : \"\";\n  const lastPart = message.parts.at(-1);\n  const isReasoningStreaming =\n    message.role === \"assistant\" &&\n    isLastMessage &&\n    status === \"streaming\" &&\n    lastPart?.type === \"reasoning\";\n  const isVotePending = pendingVote?.messageId === message.id;\n  const hasFeedbackTarget =\n    text.trim().length > 0 || files.length > 0 || sources.length > 0;\n\n  return {\n    bodyKind: getUltraChatbotAgentMessageBodyKind({\n      filesLength: files.length,\n      isLastMessage,\n      message,\n      showThinking,\n      sourcesLength: sources.length,\n      text,\n      toolPartsLength: toolParts.length,\n    }),\n    currentVote,\n    files,\n    isEditing: editingMessageId === message.id,\n    isHelpfulPending: Boolean(isVotePending && pendingVote?.target === \"up\"),\n    isNeedsWorkPending: Boolean(\n      isVotePending && pendingVote?.target === \"down\"\n    ),\n    isReasoningStreaming,\n    isVotePending: Boolean(isVotePending),\n    reasoningText,\n    showEditButton:\n      message.role === \"user\" && editingMessageId !== message.id && !isBusy,\n    showFeedbackButtons:\n      message.role === \"assistant\" &&\n      !(isLastMessage && status === \"streaming\") &&\n      !(isLastMessage && status === \"submitted\") &&\n      hasFeedbackTarget,\n    sources,\n    text,\n    toolParts,\n  };\n}\n\nfunction getUltraChatbotAgentMessageBodyKind(input: {\n  filesLength: number;\n  isLastMessage: boolean;\n  message: UIMessage;\n  showThinking: boolean;\n  sourcesLength: number;\n  text: string;\n  toolPartsLength: number;\n}): UltraChatbotAgentMessageBodyKind {\n  const {\n    filesLength,\n    isLastMessage,\n    message,\n    showThinking,\n    sourcesLength,\n    text,\n    toolPartsLength,\n  } = input;\n\n  if (text) {\n    return \"text\";\n  }\n\n  if (filesLength > 0) {\n    return \"files\";\n  }\n\n  if (\n    message.role === \"assistant\" &&\n    isLastMessage &&\n    toolPartsLength === 0 &&\n    showThinking\n  ) {\n    return \"thinking\";\n  }\n\n  if (toolPartsLength > 0) {\n    return \"tool-only\";\n  }\n\n  if (sourcesLength > 0) {\n    return \"source-only\";\n  }\n\n  return \"waiting\";\n}\n",
      "type": "registry:component",
      "target": "@components/ultra-chatbot-agent/ultra-chatbot-agent-message-rendering-model.ts"
    },
    {
      "path": "registry/ultra-chatbot-agent/components/ultra-chatbot-agent/ultra-chatbot-agent-message-response.tsx",
      "content": "\"use client\";\n\nimport {\n  MessageResponse,\n  type MessageResponseProps,\n} from \"@/components/ai-elements/message\";\nimport { cn } from \"@/lib/utils\";\n\nexport function UltraChatbotAgentMessageResponse({\n  className,\n  ...props\n}: MessageResponseProps) {\n  return (\n    <div\n      className={cn(\n        \"max-w-full overflow-x-auto\",\n        \"[&_pre]:max-w-full [&_pre]:overflow-x-auto\",\n        \"[&_table]:min-w-max [&_table]:max-w-none\",\n        className\n      )}\n    >\n      <MessageResponse {...props} />\n    </div>\n  );\n}\n",
      "type": "registry:component",
      "target": "@components/ultra-chatbot-agent/ultra-chatbot-agent-message-response.tsx"
    },
    {
      "path": "registry/ultra-chatbot-agent/components/ultra-chatbot-agent/ultra-chatbot-agent-messages.tsx",
      "content": "\"use client\";\n\nimport { ThumbsDownIcon, ThumbsUpIcon } from \"@phosphor-icons/react\";\nimport { Attachments } from \"@/components/ai-elements/attachments\";\nimport {\n  Message,\n  MessageContent,\n} from \"@/components/ai-elements/message\";\nimport { Shimmer } from \"@/components/ai-elements/shimmer\";\nimport {\n  Tool,\n  ToolContent,\n  ToolHeader,\n  ToolInput,\n  ToolOutput,\n} from \"@/components/ai-elements/tool\";\nimport { Button } from \"@/components/ui/button\";\nimport { Spinner } from \"@/components/ui/spinner\";\nimport { Textarea } from \"@/components/ui/textarea\";\nimport { cn } from \"@/lib/utils\";\nimport type { UIMessage } from \"ai\";\nimport type { ReactNode } from \"react\";\n\nimport { UltraChatbotAgentDocumentPreview } from \"./ultra-chatbot-agent-document-preview\";\nimport { UltraChatbotAgentKnowledgeBaseResultCard } from \"./ultra-chatbot-agent-knowledge-base-result\";\nimport {\n  getUltraChatbotAgentProjectDocsMcpResult,\n  isUltraChatbotAgentDocumentResult,\n  isUltraChatbotAgentKnowledgeBaseResult,\n  isUltraChatbotAgentResearchReportResult,\n  type UltraChatbotAgentToolPart,\n} from \"./ultra-chatbot-agent-message-parts\";\nimport { UltraChatbotAgentMessageReasoning } from \"./ultra-chatbot-agent-message-reasoning\";\nimport {\n  buildUltraChatbotAgentMessageRenderPlan,\n  type UltraChatbotAgentMessageRenderPlan,\n  type UltraChatbotAgentPendingVote,\n} from \"./ultra-chatbot-agent-message-rendering-model\";\nimport { UltraChatbotAgentMessageResponse } from \"./ultra-chatbot-agent-message-response\";\nimport { UltraChatbotAgentPreviewAttachment } from \"./ultra-chatbot-agent-preview-attachment\";\nimport { UltraChatbotAgentProjectDocsResultCard } from \"./ultra-chatbot-agent-project-docs-result\";\nimport { UltraChatbotAgentResearchReport } from \"./ultra-chatbot-agent-research-report\";\nimport { UltraChatbotAgentSandboxConfirmation } from \"./ultra-chatbot-agent-sandbox-confirmation\";\nimport { UltraChatbotAgentSources } from \"./ultra-chatbot-agent-sources\";\nimport { UltraChatbotAgentWeather } from \"./ultra-chatbot-agent-weather\";\n\ninterface UltraChatbotAgentMessagesProps {\n  chatId: string;\n  editingMessageId: string | null;\n  editingRetainedFileUrls: string[];\n  editingText: string;\n  isBusy: boolean;\n  isSandboxUpdating: boolean;\n  messages: UIMessage[];\n  onArtifactOpen: (documentId: string) => void;\n  onCancelEdit: () => void;\n  onEditTextChange: (value: string) => void;\n  onRemoveEditingFile: (url: string) => void;\n  onSandboxApprovalResponse: (input: {\n    approvalId: string;\n    approved: boolean;\n    reason: string;\n  }) => void | Promise<void>;\n  onSaveEdit: (message: UIMessage) => void | Promise<void>;\n  onStartEdit: (input: {\n    retainedFileUrls: string[];\n    text: string;\n    messageId: string;\n  }) => void;\n  onVote: (messageId: string, type: \"down\" | \"up\") => void | Promise<void>;\n  pendingVote: UltraChatbotAgentPendingVote | null;\n  showResumeThinking: boolean;\n  showThinking: boolean;\n  status: string;\n  votesByMessageId: Record<string, boolean>;\n}\n\nexport function UltraChatbotAgentMessages({\n  chatId,\n  editingMessageId,\n  editingRetainedFileUrls,\n  editingText,\n  isBusy,\n  isSandboxUpdating,\n  messages,\n  onArtifactOpen,\n  onCancelEdit,\n  onEditTextChange,\n  onRemoveEditingFile,\n  onSandboxApprovalResponse,\n  onSaveEdit,\n  onStartEdit,\n  onVote,\n  pendingVote,\n  showResumeThinking,\n  showThinking,\n  status,\n  votesByMessageId,\n}: UltraChatbotAgentMessagesProps) {\n  const lastMessageId = messages.at(-1)?.id;\n\n  return (\n    <>\n      {messages.map((message) => {\n        const plan = buildUltraChatbotAgentMessageRenderPlan({\n          currentVote: votesByMessageId[message.id],\n          editingMessageId,\n          isBusy,\n          isLastMessage: lastMessageId === message.id,\n          message,\n          pendingVote,\n          showThinking,\n          status,\n        });\n\n        return (\n          <UltraChatbotAgentMessage\n            chatId={chatId}\n            editingRetainedFileUrls={editingRetainedFileUrls}\n            editingText={editingText}\n            isBusy={isBusy}\n            isSandboxUpdating={isSandboxUpdating}\n            key={message.id}\n            message={message}\n            onArtifactOpen={onArtifactOpen}\n            onCancelEdit={onCancelEdit}\n            onEditTextChange={onEditTextChange}\n            onRemoveEditingFile={onRemoveEditingFile}\n            onSandboxApprovalResponse={onSandboxApprovalResponse}\n            onSaveEdit={onSaveEdit}\n            onStartEdit={onStartEdit}\n            onVote={onVote}\n            plan={plan}\n          />\n        );\n      })}\n      {showResumeThinking ? (\n        <Message from=\"assistant\">\n          <MessageContent className=\"w-full min-w-0 max-w-3xl\">\n            <Shimmer className=\"text-sm\">Thinking...</Shimmer>\n          </MessageContent>\n        </Message>\n      ) : null}\n    </>\n  );\n}\n\nfunction UltraChatbotAgentMessage({\n  chatId,\n  editingRetainedFileUrls,\n  editingText,\n  isBusy,\n  isSandboxUpdating,\n  message,\n  onArtifactOpen,\n  onCancelEdit,\n  onEditTextChange,\n  onRemoveEditingFile,\n  onSandboxApprovalResponse,\n  onSaveEdit,\n  onStartEdit,\n  onVote,\n  plan,\n}: {\n  chatId: string;\n  editingRetainedFileUrls: string[];\n  editingText: string;\n  isBusy: boolean;\n  isSandboxUpdating: boolean;\n  message: UIMessage;\n  onArtifactOpen: (documentId: string) => void;\n  onCancelEdit: () => void;\n  onEditTextChange: (value: string) => void;\n  onRemoveEditingFile: (url: string) => void;\n  onSandboxApprovalResponse: UltraChatbotAgentMessagesProps[\"onSandboxApprovalResponse\"];\n  onSaveEdit: (message: UIMessage) => void | Promise<void>;\n  onStartEdit: UltraChatbotAgentMessagesProps[\"onStartEdit\"];\n  onVote: (messageId: string, type: \"down\" | \"up\") => void | Promise<void>;\n  plan: UltraChatbotAgentMessageRenderPlan;\n}) {\n  return (\n    <Message from={message.role}>\n      <MessageContent\n        className={cn(\n          message.role === \"assistant\"\n            ? \"w-full min-w-0 max-w-[min(100%,48rem)]\"\n            : \"min-w-0 max-w-[min(100%,42rem)]\"\n        )}\n      >\n        {plan.toolParts.map((part) => (\n          <UltraChatbotAgentToolPartView\n            chatId={chatId}\n            isSandboxUpdating={isSandboxUpdating}\n            key={part.toolCallId}\n            onArtifactOpen={onArtifactOpen}\n            onSandboxApprovalResponse={onSandboxApprovalResponse}\n            part={part}\n          />\n        ))}\n        {plan.reasoningText ? (\n          <UltraChatbotAgentMessageReasoning\n            isLoading={plan.isReasoningStreaming}\n            reasoning={plan.reasoningText}\n          />\n        ) : null}\n        <UltraChatbotAgentSources sources={plan.sources} />\n        {plan.isEditing ? (\n          <UltraChatbotAgentMessageEditForm\n            editingRetainedFileUrls={editingRetainedFileUrls}\n            editingText={editingText}\n            files={plan.files}\n            isBusy={isBusy}\n            messageId={message.id}\n            onCancelEdit={onCancelEdit}\n            onEditTextChange={onEditTextChange}\n            onRemoveEditingFile={onRemoveEditingFile}\n            onSaveEdit={() => onSaveEdit(message)}\n          />\n        ) : (\n          <UltraChatbotAgentMessageBody messageId={message.id} plan={plan} />\n        )}\n        {plan.showFeedbackButtons ? (\n          <UltraChatbotAgentMessageFeedback\n            onVote={(type) => onVote(message.id, type)}\n            plan={plan}\n          />\n        ) : null}\n      </MessageContent>\n      {plan.showEditButton ? (\n        <div className=\"mt-3 flex w-full justify-end\">\n          <Button\n            disabled={isBusy}\n            onClick={() =>\n              onStartEdit({\n                messageId: message.id,\n                retainedFileUrls: plan.files.map((part) => part.url),\n                text: plan.text,\n              })\n            }\n            size=\"sm\"\n            type=\"button\"\n            variant=\"outline\"\n          >\n            Edit\n          </Button>\n        </div>\n      ) : null}\n    </Message>\n  );\n}\n\nfunction UltraChatbotAgentMessageBody({\n  messageId,\n  plan,\n}: {\n  messageId: string;\n  plan: UltraChatbotAgentMessageRenderPlan;\n}) {\n  if (plan.bodyKind === \"text\") {\n    return (\n      <div className=\"space-y-4\">\n        {plan.files.length > 0 ? (\n          <UltraChatbotAgentMessageAttachments\n            files={plan.files}\n            messageId={messageId}\n          />\n        ) : null}\n        <UltraChatbotAgentMessageResponse>\n          {plan.text}\n        </UltraChatbotAgentMessageResponse>\n      </div>\n    );\n  }\n\n  if (plan.bodyKind === \"files\") {\n    return (\n      <UltraChatbotAgentMessageAttachments\n        files={plan.files}\n        messageId={messageId}\n      />\n    );\n  }\n\n  if (plan.bodyKind === \"thinking\") {\n    return <Shimmer className=\"text-sm\">Thinking...</Shimmer>;\n  }\n\n  if (plan.bodyKind === \"tool-only\" || plan.bodyKind === \"source-only\") {\n    return null;\n  }\n\n  return (\n    <p className=\"text-muted-foreground text-sm\">Waiting for visible output.</p>\n  );\n}\n\nfunction UltraChatbotAgentMessageEditForm({\n  editingRetainedFileUrls,\n  editingText,\n  files,\n  isBusy,\n  messageId,\n  onCancelEdit,\n  onEditTextChange,\n  onRemoveEditingFile,\n  onSaveEdit,\n}: {\n  editingRetainedFileUrls: string[];\n  editingText: string;\n  files: UltraChatbotAgentMessageRenderPlan[\"files\"];\n  isBusy: boolean;\n  messageId: string;\n  onCancelEdit: () => void;\n  onEditTextChange: (value: string) => void;\n  onRemoveEditingFile: (url: string) => void;\n  onSaveEdit: () => void | Promise<void>;\n}) {\n  return (\n    <div className=\"space-y-3\">\n      {files.length > 0 ? (\n        <UltraChatbotAgentMessageAttachments\n          files={files.filter((part) =>\n            editingRetainedFileUrls.includes(part.url)\n          )}\n          messageId={messageId}\n          onRemove={onRemoveEditingFile}\n        />\n      ) : null}\n      <Textarea\n        onChange={(event) => onEditTextChange(event.target.value)}\n        value={editingText}\n      />\n      <div className=\"flex items-center justify-end gap-2\">\n        <Button\n          disabled={isBusy}\n          onClick={onSaveEdit}\n          size=\"sm\"\n          type=\"button\"\n          variant=\"outline\"\n        >\n          Save and replay\n        </Button>\n        <Button onClick={onCancelEdit} size=\"sm\" type=\"button\" variant=\"ghost\">\n          Cancel\n        </Button>\n      </div>\n    </div>\n  );\n}\n\nfunction UltraChatbotAgentMessageAttachments({\n  files,\n  messageId,\n  onRemove,\n}: {\n  files: UltraChatbotAgentMessageRenderPlan[\"files\"];\n  messageId: string;\n  onRemove?: (url: string) => void;\n}) {\n  return (\n    <Attachments variant=\"list\">\n      {files.map((part) => {\n        const attachmentId = `${messageId}-${part.filename ?? \"attachment\"}-${part.url}`;\n\n        return (\n          <UltraChatbotAgentPreviewAttachment\n            attachment={{\n              ...part,\n              id: attachmentId,\n            }}\n            key={attachmentId}\n            onRemove={onRemove ? () => onRemove(part.url) : undefined}\n          />\n        );\n      })}\n    </Attachments>\n  );\n}\n\nfunction UltraChatbotAgentMessageFeedback({\n  onVote,\n  plan,\n}: {\n  onVote: (type: \"down\" | \"up\") => void | Promise<void>;\n  plan: UltraChatbotAgentMessageRenderPlan;\n}) {\n  return (\n    <div className=\"mt-3 flex items-center gap-2\">\n      <Button\n        aria-label=\"Helpful\"\n        disabled={plan.isVotePending}\n        onClick={() => onVote(\"up\")}\n        size=\"sm\"\n        title=\"Helpful\"\n        type=\"button\"\n        variant={plan.currentVote === true ? \"secondary\" : \"outline\"}\n      >\n        {getHelpfulButtonIcon(plan)}\n        {plan.currentVote === true && !plan.isHelpfulPending ? (\n          <span>Helpful</span>\n        ) : null}\n      </Button>\n      <Button\n        aria-label=\"Needs work\"\n        disabled={plan.isVotePending}\n        onClick={() => onVote(\"down\")}\n        size=\"sm\"\n        title=\"Needs work\"\n        type=\"button\"\n        variant={plan.currentVote === false ? \"secondary\" : \"outline\"}\n      >\n        {getNeedsWorkButtonIcon(plan)}\n        {plan.currentVote === false && !plan.isNeedsWorkPending ? (\n          <span>Needs work</span>\n        ) : null}\n      </Button>\n    </div>\n  );\n}\n\nfunction getHelpfulButtonIcon(\n  plan: UltraChatbotAgentMessageRenderPlan\n): ReactNode {\n  if (plan.isHelpfulPending) {\n    return <Spinner className=\"size-3.5\" />;\n  }\n\n  return plan.currentVote === true ? (\n    <ThumbsUpIcon className=\"size-3.5 text-emerald-500\" />\n  ) : (\n    <ThumbsUpIcon className=\"size-3.5\" />\n  );\n}\n\nfunction getNeedsWorkButtonIcon(\n  plan: UltraChatbotAgentMessageRenderPlan\n): ReactNode {\n  if (plan.isNeedsWorkPending) {\n    return <Spinner className=\"size-3.5\" />;\n  }\n\n  return plan.currentVote === false ? (\n    <ThumbsDownIcon className=\"size-3.5 text-rose-500\" />\n  ) : (\n    <ThumbsDownIcon className=\"size-3.5\" />\n  );\n}\n\nfunction UltraChatbotAgentToolPartView({\n  chatId,\n  isSandboxUpdating,\n  onArtifactOpen,\n  onSandboxApprovalResponse,\n  part,\n}: {\n  chatId: string;\n  isSandboxUpdating: boolean;\n  onArtifactOpen: (documentId: string) => void;\n  onSandboxApprovalResponse: UltraChatbotAgentMessagesProps[\"onSandboxApprovalResponse\"];\n  part: UltraChatbotAgentToolPart;\n}) {\n  const documentResult =\n    part.state === \"output-available\" &&\n    isUltraChatbotAgentDocumentResult(part.output)\n      ? part.output\n      : null;\n  const weatherResult =\n    part.state === \"output-available\" &&\n    isUltraChatbotAgentWeatherResult(part.output)\n      ? part.output\n      : null;\n  const isDocumentTool = isUltraChatbotAgentDocumentTool(part);\n  const researchReportResult =\n    part.state === \"output-available\" &&\n    part.type === \"tool-createResearchReport\" &&\n    isUltraChatbotAgentResearchReportResult(part.output)\n      ? part.output\n      : null;\n  const knowledgeBaseResult =\n    part.state === \"output-available\" &&\n    part.type === \"tool-searchKnowledgeBase\" &&\n    isUltraChatbotAgentKnowledgeBaseResult(part.output)\n      ? part.output\n      : null;\n  const projectDocsResult = getUltraChatbotAgentProjectDocsMcpResult(part);\n\n  if (hasUltraChatbotAgentDocumentToolError(part, isDocumentTool)) {\n    return (\n      <div className=\"w-full rounded-xl border border-destructive/30 px-4 py-3 text-destructive text-sm\">\n        {String(part.output.error)}\n      </div>\n    );\n  }\n\n  if (documentResult && isDocumentTool) {\n    return (\n      <UltraChatbotAgentDocumentPreview\n        chatId={chatId}\n        onOpen={onArtifactOpen}\n        result={documentResult}\n      />\n    );\n  }\n\n  if (researchReportResult) {\n    return <UltraChatbotAgentResearchReport report={researchReportResult} />;\n  }\n\n  if (knowledgeBaseResult) {\n    return (\n      <UltraChatbotAgentKnowledgeBaseResultCard result={knowledgeBaseResult} />\n    );\n  }\n\n  if (projectDocsResult) {\n    return (\n      <UltraChatbotAgentProjectDocsResultCard result={projectDocsResult} />\n    );\n  }\n\n  return (\n    <div className=\"space-y-3\">\n      {part.type === \"tool-enableSandbox\" ? (\n        <UltraChatbotAgentSandboxConfirmation\n          isPending={isSandboxUpdating}\n          onApprovalResponse={onSandboxApprovalResponse}\n          part={part}\n        />\n      ) : null}\n      <Tool defaultOpen={false}>\n        {part.type === \"dynamic-tool\" ? (\n          <ToolHeader\n            state={part.state}\n            toolName={part.toolName}\n            type={part.type}\n          />\n        ) : (\n          <ToolHeader state={part.state} type={part.type} />\n        )}\n        <ToolContent>\n          {part.input ? <ToolInput input={part.input} /> : null}\n          {weatherResult ? null : (\n            <ToolOutput errorText={part.errorText} output={part.output} />\n          )}\n          {weatherResult ? (\n            <UltraChatbotAgentWeather weather={weatherResult} />\n          ) : null}\n        </ToolContent>\n      </Tool>\n    </div>\n  );\n}\n\nfunction isUltraChatbotAgentDocumentTool(part: UltraChatbotAgentToolPart) {\n  return (\n    part.type === \"tool-createDocument\" ||\n    part.type === \"tool-editDocument\" ||\n    part.type === \"tool-updateDocument\"\n  );\n}\n\nfunction hasUltraChatbotAgentDocumentToolError(\n  part: UltraChatbotAgentToolPart,\n  isDocumentTool: boolean\n): part is UltraChatbotAgentToolPart & { output: { error: unknown } } {\n  return (\n    isDocumentTool &&\n    part.state === \"output-available\" &&\n    part.output !== null &&\n    part.output !== undefined &&\n    typeof part.output === \"object\" &&\n    \"error\" in part.output\n  );\n}\n\nfunction isUltraChatbotAgentWeatherResult(\n  output: UltraChatbotAgentToolPart[\"output\"]\n): output is Parameters<typeof UltraChatbotAgentWeather>[0][\"weather\"] {\n  if (!output || typeof output !== \"object\") {\n    return false;\n  }\n\n  return (\n    \"current\" in output &&\n    typeof output.current === \"object\" &&\n    output.current !== null &&\n    \"daily\" in output &&\n    typeof output.daily === \"object\" &&\n    output.daily !== null &&\n    \"current_units\" in output &&\n    typeof output.current_units === \"object\" &&\n    output.current_units !== null\n  );\n}\n",
      "type": "registry:component",
      "target": "@components/ultra-chatbot-agent/ultra-chatbot-agent-messages.tsx"
    },
    {
      "path": "registry/ultra-chatbot-agent/components/ultra-chatbot-agent/ultra-chatbot-agent-multimodal-input.tsx",
      "content": "\"use client\";\n\nimport { PaperclipIcon } from \"@phosphor-icons/react\";\nimport { Attachments } from \"@/components/ai-elements/attachments\";\nimport {\n  PromptInput,\n  PromptInputBody,\n  PromptInputFooter,\n  PromptInputSubmit,\n  PromptInputTextarea,\n} from \"@/components/ai-elements/prompt-input\";\nimport { Button } from \"@/components/ui/button\";\nimport {\n  Tooltip,\n  TooltipContent,\n  TooltipTrigger,\n} from \"@/components/ui/tooltip\";\nimport type { ChatStatus, UIMessage } from \"ai\";\nimport type { ClipboardEvent, ReactNode } from \"react\";\nimport { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\n\nimport { ultraChatbotAgentAcceptedUploadMediaTypes } from \"@/lib/ultra-chatbot-agent/attachment-config\";\nimport {\n  UltraChatbotAgentPreviewAttachment,\n  type UltraChatbotAgentPreviewAttachmentData,\n} from \"./ultra-chatbot-agent-preview-attachment\";\n\ninterface PendingUltraChatbotAgentAttachment {\n  file: File;\n  id: string;\n  previewUrl: string;\n}\n\nfunction buildPendingAttachment(\n  file: File\n): PendingUltraChatbotAgentAttachment {\n  return {\n    file,\n    id: `${file.name}-${file.size}-${file.lastModified}`,\n    previewUrl: URL.createObjectURL(file),\n  };\n}\n\nfunction revokePendingAttachment(\n  attachment: PendingUltraChatbotAgentAttachment\n) {\n  URL.revokeObjectURL(attachment.previewUrl);\n}\n\nfunction toPreviewData(\n  attachment: PendingUltraChatbotAgentAttachment\n): UltraChatbotAgentPreviewAttachmentData {\n  return {\n    filename: attachment.file.name,\n    id: attachment.id,\n    mediaType: attachment.file.type || \"application/octet-stream\",\n    type: \"file\",\n    url: attachment.previewUrl,\n  };\n}\n\nasync function uploadUltraChatbotAgentAttachment(file: File, chatId: string) {\n  const formData = new FormData();\n  formData.append(\"chatId\", chatId);\n  formData.append(\"file\", file);\n\n  const response = await fetch(\"/api/demos/ultra-chatbot-agent/files/upload\", {\n    body: formData,\n    credentials: \"include\",\n    method: \"POST\",\n  });\n\n  if (!response.ok) {\n    const payload = (await response.json().catch(() => null)) as {\n      error?: string;\n    } | null;\n\n    throw new Error(payload?.error || \"Failed to upload the attachment.\");\n  }\n\n  const payload = (await response.json()) as {\n    contentType: string | null;\n    url: string;\n  };\n\n  return {\n    filename: file.name,\n    mediaType: payload.contentType || file.type || \"application/octet-stream\",\n    type: \"file\" as const,\n    url: payload.url,\n  };\n}\n\nexport interface UltraChatbotAgentMultimodalInputProps {\n  chatId: string;\n  disabled: boolean;\n  footerBelow?: ReactNode;\n  footerLeading: ReactNode;\n  onComposerErrorChange: (message: string | null) => void;\n  onSend: (parts: UIMessage[\"parts\"]) => Promise<void>;\n  onStop: () => void;\n  placeholder: string;\n  status: ChatStatus;\n}\n\nexport function UltraChatbotAgentMultimodalInput({\n  chatId,\n  disabled,\n  footerBelow,\n  footerLeading,\n  onComposerErrorChange,\n  onSend,\n  onStop,\n  placeholder,\n  status,\n}: UltraChatbotAgentMultimodalInputProps) {\n  const [pendingAttachments, setPendingAttachments] = useState<\n    PendingUltraChatbotAgentAttachment[]\n  >([]);\n  const [uploadingAttachmentIds, setUploadingAttachmentIds] = useState<\n    string[]\n  >([]);\n  const fileInputRef = useRef<HTMLInputElement | null>(null);\n  const pendingAttachmentsRef = useRef<PendingUltraChatbotAgentAttachment[]>(\n    []\n  );\n  const isUploading = uploadingAttachmentIds.length > 0;\n\n  useEffect(() => {\n    pendingAttachmentsRef.current = pendingAttachments;\n  }, [pendingAttachments]);\n\n  useEffect(\n    () => () => {\n      for (const attachment of pendingAttachmentsRef.current) {\n        revokePendingAttachment(attachment);\n      }\n    },\n    []\n  );\n\n  const clearFileInput = useCallback(() => {\n    if (fileInputRef.current) {\n      fileInputRef.current.value = \"\";\n    }\n  }, []);\n\n  const appendFiles = useCallback(\n    (files: File[]) => {\n      if (files.length === 0) {\n        return;\n      }\n\n      onComposerErrorChange(null);\n      const nextAttachments = files.map(buildPendingAttachment);\n\n      setPendingAttachments((current) => {\n        const replacedIds = new Set(\n          nextAttachments.map((attachment) => attachment.id)\n        );\n\n        for (const attachment of current) {\n          if (replacedIds.has(attachment.id)) {\n            revokePendingAttachment(attachment);\n          }\n        }\n\n        const preserved = current.filter(\n          (attachment) => !replacedIds.has(attachment.id)\n        );\n\n        return [...preserved, ...nextAttachments];\n      });\n      clearFileInput();\n    },\n    [clearFileInput, onComposerErrorChange]\n  );\n\n  const removePendingAttachment = useCallback(\n    (attachmentId: string) => {\n      setPendingAttachments((current) => {\n        const attachment = current.find((item) => item.id === attachmentId);\n\n        if (attachment) {\n          revokePendingAttachment(attachment);\n        }\n\n        return current.filter((item) => item.id !== attachmentId);\n      });\n      clearFileInput();\n    },\n    [clearFileInput]\n  );\n\n  const previewAttachments = useMemo(\n    () => pendingAttachments.map((attachment) => toPreviewData(attachment)),\n    [pendingAttachments]\n  );\n\n  const handlePaste = useCallback(\n    (event: ClipboardEvent<HTMLTextAreaElement>) => {\n      const items = event.clipboardData?.items;\n\n      if (!items) {\n        return;\n      }\n\n      const imageFiles = Array.from(items)\n        .filter(\n          (item) => item.kind === \"file\" && item.type.startsWith(\"image/\")\n        )\n        .map((item) => item.getAsFile())\n        .filter((file): file is File => file !== null);\n\n      if (imageFiles.length === 0) {\n        return;\n      }\n\n      event.preventDefault();\n      appendFiles(imageFiles);\n    },\n    [appendFiles]\n  );\n\n  const handleSend = useCallback(\n    async (text: string) => {\n      const trimmedText = text.trim();\n\n      if (!trimmedText && pendingAttachments.length === 0) {\n        return;\n      }\n\n      onComposerErrorChange(null);\n      setUploadingAttachmentIds(\n        pendingAttachments.map((attachment) => attachment.id)\n      );\n\n      try {\n        const uploadedFileParts = await Promise.all(\n          pendingAttachments.map((attachment) =>\n            uploadUltraChatbotAgentAttachment(attachment.file, chatId)\n          )\n        );\n        const parts = [\n          ...uploadedFileParts,\n          ...(trimmedText.length > 0\n            ? [{ text: trimmedText, type: \"text\" as const }]\n            : []),\n        ];\n\n        await onSend(parts);\n        setPendingAttachments((current) => {\n          for (const attachment of current) {\n            revokePendingAttachment(attachment);\n          }\n\n          return [];\n        });\n        clearFileInput();\n      } catch (error) {\n        onComposerErrorChange(\n          error instanceof Error\n            ? error.message\n            : \"Failed to upload the attachment.\"\n        );\n        throw error;\n      } finally {\n        setUploadingAttachmentIds([]);\n      }\n    },\n    [chatId, clearFileInput, onComposerErrorChange, onSend, pendingAttachments]\n  );\n\n  return (\n    <div className=\"space-y-3\">\n      <input\n        accept={ultraChatbotAgentAcceptedUploadMediaTypes.join(\",\")}\n        className=\"sr-only\"\n        multiple\n        onChange={(event) => appendFiles(Array.from(event.target.files ?? []))}\n        ref={fileInputRef}\n        type=\"file\"\n      />\n      <PromptInput onSubmit={({ text }) => handleSend(text)}>\n        <PromptInputBody>\n          {previewAttachments.length > 0 ? (\n            <Attachments className=\"mb-3\" variant=\"list\">\n              {previewAttachments.map((attachment) => (\n                <UltraChatbotAgentPreviewAttachment\n                  attachment={attachment}\n                  isUploading={uploadingAttachmentIds.includes(attachment.id)}\n                  key={attachment.id}\n                  onRemove={() => removePendingAttachment(attachment.id)}\n                />\n              ))}\n            </Attachments>\n          ) : null}\n          <PromptInputTextarea\n            disabled={disabled || isUploading}\n            onPaste={handlePaste}\n            placeholder={placeholder}\n          />\n        </PromptInputBody>\n        <PromptInputFooter className=\"flex items-center justify-between gap-3 border-foreground/10 border-t px-3 py-3\">\n          <div className=\"flex min-w-0 flex-wrap items-center gap-2\">\n            {footerLeading}\n          </div>\n          <div className=\"flex items-center gap-2\">\n            <Tooltip>\n              <TooltipTrigger asChild>\n                <Button\n                  disabled={disabled || isUploading}\n                  onClick={() => fileInputRef.current?.click()}\n                  size=\"icon\"\n                  type=\"button\"\n                  variant=\"outline\"\n                >\n                  <PaperclipIcon className=\"size-4\" />\n                </Button>\n              </TooltipTrigger>\n              <TooltipContent>Add image or PDF</TooltipContent>\n            </Tooltip>\n            <PromptInputSubmit\n              disabled={disabled || isUploading}\n              onStop={onStop}\n              status={isUploading ? \"submitted\" : status}\n            />\n          </div>\n        </PromptInputFooter>\n      </PromptInput>\n      {footerBelow}\n    </div>\n  );\n}\n",
      "type": "registry:component",
      "target": "@components/ultra-chatbot-agent/ultra-chatbot-agent-multimodal-input.tsx"
    },
    {
      "path": "registry/ultra-chatbot-agent/components/ultra-chatbot-agent/ultra-chatbot-agent-preview-attachment.tsx",
      "content": "\"use client\";\n\nimport {\n  Attachment,\n  AttachmentInfo,\n  AttachmentPreview,\n  AttachmentRemove,\n} from \"@/components/ai-elements/attachments\";\nimport { Spinner } from \"@/components/ui/spinner\";\nimport { cn } from \"@/lib/utils\";\nimport type { FileUIPart } from \"ai\";\n\nexport type UltraChatbotAgentPreviewAttachmentData = FileUIPart & {\n  id: string;\n};\n\nexport interface UltraChatbotAgentPreviewAttachmentProps {\n  attachment: UltraChatbotAgentPreviewAttachmentData;\n  isUploading?: boolean;\n  onRemove?: () => void;\n}\n\nexport function UltraChatbotAgentPreviewAttachment({\n  attachment,\n  isUploading = false,\n  onRemove,\n}: UltraChatbotAgentPreviewAttachmentProps) {\n  return (\n    <Attachment\n      className={cn(\n        \"w-full min-w-0 max-w-full overflow-hidden\",\n        isUploading && \"pointer-events-none opacity-80\"\n      )}\n      data={attachment}\n      onRemove={onRemove}\n    >\n      <AttachmentPreview />\n      <AttachmentInfo className=\"w-0 overflow-hidden\" showMediaType />\n      <AttachmentRemove disabled={isUploading} />\n      {isUploading ? (\n        <div className=\"absolute inset-0 flex items-center justify-center rounded-lg bg-background/70 backdrop-blur-sm\">\n          <Spinner className=\"size-4\" />\n        </div>\n      ) : null}\n    </Attachment>\n  );\n}\n",
      "type": "registry:component",
      "target": "@components/ultra-chatbot-agent/ultra-chatbot-agent-preview-attachment.tsx"
    },
    {
      "path": "registry/ultra-chatbot-agent/components/ultra-chatbot-agent/ultra-chatbot-agent-project-docs-result.tsx",
      "content": "\"use client\";\n\nimport { Badge } from \"@/components/ui/badge\";\n\nimport type { UltraChatbotAgentProjectDocsMcpResult } from \"./ultra-chatbot-agent-message-parts\";\n\nexport function UltraChatbotAgentProjectDocsResultCard({\n  result,\n}: {\n  result: UltraChatbotAgentProjectDocsMcpResult;\n}) {\n  return (\n    <article className=\"w-full space-y-4 border border-foreground/15 bg-background px-5 py-5\">\n      <div className=\"space-y-3\">\n        <div className=\"flex flex-wrap items-center gap-2\">\n          <Badge variant=\"outline\">Project Docs MCP</Badge>\n          <Badge variant=\"secondary\">{result.matches.length} matches</Badge>\n        </div>\n        <div>\n          <p className=\"text-muted-foreground text-xs uppercase tracking-[0.24em]\">\n            Docs search\n          </p>\n          <h3 className=\"mt-2 text-balance font-medium text-lg\">\n            {result.query}\n          </h3>\n        </div>\n      </div>\n\n      {result.matches.length > 0 ? (\n        <div className=\"grid gap-3\">\n          {result.matches.slice(0, 5).map((match) => (\n            <div\n              className=\"space-y-2 border border-foreground/10 bg-muted/20 px-3 py-3\"\n              key={`${match.path}:${match.line}`}\n            >\n              <p className=\"break-all font-mono text-muted-foreground text-xs\">\n                {match.path}:{match.line}\n              </p>\n              <p className=\"text-sm leading-relaxed\">{match.text}</p>\n            </div>\n          ))}\n        </div>\n      ) : (\n        <p className=\"text-muted-foreground text-sm\">\n          No project docs matched this query.\n        </p>\n      )}\n    </article>\n  );\n}\n",
      "type": "registry:component",
      "target": "@components/ultra-chatbot-agent/ultra-chatbot-agent-project-docs-result.tsx"
    },
    {
      "path": "registry/ultra-chatbot-agent/components/ultra-chatbot-agent/ultra-chatbot-agent-research-report.tsx",
      "content": "\"use client\";\n\nimport { Badge } from \"@/components/ui/badge\";\n\nimport type { UltraChatbotAgentResearchReportResult } from \"./ultra-chatbot-agent-message-parts\";\n\nfunction ReportSection({ items, title }: { items: string[]; title: string }) {\n  if (items.length === 0) {\n    return null;\n  }\n\n  return (\n    <div className=\"space-y-2\">\n      <p className=\"font-medium text-muted-foreground text-xs uppercase tracking-[0.24em]\">\n        {title}\n      </p>\n      <ul className=\"space-y-2 text-sm\">\n        {items.map((item) => (\n          <li className=\"leading-relaxed\" key={item}>\n            {item}\n          </li>\n        ))}\n      </ul>\n    </div>\n  );\n}\n\nexport function UltraChatbotAgentResearchReport({\n  report,\n}: {\n  report: UltraChatbotAgentResearchReportResult;\n}) {\n  return (\n    <article className=\"w-full space-y-5 border border-foreground/15 bg-background px-5 py-5\">\n      <div className=\"space-y-3\">\n        <div className=\"flex flex-wrap items-center gap-2\">\n          <Badge variant=\"outline\">Research report</Badge>\n          <Badge variant=\"secondary\">{report.sources.length} sources</Badge>\n        </div>\n        <div>\n          <p className=\"text-muted-foreground text-xs uppercase tracking-[0.24em]\">\n            {report.topic}\n          </p>\n          <h3 className=\"mt-2 text-balance font-medium text-xl\">\n            {report.title}\n          </h3>\n        </div>\n        <p className=\"text-muted-foreground text-sm leading-relaxed\">\n          {report.executiveSummary}\n        </p>\n      </div>\n\n      <div className=\"grid gap-5 md:grid-cols-2\">\n        <ReportSection items={report.keyFindings} title=\"Key findings\" />\n        <ReportSection items={report.recommendations} title=\"Recommendations\" />\n      </div>\n\n      <ReportSection items={report.risks} title=\"Risks\" />\n\n      {report.sources.length > 0 ? (\n        <div className=\"space-y-2 border-foreground/10 border-t pt-4\">\n          <p className=\"font-medium text-muted-foreground text-xs uppercase tracking-[0.24em]\">\n            Resources\n          </p>\n          <div className=\"flex flex-wrap gap-2\">\n            {report.sources.map((source) => {\n              const isExternalUrl =\n                source.url.startsWith(\"http://\") ||\n                source.url.startsWith(\"https://\");\n              const className =\n                \"border border-foreground/15 px-2.5 py-1 text-xs transition-colors hover:border-foreground hover:bg-foreground hover:text-background\";\n\n              return isExternalUrl ? (\n                <a\n                  className={className}\n                  href={source.url}\n                  key={`${source.url}-${source.title}`}\n                  rel=\"noreferrer\"\n                  target=\"_blank\"\n                >\n                  {source.title}\n                </a>\n              ) : (\n                <span\n                  className={className}\n                  key={`${source.url}-${source.title}`}\n                >\n                  {source.title}\n                </span>\n              );\n            })}\n          </div>\n        </div>\n      ) : null}\n    </article>\n  );\n}\n",
      "type": "registry:component",
      "target": "@components/ultra-chatbot-agent/ultra-chatbot-agent-research-report.tsx"
    },
    {
      "path": "registry/ultra-chatbot-agent/components/ultra-chatbot-agent/ultra-chatbot-agent-sandbox-approval.ts",
      "content": "export interface UltraChatbotAgentSandboxApprovalResponse {\n  approvalId: string;\n  approved: boolean;\n  reason: string;\n}\n\nexport type UltraChatbotAgentSandboxApprovalResponseHandler = (\n  response: UltraChatbotAgentSandboxApprovalResponse\n) => PromiseLike<void> | void;\n\ninterface ApplyUltraChatbotAgentSandboxApprovalInput\n  extends UltraChatbotAgentSandboxApprovalResponse {\n  addToolApprovalResponse: (response: {\n    approved: boolean;\n    id: string;\n    reason: string;\n  }) => PromiseLike<void> | void;\n  persistSandboxCapability: (\n    sandboxEnabled: boolean\n  ) => PromiseLike<void> | void;\n}\n\nexport async function applyUltraChatbotAgentSandboxApproval({\n  addToolApprovalResponse,\n  approvalId,\n  approved,\n  persistSandboxCapability,\n  reason,\n}: ApplyUltraChatbotAgentSandboxApprovalInput) {\n  await persistSandboxCapability(approved);\n  await addToolApprovalResponse({\n    approved,\n    id: approvalId,\n    reason,\n  });\n}\n",
      "type": "registry:component",
      "target": "@components/ultra-chatbot-agent/ultra-chatbot-agent-sandbox-approval.ts"
    },
    {
      "path": "registry/ultra-chatbot-agent/components/ultra-chatbot-agent/ultra-chatbot-agent-sandbox-confirmation.tsx",
      "content": "\"use client\";\n\nimport { CheckCircleIcon, XCircleIcon } from \"@phosphor-icons/react\";\nimport {\n  Confirmation,\n  ConfirmationAccepted,\n  ConfirmationAction,\n  ConfirmationActions,\n  ConfirmationRejected,\n  ConfirmationRequest,\n  ConfirmationTitle,\n} from \"@/components/ai-elements/confirmation\";\nimport type { ToolUIPart } from \"ai\";\nimport type { UltraChatbotAgentSandboxApprovalResponseHandler } from \"./ultra-chatbot-agent-sandbox-approval\";\n\ninterface SandboxApprovalToolInput {\n  reason?: string;\n  requestedToolFamilies?: string[];\n}\n\nfunction getSandboxApprovalToolInput(\n  part: ToolUIPart\n): SandboxApprovalToolInput {\n  if (\n    typeof part.input !== \"object\" ||\n    part.input === null ||\n    Array.isArray(part.input)\n  ) {\n    return {};\n  }\n\n  return part.input as SandboxApprovalToolInput;\n}\n\nexport interface UltraChatbotAgentSandboxConfirmationProps {\n  isPending?: boolean;\n  onApprovalResponse: UltraChatbotAgentSandboxApprovalResponseHandler;\n  part: ToolUIPart;\n}\n\nexport function UltraChatbotAgentSandboxConfirmation({\n  isPending = false,\n  onApprovalResponse,\n  part,\n}: UltraChatbotAgentSandboxConfirmationProps) {\n  const approval = part.approval;\n\n  if (!approval || part.type !== \"tool-enableSandbox\") {\n    return null;\n  }\n\n  const input = getSandboxApprovalToolInput(part);\n  const requestedToolFamilies = input.requestedToolFamilies ?? [];\n\n  return (\n    <Confirmation\n      approval={approval}\n      className=\"border-amber-500/30 bg-amber-500/5\"\n      state={part.state}\n    >\n      <ConfirmationTitle>Enable sandbox for this chat?</ConfirmationTitle>\n      <ConfirmationRequest>\n        <div className=\"space-y-2 text-sm\">\n          <p>\n            The agent needs sandbox-backed tools before it can continue with\n            this request.\n          </p>\n          {input.reason ? (\n            <p className=\"text-muted-foreground\">{input.reason}</p>\n          ) : null}\n          {requestedToolFamilies.length > 0 ? (\n            <div className=\"flex flex-wrap gap-2 text-xs\">\n              {requestedToolFamilies.map((toolFamily) => (\n                <span\n                  className=\"border border-foreground/10 px-2 py-1 text-muted-foreground\"\n                  key={toolFamily}\n                >\n                  {toolFamily}\n                </span>\n              ))}\n            </div>\n          ) : null}\n        </div>\n      </ConfirmationRequest>\n      <ConfirmationAccepted>\n        <CheckCircleIcon className=\"size-4\" />\n        <span>Sandbox is enabled for this chat. The agent can continue.</span>\n      </ConfirmationAccepted>\n      <ConfirmationRejected>\n        <XCircleIcon className=\"size-4\" />\n        <span>\n          Sandbox stayed disabled. The agent will continue without it.\n        </span>\n      </ConfirmationRejected>\n      <ConfirmationActions>\n        <ConfirmationAction\n          disabled={isPending}\n          onClick={() => {\n            void onApprovalResponse({\n              approvalId: approval.id,\n              approved: false,\n              reason: \"The reviewer kept sandbox disabled for this chat.\",\n            });\n          }}\n          variant=\"outline\"\n        >\n          <XCircleIcon className=\"size-3.5\" />\n          Keep disabled\n        </ConfirmationAction>\n        <ConfirmationAction\n          disabled={isPending}\n          onClick={() => {\n            void onApprovalResponse({\n              approvalId: approval.id,\n              approved: true,\n              reason: \"The reviewer approved sandbox for this chat.\",\n            });\n          }}\n        >\n          <CheckCircleIcon className=\"size-3.5\" />\n          Enable sandbox\n        </ConfirmationAction>\n      </ConfirmationActions>\n    </Confirmation>\n  );\n}\n",
      "type": "registry:component",
      "target": "@components/ultra-chatbot-agent/ultra-chatbot-agent-sandbox-confirmation.tsx"
    },
    {
      "path": "registry/ultra-chatbot-agent/components/ultra-chatbot-agent/ultra-chatbot-agent-screen.tsx",
      "content": "import { DemoWorkspaceShell } from \"@/components/demo-workspace-shell\";\nimport { TooltipProvider } from \"@/components/ui/tooltip\";\n\nimport {\n  getUltraChatbotAgentRuntimeState,\n  type UltraChatbotAgentRuntimeState,\n} from \"@/lib/ultra-chatbot-agent/server/runtime\";\nimport type { UltraChatbotAgentScreenData } from \"@/lib/ultra-chatbot-agent/server/session\";\nimport { UltraChatbotAgentWorkspace } from \"./ultra-chatbot-agent-workspace\";\n\ninterface UltraChatbotAgentScreenProps extends UltraChatbotAgentScreenData {\n  runtimeState?: UltraChatbotAgentRuntimeState;\n}\n\nexport function UltraChatbotAgentScreen({\n  draftChatId,\n  initialHistoryPage,\n  initialSession,\n  runtimeState = getUltraChatbotAgentRuntimeState(),\n}: UltraChatbotAgentScreenProps) {\n  return (\n    <TooltipProvider>\n      <DemoWorkspaceShell\n        badges={[\n          runtimeState.statusLabel,\n          runtimeState.chatModel,\n          \"Model selector\",\n        ]}\n        headerClassName=\"shrink-0\"\n        summary={\n          <>\n            A route-backed port of the pinned <code>vercel/ai-chatbot</code>{\" \"}\n            snapshot. This first slice already owns visitor isolation, model\n            selection, Postgres persistence, and resumable streams.\n          </>\n        }\n        title=\"Ultra Chatbot Agent\"\n      >\n        <UltraChatbotAgentWorkspace\n          defaultChatModel={runtimeState.chatModel}\n          draftChatId={draftChatId}\n          initialHistoryPage={initialHistoryPage}\n          initialSession={initialSession}\n          isChatAvailable={runtimeState.isChatAvailable}\n          key={initialSession?.chat.id ?? draftChatId}\n          models={runtimeState.models}\n          nodeVersion={runtimeState.nodeVersion}\n          setupMessage={runtimeState.setupMessage}\n        />\n      </DemoWorkspaceShell>\n    </TooltipProvider>\n  );\n}\n",
      "type": "registry:component",
      "target": "@components/ultra-chatbot-agent/ultra-chatbot-agent-screen.tsx"
    },
    {
      "path": "registry/ultra-chatbot-agent/components/ultra-chatbot-agent/ultra-chatbot-agent-sources.tsx",
      "content": "import {\n  Source,\n  Sources,\n  SourcesContent,\n  SourcesTrigger,\n} from \"@/components/ai-elements/sources\";\n\nimport type { UltraChatbotAgentSourcePart } from \"./ultra-chatbot-agent-message-parts\";\n\ninterface UltraChatbotAgentSourcesProps {\n  sources: UltraChatbotAgentSourcePart[];\n}\n\nexport function UltraChatbotAgentSources({\n  sources,\n}: UltraChatbotAgentSourcesProps) {\n  if (sources.length === 0) {\n    return null;\n  }\n\n  const seenSourceKeys = new Map<string, number>();\n  const keyedSources = sources.map((source) => {\n    const baseKey = `${source.sourceId}-${source.url}-${source.title}`;\n    const nextCount = (seenSourceKeys.get(baseKey) ?? 0) + 1;\n    seenSourceKeys.set(baseKey, nextCount);\n\n    return {\n      key: nextCount === 1 ? baseKey : `${baseKey}-${nextCount}`,\n      source,\n    };\n  });\n\n  return (\n    <Sources>\n      <SourcesTrigger count={sources.length} />\n      <SourcesContent>\n        {keyedSources.map(({ key, source }) => (\n          <Source href={source.url} key={key} title={source.title} />\n        ))}\n      </SourcesContent>\n    </Sources>\n  );\n}\n",
      "type": "registry:component",
      "target": "@components/ultra-chatbot-agent/ultra-chatbot-agent-sources.tsx"
    },
    {
      "path": "registry/ultra-chatbot-agent/components/ultra-chatbot-agent/ultra-chatbot-agent-suggested-actions.tsx",
      "content": "\"use client\";\n\nimport {\n  Suggestion,\n  Suggestions,\n} from \"@/components/ai-elements/suggestion\";\n\nimport { ultraChatbotAgentSuggestedActions } from \"@/lib/ultra-chatbot-agent/constants\";\n\nexport function UltraChatbotAgentSuggestedActions({\n  onSelect,\n}: {\n  onSelect: (value: string) => void | Promise<void>;\n}) {\n  return (\n    <div className=\"w-full\" data-testid=\"suggested-actions\">\n      <Suggestions className=\"w-full flex-wrap items-stretch\">\n        {ultraChatbotAgentSuggestedActions.map((suggestion) => (\n          <Suggestion\n            className=\"h-auto max-w-full justify-start whitespace-normal rounded-none px-4 text-left text-sm leading-6\"\n            key={suggestion}\n            onClick={onSelect}\n            suggestion={suggestion}\n          />\n        ))}\n      </Suggestions>\n    </div>\n  );\n}\n",
      "type": "registry:component",
      "target": "@components/ultra-chatbot-agent/ultra-chatbot-agent-suggested-actions.tsx"
    },
    {
      "path": "registry/ultra-chatbot-agent/components/ultra-chatbot-agent/ultra-chatbot-agent-version-footer.tsx",
      "content": "\"use client\";\n\nimport {\n  ArrowClockwiseIcon,\n  CaretLeftIcon,\n  CaretRightIcon,\n  CornersOutIcon,\n  FloppyDiskIcon,\n  PencilSimpleIcon,\n  RowsIcon,\n  XIcon,\n} from \"@phosphor-icons/react\";\nimport { Badge } from \"@/components/ui/badge\";\nimport { Button } from \"@/components/ui/button\";\n\nimport type { UltraChatbotAgentArtifactMode } from \"./ultra-chatbot-agent-artifact-state\";\n\nexport function UltraChatbotAgentVersionFooter({\n  currentVersionIndex,\n  isEditing = false,\n  isLatestVersionView = false,\n  mode,\n  onChangeVersion,\n  onCancelEdit,\n  onRestoreVersion,\n  onSaveVersion,\n  onSetMode,\n  onStartEdit,\n  saveDisabled = false,\n  totalVersions,\n}: {\n  currentVersionIndex: number;\n  isEditing?: boolean;\n  isLatestVersionView?: boolean;\n  mode: UltraChatbotAgentArtifactMode;\n  onChangeVersion: (direction: \"newer\" | \"older\" | \"latest\") => void;\n  onCancelEdit?: () => void;\n  onRestoreVersion: () => Promise<void> | void;\n  onSaveVersion?: () => Promise<void> | void;\n  onSetMode: (mode: UltraChatbotAgentArtifactMode) => void;\n  onStartEdit?: () => void;\n  saveDisabled?: boolean;\n  totalVersions: number;\n}) {\n  const isLatestVersion = currentVersionIndex === 0;\n  const isOldestVersion = currentVersionIndex === totalVersions - 1;\n\n  if (totalVersions === 0) {\n    return null;\n  }\n\n  return (\n    <div className=\"flex flex-wrap items-center justify-between gap-3 border-foreground/10 border-t pt-3\">\n      <div className=\"flex items-center gap-2\">\n        <Button\n          disabled={isLatestVersion}\n          onClick={() => onChangeVersion(\"newer\")}\n          size=\"icon\"\n          type=\"button\"\n          variant=\"ghost\"\n        >\n          <CaretLeftIcon className=\"size-4\" />\n          <span className=\"sr-only\">Newer version</span>\n        </Button>\n        <Badge variant=\"outline\">\n          {currentVersionIndex + 1} / {totalVersions}\n        </Badge>\n        <Button\n          disabled={isOldestVersion}\n          onClick={() => onChangeVersion(\"older\")}\n          size=\"icon\"\n          type=\"button\"\n          variant=\"ghost\"\n        >\n          <CaretRightIcon className=\"size-4\" />\n          <span className=\"sr-only\">Older version</span>\n        </Button>\n      </div>\n\n      <div className=\"flex flex-wrap items-center justify-end gap-2\">\n        <Button\n          onClick={() => onSetMode(mode === \"diff\" ? \"edit\" : \"diff\")}\n          size=\"sm\"\n          type=\"button\"\n          variant=\"ghost\"\n        >\n          {mode === \"diff\" ? (\n            <CornersOutIcon className=\"size-3.5\" />\n          ) : (\n            <RowsIcon className=\"size-3.5\" />\n          )}\n          {mode === \"diff\" ? \"Preview\" : \"Diff\"}\n        </Button>\n\n        <Button\n          disabled={isLatestVersion}\n          onClick={() => void onRestoreVersion()}\n          size=\"sm\"\n          type=\"button\"\n          variant=\"outline\"\n        >\n          <ArrowClockwiseIcon className=\"size-3.5\" />\n          Restore\n        </Button>\n\n        <Button\n          disabled={isLatestVersion}\n          onClick={() => onChangeVersion(\"latest\")}\n          size=\"sm\"\n          type=\"button\"\n          variant=\"outline\"\n        >\n          Latest\n        </Button>\n\n        {isLatestVersionView && onStartEdit && !isEditing ? (\n          <div className=\"ml-3\">\n            <Button\n              disabled={saveDisabled || mode === \"diff\"}\n              onClick={onStartEdit}\n              size=\"sm\"\n              type=\"button\"\n              variant=\"outline\"\n            >\n              <PencilSimpleIcon className=\"size-3.5\" />\n              Edit\n            </Button>\n          </div>\n        ) : null}\n\n        {isLatestVersionView && isEditing && onSaveVersion ? (\n          <div className=\"ml-3 flex items-center gap-2\">\n            <Button\n              disabled={saveDisabled}\n              onClick={onCancelEdit}\n              size=\"icon-sm\"\n              type=\"button\"\n              variant=\"outline\"\n            >\n              <XIcon className=\"size-3.5\" />\n              <span className=\"sr-only\">Cancel</span>\n            </Button>\n            <Button\n              disabled={saveDisabled}\n              onClick={() => void onSaveVersion()}\n              size=\"sm\"\n              type=\"button\"\n              variant=\"outline\"\n            >\n              <FloppyDiskIcon className=\"size-3.5\" />\n              Save version\n            </Button>\n          </div>\n        ) : null}\n      </div>\n    </div>\n  );\n}\n",
      "type": "registry:component",
      "target": "@components/ultra-chatbot-agent/ultra-chatbot-agent-version-footer.tsx"
    },
    {
      "path": "registry/ultra-chatbot-agent/components/ultra-chatbot-agent/ultra-chatbot-agent-visibility-selector.tsx",
      "content": "\"use client\";\n\nimport {\n  CaretDownIcon,\n  CheckIcon,\n  GlobeIcon,\n  LockIcon,\n} from \"@phosphor-icons/react\";\nimport { buttonVariants } from \"@/components/ui/button\";\nimport {\n  DropdownMenu,\n  DropdownMenuContent,\n  DropdownMenuItem,\n  DropdownMenuTrigger,\n} from \"@/components/ui/dropdown-menu\";\nimport { cn } from \"@/lib/utils\";\nimport { useState } from \"react\";\n\ntype UltraChatbotAgentVisibility = \"private\" | \"public\";\n\nconst ultraChatbotAgentVisibilities = [\n  {\n    description: \"Only this visitor can reopen the chat.\",\n    icon: LockIcon,\n    id: \"private\",\n    label: \"Private\",\n  },\n  {\n    description:\n      \"Shared-link behavior is not enabled yet, but the port keeps the public state.\",\n    icon: GlobeIcon,\n    id: \"public\",\n    label: \"Public\",\n  },\n] as const satisfies ReadonlyArray<{\n  description: string;\n  icon: typeof LockIcon;\n  id: UltraChatbotAgentVisibility;\n  label: string;\n}>;\n\nasync function updateUltraChatbotAgentVisibility(input: {\n  chatId: string;\n  visibility: UltraChatbotAgentVisibility;\n}) {\n  const response = await fetch(\n    `/api/demos/ultra-chatbot-agent/${input.chatId}/visibility`,\n    {\n      body: JSON.stringify({\n        visibility: input.visibility,\n      }),\n      credentials: \"include\",\n      headers: {\n        \"content-type\": \"application/json\",\n      },\n      method: \"PATCH\",\n    }\n  );\n\n  if (!response.ok) {\n    throw new Error(\"Failed to update chat visibility.\");\n  }\n\n  return (await response.json()) as {\n    visibility: UltraChatbotAgentVisibility;\n  };\n}\n\ninterface UltraChatbotAgentVisibilitySelectorProps {\n  chatId: string;\n  disabled?: boolean;\n  onChange: (visibility: UltraChatbotAgentVisibility) => void;\n  onError: (message: string | null) => void;\n  value: UltraChatbotAgentVisibility;\n}\n\nexport function UltraChatbotAgentVisibilitySelector({\n  chatId,\n  disabled = false,\n  onChange,\n  onError,\n  value,\n}: UltraChatbotAgentVisibilitySelectorProps) {\n  const [isPending, setIsPending] = useState(false);\n  const selectedVisibility =\n    ultraChatbotAgentVisibilities.find(\n      (visibility) => visibility.id === value\n    ) ?? ultraChatbotAgentVisibilities[0];\n  const SelectedIcon = selectedVisibility.icon;\n\n  async function handleSelect(nextVisibility: UltraChatbotAgentVisibility) {\n    onError(null);\n    setIsPending(true);\n\n    try {\n      const result = await updateUltraChatbotAgentVisibility({\n        chatId,\n        visibility: nextVisibility,\n      });\n      onChange(result.visibility);\n    } catch (error) {\n      onError(\n        error instanceof Error\n          ? error.message\n          : \"Failed to update chat visibility.\"\n      );\n    } finally {\n      setIsPending(false);\n    }\n  }\n\n  return (\n    <DropdownMenu>\n      <DropdownMenuTrigger\n        className={cn(\n          buttonVariants({\n            size: \"sm\",\n            variant: \"outline\",\n          }),\n          \"data-[popup-open]:bg-muted data-[popup-open]:text-foreground\"\n        )}\n        disabled={disabled || isPending}\n        type=\"button\"\n      >\n        <SelectedIcon className=\"size-3.5\" />\n        {selectedVisibility.label}\n        <CaretDownIcon className=\"size-3\" />\n      </DropdownMenuTrigger>\n      <DropdownMenuContent align=\"start\" className=\"w-72\">\n        {ultraChatbotAgentVisibilities.map((visibility) => {\n          const VisibilityIcon = visibility.icon;\n          const isSelected = visibility.id === value;\n\n          return (\n            <DropdownMenuItem\n              className=\"flex items-start justify-between gap-3\"\n              key={visibility.id}\n              onSelect={() => handleSelect(visibility.id)}\n            >\n              <div className=\"flex gap-3\">\n                <VisibilityIcon className=\"mt-0.5 size-4\" />\n                <div>\n                  <p className=\"text-sm\">{visibility.label}</p>\n                  <p className=\"text-muted-foreground text-xs/relaxed\">\n                    {visibility.description}\n                  </p>\n                </div>\n              </div>\n              {isSelected ? <CheckIcon className=\"mt-0.5 size-4\" /> : null}\n            </DropdownMenuItem>\n          );\n        })}\n      </DropdownMenuContent>\n    </DropdownMenu>\n  );\n}\n",
      "type": "registry:component",
      "target": "@components/ultra-chatbot-agent/ultra-chatbot-agent-visibility-selector.tsx"
    },
    {
      "path": "registry/ultra-chatbot-agent/components/ultra-chatbot-agent/ultra-chatbot-agent-weather.tsx",
      "content": "\"use client\";\n\nimport { Badge } from \"@/components/ui/badge\";\n\ninterface UltraChatbotAgentWeatherData {\n  cityName?: string;\n  current: {\n    temperature_2m: number;\n    time: string;\n  };\n  current_units: {\n    temperature_2m: string;\n  };\n  daily: {\n    sunrise: string[];\n    sunset: string[];\n  };\n  timezone?: string;\n}\n\nfunction formatWeatherTime(value: string | undefined) {\n  if (!value) {\n    return \"Unavailable\";\n  }\n\n  const date = new Date(value);\n\n  if (Number.isNaN(date.valueOf())) {\n    return value;\n  }\n\n  return new Intl.DateTimeFormat(\"en\", {\n    hour: \"2-digit\",\n    minute: \"2-digit\",\n  }).format(date);\n}\n\nexport function UltraChatbotAgentWeather({\n  weather,\n}: {\n  weather: UltraChatbotAgentWeatherData;\n}) {\n  return (\n    <div className=\"w-full max-w-sm space-y-3 border border-foreground/10 bg-background px-4 py-3\">\n      <div className=\"flex items-start justify-between gap-3\">\n        <div>\n          <p className=\"font-medium text-sm\">\n            {weather.cityName ?? \"Current weather\"}\n          </p>\n          <p className=\"mt-1 text-2xl\">\n            {weather.current.temperature_2m}\n            {weather.current_units.temperature_2m}\n          </p>\n        </div>\n        {weather.timezone ? (\n          <Badge variant=\"outline\">{weather.timezone}</Badge>\n        ) : null}\n      </div>\n\n      <div className=\"grid grid-cols-2 gap-2 text-xs\">\n        <div className=\"border border-foreground/10 px-3 py-2\">\n          <p className=\"text-muted-foreground\">Sunrise</p>\n          <p className=\"mt-1\">{formatWeatherTime(weather.daily.sunrise[0])}</p>\n        </div>\n        <div className=\"border border-foreground/10 px-3 py-2\">\n          <p className=\"text-muted-foreground\">Sunset</p>\n          <p className=\"mt-1\">{formatWeatherTime(weather.daily.sunset[0])}</p>\n        </div>\n      </div>\n    </div>\n  );\n}\n",
      "type": "registry:component",
      "target": "@components/ultra-chatbot-agent/ultra-chatbot-agent-weather.tsx"
    },
    {
      "path": "registry/ultra-chatbot-agent/components/ultra-chatbot-agent/ultra-chatbot-agent-workspace.tsx",
      "content": "\"use client\";\n\n/* eslint-disable react-hooks/refs, react-hooks/set-state-in-effect */\n\nimport { useChat } from \"@ai-sdk/react\";\nimport {\n  ArrowClockwiseIcon,\n  CaretDownIcon,\n  CheckCircleIcon,\n  RobotIcon,\n} from \"@phosphor-icons/react\";\nimport {\n  Conversation,\n  ConversationContent,\n  ConversationEmptyState,\n  ConversationScrollButton,\n} from \"@/components/ai-elements/conversation\";\nimport {\n  ModelSelector,\n  ModelSelectorContent,\n  ModelSelectorEmpty,\n  ModelSelectorGroup,\n  ModelSelectorInput,\n  ModelSelectorItem,\n  ModelSelectorList,\n  ModelSelectorLogo,\n  ModelSelectorName,\n  ModelSelectorTrigger,\n} from \"@/components/ai-elements/model-selector\";\nimport { Badge } from \"@/components/ui/badge\";\nimport { Button } from \"@/components/ui/button\";\nimport { Spinner } from \"@/components/ui/spinner\";\nimport {\n  DefaultChatTransport,\n  lastAssistantMessageIsCompleteWithApprovalResponses,\n  type UIMessage,\n} from \"ai\";\nimport Link from \"next/link\";\nimport {\n  type Dispatch,\n  type ReactNode,\n  type SetStateAction,\n  useEffect,\n  useMemo,\n  useRef,\n  useState,\n} from \"react\";\nimport {\n  ConversationErrorMessage,\n  useConversationErrorRetry,\n} from \"@/components/demo-chat/conversation-error-message\";\nimport { ultraChatbotAgentKnowledgeSource } from \"@/lib/ultra-chatbot-agent/knowledge-source\";\nimport type { UltraChatbotAgentCapabilities } from \"@/lib/ultra-chatbot-agent/server/capabilities\";\nimport type {\n  UltraChatbotAgentChatRecord,\n  UltraChatbotAgentChatSession,\n  UltraChatbotAgentHistoryPage,\n  UltraChatbotAgentVoteRecord,\n} from \"@/lib/ultra-chatbot-agent/server/chat-store\";\nimport type { UltraChatbotAgentModel } from \"@/lib/ultra-chatbot-agent/server/models\";\nimport { shouldShowUltraChatbotAgentResumeThinking } from \"./resume-pending-state\";\nimport { UltraChatbotAgentArtifact } from \"./ultra-chatbot-agent-artifact\";\nimport { UltraChatbotAgentHistorySidebar } from \"./ultra-chatbot-agent-history-sidebar\";\nimport {\n  getUltraChatbotAgentTextContent,\n  getUltraChatbotAgentToolParts,\n  hasUltraChatbotAgentVisibleMessageContent,\n  isUltraChatbotAgentDocumentResult,\n} from \"./ultra-chatbot-agent-message-parts\";\nimport { UltraChatbotAgentMessages } from \"./ultra-chatbot-agent-messages\";\nimport { UltraChatbotAgentMultimodalInput } from \"./ultra-chatbot-agent-multimodal-input\";\nimport { applyUltraChatbotAgentSandboxApproval } from \"./ultra-chatbot-agent-sandbox-approval\";\nimport { UltraChatbotAgentSuggestedActions } from \"./ultra-chatbot-agent-suggested-actions\";\nimport { UltraChatbotAgentVisibilitySelector } from \"./ultra-chatbot-agent-visibility-selector\";\nimport { useUltraChatbotAgentArtifact } from \"./use-ultra-chatbot-agent-artifact\";\n\nfunction buildHistoryTitleFromMessages(messages: UIMessage[]) {\n  const firstUserMessage = messages.find((message) => message.role === \"user\");\n  const text = firstUserMessage\n    ? getUltraChatbotAgentTextContent(firstUserMessage).trim()\n    : \"\";\n\n  return text.slice(0, 72) || \"New chat\";\n}\n\nfunction formatDateTime(value: string | null) {\n  if (!value) {\n    return \"Pending\";\n  }\n\n  const date = new Date(value);\n\n  if (Number.isNaN(date.valueOf())) {\n    return value;\n  }\n\n  return new Intl.DateTimeFormat(\"en\", {\n    day: \"2-digit\",\n    hour: \"2-digit\",\n    minute: \"2-digit\",\n    month: \"short\",\n  }).format(date);\n}\n\nasync function loadUltraChatbotAgentVotes(chatId: string) {\n  const searchParams = new URLSearchParams({\n    chatId,\n  });\n  const response = await fetch(\n    `/api/demos/ultra-chatbot-agent/vote?${searchParams.toString()}`,\n    {\n      credentials: \"include\",\n    }\n  );\n\n  if (!response.ok) {\n    throw new Error(\"Failed to load message votes.\");\n  }\n\n  return (await response.json()) as UltraChatbotAgentVoteRecord[];\n}\n\nasync function saveUltraChatbotAgentVote(input: {\n  chatId: string;\n  messageId: string;\n  type: \"clear\" | \"down\" | \"up\";\n}) {\n  const response = await fetch(\"/api/demos/ultra-chatbot-agent/vote\", {\n    body: JSON.stringify(input),\n    credentials: \"include\",\n    headers: {\n      \"content-type\": \"application/json\",\n    },\n    method: \"PATCH\",\n  });\n\n  if (!response.ok) {\n    throw new Error(\"Failed to save the message vote.\");\n  }\n}\n\nasync function saveUltraChatbotAgentCapabilities(input: {\n  chatId: string;\n  sandboxEnabled: boolean;\n}) {\n  const response = await fetch(\n    `/api/demos/ultra-chatbot-agent/${input.chatId}/capabilities`,\n    {\n      body: JSON.stringify({\n        sandboxEnabled: input.sandboxEnabled,\n      }),\n      credentials: \"include\",\n      headers: {\n        \"content-type\": \"application/json\",\n      },\n      method: \"PATCH\",\n    }\n  );\n\n  if (!response.ok) {\n    throw new Error(\"Failed to update sandbox capability.\");\n  }\n\n  return (await response.json()) as UltraChatbotAgentCapabilities;\n}\n\nasync function trimUltraChatbotAgentMessagesAfterEdit(input: {\n  chatId: string;\n  messageId: string;\n  retainedFileUrls?: string[];\n  text: string;\n}) {\n  const response = await fetch(\n    `/api/demos/ultra-chatbot-agent/${input.chatId}/messages`,\n    {\n      body: JSON.stringify({\n        messageId: input.messageId,\n        retainedFileUrls: input.retainedFileUrls,\n        text: input.text,\n      }),\n      credentials: \"include\",\n      headers: {\n        \"content-type\": \"application/json\",\n      },\n      method: \"PATCH\",\n    }\n  );\n\n  if (!response.ok) {\n    throw new Error(\"Failed to prepare the edited turn.\");\n  }\n}\n\nfunction toConversationPath(chatId: string) {\n  return `/demos/ultra-chatbot-agent/${chatId}`;\n}\n\nconst nodeVersionPrefixPattern = /^v/;\nconst resumeRecoveryAttemptLimit = 6;\n\nasync function loadUltraChatbotAgentSessionSnapshot(chatId: string) {\n  const response = await fetch(\n    `/api/demos/ultra-chatbot-agent/${chatId}/session`,\n    {\n      credentials: \"include\",\n    }\n  );\n\n  if (!response.ok) {\n    return null;\n  }\n\n  return (await response.json()) as UltraChatbotAgentChatSession;\n}\n\nfunction shouldApplyRecoveredSession(\n  session: UltraChatbotAgentChatSession,\n  messagesLength: number\n) {\n  return (\n    session.messages.length > messagesLength ||\n    session.messages.at(-1)?.role !== \"user\"\n  );\n}\n\ninterface UltraChatbotAgentWorkspaceProps {\n  defaultChatModel: string;\n  draftChatId: string;\n  initialHistoryPage: UltraChatbotAgentHistoryPage;\n  initialSession: UltraChatbotAgentChatSession | null;\n  isChatAvailable: boolean;\n  models: UltraChatbotAgentModel[];\n  nodeVersion: string;\n  setupMessage: string | null;\n}\n\nfunction useResumeRecovery(input: {\n  chatId: string;\n  initialSession: UltraChatbotAgentChatSession | null;\n  latestMessageRole: UIMessage[\"role\"] | undefined;\n  messagesLength: number;\n  setChatMeta: Dispatch<\n    SetStateAction<{\n      capabilities: UltraChatbotAgentCapabilities;\n      createdAt: string | null;\n      id: string;\n      selectedChatModel: string;\n      updatedAt: string | null;\n      visibility: \"private\" | \"public\";\n    }>\n  >;\n  setMessages: (messages: UIMessage[]) => void;\n  status: string;\n}) {\n  const {\n    chatId,\n    initialSession,\n    latestMessageRole,\n    messagesLength,\n    setChatMeta,\n    setMessages,\n    status,\n  } = input;\n  const shouldRecoverMissedResumeRef = useRef(\n    initialSession?.chat.activeStreamId != null ||\n      initialSession?.messages.at(-1)?.role === \"user\"\n  );\n  const initialMessageCountRef = useRef(initialSession?.messages.length ?? 0);\n\n  useEffect(() => {\n    if (!shouldRecoverMissedResumeRef.current) {\n      return;\n    }\n\n    if (status !== \"ready\") {\n      return;\n    }\n\n    if (\n      messagesLength > initialMessageCountRef.current ||\n      latestMessageRole !== \"user\"\n    ) {\n      shouldRecoverMissedResumeRef.current = false;\n      return;\n    }\n\n    let isCancelled = false;\n    let timeoutId: number | undefined;\n    let attempt = 0;\n\n    const runRecoveryAttempt = () => {\n      attempt += 1;\n\n      loadUltraChatbotAgentSessionSnapshot(chatId).then((session) => {\n        if (isCancelled) {\n          return;\n        }\n\n        if (session && shouldApplyRecoveredSession(session, messagesLength)) {\n          shouldRecoverMissedResumeRef.current = false;\n          setMessages(session.messages);\n          setChatMeta({\n            capabilities: session.chat.capabilities,\n            createdAt: session.chat.createdAt,\n            id: session.chat.id,\n            selectedChatModel: session.chat.selectedChatModel,\n            updatedAt: session.chat.updatedAt,\n            visibility: session.chat.visibility,\n          });\n          return;\n        }\n\n        if (attempt >= resumeRecoveryAttemptLimit) {\n          shouldRecoverMissedResumeRef.current = false;\n          return;\n        }\n\n        timeoutId = window.setTimeout(runRecoveryAttempt, 1500);\n      });\n    };\n\n    timeoutId = window.setTimeout(runRecoveryAttempt, 2500);\n\n    return () => {\n      isCancelled = true;\n      if (timeoutId) {\n        window.clearTimeout(timeoutId);\n      }\n    };\n  }, [\n    chatId,\n    latestMessageRole,\n    messagesLength,\n    setChatMeta,\n    setMessages,\n    status,\n  ]);\n}\n\nfunction findModel(\n  models: UltraChatbotAgentModel[],\n  modelId: string\n): UltraChatbotAgentModel | undefined {\n  return models.find((model) => model.id === modelId);\n}\n\n// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: this port keeps the vercel/chatbot-style chat surface in one workspace while QA hardens vertical slices.\n// biome-ignore lint/complexity/noExcessiveLinesPerFunction: follow-up architecture work can split the shell after the current QA checklist is closed.\nexport function UltraChatbotAgentWorkspace({\n  defaultChatModel,\n  draftChatId,\n  initialHistoryPage,\n  initialSession,\n  isChatAvailable,\n  models,\n  nodeVersion,\n  setupMessage,\n}: UltraChatbotAgentWorkspaceProps) {\n  const generatedChatIdRef = useRef(initialSession?.chat.id ?? draftChatId);\n  const hasPromotedRouteRef = useRef(Boolean(initialSession));\n  const chatId = generatedChatIdRef.current;\n  const [chatMeta, setChatMeta] = useState(() => ({\n    capabilities: initialSession?.chat.capabilities ?? {\n      sandboxEnabled: false,\n    },\n    createdAt: initialSession?.chat.createdAt ?? null,\n    id: chatId,\n    selectedChatModel:\n      initialSession?.chat.selectedChatModel ?? defaultChatModel,\n    updatedAt: initialSession?.chat.updatedAt ?? null,\n    visibility: initialSession?.chat.visibility ?? \"private\",\n  }));\n  const selectedChatModelRef = useRef(chatMeta.selectedChatModel);\n  const selectedVisibilityRef = useRef(chatMeta.visibility);\n\n  useEffect(() => {\n    selectedChatModelRef.current = chatMeta.selectedChatModel;\n    selectedVisibilityRef.current = chatMeta.visibility;\n  }, [chatMeta.selectedChatModel, chatMeta.visibility]);\n\n  const {\n    addToolApprovalResponse,\n    clearError,\n    error,\n    messages,\n    regenerate,\n    resumeStream,\n    sendMessage,\n    setMessages,\n    status,\n    stop,\n  } = useChat({\n    id: chatId,\n    messages: initialSession?.messages ?? [],\n    resume: initialSession?.chat.activeStreamId != null,\n    transport: new DefaultChatTransport({\n      api: \"/api/demos/ultra-chatbot-agent\",\n      credentials: \"include\",\n      prepareReconnectToStreamRequest: ({ id }) => ({\n        api: `/api/demos/ultra-chatbot-agent/${id}/stream`,\n        credentials: \"include\",\n      }),\n      prepareSendMessagesRequest: ({\n        id,\n        messageId,\n        messages: nextMessages,\n        trigger,\n      }) => ({\n        body: {\n          id,\n          message: nextMessages.at(-1),\n          messageId,\n          selectedChatModel: selectedChatModelRef.current,\n          selectedVisibilityType: selectedVisibilityRef.current,\n          trigger,\n        },\n      }),\n    }),\n    sendAutomaticallyWhen: lastAssistantMessageIsCompleteWithApprovalResponses,\n  });\n  const retryConversationError = useConversationErrorRetry({\n    clearError,\n    regenerate,\n  });\n  const hasMessages = messages.length > 0;\n  const isBusy = status === \"submitted\" || status === \"streaming\";\n  const [editingMessageId, setEditingMessageId] = useState<string | null>(null);\n  const [editingRetainedFileUrls, setEditingRetainedFileUrls] = useState<\n    string[]\n  >([]);\n  const [editingText, setEditingText] = useState(\"\");\n  const [pendingVote, setPendingVote] = useState<{\n    messageId: string;\n    target: \"down\" | \"up\";\n  } | null>(null);\n  const {\n    closeArtifact,\n    mode: artifactMode,\n    openArtifact,\n    refreshArtifact,\n    refreshToken: artifactRefreshToken,\n    selectedDocumentId,\n    setMode: setArtifactMode,\n  } = useUltraChatbotAgentArtifact();\n  const [editError, setEditError] = useState<string | null>(null);\n  const [composerError, setComposerError] = useState<string | null>(null);\n  const [sandboxError, setSandboxError] = useState<string | null>(null);\n  const [isSandboxUpdating, setIsSandboxUpdating] = useState(false);\n  const [visibilityError, setVisibilityError] = useState<string | null>(null);\n  const [isModelSelectorOpen, setIsModelSelectorOpen] = useState(false);\n  const [votesByMessageId, setVotesByMessageId] = useState<\n    Record<string, boolean>\n  >({});\n  const latestAssistant = [...messages]\n    .reverse()\n    .find((message) => message.role === \"assistant\");\n  const documentArtifactSignatureRef = useRef(\"\");\n  const latestMessageRole = messages.at(-1)?.role;\n  const showThinking =\n    isBusy &&\n    !(\n      latestAssistant &&\n      hasUltraChatbotAgentVisibleMessageContent(latestAssistant)\n    );\n  const showResumeThinking = shouldShowUltraChatbotAgentResumeThinking({\n    initialSession,\n    messages,\n  });\n  const submitStatus =\n    showResumeThinking && status === \"streaming\" ? \"submitted\" : status;\n  const selectedModel =\n    findModel(models, chatMeta.selectedChatModel) ?? models.at(0);\n  const currentChatTitle = hasMessages\n    ? buildHistoryTitleFromMessages(messages)\n    : \"New chat\";\n  const hasPersistedChatMeta =\n    hasMessages && Boolean(chatMeta.createdAt) && Boolean(chatMeta.updatedAt);\n  const currentChatRecordHint = useMemo(() => {\n    const createdAt = chatMeta.createdAt;\n    const updatedAt = chatMeta.updatedAt;\n\n    if (!hasPersistedChatMeta) {\n      return null;\n    }\n\n    if (!(createdAt && updatedAt)) {\n      return null;\n    }\n\n    return {\n      activeStreamId: null,\n      capabilities: chatMeta.capabilities,\n      createdAt,\n      id: chatMeta.id,\n      selectedChatModel: chatMeta.selectedChatModel,\n      title: currentChatTitle,\n      updatedAt,\n      visibility: chatMeta.visibility,\n      visitorId: initialSession?.chat.visitorId ?? \"visitor\",\n    } satisfies UltraChatbotAgentChatRecord;\n  }, [\n    chatMeta.capabilities,\n    chatMeta.createdAt,\n    chatMeta.id,\n    chatMeta.selectedChatModel,\n    chatMeta.updatedAt,\n    chatMeta.visibility,\n    currentChatTitle,\n    hasPersistedChatMeta,\n    initialSession?.chat.visitorId,\n  ]);\n  let sandboxButtonContent: ReactNode = chatMeta.capabilities.sandboxEnabled\n    ? \"Lock sandbox\"\n    : \"Enable sandbox\";\n\n  if (isSandboxUpdating) {\n    sandboxButtonContent = <Spinner className=\"size-3.5\" />;\n  }\n\n  useEffect(() => {\n    if (messages.length === 0) {\n      return;\n    }\n\n    setChatMeta((current) => ({\n      ...current,\n      createdAt: current.createdAt ?? new Date().toISOString(),\n      updatedAt: new Date().toISOString(),\n    }));\n  }, [messages.length]);\n\n  useEffect(() => {\n    if (messages.length < 2) {\n      setVotesByMessageId({});\n      return;\n    }\n\n    let isCancelled = false;\n\n    loadUltraChatbotAgentVotes(chatMeta.id)\n      .then((votes) => {\n        if (isCancelled) {\n          return;\n        }\n\n        setVotesByMessageId(\n          Object.fromEntries(\n            votes.map((vote) => [vote.messageId, vote.isUpvoted])\n          )\n        );\n      })\n      .catch((error) => {\n        if (isCancelled) {\n          return;\n        }\n\n        console.error(\"Failed to load ultra-chatbot-agent votes.\", error);\n      });\n\n    return () => {\n      isCancelled = true;\n    };\n  }, [chatMeta.id, messages.length]);\n\n  useEffect(() => {\n    const nextSignature = messages\n      .flatMap((message) =>\n        getUltraChatbotAgentToolParts(message).flatMap((part) =>\n          part.state === \"output-available\" &&\n          isUltraChatbotAgentDocumentResult(part.output)\n            ? [part.output.id]\n            : []\n        )\n      )\n      .join(\"|\");\n\n    if (documentArtifactSignatureRef.current === nextSignature) {\n      return;\n    }\n\n    documentArtifactSignatureRef.current = nextSignature;\n    if (nextSignature) {\n      refreshArtifact();\n    }\n  }, [messages, refreshArtifact]);\n\n  useResumeRecovery({\n    chatId,\n    initialSession,\n    latestMessageRole,\n    messagesLength: messages.length,\n    setChatMeta,\n    setMessages,\n    status,\n  });\n\n  async function handleSubmit(parts: UIMessage[\"parts\"]) {\n    if (parts.length === 0) {\n      return;\n    }\n\n    if (!hasPromotedRouteRef.current) {\n      hasPromotedRouteRef.current = true;\n      window.history.replaceState({}, \"\", toConversationPath(chatId));\n      setChatMeta((current) => ({\n        ...current,\n        createdAt: current.createdAt ?? new Date().toISOString(),\n      }));\n    }\n\n    await sendMessage({\n      parts,\n      role: \"user\",\n    });\n  }\n\n  async function handleVote(messageId: string, type: \"down\" | \"up\") {\n    const currentVote = votesByMessageId[messageId];\n    const nextType =\n      (type === \"up\" && currentVote === true) ||\n      (type === \"down\" && currentVote === false)\n        ? \"clear\"\n        : type;\n\n    setPendingVote({\n      messageId,\n      target: type,\n    });\n\n    try {\n      await saveUltraChatbotAgentVote({\n        chatId: chatMeta.id,\n        messageId,\n        type: nextType,\n      });\n      setVotesByMessageId((current) => {\n        if (nextType === \"clear\") {\n          const nextVotes = { ...current };\n          delete nextVotes[messageId];\n          return nextVotes;\n        }\n\n        return {\n          ...current,\n          [messageId]: nextType === \"up\",\n        };\n      });\n    } catch (error) {\n      console.error(\"Failed to save ultra-chatbot-agent vote.\", error);\n    } finally {\n      setPendingVote(null);\n    }\n  }\n\n  async function persistSandboxCapability(sandboxEnabled: boolean) {\n    const capabilities = await saveUltraChatbotAgentCapabilities({\n      chatId: chatMeta.id,\n      sandboxEnabled,\n    });\n\n    setChatMeta((current) => ({\n      ...current,\n      capabilities,\n      updatedAt: new Date().toISOString(),\n    }));\n  }\n\n  function getSandboxCapabilityErrorMessage(error: unknown) {\n    return error instanceof Error\n      ? error.message\n      : \"Failed to update sandbox capability.\";\n  }\n\n  async function handleSandboxCapabilityChange(sandboxEnabled: boolean) {\n    setSandboxError(null);\n    setIsSandboxUpdating(true);\n\n    try {\n      await persistSandboxCapability(sandboxEnabled);\n    } catch (error) {\n      setSandboxError(getSandboxCapabilityErrorMessage(error));\n    } finally {\n      setIsSandboxUpdating(false);\n    }\n  }\n\n  async function handleSandboxApprovalResponse(input: {\n    approvalId: string;\n    approved: boolean;\n    reason: string;\n  }) {\n    setSandboxError(null);\n    setIsSandboxUpdating(true);\n\n    try {\n      await applyUltraChatbotAgentSandboxApproval({\n        ...input,\n        addToolApprovalResponse,\n        persistSandboxCapability,\n      });\n    } catch (error) {\n      setSandboxError(getSandboxCapabilityErrorMessage(error));\n    } finally {\n      setIsSandboxUpdating(false);\n    }\n  }\n\n  async function handleSaveEdit(message: UIMessage) {\n    const nextText = editingText.trim();\n\n    if (!nextText) {\n      setEditError(\"Edited message text cannot be empty.\");\n      return;\n    }\n\n    setEditError(null);\n\n    try {\n      await trimUltraChatbotAgentMessagesAfterEdit({\n        chatId: chatMeta.id,\n        messageId: message.id,\n        retainedFileUrls: editingRetainedFileUrls,\n        text: nextText,\n      });\n\n      setMessages((currentMessages) => {\n        const messageIndex = currentMessages.findIndex(\n          (currentMessage) => currentMessage.id === message.id\n        );\n\n        if (messageIndex === -1) {\n          return currentMessages;\n        }\n\n        const retainedFileUrlSet = new Set(editingRetainedFileUrls);\n\n        return [\n          ...currentMessages.slice(0, messageIndex),\n          {\n            ...message,\n            parts: [\n              ...message.parts.filter((part) => {\n                if (part.type === \"text\") {\n                  return false;\n                }\n\n                if (part.type === \"file\") {\n                  return retainedFileUrlSet.has(part.url);\n                }\n\n                return true;\n              }),\n              { text: nextText, type: \"text\" as const },\n            ],\n          },\n        ];\n      });\n\n      setEditingMessageId(null);\n      setEditingRetainedFileUrls([]);\n      setEditingText(\"\");\n      await regenerate();\n    } catch (error) {\n      setEditError(\n        error instanceof Error\n          ? error.message\n          : \"Failed to prepare the edited turn.\"\n      );\n    }\n  }\n\n  return (\n    <div className=\"grid min-h-[70svh] gap-4 lg:h-full lg:min-h-0 lg:grid-cols-[18rem_minmax(0,1fr)_20rem]\">\n      <UltraChatbotAgentHistorySidebar\n        currentChatId={chatMeta.id}\n        currentChatRecordHint={currentChatRecordHint}\n        initialHistoryPage={initialHistoryPage}\n      />\n\n      <section className=\"flex min-h-[70svh] flex-col border border-foreground/10 bg-background lg:h-full lg:min-h-0\">\n        {isChatAvailable ? null : (\n          <div className=\"border-foreground/10 border-b px-4 py-3 text-muted-foreground text-xs/relaxed\">\n            {setupMessage}\n          </div>\n        )}\n\n        {editError ? (\n          <div className=\"border-foreground/10 border-b px-4 py-3 text-destructive text-xs/relaxed\">\n            {editError}\n          </div>\n        ) : null}\n        {composerError ? (\n          <div className=\"border-foreground/10 border-b px-4 py-3 text-destructive text-xs/relaxed\">\n            {composerError}\n          </div>\n        ) : null}\n        {visibilityError ? (\n          <div className=\"border-foreground/10 border-b px-4 py-3 text-destructive text-xs/relaxed\">\n            {visibilityError}\n          </div>\n        ) : null}\n        <div className=\"border-foreground/10 border-b px-4 py-3\">\n          <div className=\"mx-auto flex w-full max-w-3xl items-center justify-between gap-3\">\n            <div>\n              <p className=\"text-[11px] text-muted-foreground uppercase tracking-[0.2em]\">\n                Conversation\n              </p>\n              <p className=\"mt-1 text-sm\">\n                Route-backed chat with visitor-owned state and resumable stream.\n              </p>\n            </div>\n            <UltraChatbotAgentVisibilitySelector\n              chatId={chatMeta.id}\n              disabled={!(initialSession || hasMessages)}\n              onChange={(visibility) => {\n                selectedVisibilityRef.current = visibility;\n                setChatMeta((current) => ({\n                  ...current,\n                  visibility,\n                }));\n              }}\n              onError={setVisibilityError}\n              value={chatMeta.visibility}\n            />\n          </div>\n        </div>\n\n        <Conversation className=\"min-h-0\">\n          <ConversationContent className=\"mx-auto flex w-full max-w-3xl flex-1 gap-6 px-4 py-6\">\n            {hasMessages || error ? (\n              <>\n                {hasMessages ? (\n                  <UltraChatbotAgentMessages\n                    chatId={chatMeta.id}\n                    editingMessageId={editingMessageId}\n                    editingRetainedFileUrls={editingRetainedFileUrls}\n                    editingText={editingText}\n                    isBusy={isBusy}\n                    isSandboxUpdating={isSandboxUpdating}\n                    messages={messages}\n                    onArtifactOpen={openArtifact}\n                    onCancelEdit={() => {\n                      setEditingMessageId(null);\n                      setEditingRetainedFileUrls([]);\n                      setEditingText(\"\");\n                      setEditError(null);\n                    }}\n                    onEditTextChange={setEditingText}\n                    onRemoveEditingFile={(url) =>\n                      setEditingRetainedFileUrls((current) =>\n                        current.filter((currentUrl) => currentUrl !== url)\n                      )\n                    }\n                    onSandboxApprovalResponse={handleSandboxApprovalResponse}\n                    onSaveEdit={handleSaveEdit}\n                    onStartEdit={({ messageId, retainedFileUrls, text }) => {\n                      setEditingMessageId(messageId);\n                      setEditingRetainedFileUrls(retainedFileUrls);\n                      setEditingText(text);\n                      setEditError(null);\n                    }}\n                    onVote={handleVote}\n                    pendingVote={pendingVote}\n                    showResumeThinking={showResumeThinking}\n                    showThinking={showThinking}\n                    status={status}\n                    votesByMessageId={votesByMessageId}\n                  />\n                ) : null}\n                {error ? (\n                  <ConversationErrorMessage\n                    error={error}\n                    isRetryDisabled={isBusy}\n                    onRetry={retryConversationError}\n                  />\n                ) : null}\n              </>\n            ) : (\n              <div className=\"flex size-full flex-col items-center justify-center gap-6 py-12\">\n                <ConversationEmptyState\n                  description=\"Start a conversation, switch models, then refresh the page to reload the same route-backed chat.\"\n                  icon={<RobotIcon className=\"size-5\" />}\n                  title=\"Ultra route is ready\"\n                />\n              </div>\n            )}\n          </ConversationContent>\n          <ConversationScrollButton />\n        </Conversation>\n\n        <div className=\"border-foreground/10 border-t px-4 py-4\">\n          <div className=\"mx-auto w-full max-w-3xl\">\n            {hasMessages ? null : (\n              <div className=\"mb-4\">\n                <UltraChatbotAgentSuggestedActions\n                  onSelect={(value) =>\n                    handleSubmit([{ text: value, type: \"text\" as const }])\n                  }\n                />\n              </div>\n            )}\n            <UltraChatbotAgentMultimodalInput\n              chatId={chatMeta.id}\n              disabled={!isChatAvailable || isBusy}\n              footerLeading={\n                <>\n                  <Badge variant=\"outline\">Visitor scoped</Badge>\n                  <Badge variant=\"outline\">Blob uploads</Badge>\n                  <Badge variant=\"outline\">Project Docs MCP</Badge>\n                  <Badge variant=\"outline\">Preindexed RAG</Badge>\n                  <Badge variant=\"outline\">\n                    {chatMeta.capabilities.sandboxEnabled\n                      ? \"Sandbox enabled\"\n                      : \"Sandbox locked\"}\n                  </Badge>\n                  <ModelSelector\n                    onOpenChange={setIsModelSelectorOpen}\n                    open={isModelSelectorOpen}\n                  >\n                    <ModelSelectorTrigger className=\"inline-flex h-7 items-center gap-2 border border-input px-2 text-xs\">\n                      <ModelSelectorLogo\n                        provider={selectedModel?.provider ?? \"openai\"}\n                      />\n                      <span>\n                        {selectedModel?.name ?? chatMeta.selectedChatModel}\n                      </span>\n                      <CaretDownIcon className=\"size-3\" />\n                    </ModelSelectorTrigger>\n                    <ModelSelectorContent\n                      className=\"sm:max-w-md\"\n                      title=\"Select model\"\n                    >\n                      <ModelSelectorInput placeholder=\"Search models...\" />\n                      <ModelSelectorList>\n                        <ModelSelectorEmpty>\n                          No models found.\n                        </ModelSelectorEmpty>\n                        <ModelSelectorGroup heading=\"Available models\">\n                          {models.map((model) => (\n                            <ModelSelectorItem\n                              key={model.id}\n                              onSelect={() => {\n                                selectedChatModelRef.current = model.id;\n                                setChatMeta((current) => ({\n                                  ...current,\n                                  selectedChatModel: model.id,\n                                }));\n                                setIsModelSelectorOpen(false);\n                              }}\n                              value={`${model.name} ${model.id}`}\n                            >\n                              <ModelSelectorLogo provider={model.provider} />\n                              <ModelSelectorName>\n                                {model.name}\n                              </ModelSelectorName>\n                              <span className=\"text-muted-foreground text-xs\">\n                                {model.capabilities.reasoning\n                                  ? \"Reasoning\"\n                                  : \"Chat\"}\n                              </span>\n                              <span className=\"text-muted-foreground text-xs\">\n                                {model.expectedLatency} latency\n                              </span>\n                              <span className=\"text-muted-foreground text-xs\">\n                                {model.costProfile} cost\n                              </span>\n                            </ModelSelectorItem>\n                          ))}\n                        </ModelSelectorGroup>\n                      </ModelSelectorList>\n                    </ModelSelectorContent>\n                  </ModelSelector>\n                </>\n              }\n              onComposerErrorChange={setComposerError}\n              onSend={handleSubmit}\n              onStop={stop}\n              placeholder=\"Ask for a draft, compare models, inspect project docs, query the preindexed PDF, and attach a PDF, PNG, or JPEG before sending.\"\n              status={submitStatus}\n            />\n          </div>\n        </div>\n      </section>\n\n      <aside className=\"border border-foreground/10 bg-background p-4 lg:min-h-0 lg:overflow-y-auto\">\n        <div className=\"space-y-5\">\n          <div className=\"flex items-center justify-between gap-2\">\n            <div>\n              <p className=\"text-[11px] text-muted-foreground uppercase tracking-[0.2em]\">\n                Session\n              </p>\n              <p className=\"mt-1 font-medium text-sm\">\n                {initialSession ? \"Restored route\" : \"New route promotion\"}\n              </p>\n            </div>\n            <Link\n              className=\"inline-flex h-8 items-center justify-center border border-foreground/10 px-3 text-sm transition-colors hover:border-foreground/30\"\n              href=\"/demos/ultra-chatbot-agent\"\n            >\n              New chat\n            </Link>\n          </div>\n\n          <div>\n            <p className=\"text-[11px] text-muted-foreground uppercase tracking-[0.2em]\">\n              Chat ID\n            </p>\n            <p className=\"mt-1 break-all font-mono text-xs\">{chatMeta.id}</p>\n          </div>\n\n          <div className=\"grid gap-3 sm:grid-cols-2 lg:grid-cols-1\">\n            <div>\n              <p className=\"text-[11px] text-muted-foreground uppercase tracking-[0.2em]\">\n                Created\n              </p>\n              <p className=\"mt-1 text-sm\">\n                {formatDateTime(chatMeta.createdAt)}\n              </p>\n            </div>\n            <div>\n              <p className=\"text-[11px] text-muted-foreground uppercase tracking-[0.2em]\">\n                Updated\n              </p>\n              <p className=\"mt-1 text-sm\">\n                {formatDateTime(chatMeta.updatedAt)}\n              </p>\n            </div>\n          </div>\n\n          <div>\n            <p className=\"text-[11px] text-muted-foreground uppercase tracking-[0.2em]\">\n              Current model\n            </p>\n            <p className=\"mt-1 text-sm\">{selectedModel?.name}</p>\n            <p className=\"mt-1 text-muted-foreground text-xs/relaxed\">\n              {selectedModel?.description}\n            </p>\n          </div>\n\n          <div>\n            <p className=\"text-[11px] text-muted-foreground uppercase tracking-[0.2em]\">\n              Capabilities\n            </p>\n            <div className=\"mt-2 flex flex-wrap gap-2\">\n              <Badge\n                variant={\n                  chatMeta.capabilities.sandboxEnabled ? \"secondary\" : \"outline\"\n                }\n              >\n                {chatMeta.capabilities.sandboxEnabled\n                  ? \"Sandbox enabled\"\n                  : \"Sandbox locked\"}\n              </Badge>\n              <Badge variant=\"outline\">Preindexed RAG</Badge>\n              <Badge variant=\"outline\">Project Docs MCP</Badge>\n            </div>\n          </div>\n\n          <div className=\"border border-foreground/10 px-3 py-3\">\n            <div className=\"flex items-start justify-between gap-3\">\n              <div>\n                <p className=\"text-[11px] text-muted-foreground uppercase tracking-[0.2em]\">\n                  Sandbox\n                </p>\n                <p className=\"mt-1 text-sm\">\n                  {chatMeta.capabilities.sandboxEnabled\n                    ? \"Enabled for this chat\"\n                    : \"Approval required\"}\n                </p>\n              </div>\n              {chatMeta.capabilities.sandboxEnabled ? (\n                <CheckCircleIcon className=\"mt-0.5 size-4 text-emerald-500\" />\n              ) : null}\n            </div>\n            <p className=\"mt-2 text-muted-foreground text-xs/relaxed\">\n              Sandbox unlocks repo-local reads, shell execution, and\n              skill-backed tools for this route. HITL approvals and this switch\n              share the same per-chat capability state.\n            </p>\n            {sandboxError ? (\n              <p className=\"mt-2 text-destructive text-xs/relaxed\">\n                {sandboxError}\n              </p>\n            ) : null}\n            <Button\n              className=\"mt-3\"\n              disabled={isSandboxUpdating || !hasMessages}\n              onClick={() =>\n                handleSandboxCapabilityChange(\n                  !chatMeta.capabilities.sandboxEnabled\n                )\n              }\n              size=\"sm\"\n              type=\"button\"\n              variant=\"outline\"\n            >\n              {sandboxButtonContent}\n            </Button>\n          </div>\n\n          <div>\n            <p className=\"text-[11px] text-muted-foreground uppercase tracking-[0.2em]\">\n              Knowledge source\n            </p>\n            <p className=\"mt-1 text-sm\">\n              {ultraChatbotAgentKnowledgeSource.title}\n            </p>\n            <p className=\"mt-1 text-muted-foreground text-xs/relaxed\">\n              {ultraChatbotAgentKnowledgeSource.description}\n            </p>\n            <div className=\"mt-2 flex flex-wrap gap-2\">\n              <Link\n                className=\"inline-flex h-8 items-center justify-center border border-foreground/10 px-3 text-sm transition-colors hover:border-foreground/30\"\n                href={ultraChatbotAgentKnowledgeSource.documentUrl}\n                rel=\"noreferrer\"\n                target=\"_blank\"\n              >\n                Open PDF\n              </Link>\n              <Link\n                className=\"inline-flex h-8 items-center justify-center border border-foreground/10 px-3 text-sm transition-colors hover:border-foreground/30\"\n                href={ultraChatbotAgentKnowledgeSource.sourcePageUrl}\n                rel=\"noreferrer\"\n                target=\"_blank\"\n              >\n                Source page\n              </Link>\n            </div>\n          </div>\n\n          <div>\n            <p className=\"text-[11px] text-muted-foreground uppercase tracking-[0.2em]\">\n              Port contract\n            </p>\n            <p className=\"mt-1 text-sm\">\n              This slice keeps the route identity, visitor isolation, resumable\n              streaming, model selection, and the first document companion\n              surface.\n            </p>\n          </div>\n          <div>\n            <p className=\"text-[11px] text-muted-foreground uppercase tracking-[0.2em]\">\n              Runtime\n            </p>\n            <div className=\"mt-2 flex flex-wrap gap-2\">\n              <Badge variant=\"outline\">\n                Node {nodeVersion.replace(nodeVersionPrefixPattern, \"\")}\n              </Badge>\n              <Badge variant=\"outline\">HTTP-only cookie</Badge>\n              <Badge variant=\"outline\">Redis resume</Badge>\n            </div>\n          </div>\n\n          <UltraChatbotAgentArtifact\n            chatId={chatMeta.id}\n            disabled={!isChatAvailable || isBusy}\n            mode={artifactMode}\n            onClose={closeArtifact}\n            onModeChange={setArtifactMode}\n            onOpen={openArtifact}\n            onRefresh={refreshArtifact}\n            refreshToken={artifactRefreshToken}\n            selectedDocumentId={selectedDocumentId}\n          />\n\n          {initialSession?.chat.activeStreamId ? (\n            <Button\n              onClick={() => resumeStream()}\n              size=\"sm\"\n              type=\"button\"\n              variant=\"outline\"\n            >\n              <ArrowClockwiseIcon className=\"size-3.5\" />\n              Retry resume\n            </Button>\n          ) : null}\n        </div>\n      </aside>\n    </div>\n  );\n}\n",
      "type": "registry:component",
      "target": "@components/ultra-chatbot-agent/ultra-chatbot-agent-workspace.tsx"
    },
    {
      "path": "registry/ultra-chatbot-agent/components/ultra-chatbot-agent/use-ultra-chatbot-agent-artifact.ts",
      "content": "\"use client\";\n\nimport { useCallback, useState } from \"react\";\n\nimport {\n  closeUltraChatbotAgentArtifact,\n  createInitialUltraChatbotAgentArtifactState,\n  openUltraChatbotAgentArtifact,\n  refreshUltraChatbotAgentArtifact,\n  setUltraChatbotAgentArtifactMode,\n  type UltraChatbotAgentArtifactMode,\n} from \"./ultra-chatbot-agent-artifact-state\";\n\nexport function useUltraChatbotAgentArtifact() {\n  const [artifact, setArtifact] = useState(\n    createInitialUltraChatbotAgentArtifactState\n  );\n  const closeArtifact = useCallback(() => {\n    setArtifact((current) => closeUltraChatbotAgentArtifact(current));\n  }, []);\n  const openArtifact = useCallback((documentId: string) => {\n    setArtifact((current) =>\n      openUltraChatbotAgentArtifact(current, documentId)\n    );\n  }, []);\n  const refreshArtifact = useCallback(() => {\n    setArtifact((current) => refreshUltraChatbotAgentArtifact(current));\n  }, []);\n  const setMode = useCallback((mode: UltraChatbotAgentArtifactMode) => {\n    setArtifact((current) => setUltraChatbotAgentArtifactMode(current, mode));\n  }, []);\n\n  return {\n    closeArtifact,\n    mode: artifact.mode,\n    openArtifact,\n    refreshArtifact,\n    refreshToken: artifact.refreshToken,\n    selectedDocumentId: artifact.selectedDocumentId,\n    setMode,\n  };\n}\n",
      "type": "registry:component",
      "target": "@components/ultra-chatbot-agent/use-ultra-chatbot-agent-artifact.ts"
    },
    {
      "path": "registry/ultra-chatbot-agent/docs/ultra-chatbot-agent-registry.md",
      "content": "# Ultra Chatbot Agent Registry Notes\n\nThis registry item installs the complete Ultra Chatbot Agent frontend and API route source into a shadcn Next.js App Router project.\n\nRequired for the main chat happy path:\n\n- AI Gateway: `AI_GATEWAY_API_KEY`, `AI_GATEWAY_BASE_URL`, and `AI_GATEWAY_CHAT_MODEL`\n- Postgres: `DATABASE_URL`\n- Redis: `REDIS_URL`\n\nAdditional capability configuration:\n\n- Vercel Blob uploads: `BLOB_READ_WRITE_TOKEN`\n- Cleanup cron: `CRON_SECRET`\n- Remote Vercel Sandbox: `VERCEL_OIDC_TOKEN`, or `VERCEL_TOKEN`, `VERCEL_TEAM_ID`, and `VERCEL_PROJECT_ID`\n\nThe installed `@/lib/ultra-chatbot-agent/server/schema.ts` file contains the Drizzle table definitions for chats, messages, votes, streams, documents, and suggestions. Wire those definitions into your app's migration workflow before using a persistent Postgres database.\n\nSource docs:\n\n- https://agent-demos.hsawana9.com/registry-guide\n- https://github.com/SawanaLabs/agent-demos\n- https://sdk.vercel.ai/docs\n- https://elements.ai-sdk.dev/docs\n",
      "type": "registry:file",
      "target": "docs/ultra-chatbot-agent-registry.md"
    },
    {
      "path": "registry/ultra-chatbot-agent/lib/ai-gateway/contract.ts",
      "content": "import { createGateway } from \"ai\";\n\nexport const DEFAULT_GATEWAY_BASE_URL = \"https://ai-gateway.vercel.sh/v3/ai\";\nexport const MINIMUM_NODE_VERSION = \"22.13.0\";\nconst nodeVersionPattern = /^v?(\\d+)\\.(\\d+)\\.(\\d+)(?:[-+].*)?$/;\n\nexport type AiGatewayEnvRecord = Record<string, string | undefined>;\n\nexport interface ParsedNodeVersion {\n  major: number;\n  minor: number;\n  patch: number;\n}\n\nexport interface AiGatewayContractConfig {\n  apiKey: string;\n  baseURL: string;\n  chatModel: string;\n}\n\nexport interface AiGatewayResolvedEnv {\n  apiKey: string | undefined;\n  baseURL: string;\n  chatModel: string;\n}\n\nexport interface AiGatewaySetupConfig {\n  baseURL: string;\n  chatModel: string;\n}\n\nexport interface AiGatewayContractSetupState<\n  TConfig extends AiGatewaySetupConfig = AiGatewaySetupConfig,\n> {\n  config: TConfig;\n  isReady: boolean;\n  issues: string[];\n  nodeVersion: string;\n}\n\nexport interface AiGatewayContractOptions<\n  TConfig extends AiGatewaySetupConfig = AiGatewaySetupConfig,\n> {\n  buildConfig?: (\n    resolvedEnv: AiGatewayResolvedEnv,\n    env: AiGatewayEnvRecord\n  ) => TConfig;\n  defaultBaseURL?: string;\n  defaultChatModel: string;\n  getAdditionalIssues?: (\n    resolvedEnv: AiGatewayResolvedEnv,\n    env: AiGatewayEnvRecord\n  ) => string[];\n  missingApiKeyError: string;\n  missingApiKeyIssue?: string;\n}\n\nconst genericMissingApiKeyIssue =\n  \"AI_GATEWAY_API_KEY is missing. The demo can render, but chat requests will fail until it is configured.\";\n\nexport function parseNodeVersion(version: string): ParsedNodeVersion {\n  const match = nodeVersionPattern.exec(version);\n  const major = Number(match?.[1]);\n  const minor = Number(match?.[2]);\n  const patch = Number(match?.[3]);\n\n  if (![major, minor, patch].every(Number.isInteger)) {\n    throw new Error(`Unable to parse Node.js version: \"${version}\".`);\n  }\n\n  return { major, minor, patch };\n}\n\nexport function getNodeMajor(version: string): number {\n  return parseNodeVersion(version).major;\n}\n\nfunction compareNodeVersions(\n  left: ParsedNodeVersion,\n  right: ParsedNodeVersion\n): number {\n  if (left.major !== right.major) {\n    return left.major - right.major;\n  }\n\n  if (left.minor !== right.minor) {\n    return left.minor - right.minor;\n  }\n\n  return left.patch - right.patch;\n}\n\nexport function assertSupportedNodeRuntime(version = process.version): number {\n  const parsedVersion = parseNodeVersion(version);\n  const minimumVersion = parseNodeVersion(MINIMUM_NODE_VERSION);\n\n  if (compareNodeVersions(parsedVersion, minimumVersion) < 0) {\n    throw new Error(\n      `Node.js ${version} is unsupported. This demo workspace requires Node.js >=${MINIMUM_NODE_VERSION}.`\n    );\n  }\n\n  return parsedVersion.major;\n}\n\nexport function resolveAiGatewayContractEnv(\n  env: AiGatewayEnvRecord,\n  options: Pick<AiGatewayContractOptions, \"defaultBaseURL\" | \"defaultChatModel\">\n): AiGatewayResolvedEnv {\n  return {\n    apiKey: env.AI_GATEWAY_API_KEY,\n    baseURL:\n      env.AI_GATEWAY_BASE_URL ||\n      options.defaultBaseURL ||\n      DEFAULT_GATEWAY_BASE_URL,\n    chatModel: env.AI_GATEWAY_CHAT_MODEL || options.defaultChatModel,\n  };\n}\n\nexport function readAiGatewayContractConfig(\n  env: AiGatewayEnvRecord,\n  options: AiGatewayContractOptions\n): AiGatewayContractConfig {\n  assertSupportedNodeRuntime();\n  const resolvedEnv = resolveAiGatewayContractEnv(env, options);\n\n  if (!resolvedEnv.apiKey) {\n    throw new Error(options.missingApiKeyError);\n  }\n\n  return {\n    apiKey: resolvedEnv.apiKey,\n    baseURL: resolvedEnv.baseURL,\n    chatModel: resolvedEnv.chatModel,\n  };\n}\n\nexport function buildAiGatewayContractSetupState<\n  TConfig extends AiGatewaySetupConfig,\n>(\n  env: AiGatewayEnvRecord,\n  options: AiGatewayContractOptions<TConfig>\n): AiGatewayContractSetupState<TConfig> {\n  const issues: string[] = [];\n  const resolvedEnv = resolveAiGatewayContractEnv(env, options);\n\n  try {\n    assertSupportedNodeRuntime();\n  } catch (error) {\n    issues.push(\n      error instanceof Error ? error.message : \"Unsupported Node.js runtime.\"\n    );\n  }\n\n  if (!resolvedEnv.apiKey) {\n    issues.push(options.missingApiKeyIssue || genericMissingApiKeyIssue);\n  }\n\n  issues.push(...(options.getAdditionalIssues?.(resolvedEnv, env) ?? []));\n\n  return {\n    config:\n      options.buildConfig?.(resolvedEnv, env) ??\n      ({\n        baseURL: resolvedEnv.baseURL,\n        chatModel: resolvedEnv.chatModel,\n      } as TConfig),\n    isReady: issues.length === 0,\n    issues,\n    nodeVersion: process.version,\n  };\n}\n\nexport function createAiGatewayFromContract(\n  env: AiGatewayEnvRecord,\n  options: AiGatewayContractOptions\n): ReturnType<typeof createGateway> {\n  const { apiKey, baseURL } = readAiGatewayContractConfig(env, options);\n\n  return createGateway({\n    apiKey,\n    baseURL,\n  });\n}\n",
      "type": "registry:lib",
      "target": "@lib/ai-gateway/contract.ts"
    },
    {
      "path": "registry/ultra-chatbot-agent/lib/ultra-chatbot-agent/attachment-config.ts",
      "content": "export const ultraChatbotAgentAcceptedUploadMediaTypes = [\n  \"application/pdf\",\n  \"image/jpeg\",\n  \"image/png\",\n] as const;\n\nexport const ultraChatbotAgentMaxUploadBytes = 5 * 1024 * 1024;\n\nexport const ultraChatbotAgentUnsupportedAttachmentMediaTypeError =\n  \"Only PDF, JPEG, and PNG attachments are supported.\";\n\nconst ultraChatbotAgentAcceptedUploadMediaTypeSet = new Set<string>(\n  ultraChatbotAgentAcceptedUploadMediaTypes\n);\n\nexport function isUltraChatbotAgentAcceptedUploadMediaType(mediaType: string) {\n  return ultraChatbotAgentAcceptedUploadMediaTypeSet.has(mediaType);\n}\n",
      "type": "registry:lib",
      "target": "@lib/ultra-chatbot-agent/attachment-config.ts"
    },
    {
      "path": "registry/ultra-chatbot-agent/lib/ultra-chatbot-agent/constants.ts",
      "content": "export const ultraChatbotAgentSuggestedActions = [\n  \"Draft a launch brief for a developer-facing AI feature.\",\n  \"Create a document comparing resume streams with plain request replay.\",\n  \"Review my saved document and suggest clearer rollout language.\",\n  \"Summarize the tradeoffs between GPT-5 mini and DeepSeek v4 flash.\",\n] as const;\n",
      "type": "registry:lib",
      "target": "@lib/ultra-chatbot-agent/constants.ts"
    },
    {
      "path": "registry/ultra-chatbot-agent/lib/ultra-chatbot-agent/knowledge-source.ts",
      "content": "import { ragChatbotSourceDocument } from \"@/lib/ultra-chatbot-agent/rag-chatbot/source-document\";\n\nexport const ultraChatbotAgentKnowledgeSource = {\n  description: ragChatbotSourceDocument.description,\n  documentUrl: ragChatbotSourceDocument.documentUrl,\n  slug: ragChatbotSourceDocument.slug,\n  sourcePageUrl: ragChatbotSourceDocument.sourcePageUrl,\n  title: ragChatbotSourceDocument.title,\n} as const;\n",
      "type": "registry:lib",
      "target": "@lib/ultra-chatbot-agent/knowledge-source.ts"
    },
    {
      "path": "registry/ultra-chatbot-agent/lib/ultra-chatbot-agent/project-docs-mcp/project-docs-catalog.ts",
      "content": "import { existsSync } from \"node:fs\";\nimport { readdir, readFile } from \"node:fs/promises\";\nimport path from \"node:path\";\n\nexport const projectDemoPatterns = [\n  \"foundation\",\n  \"rag\",\n  \"loop\",\n  \"tools\",\n  \"skills\",\n  \"sandbox\",\n  \"multimodal\",\n  \"structured-output\",\n  \"mcp\",\n  \"generative-ui\",\n] as const;\n\nexport type ProjectDemoPattern = (typeof projectDemoPatterns)[number];\n\nexport const projectDemoStatuses = [\"ready\", \"roadmap\"] as const;\n\nexport type ProjectDemoStatus = (typeof projectDemoStatuses)[number];\n\nexport interface ProjectDocsCatalogEntry {\n  docsPath: string;\n  href: `/demos/${string}`;\n  pattern: ProjectDemoPattern;\n  readmePath?: string;\n  slug: string;\n  source: string;\n  status: ProjectDemoStatus;\n  summary: string;\n  title: string;\n}\n\nconst excludedFrontendDocSlugs = new Set([\n  \"DOCS\",\n  \"index\",\n  \"agent-demo-structure\",\n  \"ai-sdk-recipes-checklist\",\n  \"homepage-gallery\",\n  \"registry-sync\",\n  \"shadcn-registry-distribution\",\n  \"ultra-chatbot-agent-source-checklist\",\n  \"workspace-ui\",\n]);\nconst roadmapSlugs = new Set([\"openai-agents-sdk-demo\", \"ultra-chatbot-agent\"]);\nconst frontmatterPattern = /^---\\r?\\n([\\s\\S]*?)\\r?\\n---\\r?\\n?/;\nconst headingPattern = /^#\\s+(.+)$/m;\nconst frontmatterDescriptionPattern = /^description:\\s*(.+)$/m;\nconst frontmatterTitlePattern = /^title:\\s*(.+)$/m;\n\nfunction toRepoPath(root: string, absolutePath: string) {\n  return path.relative(root, absolutePath);\n}\n\nfunction stripFrontmatter(content: string) {\n  const match = content.match(frontmatterPattern);\n\n  return match ? content.slice(match[0].length).trim() : content.trim();\n}\n\nfunction humanizeSlug(slug: string) {\n  return slug\n    .split(\"-\")\n    .filter(Boolean)\n    .map((segment) => segment.charAt(0).toUpperCase() + segment.slice(1))\n    .join(\" \");\n}\n\nfunction inferPattern(slug: string): ProjectDemoPattern {\n  if (slug.includes(\"mcp\")) {\n    return \"mcp\";\n  }\n\n  if (slug.includes(\"skills\")) {\n    return \"skills\";\n  }\n\n  if (slug.includes(\"sandbox\")) {\n    return \"sandbox\";\n  }\n\n  if (slug.includes(\"loop\")) {\n    return \"loop\";\n  }\n\n  if (slug.includes(\"rag\")) {\n    return \"rag\";\n  }\n\n  if (slug.includes(\"multimodal\")) {\n    return \"multimodal\";\n  }\n\n  if (slug.includes(\"generation\")) {\n    return \"structured-output\";\n  }\n\n  return \"tools\";\n}\n\nfunction inferStatus(slug: string): ProjectDemoStatus {\n  return roadmapSlugs.has(slug) ? \"roadmap\" : \"ready\";\n}\n\nfunction firstParagraph(body: string) {\n  return body\n    .split(/\\r?\\n/)\n    .map((line) => line.trim())\n    .filter(Boolean)\n    .find(\n      (line) =>\n        !(\n          line.startsWith(\"#\") ||\n          line.startsWith(\"- \") ||\n          line.startsWith(\"* \")\n        )\n    );\n}\n\nexport function findProjectRoot(start = process.cwd()) {\n  let current = start;\n  let packageRoot: string | null = null;\n\n  while (current !== path.dirname(current)) {\n    if (!packageRoot && existsSync(path.join(current, \"package.json\"))) {\n      packageRoot = current;\n    }\n\n    if (\n      existsSync(path.join(current, \"AGENTS.md\")) ||\n      existsSync(path.join(current, \"pnpm-workspace.yaml\")) ||\n      existsSync(path.join(current, \"docs\"))\n    ) {\n      return current;\n    }\n\n    current = path.dirname(current);\n  }\n\n  return packageRoot ?? start;\n}\n\nasync function readDocsMetadata(absolutePath: string, slug: string) {\n  const content = await readFile(absolutePath, \"utf8\");\n  const frontmatter = content.match(frontmatterPattern)?.[1] ?? \"\";\n  const body = stripFrontmatter(content);\n\n  return {\n    summary:\n      frontmatter.match(frontmatterDescriptionPattern)?.[1]?.trim() ??\n      firstParagraph(body) ??\n      `${humanizeSlug(slug)} project docs.`,\n    title:\n      frontmatter.match(frontmatterTitlePattern)?.[1]?.trim() ??\n      body.match(headingPattern)?.[1]?.trim() ??\n      humanizeSlug(slug),\n  };\n}\n\nexport async function loadProjectDocsCatalog(\n  root = findProjectRoot()\n): Promise<ProjectDocsCatalogEntry[]> {\n  const docsDirectory = path.join(root, \"docs/frontend\");\n\n  if (!existsSync(docsDirectory)) {\n    return [];\n  }\n\n  const entries = await readdir(docsDirectory);\n  const catalog: ProjectDocsCatalogEntry[] = [];\n\n  for (const entry of entries.sort()) {\n    if (!entry.endsWith(\".md\")) {\n      continue;\n    }\n\n    const slug = entry.slice(0, -3);\n\n    if (excludedFrontendDocSlugs.has(slug)) {\n      continue;\n    }\n\n    const absoluteDocsPath = path.join(docsDirectory, entry);\n    const metadata = await readDocsMetadata(absoluteDocsPath, slug);\n    const absoluteReadmePath = path.join(\n      root,\n      \"apps/web/features\",\n      slug,\n      \"README.md\"\n    );\n\n    catalog.push({\n      docsPath: toRepoPath(root, absoluteDocsPath),\n      href: `/demos/${slug}`,\n      pattern: inferPattern(slug),\n      readmePath: existsSync(absoluteReadmePath)\n        ? toRepoPath(root, absoluteReadmePath)\n        : undefined,\n      slug,\n      source: \"docs/frontend\",\n      status: inferStatus(slug),\n      summary: metadata.summary,\n      title: metadata.title,\n    });\n  }\n\n  return catalog.sort((left, right) => left.title.localeCompare(right.title));\n}\n\nasync function collectMarkdownPaths(root: string, directory: string) {\n  const absoluteDirectory = path.join(root, directory);\n\n  if (!existsSync(absoluteDirectory)) {\n    return [];\n  }\n\n  const entries = await readdir(absoluteDirectory, { withFileTypes: true });\n  const files: string[] = [];\n\n  for (const entry of entries) {\n    const relativePath = path.join(directory, entry.name);\n\n    if (entry.isDirectory()) {\n      files.push(...(await collectMarkdownPaths(root, relativePath)));\n      continue;\n    }\n\n    if (entry.isFile() && entry.name.endsWith(\".md\")) {\n      files.push(relativePath);\n    }\n  }\n\n  return files;\n}\n\nexport async function listProjectDocsSearchPaths(root = findProjectRoot()) {\n  const paths = new Set(await collectMarkdownPaths(root, \"docs\"));\n  const catalog = await loadProjectDocsCatalog(root);\n\n  for (const entry of catalog) {\n    if (entry.readmePath) {\n      paths.add(entry.readmePath);\n    }\n  }\n\n  return Array.from(paths).sort();\n}\n",
      "type": "registry:lib",
      "target": "@lib/ultra-chatbot-agent/project-docs-mcp/project-docs-catalog.ts"
    },
    {
      "path": "registry/ultra-chatbot-agent/lib/ultra-chatbot-agent/project-docs-mcp/project-mcp-server.ts",
      "content": "// biome-ignore lint/correctness/noUnresolvedImports: the MCP SDK wildcard export requires the .js subpath at runtime.\nimport { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\n// biome-ignore lint/correctness/noUnresolvedImports: the MCP SDK wildcard export requires the .js subpath at runtime.\nimport { WebStandardStreamableHTTPServerTransport } from \"@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js\";\nimport { z } from \"zod\";\n\nimport {\n  projectDemoPatterns,\n  projectDemoStatuses,\n} from \"./project-docs-catalog\";\nimport {\n  listDemoCatalogForMcp,\n  readDemoDocsForMcp,\n  searchProjectDocsForMcp,\n} from \"./project-tools\";\n\nconst jsonText = (value: unknown) => ({\n  content: [\n    {\n      text: JSON.stringify(value, null, 2),\n      type: \"text\" as const,\n    },\n  ],\n});\n\nexport function createProjectDocsMcpServer() {\n  const server = new McpServer(\n    {\n      name: \"mcp-agent-project-docs\",\n      title: \"MCP Agent Project Docs\",\n      version: \"0.1.0\",\n    },\n    {\n      instructions:\n        \"Use these tools for repository docs, demo catalog, and AI SDK recipes checklist questions.\",\n    }\n  );\n\n  server.registerTool(\n    \"list_demos\",\n    {\n      description:\n        \"List demo catalog entries, optionally filtered by status or pattern.\",\n      inputSchema: {\n        pattern: z.enum(projectDemoPatterns).optional(),\n        status: z.enum(projectDemoStatuses).optional(),\n      },\n    },\n    async ({ pattern, status }) =>\n      jsonText(await listDemoCatalogForMcp({ pattern, status }))\n  );\n\n  server.registerTool(\n    \"read_demo_docs\",\n    {\n      description:\n        \"Read durable docs and the feature README for one demo slug when present.\",\n      inputSchema: {\n        slug: z.string().min(1),\n      },\n    },\n    async ({ slug }) => jsonText(await readDemoDocsForMcp({ slug }))\n  );\n\n  server.registerTool(\n    \"search_project_docs\",\n    {\n      description: \"Search durable project docs for line-level matches.\",\n      inputSchema: {\n        limit: z.number().int().min(1).max(30).optional(),\n        query: z.string().min(1),\n      },\n    },\n    async ({ limit, query }) =>\n      jsonText(await searchProjectDocsForMcp({ limit, query }))\n  );\n\n  return server;\n}\n\nexport async function handleProjectDocsMcpRequest(request: Request) {\n  const server = createProjectDocsMcpServer();\n  const transport = new WebStandardStreamableHTTPServerTransport({\n    enableJsonResponse: true,\n    sessionIdGenerator: undefined,\n  });\n\n  await server.connect(transport);\n\n  try {\n    return await transport.handleRequest(request);\n  } finally {\n    await server.close();\n  }\n}\n",
      "type": "registry:lib",
      "target": "@lib/ultra-chatbot-agent/project-docs-mcp/project-mcp-server.ts"
    },
    {
      "path": "registry/ultra-chatbot-agent/lib/ultra-chatbot-agent/project-docs-mcp/project-tools.ts",
      "content": "import { existsSync } from \"node:fs\";\nimport { readFile } from \"node:fs/promises\";\nimport path from \"node:path\";\n\nimport {\n  findProjectRoot,\n  listProjectDocsSearchPaths,\n  loadProjectDocsCatalog,\n  type ProjectDemoPattern,\n  type ProjectDemoStatus,\n  type ProjectDocsCatalogEntry,\n} from \"./project-docs-catalog\";\n\nexport interface ProjectDocFile {\n  content: string;\n  path: string;\n}\n\nexport interface DemoDocsBundle {\n  files: ProjectDocFile[];\n  meta: ProjectDocsCatalogEntry;\n  slug: string;\n}\n\nexport interface ProjectDocSearchMatch {\n  line: number;\n  path: string;\n  text: string;\n}\n\ninterface ScoredProjectDocSearchMatch extends ProjectDocSearchMatch {\n  order: number;\n  score: number;\n}\n\nexport const projectMcpToolDefinitions = [\n  {\n    description:\n      \"List demo catalog entries, optionally filtered by status or pattern.\",\n    name: \"list_demos\",\n  },\n  {\n    description:\n      \"Read the durable docs bundle for one demo slug, including docs/frontend and feature README when present.\",\n    name: \"read_demo_docs\",\n  },\n  {\n    description:\n      \"Search the project docs system for concise line-level matches.\",\n    name: \"search_project_docs\",\n  },\n] as const;\n\nasync function readRepoFile(\n  relativePath: string,\n  root = findProjectRoot()\n): Promise<ProjectDocFile | null> {\n  const absolutePath = path.join(root, relativePath);\n\n  if (!existsSync(absolutePath)) {\n    return null;\n  }\n\n  return {\n    content: await readFile(absolutePath, \"utf8\"),\n    path: path.relative(root, absolutePath),\n  };\n}\n\nconst tokenPattern = /[\\p{L}\\p{N}][\\p{L}\\p{N}._/-]*/gu;\nconst cjkPhrasePattern = /[\\p{Script=Han}]{2,}/gu;\nconst tokenSeparatorPattern = /[._/-]+/g;\nconst searchStopWords = new Set([\n  \"and\",\n  \"based\",\n  \"between\",\n  \"core\",\n  \"docs\",\n  \"for\",\n  \"from\",\n  \"the\",\n  \"with\",\n]);\n\nfunction normalizeSearchText(value: string) {\n  return value.normalize(\"NFKC\").toLowerCase();\n}\n\nfunction pushSearchToken(tokens: Set<string>, token: string) {\n  const normalizedToken = token.trim();\n\n  if (\n    normalizedToken.length < 2 ||\n    searchStopWords.has(normalizedToken) ||\n    tokens.size >= 32\n  ) {\n    return;\n  }\n\n  tokens.add(normalizedToken);\n}\n\nfunction getSearchTokens(query: string) {\n  const normalizedQuery = normalizeSearchText(query);\n  const tokens = new Set<string>();\n\n  for (const match of normalizedQuery.matchAll(tokenPattern)) {\n    for (const segment of match[0].split(tokenSeparatorPattern)) {\n      pushSearchToken(tokens, segment);\n    }\n  }\n\n  for (const match of normalizedQuery.matchAll(cjkPhrasePattern)) {\n    const phrase = match[0];\n\n    for (let index = 0; index < phrase.length - 1; index += 1) {\n      pushSearchToken(tokens, phrase.slice(index, index + 2));\n    }\n  }\n\n  return [...tokens];\n}\n\nfunction scoreSearchLine(input: {\n  line: string;\n  normalizedQuery: string;\n  path: string;\n  tokens: string[];\n}) {\n  const normalizedLine = normalizeSearchText(input.line);\n  const normalizedPath = normalizeSearchText(input.path);\n  const haystack = `${normalizedPath} ${normalizedLine}`;\n  let score = haystack.includes(input.normalizedQuery) ? 1000 : 0;\n\n  for (const token of input.tokens) {\n    if (normalizedLine.includes(token)) {\n      score += token.length >= 6 ? 10 : 5;\n    }\n\n    if (normalizedPath.includes(token)) {\n      score += token.length >= 6 ? 6 : 3;\n    }\n  }\n\n  if (score === 0) {\n    return 0;\n  }\n\n  if (input.line.trimStart().startsWith(\"#\")) {\n    score += 4;\n  }\n\n  return score;\n}\n\nexport async function listDemoCatalogForMcp({\n  pattern,\n  status,\n}: {\n  pattern?: ProjectDemoPattern;\n  status?: ProjectDemoStatus;\n} = {}) {\n  const catalog = await loadProjectDocsCatalog();\n\n  return catalog\n    .filter((entry) => (status ? entry.status === status : true))\n    .filter((entry) => (pattern ? entry.pattern === pattern : true))\n    .map((entry) => ({\n      href: entry.status === \"ready\" ? entry.href : undefined,\n      pattern: entry.pattern,\n      slug: entry.slug,\n      source: entry.source,\n      status: entry.status,\n      summary: entry.summary,\n      title: entry.title,\n    }));\n}\n\nexport async function readDemoDocsForMcp({\n  slug,\n}: {\n  slug: string;\n}): Promise<DemoDocsBundle> {\n  const root = findProjectRoot();\n  const catalog = await loadProjectDocsCatalog(root);\n  const meta = catalog.find((entry) => entry.slug === slug);\n\n  if (!meta) {\n    throw new Error(`Unknown demo slug: ${slug}`);\n  }\n\n  const candidatePaths = [meta.docsPath, meta.readmePath].filter(\n    (candidate): candidate is string => Boolean(candidate)\n  );\n  const files = (\n    await Promise.all(\n      candidatePaths.map((candidate) => readRepoFile(candidate, root))\n    )\n  ).filter((file): file is ProjectDocFile => Boolean(file));\n\n  if (files.length === 0) {\n    throw new Error(`No durable docs were found for demo slug: ${slug}`);\n  }\n\n  return {\n    files,\n    meta,\n    slug,\n  };\n}\n\nexport async function searchProjectDocsForMcp({\n  limit = 12,\n  query,\n}: {\n  limit?: number;\n  query: string;\n}) {\n  const normalizedQuery = normalizeSearchText(query.trim());\n\n  if (!normalizedQuery) {\n    throw new Error(\"Expected a non-empty docs search query.\");\n  }\n\n  const tokens = getSearchTokens(query);\n  const matches: ScoredProjectDocSearchMatch[] = [];\n  const root = findProjectRoot();\n  const docsSearchPaths = await listProjectDocsSearchPaths(root);\n  let order = 0;\n\n  for (const docsPath of docsSearchPaths) {\n    const file = await readRepoFile(docsPath, root);\n\n    if (!file) {\n      continue;\n    }\n\n    file.content.split(\"\\n\").forEach((line, index) => {\n      const trimmedLine = line.trim();\n\n      if (!trimmedLine) {\n        return;\n      }\n\n      const score = scoreSearchLine({\n        line: trimmedLine,\n        normalizedQuery,\n        path: file.path,\n        tokens,\n      });\n\n      if (score > 0) {\n        matches.push({\n          line: index + 1,\n          order,\n          path: file.path,\n          score,\n          text: line.trim(),\n        });\n        order += 1;\n      }\n    });\n  }\n\n  return {\n    matches: matches\n      .sort(\n        (left, right) => right.score - left.score || left.order - right.order\n      )\n      .slice(0, limit)\n      .map(({ line, path: matchPath, text }) => ({\n        line,\n        path: matchPath,\n        text,\n      })),\n    query,\n  };\n}\n",
      "type": "registry:lib",
      "target": "@lib/ultra-chatbot-agent/project-docs-mcp/project-tools.ts"
    },
    {
      "path": "registry/ultra-chatbot-agent/lib/ultra-chatbot-agent/rag-chatbot/chat.ts",
      "content": "import {\n  convertToModelMessages,\n  stepCountIs,\n  streamText,\n  tool,\n  type UIMessage,\n} from \"ai\";\nimport { z } from \"zod\";\n\nimport {\n  createRagChatbotGateway,\n  getRagChatbotConfig,\n  getRagChatbotEnv,\n  type RagChatbotEnv,\n} from \"./env\";\nimport { findRelevantContent } from \"./retrieval\";\n\nconst systemPrompt = [\n  \"You are the rag-chatbot demo for an independent-site document support agent.\",\n  \"Answer only with information grounded in the indexed document knowledge base.\",\n  \"Use the getInformation tool before answering any question about the document.\",\n  'If the tool returns no relevant evidence, reply exactly with \"Sorry, I don\\'t know.\"',\n  \"When evidence exists, answer concisely and mention the most relevant document section or page when possible.\",\n  \"Do not turn document guidance into legal, trademark, or commercial authorization claims.\",\n].join(\" \");\n\nexport async function streamRagChatbot(\n  messages: UIMessage[],\n  env: RagChatbotEnv = getRagChatbotEnv()\n) {\n  const gateway = createRagChatbotGateway(env);\n  const { chatModel } = getRagChatbotConfig(env);\n\n  const result = streamText({\n    model: gateway(chatModel),\n    system: systemPrompt,\n    messages: await convertToModelMessages(messages),\n    stopWhen: stepCountIs(5),\n    tools: {\n      getInformation: tool({\n        description:\n          \"Retrieve grounded document snippets from the indexed knowledge base to answer the user's question.\",\n        inputSchema: z.object({\n          question: z\n            .string()\n            .min(1)\n            .describe(\"The user's question about the indexed document.\"),\n        }),\n        execute: async ({ question }) => findRelevantContent(question, env),\n      }),\n    },\n  });\n\n  return result.toUIMessageStreamResponse();\n}\n",
      "type": "registry:lib",
      "target": "@lib/ultra-chatbot-agent/rag-chatbot/chat.ts"
    },
    {
      "path": "registry/ultra-chatbot-agent/lib/ultra-chatbot-agent/rag-chatbot/database.ts",
      "content": "import { Pool } from \"@neondatabase/serverless\";\nimport { drizzle } from \"drizzle-orm/neon-serverless\";\n\nimport {\n  getRagChatbotDatabaseConfig,\n  getRagChatbotEnv,\n  type RagChatbotEnv,\n} from \"./env\";\nimport {\n  ragChatbotEmbeddings,\n  ragChatbotResources,\n  ragChatbotSchema,\n} from \"./schema\";\n\nfunction createRagChatbotDatabase(connectionString: string) {\n  const client = new Pool({ connectionString });\n  const database = drizzle({\n    client,\n    schema: ragChatbotSchema,\n  });\n\n  return { client, database };\n}\n\ntype RagChatbotDatabase = ReturnType<\n  typeof createRagChatbotDatabase\n>[\"database\"];\n\nexport interface RagChatbotDatabaseModule {\n  database: RagChatbotDatabase;\n  ragChatbotEmbeddings: typeof ragChatbotEmbeddings;\n  ragChatbotResources: typeof ragChatbotResources;\n}\n\nlet databaseModulePromise: Promise<RagChatbotDatabaseModule> | null = null;\n\nexport async function loadRagChatbotDatabase(\n  env: RagChatbotEnv = getRagChatbotEnv()\n): Promise<RagChatbotDatabaseModule> {\n  if (!databaseModulePromise) {\n    const { databaseUrl } = getRagChatbotDatabaseConfig(env);\n    const { database } = createRagChatbotDatabase(databaseUrl);\n\n    databaseModulePromise = Promise.resolve({\n      database,\n      ragChatbotEmbeddings,\n      ragChatbotResources,\n    });\n  }\n\n  return databaseModulePromise;\n}\n",
      "type": "registry:lib",
      "target": "@lib/ultra-chatbot-agent/rag-chatbot/database.ts"
    },
    {
      "path": "registry/ultra-chatbot-agent/lib/ultra-chatbot-agent/rag-chatbot/env-source.ts",
      "content": "import type { RagChatbotEnv } from \"./env\";\n\nexport function getRagChatbotAppEnv(): RagChatbotEnv {\n  // biome-ignore lint/style/noProcessEnv: Registry source installs into consumer apps without this repo's env wrapper.\n  return process.env;\n}\n",
      "type": "registry:lib",
      "target": "@lib/ultra-chatbot-agent/rag-chatbot/env-source.ts"
    },
    {
      "path": "registry/ultra-chatbot-agent/lib/ultra-chatbot-agent/rag-chatbot/env.ts",
      "content": "import { getRagChatbotAppEnv } from \"./env-source\";\nimport {\n  buildAiGatewayContractSetupState,\n  createAiGatewayFromContract,\n  readAiGatewayContractConfig,\n  type AiGatewayContractConfig,\n  type AiGatewayContractSetupState,\n  type AiGatewayEnvRecord,\n  type AiGatewaySetupConfig,\n} from \"@/lib/ai-gateway/contract\";\n\nconst DEFAULT_RAG_CHATBOT_CHAT_MODEL = \"openai/gpt-4.1-mini\";\nconst DEFAULT_EMBEDDING_MODEL = \"openai/text-embedding-3-small\";\n\nexport type RagChatbotEnv = AiGatewayEnvRecord;\n\nexport interface RagChatbotConfig extends AiGatewayContractConfig {\n  databaseUrl: string | undefined;\n  embeddingModel: string;\n}\n\nexport interface RagChatbotSetupConfig extends AiGatewaySetupConfig {\n  embeddingModel: string;\n}\n\nexport type RagChatbotSetupState =\n  AiGatewayContractSetupState<RagChatbotSetupConfig>;\n\nexport type RagChatbotGateway = ReturnType<typeof createAiGatewayFromContract>;\n\nconst ragChatbotContract = {\n  defaultChatModel: DEFAULT_RAG_CHATBOT_CHAT_MODEL,\n  missingApiKeyError:\n    \"Missing AI_GATEWAY_API_KEY. Add it to .env.local before using the RAG chatbot.\",\n  missingApiKeyIssue:\n    \"AI_GATEWAY_API_KEY is missing. The demo can render, but chat requests will fail until it is configured.\",\n} as const;\n\nexport function getRagChatbotEnv(): RagChatbotEnv {\n  return getRagChatbotAppEnv();\n}\n\nfunction resolveRagChatbotEmbeddingModel(env: RagChatbotEnv) {\n  return env.AI_GATEWAY_EMBEDDING_MODEL || DEFAULT_EMBEDDING_MODEL;\n}\n\nexport function getRagChatbotConfig(\n  env: RagChatbotEnv = getRagChatbotEnv()\n): RagChatbotConfig {\n  return {\n    ...readAiGatewayContractConfig(env, ragChatbotContract),\n    databaseUrl: env.DATABASE_URL,\n    embeddingModel: resolveRagChatbotEmbeddingModel(env),\n  };\n}\n\nexport function getRagChatbotDatabaseConfig(\n  env: RagChatbotEnv = getRagChatbotEnv()\n) {\n  const databaseUrl = env.DATABASE_URL;\n\n  if (!databaseUrl) {\n    throw new Error(\n      \"DATABASE_URL is missing. The RAG chatbot requires a writable pgvector database.\"\n    );\n  }\n\n  return { databaseUrl };\n}\n\nexport function getRagChatbotSetupState(\n  env: RagChatbotEnv = getRagChatbotEnv()\n): RagChatbotSetupState {\n  return buildAiGatewayContractSetupState(env, {\n    ...ragChatbotContract,\n    buildConfig: (resolvedEnv, currentEnv) => ({\n      baseURL: resolvedEnv.baseURL,\n      chatModel: resolvedEnv.chatModel,\n      embeddingModel: resolveRagChatbotEmbeddingModel(currentEnv),\n    }),\n  });\n}\n\nexport function getRagChatbotIndexSetupIssue(\n  env: RagChatbotEnv = getRagChatbotEnv()\n): string | null {\n  if (!env.AI_GATEWAY_API_KEY) {\n    return \"AI_GATEWAY_API_KEY is missing. Source indexing requires embedding generation through AI Gateway.\";\n  }\n\n  if (!env.DATABASE_URL) {\n    return \"DATABASE_URL is missing. Source indexing requires a writable pgvector database.\";\n  }\n\n  return null;\n}\n\nexport function createRagChatbotGateway(\n  env: RagChatbotEnv = getRagChatbotEnv()\n): RagChatbotGateway {\n  return createAiGatewayFromContract(env, ragChatbotContract);\n}\n",
      "type": "registry:lib",
      "target": "@lib/ultra-chatbot-agent/rag-chatbot/env.ts"
    },
    {
      "path": "registry/ultra-chatbot-agent/lib/ultra-chatbot-agent/rag-chatbot/index-source.ts",
      "content": "import { createHash } from \"node:crypto\";\nimport { embedMany } from \"ai\";\nimport { count, eq } from \"drizzle-orm\";\n\nimport {\n  type RagChatbotDatabaseModule,\n  loadRagChatbotDatabase,\n} from \"./database\";\nimport {\n  createRagChatbotGateway,\n  getRagChatbotConfig,\n  getRagChatbotEnv,\n  type RagChatbotEnv,\n} from \"./env\";\nimport {\n  buildRagPdfChunks,\n  downloadPdfDocument,\n  type ExtractedPdfPage,\n  extractPdfPages,\n} from \"./ingestion\";\nimport { ragChatbotSourceDocument } from \"./source-document\";\n\ninterface IndexRagChatbotSourceDependencies {\n  downloadDocument: (documentUrl: string) => Promise<Uint8Array>;\n  extractPages: (pdfBytes: Uint8Array) => Promise<ExtractedPdfPage[]>;\n  loadDatabase: () => Promise<RagChatbotDatabaseModule>;\n}\n\nexport interface RagSourceIndexResult {\n  chunkCount: number;\n  sourceSlug: string;\n  status: \"already-indexed\" | \"indexed\";\n}\n\nfunction buildDocumentHash(pdfBytes: Uint8Array) {\n  return createHash(\"sha256\").update(pdfBytes).digest(\"hex\");\n}\n\nfunction buildEmbeddingRows(\n  chunks: ReturnType<typeof buildRagPdfChunks>,\n  embeddings: Awaited<ReturnType<typeof embedMany>>[\"embeddings\"],\n  resourceId: string\n) {\n  return chunks.map((chunk, index) => {\n    const embedding = embeddings[index];\n\n    if (!embedding) {\n      throw new Error(\n        `Missing embedding ${index} while indexing ${ragChatbotSourceDocument.slug}.`\n      );\n    }\n\n    return {\n      chunkIndex: index,\n      content: chunk.content,\n      embedding,\n      pageLabel: chunk.pageLabel,\n      resourceId,\n      sectionTitle: chunk.sectionTitle,\n    };\n  });\n}\n\nexport async function indexRagChatbotSource(\n  env: RagChatbotEnv = getRagChatbotEnv(),\n  dependencies: IndexRagChatbotSourceDependencies = {\n    downloadDocument: downloadPdfDocument,\n    extractPages: extractPdfPages,\n    loadDatabase: loadRagChatbotDatabase,\n  }\n): Promise<RagSourceIndexResult> {\n  const pdfBytes = await dependencies.downloadDocument(\n    ragChatbotSourceDocument.documentUrl\n  );\n  const contentHash = buildDocumentHash(pdfBytes);\n  const { database, ragChatbotEmbeddings, ragChatbotResources } =\n    await dependencies.loadDatabase();\n  const existingResources = await database\n    .select({\n      contentHash: ragChatbotResources.contentHash,\n      id: ragChatbotResources.id,\n    })\n    .from(ragChatbotResources)\n    .where(eq(ragChatbotResources.sourceSlug, ragChatbotSourceDocument.slug))\n    .limit(1);\n  const existingResource = existingResources[0];\n\n  if (existingResource?.contentHash === contentHash) {\n    const chunkCountRows = await database\n      .select({ chunkCount: count() })\n      .from(ragChatbotEmbeddings)\n      .where(eq(ragChatbotEmbeddings.resourceId, existingResource.id));\n\n    return {\n      chunkCount: chunkCountRows[0]?.chunkCount ?? 0,\n      sourceSlug: ragChatbotSourceDocument.slug,\n      status: \"already-indexed\",\n    };\n  }\n\n  const pages = await dependencies.extractPages(pdfBytes);\n  const chunks = buildRagPdfChunks(pages);\n\n  if (chunks.length === 0) {\n    throw new Error(\n      `The RAG source PDF at ${ragChatbotSourceDocument.documentUrl} did not produce any indexable text.`\n    );\n  }\n\n  const gateway = createRagChatbotGateway(env);\n  const { embeddingModel } = getRagChatbotConfig(env);\n  const { embeddings } = await embedMany({\n    model: gateway.embeddingModel(embeddingModel),\n    values: chunks.map((chunk) => chunk.content),\n  });\n  const resourceValues = {\n    contentHash,\n    description: ragChatbotSourceDocument.description,\n    documentUrl: ragChatbotSourceDocument.documentUrl,\n    sourcePageUrl: ragChatbotSourceDocument.sourcePageUrl,\n    sourceSlug: ragChatbotSourceDocument.slug,\n    title: ragChatbotSourceDocument.title,\n    updatedAt: new Date(),\n  };\n\n  if (existingResource) {\n    await database.transaction(async (transaction) => {\n      await transaction\n        .update(ragChatbotResources)\n        .set(resourceValues)\n        .where(eq(ragChatbotResources.id, existingResource.id));\n      await transaction\n        .delete(ragChatbotEmbeddings)\n        .where(eq(ragChatbotEmbeddings.resourceId, existingResource.id));\n      await transaction\n        .insert(ragChatbotEmbeddings)\n        .values(buildEmbeddingRows(chunks, embeddings, existingResource.id));\n    });\n  } else {\n    await database.transaction(async (transaction) => {\n      const [resource] = await transaction\n        .insert(ragChatbotResources)\n        .values(resourceValues)\n        .returning({ id: ragChatbotResources.id });\n\n      if (!resource) {\n        throw new Error(\n          \"Failed to create the source resource row for the RAG chatbot.\"\n        );\n      }\n\n      await transaction\n        .insert(ragChatbotEmbeddings)\n        .values(buildEmbeddingRows(chunks, embeddings, resource.id));\n    });\n  }\n\n  return {\n    chunkCount: chunks.length,\n    sourceSlug: ragChatbotSourceDocument.slug,\n    status: \"indexed\",\n  };\n}\n",
      "type": "registry:lib",
      "target": "@lib/ultra-chatbot-agent/rag-chatbot/index-source.ts"
    },
    {
      "path": "registry/ultra-chatbot-agent/lib/ultra-chatbot-agent/rag-chatbot/ingestion.ts",
      "content": "import { getDocument } from \"pdfjs-dist/legacy/build/pdf.mjs\";\n\nexport interface ExtractedPdfPage {\n  pageNumber: number;\n  text: string;\n}\n\nexport interface RagPdfChunk {\n  content: string;\n  pageLabel: string;\n  sectionTitle: string | null;\n}\n\ninterface BuildRagPdfChunksOptions {\n  maxChunkLength?: number;\n}\n\nconst defaultMaxChunkLength = 700;\nfunction normalizeExtractedLine(text: string) {\n  return text.replace(/\\s+/g, \" \").replace(/\\s+([,.;:!?])/g, \"$1\").trim();\n}\n\nfunction normalizePageLines(text: string) {\n  return text\n    .split(\"\\n\")\n    .map((line) => line.trim())\n    .filter((line) => line.length > 0);\n}\n\nfunction deriveSectionTitle(lines: string[]) {\n  const candidate = lines[0];\n\n  if (!candidate || candidate.length > 80) {\n    return null;\n  }\n\n  return candidate;\n}\n\nfunction splitIntoSentences(text: string) {\n  return text\n    .split(/(?<=[.!?])\\s+/)\n    .map((sentence) => sentence.trim())\n    .filter((sentence) => sentence.length > 0);\n}\n\nexport function buildRagPdfChunks(\n  pages: ExtractedPdfPage[],\n  options: BuildRagPdfChunksOptions = {}\n): RagPdfChunk[] {\n  const maxChunkLength = options.maxChunkLength ?? defaultMaxChunkLength;\n\n  return pages.flatMap((page) => {\n    const lines = normalizePageLines(page.text);\n\n    if (lines.length === 0) {\n      return [];\n    }\n\n    const sectionTitle = deriveSectionTitle(lines);\n    const normalizedText = lines.join(\" \");\n    const sentences = splitIntoSentences(normalizedText);\n    const chunks: RagPdfChunk[] = [];\n    let currentChunk = \"\";\n\n    for (const sentence of sentences) {\n      const candidate = currentChunk\n        ? `${currentChunk} ${sentence}`\n        : sentence;\n\n      if (candidate.length <= maxChunkLength || currentChunk.length === 0) {\n        currentChunk = candidate;\n        continue;\n      }\n\n      chunks.push({\n        content: currentChunk,\n        pageLabel: String(page.pageNumber),\n        sectionTitle,\n      });\n      currentChunk = sentence;\n    }\n\n    if (currentChunk.length > 0) {\n      chunks.push({\n        content: currentChunk,\n        pageLabel: String(page.pageNumber),\n        sectionTitle,\n      });\n    }\n\n    return chunks;\n  });\n}\n\nexport async function downloadPdfDocument(\n  documentUrl: string,\n  download = fetch\n): Promise<Uint8Array> {\n  const response = await download(documentUrl);\n\n  if (!response.ok) {\n    throw new Error(\n      `Failed to download the RAG source PDF from ${documentUrl}. Received ${response.status}.`\n    );\n  }\n\n  return new Uint8Array(await response.arrayBuffer());\n}\n\nexport async function extractPdfPages(\n  pdfBytes: Uint8Array\n): Promise<ExtractedPdfPage[]> {\n  const pdf = await getDocument({ data: pdfBytes }).promise;\n  const pages: ExtractedPdfPage[] = [];\n\n  for (let pageNumber = 1; pageNumber <= pdf.numPages; pageNumber += 1) {\n    const page = await pdf.getPage(pageNumber);\n    const textContent = await page.getTextContent();\n    const lines: string[] = [];\n    let currentLine = \"\";\n\n    for (const item of textContent.items as Array<{\n      hasEOL?: boolean;\n      str: string;\n    }>) {\n      const value = normalizeExtractedLine(item.str);\n\n      if (value.length > 0) {\n        currentLine = currentLine ? `${currentLine} ${value}` : value;\n      }\n\n      if (item.hasEOL) {\n        lines.push(normalizeExtractedLine(currentLine));\n        currentLine = \"\";\n      }\n    }\n\n    if (currentLine.length > 0) {\n      lines.push(normalizeExtractedLine(currentLine));\n    }\n\n    const text = lines.filter((line) => line.length > 0).join(\"\\n\");\n\n    if (text.length > 0) {\n      pages.push({\n        pageNumber,\n        text,\n      });\n    }\n  }\n\n  return pages;\n}\n",
      "type": "registry:lib",
      "target": "@lib/ultra-chatbot-agent/rag-chatbot/ingestion.ts"
    },
    {
      "path": "registry/ultra-chatbot-agent/lib/ultra-chatbot-agent/rag-chatbot/knowledge-base-status.ts",
      "content": "import { count, eq } from \"drizzle-orm\";\n\nimport {\n  type RagChatbotDatabaseModule,\n  loadRagChatbotDatabase,\n} from \"./database\";\nimport { getPortableRagIndexResourceCount } from \"./portable-index\";\nimport { ragChatbotSourceDocument } from \"./source-document\";\n\ntype DemoEnv = Record<string, string | undefined>;\ntype RagRetrievalLabel = \"Portable index\" | \"pgvector\";\n\nexport interface RagKnowledgeBaseStatus {\n  indexedResourceCount: number;\n  isReady: boolean;\n  message: string | null;\n  retrievalLabel: RagRetrievalLabel;\n  statusLabel: \"Index required\" | \"Ready\" | \"Setup required\";\n}\n\nexport interface RagKnowledgeBaseStatusDependencies {\n  getIndexedResourceCount?: (env: DemoEnv) => Promise<number>;\n  loadDatabase?: () => Promise<RagChatbotDatabaseModule>;\n}\n\nexport function getRagDatabaseSetupIssue(env: DemoEnv): string | null {\n  if (env.DATABASE_URL) {\n    return null;\n  }\n\n  return null;\n}\n\nexport function getRagIndexRequiredMessage() {\n  return `No preindexed documents are available for the RAG chatbot. Run POST /api/demos/rag-chatbot/index to index ${ragChatbotSourceDocument.title}.`;\n}\n\nexport async function getIndexedResourceCount(\n  env: DemoEnv,\n  dependencies: RagKnowledgeBaseStatusDependencies = {}\n): Promise<number> {\n  if (!env.DATABASE_URL) {\n    return 0;\n  }\n\n  const loadDatabase = dependencies.loadDatabase ?? loadRagChatbotDatabase;\n  const { database, ragChatbotResources } = await loadDatabase();\n  const rows = await database\n    .select({ resourceCount: count() })\n    .from(ragChatbotResources)\n    .where(eq(ragChatbotResources.sourceSlug, ragChatbotSourceDocument.slug));\n\n  return rows[0]?.resourceCount ?? 0;\n}\n\nexport async function getRagKnowledgeBaseStatus(\n  env: DemoEnv,\n  dependencies: RagKnowledgeBaseStatusDependencies = {}\n): Promise<RagKnowledgeBaseStatus> {\n  const databaseIssue = getRagDatabaseSetupIssue(env);\n\n  if (databaseIssue) {\n    return {\n      indexedResourceCount: 0,\n      isReady: false,\n      message: databaseIssue,\n      retrievalLabel: \"pgvector\",\n      statusLabel: \"Setup required\",\n    };\n  }\n\n  if (!env.DATABASE_URL) {\n    return {\n      indexedResourceCount: getPortableRagIndexResourceCount(),\n      isReady: true,\n      message: null,\n      retrievalLabel: \"Portable index\",\n      statusLabel: \"Ready\",\n    };\n  }\n\n  try {\n    const indexedResourceCount = dependencies.getIndexedResourceCount\n      ? await dependencies.getIndexedResourceCount(env)\n      : await getIndexedResourceCount(env, dependencies);\n\n    if (indexedResourceCount === 0) {\n      return {\n        indexedResourceCount,\n        isReady: false,\n        message: getRagIndexRequiredMessage(),\n        retrievalLabel: \"pgvector\",\n        statusLabel: \"Index required\",\n      };\n    }\n\n    return {\n      indexedResourceCount,\n      isReady: true,\n      message: null,\n      retrievalLabel: \"pgvector\",\n      statusLabel: \"Ready\",\n    };\n  } catch (error) {\n    return {\n      indexedResourceCount: 0,\n      isReady: false,\n      message:\n        error instanceof Error\n          ? `Failed to inspect the indexed document state. ${error.message}`\n          : \"Failed to inspect the indexed document state.\",\n      retrievalLabel: \"pgvector\",\n      statusLabel: \"Setup required\",\n    };\n  }\n}\n\nexport async function ensureRagKnowledgeBaseReady(\n  env: DemoEnv,\n  dependencies: RagKnowledgeBaseStatusDependencies = {}\n) {\n  const status = await getRagKnowledgeBaseStatus(env, dependencies);\n\n  if (!status.isReady) {\n    throw new Error(status.message ?? \"The RAG knowledge base is unavailable.\");\n  }\n}\n",
      "type": "registry:lib",
      "target": "@lib/ultra-chatbot-agent/rag-chatbot/knowledge-base-status.ts"
    },
    {
      "path": "registry/ultra-chatbot-agent/lib/ultra-chatbot-agent/rag-chatbot/portable-index.ts",
      "content": "import { ragChatbotSourceDocument } from \"./source-document\";\n\nexport interface PortableRagIndexEntry {\n  content: string;\n  pageLabel: string | null;\n  sectionTitle: string;\n}\n\nconst portableRagIndex: PortableRagIndexEntry[] = [\n  {\n    content:\n      \"The NASA logotype is the primary identifying element in the graphics system. The manual treats it as the agency's routine visual signature and expects it to be used consistently across approved communications.\",\n    pageLabel: null,\n    sectionTitle: \"The NASA Logotype\",\n  },\n  {\n    content:\n      \"The logotype should keep strong contrast and controlled color use. The sample system emphasizes NASA red, black, and white as core identity colors, with the logotype kept clear of busy backgrounds.\",\n    pageLabel: null,\n    sectionTitle: \"The NASA Logotype: Use of Color\",\n  },\n  {\n    content:\n      \"The NASA seal has a narrower role than the logotype. It is reserved for formal, ceremonial, and official contexts, while the logotype is the regular identifier for publications, signage, and day-to-day communications.\",\n    pageLabel: null,\n    sectionTitle: \"Seal and Logotype Usage\",\n  },\n  {\n    content:\n      \"The core visual system combines the NASA logotype, the agency color palette, typography, layout discipline, and clear reproduction rules so that public materials feel consistent across many formats.\",\n    pageLabel: null,\n    sectionTitle: \"Core Visual System\",\n  },\n];\n\nconst wordPattern = /[a-z0-9]+/g;\nconst stopWords = new Set([\n  \"a\",\n  \"about\",\n  \"and\",\n  \"be\",\n  \"does\",\n  \"how\",\n  \"in\",\n  \"is\",\n  \"it\",\n  \"manual\",\n  \"of\",\n  \"say\",\n  \"should\",\n  \"summarize\",\n  \"the\",\n  \"this\",\n  \"to\",\n  \"used\",\n  \"what\",\n]);\n\nfunction tokenize(value: string): string[] {\n  return [...value.toLowerCase().matchAll(wordPattern)]\n    .map((match) => match[0])\n    .filter((token) => !stopWords.has(token));\n}\n\nfunction scorePortableEntry(queryTokens: string[], entry: PortableRagIndexEntry) {\n  const searchableText = `${entry.sectionTitle} ${entry.content}`.toLowerCase();\n  const matchedTokens = queryTokens.filter((token) =>\n    searchableText.includes(token)\n  );\n\n  return matchedTokens.length / Math.max(queryTokens.length, 1);\n}\n\nexport function getPortableRagIndexResourceCount(): number {\n  return 1;\n}\n\nexport function findPortableRagMatches(query: string) {\n  const queryTokens = tokenize(query);\n\n  return portableRagIndex\n    .map((entry) => ({\n      content: entry.content,\n      documentUrl: ragChatbotSourceDocument.documentUrl,\n      pageLabel: entry.pageLabel,\n      sectionTitle: entry.sectionTitle,\n      similarity: scorePortableEntry(queryTokens, entry),\n      title: ragChatbotSourceDocument.title,\n    }))\n    .filter((match) => match.similarity > 0)\n    .sort((left, right) => right.similarity - left.similarity)\n    .slice(0, 4);\n}\n",
      "type": "registry:lib",
      "target": "@lib/ultra-chatbot-agent/rag-chatbot/portable-index.ts"
    },
    {
      "path": "registry/ultra-chatbot-agent/lib/ultra-chatbot-agent/rag-chatbot/retrieval.ts",
      "content": "import {\n  and,\n  cosineDistance,\n  desc,\n  eq,\n  gt,\n  sql,\n} from \"drizzle-orm\";\n\nimport { embed } from \"ai\";\n\nimport {\n  type RagChatbotDatabaseModule,\n  loadRagChatbotDatabase,\n} from \"./database\";\nimport {\n  createRagChatbotGateway,\n  getRagChatbotConfig,\n  getRagChatbotEnv,\n  type RagChatbotEnv,\n} from \"./env\";\nimport {\n  ensureRagKnowledgeBaseReady,\n} from \"./knowledge-base-status\";\nimport { findPortableRagMatches } from \"./portable-index\";\nimport { ragChatbotSourceDocument } from \"./source-document\";\n\nconst matchLimit = 4;\nconst minimumSimilarity = 0.55;\n\nexport interface RetrievedRagContent {\n  content: string;\n  documentUrl: string;\n  pageLabel: string | null;\n  sectionTitle: string | null;\n  similarity: number;\n  title: string;\n}\n\nexport interface RagToolSource {\n  citationLabel: string;\n  content: string;\n  documentUrl: string;\n  pageLabel: string | null;\n  sectionTitle: string | null;\n  similarity: number;\n  title: string;\n}\n\nexport interface RagToolResult {\n  answerable: boolean;\n  message: string;\n  sources: RagToolSource[];\n}\n\ninterface FindRelevantContentDependencies {\n  ensureKnowledgeBaseReady?: (env: RagChatbotEnv) => Promise<void>;\n  findMatches?: (input: {\n    queryEmbedding: number[];\n    sourceSlug: string;\n  }) => Promise<RetrievedRagContent[]>;\n  generateEmbedding: (value: string, env: RagChatbotEnv) => Promise<number[]>;\n  loadDatabase?: () => Promise<RagChatbotDatabaseModule>;\n}\n\nfunction createCitationLabel(source: RetrievedRagContent): string {\n  return source.pageLabel\n    ? `${source.title}, p. ${source.pageLabel}`\n    : source.title;\n}\n\nexport function createRagToolResult(\n  matches: RetrievedRagContent[]\n): RagToolResult {\n  if (matches.length === 0) {\n    return {\n      answerable: false,\n      message:\n        \"No relevant indexed document snippets were found for this question.\",\n      sources: [],\n    };\n  }\n\n  return {\n    answerable: true,\n    message: `Found ${matches.length} relevant indexed document ${\n      matches.length === 1 ? \"snippet\" : \"snippets\"\n    }.`,\n    sources: matches.map((match) => ({\n      citationLabel: createCitationLabel(match),\n      content: match.content,\n      documentUrl: match.documentUrl,\n      pageLabel: match.pageLabel,\n      sectionTitle: match.sectionTitle,\n      similarity: match.similarity,\n      title: match.title,\n    })),\n  };\n}\n\nasync function findMatchesForSource(\n  input: {\n    queryEmbedding: number[];\n    sourceSlug: string;\n  },\n  loadDatabase: () => Promise<RagChatbotDatabaseModule>\n): Promise<RetrievedRagContent[]> {\n  const { database, ragChatbotEmbeddings, ragChatbotResources } =\n    await loadDatabase();\n  const similarity = sql<number>`1 - (${cosineDistance(\n    ragChatbotEmbeddings.embedding,\n    input.queryEmbedding\n  )})`;\n\n  return database\n    .select({\n      content: ragChatbotEmbeddings.content,\n      documentUrl: ragChatbotResources.documentUrl,\n      pageLabel: ragChatbotEmbeddings.pageLabel,\n      sectionTitle: ragChatbotEmbeddings.sectionTitle,\n      similarity,\n      title: ragChatbotResources.title,\n    })\n    .from(ragChatbotEmbeddings)\n    .innerJoin(\n      ragChatbotResources,\n      eq(ragChatbotEmbeddings.resourceId, ragChatbotResources.id)\n    )\n    .where(\n      and(\n        eq(ragChatbotResources.sourceSlug, input.sourceSlug),\n        gt(similarity, minimumSimilarity)\n      )\n    )\n    .orderBy((table) => desc(table.similarity))\n    .limit(matchLimit);\n}\n\nexport async function generateRagEmbedding(\n  value: string,\n  env: RagChatbotEnv = getRagChatbotEnv()\n): Promise<number[]> {\n  const gateway = createRagChatbotGateway(env);\n  const normalizedValue = value.replaceAll(\"\\n\", \" \").trim();\n  const { embeddingModel } = getRagChatbotConfig(env);\n  const { embedding } = await embed({\n    model: gateway.embeddingModel(embeddingModel),\n    value: normalizedValue,\n  });\n\n  return embedding;\n}\n\nexport async function findRelevantContent(\n  userQuery: string,\n  env: RagChatbotEnv = getRagChatbotEnv(),\n  dependencies: FindRelevantContentDependencies = {\n    generateEmbedding: generateRagEmbedding,\n  }\n): Promise<RagToolResult> {\n  const normalizedQuery = userQuery.trim();\n\n  if (normalizedQuery.length === 0) {\n    return createRagToolResult([]);\n  }\n\n  const sourceSlug = ragChatbotSourceDocument.slug;\n\n  if (!env.DATABASE_URL) {\n    return createRagToolResult(findPortableRagMatches(normalizedQuery));\n  }\n\n  const loadDatabase = dependencies.loadDatabase ?? loadRagChatbotDatabase;\n  const ensureKnowledgeBaseReady =\n    dependencies.ensureKnowledgeBaseReady ??\n    ((nextEnv: RagChatbotEnv) =>\n      ensureRagKnowledgeBaseReady(nextEnv, {\n        loadDatabase: loadDatabase as never,\n      }));\n  const findMatches =\n    dependencies.findMatches ??\n    ((input: { queryEmbedding: number[]; sourceSlug: string }) =>\n      findMatchesForSource(input, loadDatabase));\n\n  await ensureKnowledgeBaseReady(env);\n\n  const queryEmbedding = await dependencies.generateEmbedding(\n    normalizedQuery,\n    env\n  );\n  const matches = await findMatches({\n    queryEmbedding,\n    sourceSlug,\n  });\n\n  return createRagToolResult(matches);\n}\n",
      "type": "registry:lib",
      "target": "@lib/ultra-chatbot-agent/rag-chatbot/retrieval.ts"
    },
    {
      "path": "registry/ultra-chatbot-agent/lib/ultra-chatbot-agent/rag-chatbot/runtime.ts",
      "content": "import { type UIMessage, validateUIMessages } from \"ai\";\n\nimport { streamRagChatbot } from \"./chat\";\nimport {\n  getRagChatbotEnv,\n  getRagChatbotSetupState,\n  type RagChatbotEnv,\n} from \"./env\";\nimport {\n  getRagKnowledgeBaseStatus,\n  type RagKnowledgeBaseStatus,\n} from \"./knowledge-base-status\";\nimport { ragChatbotSourceDocument } from \"./source-document\";\n\ninterface RagChatbotRequestBody {\n  messages?: UIMessage[];\n}\n\nexport interface RagChatbotRuntimeState {\n  chatModel: string;\n  isChatAvailable: boolean;\n  nodeVersion: string;\n  retrievalLabel: string;\n  setupMessage: string | null;\n  sourceDocument: typeof ragChatbotSourceDocument;\n  statusLabel: \"Index required\" | \"Ready\" | \"Setup required\";\n}\n\nexport interface RagChatbotRuntimeDependencies {\n  getKnowledgeBaseStatus: (\n    env: RagChatbotEnv\n  ) => Promise<RagKnowledgeBaseStatus>;\n}\n\ninterface RagChatbotRequestDependencies extends RagChatbotRuntimeDependencies {\n  streamRagChatbot: (\n    messages: UIMessage[],\n    env: RagChatbotEnv\n  ) => Promise<Response>;\n}\n\nconst invalidMessagesError = 'Expected a JSON body with a \"messages\" array.';\nconst invalidUiMessagesError =\n  'Expected each \"messages\" entry to match the UIMessage format.';\nconst malformedJsonError = \"Expected a valid JSON request body.\";\n\nexport async function getRagChatbotRuntimeState(\n  env: RagChatbotEnv = getRagChatbotEnv(),\n  dependencies: RagChatbotRuntimeDependencies = {\n    getKnowledgeBaseStatus: getRagKnowledgeBaseStatus,\n  }\n): Promise<RagChatbotRuntimeState> {\n  const gatewaySetup = getRagChatbotSetupState(env);\n\n  if (gatewaySetup.issues.length > 0) {\n    return {\n      chatModel: gatewaySetup.config.chatModel,\n      isChatAvailable: false,\n      nodeVersion: gatewaySetup.nodeVersion,\n      retrievalLabel: \"Portable index\",\n      sourceDocument: ragChatbotSourceDocument,\n      setupMessage: gatewaySetup.issues.join(\" \"),\n      statusLabel: \"Setup required\",\n    };\n  }\n\n  const knowledgeBaseStatus = await dependencies.getKnowledgeBaseStatus(env);\n\n  return {\n    chatModel: gatewaySetup.config.chatModel,\n    isChatAvailable: knowledgeBaseStatus.isReady,\n    nodeVersion: gatewaySetup.nodeVersion,\n    retrievalLabel: knowledgeBaseStatus.retrievalLabel,\n    sourceDocument: ragChatbotSourceDocument,\n    setupMessage: knowledgeBaseStatus.message,\n    statusLabel: knowledgeBaseStatus.statusLabel,\n  };\n}\n\nasync function readRagChatbotMessages(body: unknown): Promise<UIMessage[]> {\n  const { messages } = (body ?? {}) as RagChatbotRequestBody;\n\n  if (!Array.isArray(messages)) {\n    throw new Error(invalidMessagesError);\n  }\n\n  try {\n    return await validateUIMessages({ messages });\n  } catch {\n    throw new Error(invalidUiMessagesError);\n  }\n}\n\nexport async function handleRagChatbotRequest(\n  request: Request,\n  env: RagChatbotEnv = getRagChatbotEnv(),\n  dependencies: RagChatbotRequestDependencies = {\n    getKnowledgeBaseStatus: getRagKnowledgeBaseStatus,\n    streamRagChatbot,\n  }\n) {\n  const runtimeState = await getRagChatbotRuntimeState(env, dependencies);\n\n  if (!runtimeState.isChatAvailable) {\n    return Response.json(\n      {\n        error: runtimeState.setupMessage,\n      },\n      { status: 500 }\n    );\n  }\n\n  let messages: UIMessage[];\n\n  try {\n    messages = await readRagChatbotMessages(await request.json());\n  } catch (error) {\n    if (error instanceof SyntaxError) {\n      return Response.json(\n        {\n          error: malformedJsonError,\n        },\n        { status: 400 }\n      );\n    }\n\n    if (\n      error instanceof Error &&\n      [invalidMessagesError, invalidUiMessagesError].includes(error.message)\n    ) {\n      return Response.json(\n        {\n          error: error.message,\n        },\n        { status: 400 }\n      );\n    }\n\n    throw error;\n  }\n\n  return dependencies.streamRagChatbot(messages, env);\n}\n",
      "type": "registry:lib",
      "target": "@lib/ultra-chatbot-agent/rag-chatbot/runtime.ts"
    },
    {
      "path": "registry/ultra-chatbot-agent/lib/ultra-chatbot-agent/rag-chatbot/schema.ts",
      "content": "import {\n  index,\n  integer,\n  pgTable,\n  text,\n  timestamp,\n  uniqueIndex,\n  uuid,\n  varchar,\n  vector,\n} from \"drizzle-orm/pg-core\";\n\nexport const ragChatbotResources = pgTable(\n  \"rag_chatbot_resources\",\n  {\n    id: uuid(\"id\").defaultRandom().primaryKey(),\n    sourceSlug: varchar(\"source_slug\", { length: 191 }).notNull(),\n    title: text(\"title\").notNull(),\n    sourcePageUrl: text(\"source_page_url\").notNull(),\n    documentUrl: text(\"document_url\").notNull(),\n    description: text(\"description\"),\n    contentHash: varchar(\"content_hash\", { length: 128 }).notNull(),\n    createdAt: timestamp(\"created_at\", { withTimezone: true })\n      .defaultNow()\n      .notNull(),\n    updatedAt: timestamp(\"updated_at\", { withTimezone: true })\n      .defaultNow()\n      .notNull(),\n  },\n  (table) => ({\n    sourceSlugIndex: uniqueIndex(\"rag_chatbot_resources_source_slug_idx\").on(\n      table.sourceSlug\n    ),\n  })\n);\n\nexport const ragChatbotEmbeddings = pgTable(\n  \"rag_chatbot_embeddings\",\n  {\n    id: uuid(\"id\").defaultRandom().primaryKey(),\n    resourceId: uuid(\"resource_id\")\n      .notNull()\n      .references(() => ragChatbotResources.id, { onDelete: \"cascade\" }),\n    chunkIndex: integer(\"chunk_index\").notNull(),\n    content: text(\"content\").notNull(),\n    pageLabel: varchar(\"page_label\", { length: 64 }),\n    sectionTitle: text(\"section_title\"),\n    embedding: vector(\"embedding\", { dimensions: 1536 }).notNull(),\n    createdAt: timestamp(\"created_at\", { withTimezone: true })\n      .defaultNow()\n      .notNull(),\n  },\n  (table) => ({\n    chunkIndex: uniqueIndex(\"rag_chatbot_embeddings_resource_chunk_idx\").on(\n      table.resourceId,\n      table.chunkIndex\n    ),\n    embeddingIndex: index(\"rag_chatbot_embeddings_embedding_idx\").using(\n      \"hnsw\",\n      table.embedding.op(\"vector_cosine_ops\")\n    ),\n  })\n);\n\nexport const ragChatbotSchema = {\n  ragChatbotEmbeddings,\n  ragChatbotResources,\n};\n",
      "type": "registry:lib",
      "target": "@lib/ultra-chatbot-agent/rag-chatbot/schema.ts"
    },
    {
      "path": "registry/ultra-chatbot-agent/lib/ultra-chatbot-agent/rag-chatbot/source-document.ts",
      "content": "export const ragChatbotSourceDocument = {\n  slug: \"nasa-graphics-standards-manual\",\n  title: \"NASA Graphics Standards Manual\",\n  documentUrl:\n    \"https://www.nasa.gov/wp-content/uploads/2015/01/nasa_graphics_manual_nhb_1430-2_jan_1976.pdf\",\n  sourcePageUrl:\n    \"https://www.nasa.gov/image-article/nasa-graphics-standards-manual/\",\n  description:\n    \"A public design-guidelines PDF used as the preindexed knowledge source for the document support chatbot demo.\",\n} as const;\n",
      "type": "registry:lib",
      "target": "@lib/ultra-chatbot-agent/rag-chatbot/source-document.ts"
    },
    {
      "path": "registry/ultra-chatbot-agent/lib/ultra-chatbot-agent/server/blob-cleanup.ts",
      "content": "import { getUltraChatbotAgentAppEnv } from \"./env\";\n\nconst appEnv = getUltraChatbotAgentAppEnv();\nimport { demoDataRetentionDays } from \"./demo-data-retention-policy\";\nimport { getVercelBlobToken } from \"./env\";\n\nimport {\n  cleanupExpiredUltraChatbotAgentBlobs,\n  type UltraChatbotAgentBlobStorageEnv,\n} from \"./blob-storage\";\n\nexport const ultraChatbotAgentBlobCleanupRetentionDays = demoDataRetentionDays;\nexport const ultraChatbotAgentBlobCleanupCronScheduleUtc = \"0 20 * * *\";\n\nexport function cleanupExpiredUltraChatbotAgentUploadBlobs(\n  env: UltraChatbotAgentBlobStorageEnv = appEnv,\n  input: {\n    now?: Date;\n    retentionDays?: number;\n  } = {}\n) {\n  return cleanupExpiredUltraChatbotAgentBlobs({\n    now: input.now,\n    retentionDays:\n      input.retentionDays ?? ultraChatbotAgentBlobCleanupRetentionDays,\n    token: getVercelBlobToken(env),\n  });\n}\n",
      "type": "registry:lib",
      "target": "@lib/ultra-chatbot-agent/server/blob-cleanup.ts"
    },
    {
      "path": "registry/ultra-chatbot-agent/lib/ultra-chatbot-agent/server/blob-storage.ts",
      "content": "import { del, list } from \"@vercel/blob\";\n\nexport const ultraChatbotAgentUploadPathRoot = \"ultra-chatbot-agent\";\nexport const ultraChatbotAgentUploadListLimit = 1000;\n\nconst millisecondsPerDay = 24 * 60 * 60 * 1000;\nconst ultraChatbotAgentBlobDeleteBatchSize = 100;\nconst ownedUploadPathSegmentRe = /^[a-zA-Z0-9_-]+$/;\n\nexport interface UltraChatbotAgentBlobStorageEnv {\n  BLOB_READ_WRITE_TOKEN?: string;\n}\n\nexport function validateUltraChatbotAgentBlobPathSegment(\n  value: string,\n  label: string\n) {\n  const trimmed = value.trim();\n\n  if (!ownedUploadPathSegmentRe.test(trimmed)) {\n    throw new Error(\n      `${label} must contain only letters, numbers, hyphens, or underscores.`\n    );\n  }\n\n  return trimmed;\n}\n\nexport function formatUltraChatbotAgentUploadDateBucket(date: Date) {\n  return date.toISOString().slice(0, 10);\n}\n\nexport function buildUltraChatbotAgentDailyUploadPrefix(input: {\n  dateBucket: string;\n  visitorId: string;\n}) {\n  return `${ultraChatbotAgentUploadPathRoot}/${input.visitorId}/${input.dateBucket}/`;\n}\n\nexport function buildUltraChatbotAgentVisitorUploadPrefix(visitorId: string) {\n  return `${ultraChatbotAgentUploadPathRoot}/${visitorId}/`;\n}\n\nexport function buildUltraChatbotAgentUploadPath(input: {\n  chatId: string;\n  dateBucket: string;\n  filename: string;\n  uploadId: string;\n  visitorId: string;\n}) {\n  return `${buildUltraChatbotAgentDailyUploadPrefix({\n    dateBucket: input.dateBucket,\n    visitorId: input.visitorId,\n  })}${input.chatId}/${input.uploadId}-${input.filename}`;\n}\n\nasync function listUltraChatbotAgentBlobs(input: {\n  prefix: string;\n  token: string;\n}) {\n  let cursor: string | undefined;\n  const blobs: Awaited<ReturnType<typeof list>>[\"blobs\"] = [];\n\n  do {\n    const listOptions: {\n      cursor?: string;\n      limit: number;\n      prefix: string;\n      token: string;\n    } = {\n      limit: ultraChatbotAgentUploadListLimit,\n      prefix: input.prefix,\n      token: input.token,\n    };\n\n    if (cursor) {\n      listOptions.cursor = cursor;\n    }\n\n    const result = await list(listOptions);\n    blobs.push(...result.blobs);\n    cursor = result.hasMore ? result.cursor : undefined;\n  } while (cursor);\n\n  return blobs;\n}\n\nexport async function readUltraChatbotAgentBlobUsageForPrefix(input: {\n  prefix: string;\n  token: string;\n}) {\n  const blobs = await listUltraChatbotAgentBlobs(input);\n\n  return {\n    fileCount: blobs.length,\n    totalBytes: blobs.reduce((sum, blob) => sum + blob.size, 0),\n  };\n}\n\nasync function deleteUltraChatbotAgentBlobPathnames(input: {\n  pathnames: string[];\n  token: string;\n}) {\n  for (\n    let index = 0;\n    index < input.pathnames.length;\n    index += ultraChatbotAgentBlobDeleteBatchSize\n  ) {\n    const batch = input.pathnames.slice(\n      index,\n      index + ultraChatbotAgentBlobDeleteBatchSize\n    );\n\n    if (batch.length > 0) {\n      await del(batch, { token: input.token });\n    }\n  }\n}\n\nfunction isUltraChatbotAgentChatBlob(input: {\n  chatId: string;\n  pathname: string;\n  visitorId: string;\n}) {\n  const [root, visitorId, , chatId] = input.pathname.split(\"/\");\n  return (\n    root === ultraChatbotAgentUploadPathRoot &&\n    visitorId === input.visitorId &&\n    chatId === input.chatId\n  );\n}\n\nexport async function deleteUltraChatbotAgentBlobsForChat(input: {\n  chatId: string;\n  token: string;\n  visitorId: string;\n}) {\n  const visitorId = validateUltraChatbotAgentBlobPathSegment(\n    input.visitorId,\n    \"Visitor id\"\n  );\n  const chatId = validateUltraChatbotAgentBlobPathSegment(\n    input.chatId,\n    \"Chat id\"\n  );\n  const blobs = await listUltraChatbotAgentBlobs({\n    prefix: buildUltraChatbotAgentVisitorUploadPrefix(visitorId),\n    token: input.token,\n  });\n  const pathnames = blobs\n    .filter((blob) =>\n      isUltraChatbotAgentChatBlob({\n        chatId,\n        pathname: blob.pathname,\n        visitorId,\n      })\n    )\n    .map((blob) => blob.pathname);\n\n  await deleteUltraChatbotAgentBlobPathnames({\n    pathnames,\n    token: input.token,\n  });\n\n  return {\n    deletedCount: pathnames.length,\n  };\n}\n\nexport async function deleteUltraChatbotAgentBlobsForVisitor(input: {\n  token: string;\n  visitorId: string;\n}) {\n  const visitorId = validateUltraChatbotAgentBlobPathSegment(\n    input.visitorId,\n    \"Visitor id\"\n  );\n  const blobs = await listUltraChatbotAgentBlobs({\n    prefix: buildUltraChatbotAgentVisitorUploadPrefix(visitorId),\n    token: input.token,\n  });\n  const pathnames = blobs.map((blob) => blob.pathname);\n\n  await deleteUltraChatbotAgentBlobPathnames({\n    pathnames,\n    token: input.token,\n  });\n\n  return {\n    deletedCount: pathnames.length,\n  };\n}\n\nexport async function cleanupExpiredUltraChatbotAgentBlobs(input: {\n  now?: Date;\n  retentionDays: number;\n  token: string;\n}) {\n  const now = input.now ?? new Date();\n  const expiresBefore = new Date(\n    now.getTime() - input.retentionDays * millisecondsPerDay\n  );\n  const blobs = await listUltraChatbotAgentBlobs({\n    prefix: `${ultraChatbotAgentUploadPathRoot}/`,\n    token: input.token,\n  });\n  const pathnames = blobs\n    .filter((blob) => blob.uploadedAt < expiresBefore)\n    .map((blob) => blob.pathname);\n\n  await deleteUltraChatbotAgentBlobPathnames({\n    pathnames,\n    token: input.token,\n  });\n\n  return {\n    deletedCount: pathnames.length,\n    expiresBefore: expiresBefore.toISOString(),\n    retentionDays: input.retentionDays,\n  };\n}\n",
      "type": "registry:lib",
      "target": "@lib/ultra-chatbot-agent/server/blob-storage.ts"
    },
    {
      "path": "registry/ultra-chatbot-agent/lib/ultra-chatbot-agent/server/capabilities.ts",
      "content": "export interface UltraChatbotAgentCapabilities {\n  sandboxEnabled: boolean;\n}\n\nexport function getUltraChatbotAgentDefaultCapabilities(): UltraChatbotAgentCapabilities {\n  return {\n    sandboxEnabled: false,\n  };\n}\n\nexport function normalizeUltraChatbotAgentCapabilities(\n  input: unknown\n): UltraChatbotAgentCapabilities {\n  const defaults = getUltraChatbotAgentDefaultCapabilities();\n\n  if (!input || typeof input !== \"object\" || Array.isArray(input)) {\n    return defaults;\n  }\n\n  return {\n    sandboxEnabled:\n      \"sandboxEnabled\" in input && typeof input.sandboxEnabled === \"boolean\"\n        ? input.sandboxEnabled\n        : defaults.sandboxEnabled,\n  };\n}\n",
      "type": "registry:lib",
      "target": "@lib/ultra-chatbot-agent/server/capabilities.ts"
    },
    {
      "path": "registry/ultra-chatbot-agent/lib/ultra-chatbot-agent/server/capability-settings.ts",
      "content": "import { z } from \"zod\";\n\nimport { createUltraChatbotAgentChatStore } from \"./chat-store\";\n\nconst ultraChatbotAgentCapabilitySettingsSchema = z.object({\n  sandboxEnabled: z.boolean(),\n});\n\nexport async function handleUltraChatbotAgentCapabilitySettingsPatchRequest(\n  request: Request,\n  viewer: { chatId: string; visitorId: string }\n) {\n  const parsedBody = ultraChatbotAgentCapabilitySettingsSchema.safeParse(\n    await request.json()\n  );\n\n  if (!parsedBody.success) {\n    return Response.json(\n      {\n        error: \"A valid capability settings payload is required.\",\n      },\n      { status: 400 }\n    );\n  }\n\n  const chatStore = createUltraChatbotAgentChatStore();\n  const session = await chatStore.loadChatSession(\n    viewer.chatId,\n    viewer.visitorId\n  );\n\n  if (!session) {\n    return Response.json(\n      {\n        error: \"Chat not found for this visitor.\",\n      },\n      { status: 404 }\n    );\n  }\n\n  const capabilities = await chatStore.setChatCapabilities({\n    capabilities: {\n      sandboxEnabled: parsedBody.data.sandboxEnabled,\n    },\n    chatId: viewer.chatId,\n    visitorId: viewer.visitorId,\n  });\n\n  return Response.json(capabilities, { status: 200 });\n}\n",
      "type": "registry:lib",
      "target": "@lib/ultra-chatbot-agent/server/capability-settings.ts"
    },
    {
      "path": "registry/ultra-chatbot-agent/lib/ultra-chatbot-agent/server/chat-history.ts",
      "content": "import { isFileUIPart, isTextUIPart, type UIMessage } from \"ai\";\n\nfunction getReplayableAssistantText(message: UIMessage) {\n  return message.parts\n    .filter(isTextUIPart)\n    .map((part) => part.text)\n    .join(\"\\n\")\n    .trim();\n}\n\nexport function projectUltraChatbotAgentHistoryForModel(messages: UIMessage[]) {\n  return messages.flatMap((message) => {\n    if (message.role === \"assistant\") {\n      const text = getReplayableAssistantText(message);\n\n      return text\n        ? [\n            {\n              id: message.id,\n              parts: [\n                {\n                  text,\n                  type: \"text\" as const,\n                },\n              ],\n              role: \"assistant\" as const,\n            },\n          ]\n        : [];\n    }\n\n    if (message.role === \"user\") {\n      const parts = message.parts.filter(\n        (part) => isTextUIPart(part) || isFileUIPart(part)\n      );\n\n      return parts.length > 0\n        ? [\n            {\n              ...message,\n              parts,\n            },\n          ]\n        : [];\n    }\n\n    if (message.role === \"system\") {\n      const parts = message.parts.filter(isTextUIPart);\n\n      return parts.length > 0\n        ? [\n            {\n              ...message,\n              parts,\n            },\n          ]\n        : [];\n    }\n\n    return [];\n  });\n}\n",
      "type": "registry:lib",
      "target": "@lib/ultra-chatbot-agent/server/chat-history.ts"
    },
    {
      "path": "registry/ultra-chatbot-agent/lib/ultra-chatbot-agent/server/chat-store.ts",
      "content": "import {\n  and,\n  asc,\n  desc,\n  eq,\n  gt,\n  inArray,\n  lt,\n} from \"drizzle-orm\";\nimport type { UIMessage } from \"ai\";\n\nimport { loadUltraChatbotAgentDatabase as loadUltraChatbotAgentDatabaseModule } from \"./database\";\nimport { demoDataRetentionDays } from \"./demo-data-retention-policy\";\n\nimport {\n  getUltraChatbotAgentDefaultCapabilities,\n  normalizeUltraChatbotAgentCapabilities,\n  type UltraChatbotAgentCapabilities,\n} from \"./capabilities\";\n\nexport interface UltraChatbotAgentChatRecord {\n  activeStreamId: string | null;\n  capabilities: UltraChatbotAgentCapabilities;\n  createdAt: string;\n  id: string;\n  selectedChatModel: string;\n  title: string;\n  updatedAt: string;\n  visibility: \"private\" | \"public\";\n  visitorId: string;\n}\n\nexport interface UltraChatbotAgentChatSession {\n  chat: UltraChatbotAgentChatRecord;\n  messages: UIMessage[];\n}\n\nexport interface UltraChatbotAgentHistoryPage {\n  chats: UltraChatbotAgentChatRecord[];\n  hasMore: boolean;\n}\n\nexport interface UltraChatbotAgentVoteRecord {\n  chatId: string;\n  isUpvoted: boolean;\n  messageId: string;\n  visitorId: string;\n}\n\nexport const ultraChatbotAgentCleanupRetentionDays = demoDataRetentionDays;\n\nexport interface UltraChatbotAgentExpiredChatRecord {\n  id: string;\n  updatedAt: string;\n}\n\nexport interface UltraChatbotAgentChatCleanupResult {\n  chatIds: string[];\n  deletedChats: number;\n  deletedVotes: number;\n  expiresBefore: string;\n  retentionDays: number;\n}\n\nexport interface UltraChatbotAgentChatCleanupPersistence {\n  deleteExpiredChatsByIds(chatIds: string[]): Promise<{\n    deletedChats: number;\n    deletedVotes: number;\n  }>;\n  findExpiredChats(input: {\n    olderThan: Date;\n  }): Promise<UltraChatbotAgentExpiredChatRecord[]>;\n}\n\ninterface UltraChatbotAgentDatabaseModule {\n  database: typeof import(\"./database\")[\"database\"];\n  ultraChatbotAgentChats: typeof import(\"./database\")[\"ultraChatbotAgentChats\"];\n  ultraChatbotAgentMessages: typeof import(\"./database\")[\"ultraChatbotAgentMessages\"];\n  ultraChatbotAgentVotes: typeof import(\"./database\")[\"ultraChatbotAgentVotes\"];\n}\n\nfunction toIsoString(value: Date | string) {\n  return value instanceof Date ? value.toISOString() : value;\n}\n\nfunction subtractDays(date: Date, days: number) {\n  return new Date(date.getTime() - days * 24 * 60 * 60 * 1000);\n}\n\nfunction normalizeChatRecord(record: {\n  activeStreamId: string | null;\n  capabilities: unknown;\n  createdAt: Date | string;\n  id: string;\n  selectedChatModel: string;\n  title: string;\n  updatedAt: Date | string;\n  visibility: \"private\" | \"public\";\n  visitorId: string;\n}) {\n  return {\n    activeStreamId: record.activeStreamId,\n    capabilities: normalizeUltraChatbotAgentCapabilities(record.capabilities),\n    createdAt: toIsoString(record.createdAt),\n    id: record.id,\n    selectedChatModel: record.selectedChatModel,\n    title: record.title,\n    updatedAt: toIsoString(record.updatedAt),\n    visibility: record.visibility,\n    visitorId: record.visitorId,\n  } satisfies UltraChatbotAgentChatRecord;\n}\n\nasync function loadUltraChatbotAgentChatDatabase(): Promise<UltraChatbotAgentDatabaseModule> {\n  const databaseModule = await loadUltraChatbotAgentDatabaseModule();\n\n  return {\n    database: databaseModule.database,\n    ultraChatbotAgentChats: databaseModule.ultraChatbotAgentChats,\n    ultraChatbotAgentMessages: databaseModule.ultraChatbotAgentMessages,\n    ultraChatbotAgentVotes: databaseModule.ultraChatbotAgentVotes,\n  };\n}\n\nfunction getMessageText(message: UIMessage) {\n  return message.parts\n    .map((part) => (part.type === \"text\" ? part.text : \"\"))\n    .filter((part) => part.length > 0)\n    .join(\" \")\n    .replace(/\\s+/g, \" \")\n    .trim();\n}\n\nfunction buildChatTitle(message: UIMessage) {\n  const text = getMessageText(message);\n\n  if (text.length === 0) {\n    return \"New chat\";\n  }\n\n  return text.slice(0, 72);\n}\n\nfunction readMessageCreatedAt(message: UIMessage, fallback: Date) {\n  const metadata = message.metadata as { createdAt?: string } | undefined;\n\n  if (!metadata?.createdAt) {\n    return fallback;\n  }\n\n  const createdAt = new Date(metadata.createdAt);\n\n  return Number.isNaN(createdAt.valueOf()) ? fallback : createdAt;\n}\n\nfunction toUiMessage(row: {\n  createdAt: Date | string;\n  messageId: string;\n  parts: unknown;\n  role: string;\n}) {\n  return {\n    id: row.messageId,\n    metadata: {\n      createdAt: toIsoString(row.createdAt),\n    },\n    parts: row.parts as UIMessage[\"parts\"],\n    role: row.role as UIMessage[\"role\"],\n  } satisfies UIMessage;\n}\n\nfunction getStoredAttachments(message: UIMessage) {\n  return message.parts\n    .filter(\n      (part): part is Extract<UIMessage[\"parts\"][number], { type: \"file\" }> =>\n        part.type === \"file\" && typeof part.url === \"string\"\n    )\n    .map((part) => ({\n      filename: part.filename ?? null,\n      mediaType: part.mediaType ?? null,\n      url: part.url,\n    }));\n}\n\nexport function getUltraChatbotAgentChatNotFoundError(chatId: string) {\n  return `No ultra-chatbot-agent chat found for ${chatId}.`;\n}\n\nfunction isInvalidUltraChatbotAgentChatIdError(error: unknown) {\n  const candidates =\n    error instanceof Error\n      ? [error, (error as Error & { cause?: unknown }).cause]\n      : [error];\n\n  return candidates.some((candidate) => {\n    if (!(candidate instanceof Error)) {\n      return false;\n    }\n\n    const candidateWithCode = candidate as Error & { code?: string };\n\n    return (\n      candidateWithCode.code === \"22P02\" ||\n      candidate.message.includes(\"invalid input syntax for type uuid\")\n    );\n  });\n}\n\nfunction createDatabaseBackedUltraChatbotAgentChatCleanupPersistence(): UltraChatbotAgentChatCleanupPersistence {\n  return {\n    async deleteExpiredChatsByIds(chatIds) {\n      if (chatIds.length === 0) {\n        return {\n          deletedChats: 0,\n          deletedVotes: 0,\n        };\n      }\n\n      const { database, ultraChatbotAgentChats, ultraChatbotAgentVotes } =\n        await loadUltraChatbotAgentChatDatabase();\n\n      return database.transaction(async (tx) => {\n        const deletedVotes = await tx\n          .delete(ultraChatbotAgentVotes)\n          .where(inArray(ultraChatbotAgentVotes.chatId, chatIds))\n          .returning({\n            chatId: ultraChatbotAgentVotes.chatId,\n            messageId: ultraChatbotAgentVotes.messageId,\n            visitorId: ultraChatbotAgentVotes.visitorId,\n          });\n        const deletedChats = await tx\n          .delete(ultraChatbotAgentChats)\n          .where(inArray(ultraChatbotAgentChats.id, chatIds))\n          .returning({ id: ultraChatbotAgentChats.id });\n\n        return {\n          deletedChats: deletedChats.length,\n          deletedVotes: deletedVotes.length,\n        };\n      });\n    },\n    async findExpiredChats(input) {\n      const { database, ultraChatbotAgentChats } =\n        await loadUltraChatbotAgentChatDatabase();\n      const rows = await database\n        .select({\n          id: ultraChatbotAgentChats.id,\n          updatedAt: ultraChatbotAgentChats.updatedAt,\n        })\n        .from(ultraChatbotAgentChats)\n        .where(lt(ultraChatbotAgentChats.updatedAt, input.olderThan));\n\n      return rows.map((row) => ({\n        id: row.id,\n        updatedAt: toIsoString(row.updatedAt),\n      }));\n    },\n  };\n}\n\nexport async function cleanupExpiredUltraChatbotAgentChats(\n  input: {\n    now?: Date;\n    retentionDays?: number;\n  } = {},\n  dependencies: {\n    persistence?: UltraChatbotAgentChatCleanupPersistence;\n  } = {}\n): Promise<UltraChatbotAgentChatCleanupResult> {\n  const now = input.now ?? new Date();\n  const retentionDays =\n    input.retentionDays ?? ultraChatbotAgentCleanupRetentionDays;\n  const expiresBefore = subtractDays(now, retentionDays);\n  const persistence =\n    dependencies.persistence ??\n    createDatabaseBackedUltraChatbotAgentChatCleanupPersistence();\n  const expiredChats = await persistence.findExpiredChats({\n    olderThan: expiresBefore,\n  });\n  const chatIds = expiredChats.map((chat) => chat.id);\n\n  if (chatIds.length === 0) {\n    return {\n      chatIds: [],\n      deletedChats: 0,\n      deletedVotes: 0,\n      expiresBefore: expiresBefore.toISOString(),\n      retentionDays,\n    };\n  }\n\n  const { deletedChats, deletedVotes } =\n    await persistence.deleteExpiredChatsByIds(chatIds);\n\n  return {\n    chatIds,\n    deletedChats,\n    deletedVotes,\n    expiresBefore: expiresBefore.toISOString(),\n    retentionDays,\n  };\n}\n\nexport function createUltraChatbotAgentChatStore() {\n  return {\n    async listChatsForVisitor(visitorId: string) {\n      const { database, ultraChatbotAgentChats } =\n        await loadUltraChatbotAgentChatDatabase();\n      const rows = await database\n        .select()\n        .from(ultraChatbotAgentChats)\n        .where(eq(ultraChatbotAgentChats.visitorId, visitorId))\n        .orderBy(desc(ultraChatbotAgentChats.updatedAt));\n\n      return rows.map(normalizeChatRecord);\n    },\n    async loadChatSession(\n      chatId: string,\n      visitorId: string\n    ): Promise<UltraChatbotAgentChatSession | null> {\n      try {\n        const { database, ultraChatbotAgentChats, ultraChatbotAgentMessages } =\n          await loadUltraChatbotAgentChatDatabase();\n        const [chat] = await database\n          .select()\n          .from(ultraChatbotAgentChats)\n          .where(\n            and(\n              eq(ultraChatbotAgentChats.id, chatId),\n              eq(ultraChatbotAgentChats.visitorId, visitorId)\n            )\n          )\n          .limit(1);\n\n        if (!chat) {\n          return null;\n        }\n\n        const rows = await database\n          .select()\n          .from(ultraChatbotAgentMessages)\n          .where(eq(ultraChatbotAgentMessages.chatId, chatId))\n          .orderBy(asc(ultraChatbotAgentMessages.createdAt));\n\n        return {\n          chat: normalizeChatRecord(chat),\n          messages: rows.map(toUiMessage),\n        };\n      } catch (error) {\n        if (isInvalidUltraChatbotAgentChatIdError(error)) {\n          return null;\n        }\n\n        throw error;\n      }\n    },\n    async listVotesForChat(input: {\n      chatId: string;\n      visitorId: string;\n    }): Promise<UltraChatbotAgentVoteRecord[]> {\n      const { database, ultraChatbotAgentVotes } =\n        await loadUltraChatbotAgentChatDatabase();\n      const rows = await database\n        .select()\n        .from(ultraChatbotAgentVotes)\n        .where(\n          and(\n            eq(ultraChatbotAgentVotes.chatId, input.chatId),\n            eq(ultraChatbotAgentVotes.visitorId, input.visitorId)\n          )\n        );\n\n      return rows;\n    },\n    async listChatsForVisitorPage(input: {\n      endingBefore: string | null;\n      limit: number;\n      startingAfter: string | null;\n      visitorId: string;\n    }): Promise<UltraChatbotAgentHistoryPage> {\n      const { database, ultraChatbotAgentChats } =\n        await loadUltraChatbotAgentChatDatabase();\n      const extendedLimit = input.limit + 1;\n\n      const loadAnchor = async (chatId: string) => {\n        const [anchor] = await database\n          .select({\n            createdAt: ultraChatbotAgentChats.createdAt,\n            id: ultraChatbotAgentChats.id,\n          })\n          .from(ultraChatbotAgentChats)\n          .where(\n            and(\n              eq(ultraChatbotAgentChats.id, chatId),\n              eq(ultraChatbotAgentChats.visitorId, input.visitorId)\n            )\n          )\n          .limit(1);\n\n        return anchor ?? null;\n      };\n\n      let rows: (typeof ultraChatbotAgentChats.$inferSelect)[] = [];\n\n      if (input.startingAfter) {\n        const anchor = await loadAnchor(input.startingAfter);\n\n        if (!anchor) {\n          return {\n            chats: [],\n            hasMore: false,\n          };\n        }\n\n        rows = await database\n          .select()\n          .from(ultraChatbotAgentChats)\n          .where(\n            and(\n              eq(ultraChatbotAgentChats.visitorId, input.visitorId),\n              gt(ultraChatbotAgentChats.createdAt, anchor.createdAt)\n            )\n          )\n          .orderBy(desc(ultraChatbotAgentChats.createdAt))\n          .limit(extendedLimit);\n      } else if (input.endingBefore) {\n        const anchor = await loadAnchor(input.endingBefore);\n\n        if (!anchor) {\n          return {\n            chats: [],\n            hasMore: false,\n          };\n        }\n\n        rows = await database\n          .select()\n          .from(ultraChatbotAgentChats)\n          .where(\n            and(\n              eq(ultraChatbotAgentChats.visitorId, input.visitorId),\n              lt(ultraChatbotAgentChats.createdAt, anchor.createdAt)\n            )\n          )\n          .orderBy(desc(ultraChatbotAgentChats.createdAt))\n          .limit(extendedLimit);\n      } else {\n        rows = await database\n          .select()\n          .from(ultraChatbotAgentChats)\n          .where(eq(ultraChatbotAgentChats.visitorId, input.visitorId))\n          .orderBy(desc(ultraChatbotAgentChats.createdAt))\n          .limit(extendedLimit);\n      }\n\n      const hasMore = rows.length > input.limit;\n\n      return {\n        chats: rows.slice(0, input.limit).map(normalizeChatRecord),\n        hasMore,\n      };\n    },\n    async saveIncomingUserMessage(input: {\n      chatId: string;\n      message: UIMessage;\n      selectedChatModel: string;\n      selectedVisibilityType: \"private\" | \"public\";\n      visitorId: string;\n    }) {\n      const { database, ultraChatbotAgentChats, ultraChatbotAgentMessages } =\n        await loadUltraChatbotAgentChatDatabase();\n      const now = new Date();\n      const title = buildChatTitle(input.message);\n\n      await database.transaction(async (tx) => {\n        const [existingChat] = await tx\n          .select()\n          .from(ultraChatbotAgentChats)\n          .where(eq(ultraChatbotAgentChats.id, input.chatId))\n          .limit(1);\n\n        if (existingChat && existingChat.visitorId !== input.visitorId) {\n          throw new Error(getUltraChatbotAgentChatNotFoundError(input.chatId));\n        }\n\n        if (existingChat) {\n          await tx\n            .update(ultraChatbotAgentChats)\n            .set({\n              activeStreamId: null,\n              selectedChatModel: input.selectedChatModel,\n              title:\n                existingChat.title === \"New chat\" && title !== \"New chat\"\n                  ? title\n                  : existingChat.title,\n              updatedAt: now,\n              visibility: input.selectedVisibilityType,\n            })\n            .where(eq(ultraChatbotAgentChats.id, input.chatId));\n        } else {\n          await tx.insert(ultraChatbotAgentChats).values({\n            activeStreamId: null,\n            capabilities: getUltraChatbotAgentDefaultCapabilities(),\n            id: input.chatId,\n            selectedChatModel: input.selectedChatModel,\n            title,\n            updatedAt: now,\n            visibility: input.selectedVisibilityType,\n            visitorId: input.visitorId,\n          });\n        }\n\n        await tx\n          .insert(ultraChatbotAgentMessages)\n          .values({\n            attachments: getStoredAttachments(input.message),\n            chatId: input.chatId,\n            createdAt: readMessageCreatedAt(input.message, now),\n            messageId: input.message.id,\n            parts: input.message.parts,\n            role: input.message.role,\n          })\n          .onConflictDoUpdate({\n            set: {\n              attachments: getStoredAttachments(input.message),\n              parts: input.message.parts,\n              role: input.message.role,\n            },\n            target: [\n              ultraChatbotAgentMessages.chatId,\n              ultraChatbotAgentMessages.messageId,\n            ],\n          });\n      });\n    },\n    async saveFinishedMessages(input: {\n      chatId: string;\n      messages: UIMessage[];\n      visitorId: string;\n    }) {\n      const { database, ultraChatbotAgentChats, ultraChatbotAgentMessages } =\n        await loadUltraChatbotAgentChatDatabase();\n      const now = new Date();\n\n      await database.transaction(async (tx) => {\n        const [chat] = await tx\n          .select()\n          .from(ultraChatbotAgentChats)\n          .where(\n            and(\n              eq(ultraChatbotAgentChats.id, input.chatId),\n              eq(ultraChatbotAgentChats.visitorId, input.visitorId)\n            )\n          )\n          .limit(1);\n\n        if (!chat) {\n          throw new Error(getUltraChatbotAgentChatNotFoundError(input.chatId));\n        }\n\n        if (input.messages.length > 0) {\n          await tx\n            .insert(ultraChatbotAgentMessages)\n            .values(\n              input.messages.map((message) => ({\n                attachments: getStoredAttachments(message),\n                chatId: input.chatId,\n                createdAt: readMessageCreatedAt(message, now),\n                messageId: message.id,\n                parts: message.parts,\n                role: message.role,\n              }))\n            )\n            .onConflictDoNothing({\n              target: [\n                ultraChatbotAgentMessages.chatId,\n                ultraChatbotAgentMessages.messageId,\n              ],\n            });\n        }\n\n        await tx\n          .update(ultraChatbotAgentChats)\n          .set({\n            activeStreamId: null,\n            updatedAt: now,\n          })\n          .where(eq(ultraChatbotAgentChats.id, input.chatId));\n      });\n    },\n    async setActiveStream(input: {\n      activeStreamId: string | null;\n      chatId: string;\n      visitorId: string;\n    }) {\n      const { database, ultraChatbotAgentChats } =\n        await loadUltraChatbotAgentChatDatabase();\n      const rows = await database\n        .update(ultraChatbotAgentChats)\n        .set({\n          activeStreamId: input.activeStreamId,\n          updatedAt: new Date(),\n        })\n        .where(\n          and(\n            eq(ultraChatbotAgentChats.id, input.chatId),\n            eq(ultraChatbotAgentChats.visitorId, input.visitorId)\n          )\n        )\n        .returning({ id: ultraChatbotAgentChats.id });\n\n      if (rows.length === 0) {\n        throw new Error(getUltraChatbotAgentChatNotFoundError(input.chatId));\n      }\n    },\n    async setChatCapabilities(input: {\n      capabilities: Partial<UltraChatbotAgentCapabilities>;\n      chatId: string;\n      visitorId: string;\n    }) {\n      const { database, ultraChatbotAgentChats } =\n        await loadUltraChatbotAgentChatDatabase();\n      const [existingChat] = await database\n        .select({\n          capabilities: ultraChatbotAgentChats.capabilities,\n        })\n        .from(ultraChatbotAgentChats)\n        .where(\n          and(\n            eq(ultraChatbotAgentChats.id, input.chatId),\n            eq(ultraChatbotAgentChats.visitorId, input.visitorId)\n          )\n        )\n        .limit(1);\n\n      if (!existingChat) {\n        throw new Error(getUltraChatbotAgentChatNotFoundError(input.chatId));\n      }\n\n      const nextCapabilities = {\n        ...normalizeUltraChatbotAgentCapabilities(existingChat.capabilities),\n        ...input.capabilities,\n      } satisfies UltraChatbotAgentCapabilities;\n\n      const rows = await database\n        .update(ultraChatbotAgentChats)\n        .set({\n          capabilities: nextCapabilities,\n          updatedAt: new Date(),\n        })\n        .where(\n          and(\n            eq(ultraChatbotAgentChats.id, input.chatId),\n            eq(ultraChatbotAgentChats.visitorId, input.visitorId)\n          )\n        )\n        .returning({ capabilities: ultraChatbotAgentChats.capabilities });\n\n      return normalizeUltraChatbotAgentCapabilities(rows[0]?.capabilities);\n    },\n    async saveVote(input: {\n      chatId: string;\n      isUpvoted: boolean;\n      messageId: string;\n      visitorId: string;\n    }) {\n      const { database, ultraChatbotAgentVotes } =\n        await loadUltraChatbotAgentChatDatabase();\n\n      await database\n        .insert(ultraChatbotAgentVotes)\n        .values({\n          chatId: input.chatId,\n          isUpvoted: input.isUpvoted,\n          messageId: input.messageId,\n          visitorId: input.visitorId,\n        })\n        .onConflictDoUpdate({\n          set: {\n            isUpvoted: input.isUpvoted,\n          },\n          target: [\n            ultraChatbotAgentVotes.chatId,\n            ultraChatbotAgentVotes.messageId,\n            ultraChatbotAgentVotes.visitorId,\n          ],\n        });\n    },\n    async deleteVote(input: {\n      chatId: string;\n      messageId: string;\n      visitorId: string;\n    }) {\n      const { database, ultraChatbotAgentVotes } =\n        await loadUltraChatbotAgentChatDatabase();\n\n      await database\n        .delete(ultraChatbotAgentVotes)\n        .where(\n          and(\n            eq(ultraChatbotAgentVotes.chatId, input.chatId),\n            eq(ultraChatbotAgentVotes.messageId, input.messageId),\n            eq(ultraChatbotAgentVotes.visitorId, input.visitorId)\n          )\n        );\n    },\n    async setChatVisibility(input: {\n      chatId: string;\n      visibility: \"private\" | \"public\";\n      visitorId: string;\n    }) {\n      const { database, ultraChatbotAgentChats } =\n        await loadUltraChatbotAgentChatDatabase();\n      const rows = await database\n        .update(ultraChatbotAgentChats)\n        .set({\n          updatedAt: new Date(),\n          visibility: input.visibility,\n        })\n        .where(\n          and(\n            eq(ultraChatbotAgentChats.id, input.chatId),\n            eq(ultraChatbotAgentChats.visitorId, input.visitorId)\n          )\n        )\n        .returning({ visibility: ultraChatbotAgentChats.visibility });\n\n      if (rows.length === 0) {\n        throw new Error(getUltraChatbotAgentChatNotFoundError(input.chatId));\n      }\n\n      return rows[0];\n    },\n    async deleteMessagesAfterMessage(input: {\n      chatId: string;\n      messageId: string;\n      visitorId: string;\n    }) {\n      const { database, ultraChatbotAgentChats, ultraChatbotAgentMessages } =\n        await loadUltraChatbotAgentChatDatabase();\n\n      const [targetMessage] = await database\n        .select({\n          createdAt: ultraChatbotAgentMessages.createdAt,\n        })\n        .from(ultraChatbotAgentMessages)\n        .innerJoin(\n          ultraChatbotAgentChats,\n          eq(ultraChatbotAgentMessages.chatId, ultraChatbotAgentChats.id)\n        )\n        .where(\n          and(\n            eq(ultraChatbotAgentMessages.chatId, input.chatId),\n            eq(ultraChatbotAgentMessages.messageId, input.messageId),\n            eq(ultraChatbotAgentChats.visitorId, input.visitorId)\n          )\n        )\n        .limit(1);\n\n      if (!targetMessage) {\n        throw new Error(getUltraChatbotAgentChatNotFoundError(input.chatId));\n      }\n\n      const deletedRows = await database\n        .delete(ultraChatbotAgentMessages)\n        .where(\n          and(\n            eq(ultraChatbotAgentMessages.chatId, input.chatId),\n            gt(ultraChatbotAgentMessages.createdAt, targetMessage.createdAt)\n          )\n        )\n        .returning({ messageId: ultraChatbotAgentMessages.messageId });\n\n      await database\n        .update(ultraChatbotAgentChats)\n        .set({\n          activeStreamId: null,\n          updatedAt: new Date(),\n        })\n        .where(\n          and(\n            eq(ultraChatbotAgentChats.id, input.chatId),\n            eq(ultraChatbotAgentChats.visitorId, input.visitorId)\n          )\n        );\n\n      return {\n        deletedCount: deletedRows.length,\n      };\n    },\n    async deleteAllChatsForVisitor(input: { visitorId: string }) {\n      const {\n        database,\n        ultraChatbotAgentChats,\n        ultraChatbotAgentMessages,\n        ultraChatbotAgentStreams,\n        ultraChatbotAgentVotes,\n      } = await loadUltraChatbotAgentDatabaseModule();\n      const visitorChats = await database\n        .select({ id: ultraChatbotAgentChats.id })\n        .from(ultraChatbotAgentChats)\n        .where(eq(ultraChatbotAgentChats.visitorId, input.visitorId));\n\n      if (visitorChats.length === 0) {\n        return {\n          deletedCount: 0,\n        };\n      }\n\n      const chatIds = visitorChats.map((chat) => chat.id);\n\n      await database\n        .delete(ultraChatbotAgentVotes)\n        .where(inArray(ultraChatbotAgentVotes.chatId, chatIds));\n      await database\n        .delete(ultraChatbotAgentMessages)\n        .where(inArray(ultraChatbotAgentMessages.chatId, chatIds));\n      await database\n        .delete(ultraChatbotAgentStreams)\n        .where(inArray(ultraChatbotAgentStreams.chatId, chatIds));\n\n      const deletedChats = await database\n        .delete(ultraChatbotAgentChats)\n        .where(eq(ultraChatbotAgentChats.visitorId, input.visitorId))\n        .returning({ id: ultraChatbotAgentChats.id });\n\n      return {\n        deletedCount: deletedChats.length,\n      };\n    },\n    async deleteChatForVisitor(input: { chatId: string; visitorId: string }) {\n      try {\n        const { database, ultraChatbotAgentChats, ultraChatbotAgentVotes } =\n          await loadUltraChatbotAgentDatabaseModule();\n\n        const deletedChats = await database.transaction(async (tx) => {\n          const rows = await tx\n            .delete(ultraChatbotAgentChats)\n            .where(\n              and(\n                eq(ultraChatbotAgentChats.id, input.chatId),\n                eq(ultraChatbotAgentChats.visitorId, input.visitorId)\n              )\n            )\n            .returning({ id: ultraChatbotAgentChats.id });\n\n          if (rows.length > 0) {\n            await tx\n              .delete(ultraChatbotAgentVotes)\n              .where(eq(ultraChatbotAgentVotes.chatId, input.chatId));\n          }\n\n          return rows;\n        });\n\n        return {\n          deletedCount: deletedChats.length,\n        };\n      } catch (error) {\n        if (isInvalidUltraChatbotAgentChatIdError(error)) {\n          return {\n            deletedCount: 0,\n          };\n        }\n\n        throw error;\n      }\n    },\n    cleanupExpiredChats(input?: { now?: Date }) {\n      return cleanupExpiredUltraChatbotAgentChats(input);\n    },\n  };\n}\n",
      "type": "registry:lib",
      "target": "@lib/ultra-chatbot-agent/server/chat-store.ts"
    },
    {
      "path": "registry/ultra-chatbot-agent/lib/ultra-chatbot-agent/server/cleanup.ts",
      "content": "import { getUltraChatbotAgentAppEnv } from \"./env\";\n\nconst appEnv = getUltraChatbotAgentAppEnv();\nimport { demoDataRetentionDays } from \"./demo-data-retention-policy\";\n\nimport { cleanupExpiredUltraChatbotAgentUploadBlobs } from \"./blob-cleanup\";\nimport type { UltraChatbotAgentBlobStorageEnv } from \"./blob-storage\";\nimport { cleanupExpiredUltraChatbotAgentChats } from \"./chat-store\";\n\nexport const ultraChatbotAgentCleanupRetentionDays = demoDataRetentionDays;\nexport const ultraChatbotAgentCleanupCronScheduleUtc = \"0 20 * * *\";\n\nexport async function cleanupExpiredUltraChatbotAgentDemoData(\n  input: {\n    env?: UltraChatbotAgentBlobStorageEnv;\n    now?: Date;\n    retentionDays?: number;\n  } = {}\n) {\n  const retentionDays =\n    input.retentionDays ?? ultraChatbotAgentCleanupRetentionDays;\n  const [database, blobs] = await Promise.all([\n    cleanupExpiredUltraChatbotAgentChats({\n      now: input.now,\n      retentionDays,\n    }),\n    cleanupExpiredUltraChatbotAgentUploadBlobs(input.env ?? appEnv, {\n      now: input.now,\n      retentionDays,\n    }),\n  ]);\n\n  return {\n    blobs,\n    database,\n    retentionDays,\n  };\n}\n",
      "type": "registry:lib",
      "target": "@lib/ultra-chatbot-agent/server/cleanup.ts"
    },
    {
      "path": "registry/ultra-chatbot-agent/lib/ultra-chatbot-agent/server/create-document.ts",
      "content": "import { tool } from \"ai\";\nimport { z } from \"zod\";\n\nimport { createUltraChatbotAgentDocumentStore } from \"./document-store\";\n\nconst createDocumentInputSchema = z.object({\n  content: z.string().trim().min(1),\n  kind: z.enum([\"code\", \"image\", \"sheet\", \"text\"]),\n  title: z.string().trim().min(1),\n});\n\nexport function createUltraChatbotAgentCreateDocumentTool(input: {\n  chatId: string;\n  visitorId: string;\n}) {\n  return tool({\n    description:\n      \"Create a persistent document artifact for the current visitor. Use this when the user explicitly asks you to draft, write, or create a document they may continue editing later.\",\n    inputSchema: createDocumentInputSchema,\n    execute: async ({ content, kind, title }) => {\n      const savedDocument =\n        await createUltraChatbotAgentDocumentStore().saveDocument({\n          chatId: input.chatId,\n          content,\n          documentId: crypto.randomUUID(),\n          kind,\n          title,\n          visitorId: input.visitorId,\n        });\n\n      return {\n        id: savedDocument.id,\n        kind: savedDocument.kind,\n        title: savedDocument.title,\n      };\n    },\n  });\n}\n",
      "type": "registry:lib",
      "target": "@lib/ultra-chatbot-agent/server/create-document.ts"
    },
    {
      "path": "registry/ultra-chatbot-agent/lib/ultra-chatbot-agent/server/database.ts",
      "content": "import \"server-only\";\n\nimport { Pool } from \"@neondatabase/serverless\";\nimport { drizzle } from \"drizzle-orm/neon-serverless\";\n\nimport {\n  getUltraChatbotAgentDatabaseConfig,\n  getUltraChatbotAgentAppEnv,\n  type UltraChatbotAgentEnv,\n} from \"./env\";\nimport {\n  ultraChatbotAgentChats,\n  ultraChatbotAgentDocuments,\n  ultraChatbotAgentMessages,\n  ultraChatbotAgentSchema,\n  ultraChatbotAgentStreams,\n  ultraChatbotAgentSuggestions,\n  ultraChatbotAgentVotes,\n} from \"./schema\";\n\nfunction createUltraChatbotAgentDatabase(connectionString: string) {\n  const client = new Pool({ connectionString });\n  const database = drizzle({\n    client,\n    schema: ultraChatbotAgentSchema,\n  });\n\n  return { client, database };\n}\n\nexport type UltraChatbotAgentDatabase = ReturnType<\n  typeof createUltraChatbotAgentDatabase\n>[\"database\"];\n\nlet databaseModulePromise:\n  | Promise<{\n      database: UltraChatbotAgentDatabase;\n      ultraChatbotAgentChats: typeof ultraChatbotAgentChats;\n      ultraChatbotAgentDocuments: typeof ultraChatbotAgentDocuments;\n      ultraChatbotAgentMessages: typeof ultraChatbotAgentMessages;\n      ultraChatbotAgentStreams: typeof ultraChatbotAgentStreams;\n      ultraChatbotAgentSuggestions: typeof ultraChatbotAgentSuggestions;\n      ultraChatbotAgentVotes: typeof ultraChatbotAgentVotes;\n    }>\n  | null = null;\n\nexport async function loadUltraChatbotAgentDatabase(\n  env: UltraChatbotAgentEnv = getUltraChatbotAgentAppEnv()\n) {\n  if (!databaseModulePromise) {\n    const { databaseUrl } = getUltraChatbotAgentDatabaseConfig(env);\n    const { database } = createUltraChatbotAgentDatabase(databaseUrl);\n\n    databaseModulePromise = Promise.resolve({\n      database,\n      ultraChatbotAgentChats,\n      ultraChatbotAgentDocuments,\n      ultraChatbotAgentMessages,\n      ultraChatbotAgentStreams,\n      ultraChatbotAgentSuggestions,\n      ultraChatbotAgentVotes,\n    });\n  }\n\n  return databaseModulePromise;\n}\n\nexport {\n  ultraChatbotAgentChats,\n  ultraChatbotAgentDocuments,\n  ultraChatbotAgentMessages,\n  ultraChatbotAgentStreams,\n  ultraChatbotAgentSuggestions,\n  ultraChatbotAgentVotes,\n};\n\nexport const database = new Proxy({} as UltraChatbotAgentDatabase, {\n  get() {\n    throw new Error(\n      \"Use loadUltraChatbotAgentDatabase() so DATABASE_URL is resolved lazily in registry installs.\"\n    );\n  },\n});\n",
      "type": "registry:lib",
      "target": "@lib/ultra-chatbot-agent/server/database.ts"
    },
    {
      "path": "registry/ultra-chatbot-agent/lib/ultra-chatbot-agent/server/demo-data-retention-policy.ts",
      "content": "export const demoDataRetentionDays = 7;\n",
      "type": "registry:lib",
      "target": "@lib/ultra-chatbot-agent/server/demo-data-retention-policy.ts"
    },
    {
      "path": "registry/ultra-chatbot-agent/lib/ultra-chatbot-agent/server/document-store.ts",
      "content": "import { and, desc, eq, gt } from \"drizzle-orm\";\n\nimport { loadUltraChatbotAgentDatabase } from \"./database\";\n\nexport interface UltraChatbotAgentDocumentRecord {\n  chatId: string | null;\n  content: string | null;\n  createdAt: string;\n  id: string;\n  kind: \"code\" | \"image\" | \"sheet\" | \"text\";\n  title: string;\n  visitorId: string;\n}\n\ninterface UltraChatbotAgentDocumentDatabaseModule {\n  database: typeof import(\"./database\")[\"database\"];\n  ultraChatbotAgentDocuments: typeof import(\"./database\")[\"ultraChatbotAgentDocuments\"];\n}\n\nfunction toIsoString(value: Date | string) {\n  return value instanceof Date ? value.toISOString() : value;\n}\n\nfunction normalizeDocumentRecord(record: {\n  chatId: string | null;\n  content: string | null;\n  createdAt: Date | string;\n  id: string;\n  kind: \"code\" | \"image\" | \"sheet\" | \"text\";\n  title: string;\n  visitorId: string;\n}) {\n  return {\n    chatId: record.chatId,\n    content: record.content,\n    createdAt: toIsoString(record.createdAt),\n    id: record.id,\n    kind: record.kind,\n    title: record.title,\n    visitorId: record.visitorId,\n  } satisfies UltraChatbotAgentDocumentRecord;\n}\n\nasync function loadUltraChatbotAgentDocumentDatabase(): Promise<UltraChatbotAgentDocumentDatabaseModule> {\n  const databaseModule = await loadUltraChatbotAgentDatabase();\n\n  return {\n    database: databaseModule.database,\n    ultraChatbotAgentDocuments: databaseModule.ultraChatbotAgentDocuments,\n  };\n}\n\nexport function createUltraChatbotAgentDocumentStore() {\n  return {\n    async loadLatestDocument(input: {\n      chatId: string;\n      documentId: string;\n      visitorId: string;\n    }) {\n      const versions = await this.listDocumentVersions(input);\n\n      return versions[0] ?? null;\n    },\n    async listDocumentVersions(input: {\n      chatId: string;\n      documentId: string;\n      visitorId: string;\n    }) {\n      const { database, ultraChatbotAgentDocuments } =\n        await loadUltraChatbotAgentDocumentDatabase();\n      const rows = await database\n        .select()\n        .from(ultraChatbotAgentDocuments)\n        .where(\n          and(\n            eq(ultraChatbotAgentDocuments.chatId, input.chatId),\n            eq(ultraChatbotAgentDocuments.id, input.documentId),\n            eq(ultraChatbotAgentDocuments.visitorId, input.visitorId)\n          )\n        )\n        .orderBy(desc(ultraChatbotAgentDocuments.createdAt));\n\n      return rows.map(normalizeDocumentRecord);\n    },\n    async listLatestDocumentsForChat(input: {\n      chatId: string;\n      limit: number;\n      visitorId: string;\n    }) {\n      const { database, ultraChatbotAgentDocuments } =\n        await loadUltraChatbotAgentDocumentDatabase();\n      const rows = await database\n        .select()\n        .from(ultraChatbotAgentDocuments)\n        .where(\n          and(\n            eq(ultraChatbotAgentDocuments.chatId, input.chatId),\n            eq(ultraChatbotAgentDocuments.visitorId, input.visitorId)\n          )\n        )\n        .orderBy(desc(ultraChatbotAgentDocuments.createdAt));\n\n      const latestById = new Map<string, UltraChatbotAgentDocumentRecord>();\n\n      for (const row of rows) {\n        if (latestById.has(row.id)) {\n          continue;\n        }\n\n        latestById.set(row.id, normalizeDocumentRecord(row));\n\n        if (latestById.size >= input.limit) {\n          break;\n        }\n      }\n\n      return [...latestById.values()];\n    },\n    async saveDocument(input: {\n      chatId: string;\n      content: string;\n      documentId: string;\n      kind: \"code\" | \"image\" | \"sheet\" | \"text\";\n      title: string;\n      visitorId: string;\n    }) {\n      const { database, ultraChatbotAgentDocuments } =\n        await loadUltraChatbotAgentDocumentDatabase();\n      const [row] = await database\n        .insert(ultraChatbotAgentDocuments)\n        .values({\n          chatId: input.chatId,\n          content: input.content,\n          createdAt: new Date(),\n          id: input.documentId,\n          kind: input.kind,\n          title: input.title,\n          visitorId: input.visitorId,\n        })\n        .returning();\n\n      if (!row) {\n        throw new Error(\"Failed to save the ultra-chatbot-agent document.\");\n      }\n\n      return normalizeDocumentRecord(row);\n    },\n    async deleteDocumentVersionsAfterTimestamp(input: {\n      chatId: string;\n      documentId: string;\n      timestamp: Date;\n      visitorId: string;\n    }) {\n      const { database, ultraChatbotAgentDocuments } =\n        await loadUltraChatbotAgentDocumentDatabase();\n      const deletedRows = await database\n        .delete(ultraChatbotAgentDocuments)\n        .where(\n          and(\n            eq(ultraChatbotAgentDocuments.chatId, input.chatId),\n            eq(ultraChatbotAgentDocuments.id, input.documentId),\n            eq(ultraChatbotAgentDocuments.visitorId, input.visitorId),\n            gt(ultraChatbotAgentDocuments.createdAt, input.timestamp)\n          )\n        )\n        .returning({ id: ultraChatbotAgentDocuments.id });\n\n      return {\n        deletedCount: deletedRows.length,\n      };\n    },\n  };\n}\n",
      "type": "registry:lib",
      "target": "@lib/ultra-chatbot-agent/server/document-store.ts"
    },
    {
      "path": "registry/ultra-chatbot-agent/lib/ultra-chatbot-agent/server/document-target.ts",
      "content": "import type { UltraChatbotAgentDocumentRecord } from \"./document-store\";\n\nconst placeholderDocumentId = \"00000000-0000-0000-0000-000000000000\";\n\nfunction normalizeTitle(value: string) {\n  return value.trim().toLocaleLowerCase();\n}\n\nfunction normalizeDocumentId(value: string | undefined) {\n  if (!value || value === placeholderDocumentId) {\n    return;\n  }\n\n  return value;\n}\n\nexport function getUltraChatbotAgentDocumentLookupError(input: {\n  documentId?: string;\n  documentTitle?: string;\n  documentsCount: number;\n}) {\n  const documentId = normalizeDocumentId(input.documentId);\n\n  if (input.documentTitle) {\n    return `No document found with the title \"${input.documentTitle}\".`;\n  }\n\n  if (documentId) {\n    return `No document found for ${documentId}.`;\n  }\n\n  if (input.documentsCount === 0) {\n    return \"There is no saved document yet. Create one before editing or updating it.\";\n  }\n\n  return \"More than one saved document exists. Pass a documentTitle so the tool can target the right artifact.\";\n}\n\nexport function findUltraChatbotAgentTargetDocument(input: {\n  documentId?: string;\n  documentTitle?: string;\n  latestDocuments: UltraChatbotAgentDocumentRecord[];\n}) {\n  const documentId = normalizeDocumentId(input.documentId);\n  const { documentTitle, latestDocuments } = input;\n\n  if (documentId) {\n    const matchedById =\n      latestDocuments.find((document) => document.id === documentId) ?? null;\n\n    if (matchedById) {\n      return matchedById;\n    }\n  }\n\n  if (documentTitle) {\n    return (\n      latestDocuments.find(\n        (document) =>\n          normalizeTitle(document.title) === normalizeTitle(documentTitle)\n      ) ?? null\n    );\n  }\n\n  if (latestDocuments.length === 1) {\n    return latestDocuments[0];\n  }\n\n  return null;\n}\n",
      "type": "registry:lib",
      "target": "@lib/ultra-chatbot-agent/server/document-target.ts"
    },
    {
      "path": "registry/ultra-chatbot-agent/lib/ultra-chatbot-agent/server/documents.ts",
      "content": "import { z } from \"zod\";\nimport { createUltraChatbotAgentDocumentStore } from \"./document-store\";\n\nconst documentBodySchema = z.object({\n  content: z.string(),\n  isManualEdit: z.boolean().optional(),\n  kind: z.enum([\"code\", \"image\", \"sheet\", \"text\"]),\n  title: z.string().trim().min(1),\n});\n\nfunction getUltraChatbotAgentDocumentNotFoundError(documentId: string) {\n  return `No ultra-chatbot-agent document found for ${documentId}.`;\n}\n\nexport async function handleUltraChatbotAgentDocumentRequest(\n  request: Request,\n  viewer: { visitorId: string }\n) {\n  const url = new URL(request.url);\n  const chatId = url.searchParams.get(\"chatId\");\n  const documentId = url.searchParams.get(\"id\");\n  const store = createUltraChatbotAgentDocumentStore();\n\n  if (!chatId) {\n    return Response.json(\n      { error: 'Expected the \"chatId\" search parameter.' },\n      { status: 400 }\n    );\n  }\n\n  if (request.method === \"GET\") {\n    if (!documentId) {\n      return Response.json(\n        await store.listLatestDocumentsForChat({\n          chatId,\n          limit: 12,\n          visitorId: viewer.visitorId,\n        })\n      );\n    }\n\n    const documents = await store.listDocumentVersions({\n      chatId,\n      documentId,\n      visitorId: viewer.visitorId,\n    });\n\n    if (documents.length === 0) {\n      return Response.json(\n        { error: getUltraChatbotAgentDocumentNotFoundError(documentId) },\n        { status: 404 }\n      );\n    }\n\n    return Response.json(documents);\n  }\n\n  if (request.method === \"POST\") {\n    if (!documentId) {\n      return Response.json(\n        { error: 'Expected the \"id\" search parameter.' },\n        { status: 400 }\n      );\n    }\n\n    let body: z.infer<typeof documentBodySchema>;\n\n    try {\n      body = documentBodySchema.parse(await request.json());\n    } catch {\n      return Response.json(\n        { error: \"Expected a valid document payload.\" },\n        { status: 400 }\n      );\n    }\n\n    const existingDocuments = await store.listDocumentVersions({\n      chatId,\n      documentId,\n      visitorId: viewer.visitorId,\n    });\n    const latestDocument = existingDocuments[0];\n\n    if (body.isManualEdit && !latestDocument) {\n      return Response.json(\n        { error: getUltraChatbotAgentDocumentNotFoundError(documentId) },\n        { status: 404 }\n      );\n    }\n\n    const savedDocument = await store.saveDocument({\n      chatId,\n      content: body.content,\n      documentId,\n      kind: latestDocument?.kind ?? body.kind,\n      title: latestDocument?.title ?? body.title,\n      visitorId: viewer.visitorId,\n    });\n\n    return Response.json(savedDocument);\n  }\n\n  if (request.method === \"DELETE\") {\n    if (!documentId) {\n      return Response.json(\n        { error: 'Expected the \"id\" search parameter.' },\n        { status: 400 }\n      );\n    }\n\n    const rawTimestamp = url.searchParams.get(\"timestamp\");\n\n    if (!rawTimestamp) {\n      return Response.json(\n        { error: 'Expected the \"timestamp\" search parameter.' },\n        { status: 400 }\n      );\n    }\n\n    const timestamp = new Date(rawTimestamp);\n\n    if (Number.isNaN(timestamp.valueOf())) {\n      return Response.json(\n        { error: 'Expected a valid \"timestamp\" value.' },\n        { status: 400 }\n      );\n    }\n\n    const existingDocuments = await store.listDocumentVersions({\n      chatId,\n      documentId,\n      visitorId: viewer.visitorId,\n    });\n\n    if (existingDocuments.length === 0) {\n      return Response.json(\n        { error: getUltraChatbotAgentDocumentNotFoundError(documentId) },\n        { status: 404 }\n      );\n    }\n\n    return Response.json(\n      await store.deleteDocumentVersionsAfterTimestamp({\n        chatId,\n        documentId,\n        timestamp,\n        visitorId: viewer.visitorId,\n      })\n    );\n  }\n\n  return Response.json({ error: \"Method not allowed.\" }, { status: 405 });\n}\n",
      "type": "registry:lib",
      "target": "@lib/ultra-chatbot-agent/server/documents.ts"
    },
    {
      "path": "registry/ultra-chatbot-agent/lib/ultra-chatbot-agent/server/edit-document.ts",
      "content": "import { tool } from \"ai\";\nimport { z } from \"zod\";\n\nimport { createUltraChatbotAgentDocumentStore } from \"./document-store\";\nimport {\n  findUltraChatbotAgentTargetDocument,\n  getUltraChatbotAgentDocumentLookupError,\n} from \"./document-target\";\n\nconst editDocumentInputSchema = z.object({\n  documentId: z.string().uuid().optional(),\n  documentTitle: z.string().trim().min(1).optional(),\n  newText: z.string(),\n  oldText: z.string().trim().min(1),\n  replaceAll: z.boolean().optional().default(false),\n});\n\nexport function createUltraChatbotAgentEditDocumentTool(input: {\n  chatId: string;\n  visitorId: string;\n}) {\n  return tool({\n    description:\n      \"Make a targeted edit to an existing saved document by replacing exact text. Prefer this over updateDocument when the user asks for a small or local change.\",\n    inputSchema: editDocumentInputSchema,\n    execute: async ({\n      documentId,\n      documentTitle,\n      newText,\n      oldText,\n      replaceAll,\n    }) => {\n      const documentStore = createUltraChatbotAgentDocumentStore();\n      const latestDocuments = await documentStore.listLatestDocumentsForChat({\n        chatId: input.chatId,\n        limit: 24,\n        visitorId: input.visitorId,\n      });\n      const matchedDocument = findUltraChatbotAgentTargetDocument({\n        documentId,\n        documentTitle,\n        latestDocuments,\n      });\n\n      if (!matchedDocument?.content) {\n        return {\n          error: getUltraChatbotAgentDocumentLookupError({\n            documentId,\n            documentTitle,\n            documentsCount: latestDocuments.length,\n          }),\n        };\n      }\n\n      if (!matchedDocument.content.includes(oldText)) {\n        return {\n          error: `Could not find \"${oldText}\" in \"${matchedDocument.title}\".`,\n        };\n      }\n\n      const nextContent = replaceAll\n        ? matchedDocument.content.replaceAll(oldText, newText)\n        : matchedDocument.content.replace(oldText, newText);\n\n      const savedDocument = await documentStore.saveDocument({\n        chatId: input.chatId,\n        content: nextContent,\n        documentId: matchedDocument.id,\n        kind: matchedDocument.kind,\n        title: matchedDocument.title,\n        visitorId: input.visitorId,\n      });\n\n      return {\n        id: savedDocument.id,\n        kind: savedDocument.kind,\n        title: savedDocument.title,\n      };\n    },\n  });\n}\n",
      "type": "registry:lib",
      "target": "@lib/ultra-chatbot-agent/server/edit-document.ts"
    },
    {
      "path": "registry/ultra-chatbot-agent/lib/ultra-chatbot-agent/server/enable-sandbox.ts",
      "content": "import { tool } from \"ai\";\nimport { z } from \"zod\";\n\nimport { createUltraChatbotAgentChatStore } from \"./chat-store\";\n\nconst sandboxEnablementInputSchema = z.object({\n  reason: z\n    .string()\n    .trim()\n    .min(1)\n    .describe(\"Why sandbox access is required for the current request.\"),\n  requestedToolFamilies: z\n    .array(z.string().trim().min(1))\n    .min(1)\n    .describe(\"The higher-risk capability families that require sandbox.\"),\n});\n\nexport function createUltraChatbotAgentEnableSandboxTool(input: {\n  chatId: string;\n  visitorId: string;\n}) {\n  return tool({\n    description:\n      \"Request human approval to enable sandbox-backed capabilities for this chat before using execution-heavy tools.\",\n    inputSchema: sandboxEnablementInputSchema,\n    needsApproval: true,\n    execute: async ({ reason, requestedToolFamilies }) => {\n      const capabilities =\n        await createUltraChatbotAgentChatStore().setChatCapabilities({\n          capabilities: {\n            sandboxEnabled: true,\n          },\n          chatId: input.chatId,\n          visitorId: input.visitorId,\n        });\n\n      return {\n        capabilities,\n        reason,\n        requestedToolFamilies,\n        sandboxEnabled: capabilities.sandboxEnabled,\n      };\n    },\n  });\n}\n",
      "type": "registry:lib",
      "target": "@lib/ultra-chatbot-agent/server/enable-sandbox.ts"
    },
    {
      "path": "registry/ultra-chatbot-agent/lib/ultra-chatbot-agent/server/env.ts",
      "content": "import {\n  buildAiGatewayContractSetupState,\n  createAiGatewayFromContract,\n  readAiGatewayContractConfig,\n  type AiGatewayContractConfig,\n  type AiGatewayContractSetupState,\n  type AiGatewayEnvRecord,\n  type AiGatewaySetupConfig,\n} from \"@/lib/ai-gateway/contract\";\n\nconst DEFAULT_ULTRA_CHATBOT_AGENT_CHAT_MODEL = \"openai/gpt-5-mini\";\n\nexport type UltraChatbotAgentEnv = AiGatewayEnvRecord & {\n  BLOB_READ_WRITE_TOKEN?: string;\n  CRON_SECRET?: string;\n  DATABASE_URL?: string;\n  REDIS_URL?: string;\n  VERCEL_OIDC_TOKEN?: string;\n  VERCEL_PROJECT_ID?: string;\n  VERCEL_TEAM_ID?: string;\n  VERCEL_TOKEN?: string;\n};\n\nexport interface UltraChatbotAgentConfig extends AiGatewayContractConfig {\n  databaseUrl: string | undefined;\n}\n\nexport type UltraChatbotAgentSetupState =\n  AiGatewayContractSetupState<AiGatewaySetupConfig>;\n\nexport interface DatabaseSetupState {\n  isReady: boolean;\n  issues: string[];\n}\n\nexport type AiGatewayProvider = ReturnType<typeof createAiGatewayFromContract>;\n\nconst ultraChatbotAgentContract = {\n  defaultChatModel: DEFAULT_ULTRA_CHATBOT_AGENT_CHAT_MODEL,\n  missingApiKeyError:\n    \"Missing AI_GATEWAY_API_KEY. Add it to .env.local before using Ultra Chatbot Agent.\",\n  missingApiKeyIssue:\n    \"AI_GATEWAY_API_KEY is missing. Ultra can render, but chat requests will fail until it is configured.\",\n} as const;\n\nexport function getUltraChatbotAgentAppEnv(): UltraChatbotAgentEnv {\n  // biome-ignore lint/style/noProcessEnv: Registry source installs into consumer apps without this repo's env wrapper.\n  return process.env;\n}\n\nexport function getAiGatewayConfig(\n  env: UltraChatbotAgentEnv = getUltraChatbotAgentAppEnv()\n): UltraChatbotAgentConfig {\n  return {\n    ...readAiGatewayContractConfig(env, ultraChatbotAgentContract),\n    databaseUrl: env.DATABASE_URL,\n  };\n}\n\nexport function getAiGatewaySetupState(\n  env: UltraChatbotAgentEnv = getUltraChatbotAgentAppEnv()\n): UltraChatbotAgentSetupState {\n  return buildAiGatewayContractSetupState(env, ultraChatbotAgentContract);\n}\n\nexport function createAiGateway(\n  env: UltraChatbotAgentEnv = getUltraChatbotAgentAppEnv()\n): AiGatewayProvider {\n  return createAiGatewayFromContract(env, ultraChatbotAgentContract);\n}\n\nexport function getDatabaseSetupState(\n  env: UltraChatbotAgentEnv = getUltraChatbotAgentAppEnv()\n): DatabaseSetupState {\n  const issues = env.DATABASE_URL\n    ? []\n    : [\n        \"DATABASE_URL is missing. Ultra Chatbot Agent requires a writable Postgres database for chats, messages, votes, documents, suggestions, and stream metadata.\",\n      ];\n\n  return {\n    isReady: issues.length === 0,\n    issues,\n  };\n}\n\nexport function getUltraChatbotAgentDatabaseConfig(\n  env: UltraChatbotAgentEnv = getUltraChatbotAgentAppEnv()\n) {\n  if (!env.DATABASE_URL) {\n    throw new Error(\n      \"DATABASE_URL is missing. Add it to .env.local before using Ultra Chatbot Agent persistence.\"\n    );\n  }\n\n  return { databaseUrl: env.DATABASE_URL };\n}\n\nexport interface VercelBlobEnv {\n  BLOB_READ_WRITE_TOKEN?: string;\n}\n\nexport function getVercelBlobToken(\n  env: VercelBlobEnv = getUltraChatbotAgentAppEnv()\n) {\n  if (!env.BLOB_READ_WRITE_TOKEN) {\n    throw new Error(\n      \"BLOB_READ_WRITE_TOKEN is missing. Add it to .env.local using a Vercel Blob store before using file uploads.\"\n    );\n  }\n\n  return env.BLOB_READ_WRITE_TOKEN;\n}\n\nexport interface CronEnv {\n  CRON_SECRET?: string;\n}\n\nexport function getCronSecretError() {\n  return \"CRON_SECRET is missing. Cleanup cron routes require an authenticated secret.\";\n}\n\nexport function getCronSecret(env: CronEnv = getUltraChatbotAgentAppEnv()) {\n  if (!env.CRON_SECRET) {\n    throw new Error(getCronSecretError());\n  }\n\n  return env.CRON_SECRET;\n}\n",
      "type": "registry:lib",
      "target": "@lib/ultra-chatbot-agent/server/env.ts"
    },
    {
      "path": "registry/ultra-chatbot-agent/lib/ultra-chatbot-agent/server/get-weather.ts",
      "content": "import { tool } from \"ai\";\nimport { z } from \"zod\";\n\nconst weatherInputSchema = z\n  .object({\n    city: z.string().trim().min(1).optional(),\n    latitude: z.number().optional(),\n    longitude: z.number().optional(),\n  })\n  .refine(\n    (input) =>\n      Boolean(input.city) ||\n      (typeof input.latitude === \"number\" &&\n        typeof input.longitude === \"number\"),\n    {\n      message:\n        \"Provide either a city name or both latitude and longitude coordinates.\",\n    }\n  );\n\ninterface UltraChatbotAgentWeatherResult {\n  cityName?: string;\n  current: {\n    interval: number;\n    temperature_2m: number;\n    time: string;\n  };\n  current_units: {\n    interval: string;\n    temperature_2m: string;\n    time: string;\n  };\n  daily: {\n    sunrise: string[];\n    sunset: string[];\n    time: string[];\n  };\n  daily_units: {\n    sunrise: string;\n    sunset: string;\n    time: string;\n  };\n  elevation: number;\n  generationtime_ms: number;\n  hourly: {\n    temperature_2m: number[];\n    time: string[];\n  };\n  hourly_units: {\n    temperature_2m: string;\n    time: string;\n  };\n  latitude: number;\n  longitude: number;\n  timezone: string;\n  timezone_abbreviation: string;\n  utc_offset_seconds: number;\n}\n\nasync function geocodeCity(city: string) {\n  const response = await fetch(\n    `https://geocoding-api.open-meteo.com/v1/search?name=${encodeURIComponent(\n      city\n    )}&count=1&language=en&format=json`\n  );\n\n  if (!response.ok) {\n    throw new Error(`Failed to geocode \"${city}\".`);\n  }\n\n  const data = (await response.json()) as {\n    results?: Array<{\n      latitude: number;\n      longitude: number;\n    }>;\n  };\n\n  const result = data.results?.[0];\n\n  if (!result) {\n    return null;\n  }\n\n  return {\n    latitude: result.latitude,\n    longitude: result.longitude,\n  };\n}\n\nasync function loadWeather(input: { latitude: number; longitude: number }) {\n  const response = await fetch(\n    `https://api.open-meteo.com/v1/forecast?latitude=${input.latitude}&longitude=${input.longitude}&current=temperature_2m&hourly=temperature_2m&daily=sunrise,sunset&timezone=auto`\n  );\n\n  if (!response.ok) {\n    throw new Error(\"Failed to load the current weather.\");\n  }\n\n  return (await response.json()) as UltraChatbotAgentWeatherResult;\n}\n\nexport function createUltraChatbotAgentGetWeatherTool() {\n  return tool({\n    description:\n      \"Get the current weather for a city or set of coordinates. Use this when the user explicitly asks for the weather.\",\n    inputSchema: weatherInputSchema,\n    execute: async ({ city, latitude, longitude }) => {\n      let resolvedLatitude = latitude;\n      let resolvedLongitude = longitude;\n\n      if (city) {\n        const coordinates = await geocodeCity(city);\n\n        if (!coordinates) {\n          return {\n            error: `Could not find coordinates for \"${city}\".`,\n          };\n        }\n\n        resolvedLatitude = coordinates.latitude;\n        resolvedLongitude = coordinates.longitude;\n      }\n\n      if (\n        typeof resolvedLatitude !== \"number\" ||\n        typeof resolvedLongitude !== \"number\"\n      ) {\n        return {\n          error:\n            \"Provide either a city name or both latitude and longitude coordinates.\",\n        };\n      }\n\n      const weather = await loadWeather({\n        latitude: resolvedLatitude,\n        longitude: resolvedLongitude,\n      });\n\n      if (city) {\n        weather.cityName = city;\n      }\n\n      return weather;\n    },\n  });\n}\n",
      "type": "registry:lib",
      "target": "@lib/ultra-chatbot-agent/server/get-weather.ts"
    },
    {
      "path": "registry/ultra-chatbot-agent/lib/ultra-chatbot-agent/server/history.ts",
      "content": "import { getVercelBlobToken } from \"./env\";\nimport {\n  deleteUltraChatbotAgentBlobsForChat,\n  deleteUltraChatbotAgentBlobsForVisitor,\n} from \"./blob-storage\";\nimport { createUltraChatbotAgentChatStore } from \"./chat-store\";\n\nfunction clampHistoryLimit(value: string | null) {\n  const parsedValue = Number.parseInt(value || \"10\", 10);\n\n  if (Number.isNaN(parsedValue)) {\n    return 10;\n  }\n\n  return Math.min(Math.max(parsedValue, 1), 50);\n}\n\nexport async function handleUltraChatbotAgentHistoryRequest(\n  request: Request,\n  viewer: { visitorId: string }\n) {\n  const url = new URL(request.url);\n  const startingAfter = url.searchParams.get(\"starting_after\");\n  const endingBefore = url.searchParams.get(\"ending_before\");\n\n  if (startingAfter && endingBefore) {\n    return Response.json(\n      {\n        error: \"Only one of starting_after or ending_before can be provided.\",\n      },\n      { status: 400 }\n    );\n  }\n\n  const result =\n    await createUltraChatbotAgentChatStore().listChatsForVisitorPage({\n      endingBefore,\n      limit: clampHistoryLimit(url.searchParams.get(\"limit\")),\n      startingAfter,\n      visitorId: viewer.visitorId,\n    });\n\n  return Response.json(result);\n}\n\nexport async function handleUltraChatbotAgentDeleteHistoryRequest(viewer: {\n  visitorId: string;\n}) {\n  const result =\n    await createUltraChatbotAgentChatStore().deleteAllChatsForVisitor({\n      visitorId: viewer.visitorId,\n    });\n\n  await deleteUltraChatbotAgentBlobsForVisitor({\n    token: getVercelBlobToken(),\n    visitorId: viewer.visitorId,\n  });\n\n  return Response.json(result, { status: 200 });\n}\n\nexport async function handleUltraChatbotAgentDeleteChatRequest(\n  chatId: string,\n  viewer: {\n    visitorId: string;\n  }\n) {\n  if (chatId.trim().length === 0) {\n    return Response.json(\n      { error: \"A valid chat id is required.\" },\n      { status: 400 }\n    );\n  }\n\n  const result = await createUltraChatbotAgentChatStore().deleteChatForVisitor({\n    chatId,\n    visitorId: viewer.visitorId,\n  });\n\n  if (result.deletedCount === 0) {\n    return Response.json({ error: \"Chat not found.\" }, { status: 404 });\n  }\n\n  await deleteUltraChatbotAgentBlobsForChat({\n    chatId,\n    token: getVercelBlobToken(),\n    visitorId: viewer.visitorId,\n  });\n\n  return Response.json(result, { status: 200 });\n}\n",
      "type": "registry:lib",
      "target": "@lib/ultra-chatbot-agent/server/history.ts"
    },
    {
      "path": "registry/ultra-chatbot-agent/lib/ultra-chatbot-agent/server/message-edit.ts",
      "content": "import type { UIMessage } from \"ai\";\nimport { z } from \"zod\";\n\nimport { createUltraChatbotAgentChatStore } from \"./chat-store\";\n\nconst ultraChatbotAgentMessageEditSchema = z.object({\n  messageId: z.string().min(1),\n  retainedFileUrls: z.array(z.string().trim().min(1)).max(20).optional(),\n  text: z.string().trim().min(1),\n});\n\nfunction buildEditedUserMessage(input: {\n  message: UIMessage;\n  retainedFileUrls?: string[];\n  text: string;\n}) {\n  const retainedFileUrlSet = input.retainedFileUrls\n    ? new Set(input.retainedFileUrls)\n    : null;\n  const preservedParts = input.message.parts.filter((part) => {\n    if (part.type === \"text\") {\n      return false;\n    }\n\n    if (part.type === \"file\" && retainedFileUrlSet) {\n      return retainedFileUrlSet.has(part.url);\n    }\n\n    return true;\n  });\n\n  return {\n    ...input.message,\n    parts: [\n      ...preservedParts,\n      {\n        text: input.text,\n        type: \"text\" as const,\n      },\n    ],\n  } satisfies UIMessage;\n}\n\nexport async function handleUltraChatbotAgentMessageEditRequest(\n  request: Request,\n  viewer: { chatId: string; visitorId: string }\n) {\n  const parsedBody = ultraChatbotAgentMessageEditSchema.safeParse(\n    await request.json()\n  );\n\n  if (!parsedBody.success) {\n    return Response.json(\n      {\n        error: \"A valid messageId and edited text are required.\",\n      },\n      { status: 400 }\n    );\n  }\n\n  const chatStore = createUltraChatbotAgentChatStore();\n  const session = await chatStore.loadChatSession(\n    viewer.chatId,\n    viewer.visitorId\n  );\n\n  if (!session) {\n    return Response.json(\n      {\n        error: \"Chat not found for this visitor.\",\n      },\n      { status: 404 }\n    );\n  }\n\n  const targetMessage = session.messages.find(\n    (message) => message.id === parsedBody.data.messageId\n  );\n\n  if (!targetMessage || targetMessage.role !== \"user\") {\n    return Response.json(\n      {\n        error: \"Only persisted user messages can be edited.\",\n      },\n      { status: 400 }\n    );\n  }\n\n  await chatStore.saveIncomingUserMessage({\n    chatId: viewer.chatId,\n    message: buildEditedUserMessage({\n      message: targetMessage,\n      retainedFileUrls: parsedBody.data.retainedFileUrls,\n      text: parsedBody.data.text,\n    }),\n    selectedChatModel: session.chat.selectedChatModel,\n    selectedVisibilityType: session.chat.visibility,\n    visitorId: viewer.visitorId,\n  });\n\n  const result = await chatStore.deleteMessagesAfterMessage({\n    chatId: viewer.chatId,\n    messageId: parsedBody.data.messageId,\n    visitorId: viewer.visitorId,\n  });\n\n  return Response.json(result, { status: 200 });\n}\n",
      "type": "registry:lib",
      "target": "@lib/ultra-chatbot-agent/server/message-edit.ts"
    },
    {
      "path": "registry/ultra-chatbot-agent/lib/ultra-chatbot-agent/server/models.ts",
      "content": "export interface UltraChatbotAgentModel {\n  capabilities: {\n    artifactTooling: boolean;\n    attachments: {\n      images: boolean;\n      pdfs: boolean;\n    };\n    reasoning: boolean;\n    tools: boolean;\n    vision: boolean;\n  };\n  costProfile: \"low\" | \"medium\" | \"high\";\n  description: string;\n  expectedLatency: \"low\" | \"medium\" | \"high\";\n  id: string;\n  name: string;\n  provider: \"deepseek\" | \"openai\";\n}\n\nconst ultraChatbotAgentModels = [\n  {\n    capabilities: {\n      artifactTooling: true,\n      attachments: {\n        images: true,\n        pdfs: true,\n      },\n      reasoning: false,\n      tools: true,\n      vision: true,\n    },\n    costProfile: \"low\",\n    description: \"Fast baseline model for the main chat path.\",\n    expectedLatency: \"low\",\n    id: \"openai/gpt-4.1-mini\",\n    name: \"GPT-4.1 mini\",\n    provider: \"openai\",\n  },\n  {\n    capabilities: {\n      artifactTooling: true,\n      attachments: {\n        images: true,\n        pdfs: true,\n      },\n      reasoning: true,\n      tools: true,\n      vision: true,\n    },\n    costProfile: \"medium\",\n    description: \"Compact reasoning model for longer agent turns.\",\n    expectedLatency: \"medium\",\n    id: \"openai/gpt-5-mini\",\n    name: \"GPT-5 mini\",\n    provider: \"openai\",\n  },\n  {\n    capabilities: {\n      artifactTooling: true,\n      attachments: {\n        images: true,\n        pdfs: true,\n      },\n      reasoning: false,\n      tools: true,\n      vision: true,\n    },\n    costProfile: \"low\",\n    description: \"Fast DeepSeek chat profile.\",\n    expectedLatency: \"low\",\n    id: \"deepseek/deepseek-v4-flash\",\n    name: \"DeepSeek V4 Flash\",\n    provider: \"deepseek\",\n  },\n  {\n    capabilities: {\n      artifactTooling: true,\n      attachments: {\n        images: true,\n        pdfs: true,\n      },\n      reasoning: true,\n      tools: true,\n      vision: true,\n    },\n    costProfile: \"medium\",\n    description: \"Reasoning-oriented DeepSeek profile.\",\n    expectedLatency: \"medium\",\n    id: \"deepseek/deepseek-v4-pro\",\n    name: \"DeepSeek V4 Pro\",\n    provider: \"deepseek\",\n  },\n] satisfies readonly UltraChatbotAgentModel[];\n\nconst ultraChatbotAgentModelIdSet = new Set(\n  ultraChatbotAgentModels.map((model) => model.id)\n);\n\nconst defaultUltraChatbotAgentModelId = \"openai/gpt-4.1-mini\";\n\nexport function getUltraChatbotAgentModelCatalog() {\n  return [...ultraChatbotAgentModels];\n}\n\nexport function getUltraChatbotAgentDefaultModel() {\n  return defaultUltraChatbotAgentModelId;\n}\n\nexport function isUltraChatbotAgentModelId(value: string) {\n  return ultraChatbotAgentModelIdSet.has(value);\n}\n\nexport function resolveUltraChatbotAgentDefaultModel(\n  candidate: string | undefined\n) {\n  if (candidate && isUltraChatbotAgentModelId(candidate)) {\n    return candidate;\n  }\n\n  return getUltraChatbotAgentDefaultModel();\n}\n",
      "type": "registry:lib",
      "target": "@lib/ultra-chatbot-agent/server/models.ts"
    },
    {
      "path": "registry/ultra-chatbot-agent/lib/ultra-chatbot-agent/server/project-docs-mcp.ts",
      "content": "import { createMCPClient } from \"@ai-sdk/mcp\";\nimport type { ToolSet } from \"ai\";\n\nexport interface UltraChatbotAgentProjectDocsMcpToolbox {\n  close: () => Promise<void>;\n  tools: ToolSet;\n}\n\nfunction namespaceProjectDocsMcpTools(tools: ToolSet): ToolSet {\n  return Object.fromEntries(\n    Object.entries(tools).map(([name, tool]) => [`project__${name}`, tool])\n  );\n}\n\nexport async function createUltraChatbotAgentProjectDocsMcpToolbox({\n  origin,\n}: {\n  origin: string;\n}): Promise<UltraChatbotAgentProjectDocsMcpToolbox> {\n  const client = await createMCPClient({\n    clientName: \"ultra-chatbot-agent-project-docs-client\",\n    transport: {\n      type: \"http\",\n      url: new URL(\n        \"/api/demos/ultra-chatbot-agent/mcp/project-docs\",\n        origin\n      ).toString(),\n    },\n  });\n\n  const tools = namespaceProjectDocsMcpTools((await client.tools()) as ToolSet);\n\n  return {\n    close: async () => {\n      await client.close();\n    },\n    tools,\n  };\n}\n",
      "type": "registry:lib",
      "target": "@lib/ultra-chatbot-agent/server/project-docs-mcp.ts"
    },
    {
      "path": "registry/ultra-chatbot-agent/lib/ultra-chatbot-agent/server/prompts.ts",
      "content": "import { ultraChatbotAgentKnowledgeSource } from \"../knowledge-source\";\nimport type { UltraChatbotAgentCapabilities } from \"./capabilities\";\n\nexport function getUltraChatbotAgentSystemPrompt(input?: {\n  capabilities?: UltraChatbotAgentCapabilities;\n  sandboxContextText?: string | null;\n}) {\n  const sandboxEnabled = input?.capabilities?.sandboxEnabled === true;\n\n  return [\n    \"You are the Ultra Chatbot Agent demo in an AI SDK examples workspace.\",\n    \"This workspace is an application-shape port of vercel/ai-chatbot.\",\n    \"Answer like an experienced product engineer.\",\n    \"Keep replies concrete and concise.\",\n    \"For non-trivial factual research or current web facts, call web_search before finalizing the answer.\",\n    \"For questions about this repository, demo catalog, feature docs, or implementation boundaries, call project__search_project_docs first, then read a specific demo doc only when the search results point to one.\",\n    \"When the user explicitly asks you to search project docs, call project__search_project_docs and surface the result card.\",\n    `For questions that should be answered from the preindexed PDF knowledge base, use searchKnowledgeBase. The active knowledge source is ${ultraChatbotAgentKnowledgeSource.title}.`,\n    \"User-uploaded PDFs are direct multimodal attachments. Do not treat uploaded files as the preindexed RAG knowledge base unless the user explicitly asks about the configured knowledge source.\",\n    \"Use one broad web_search first.\",\n    \"Only run a second web_search if the first search leaves a concrete evidence gap.\",\n    \"Do not exceed two web_search calls in a single answer.\",\n    \"When web_search returns enough evidence, ground the answer in those sources and cite at least two concrete resources.\",\n    \"When citing web research, include markdown links for the concrete resources you relied on.\",\n    \"After creating or editing a document artifact, never repeat its content in chat.\",\n    \"After createDocument, editDocument, or updateDocument, respond with only a short 1-2 sentence confirmation.\",\n    \"When the user asks you to draft, write, or create a persistent document they may revisit later, use the createDocument tool.\",\n    'When the user asks for code they may inspect or edit later, use createDocument with kind \"code\".',\n    \"When the user explicitly asks for a small or exact document change, use the editDocument tool.\",\n    \"When the user explicitly asks for a broader rewrite of a saved document, use the updateDocument tool.\",\n    \"When the user explicitly asks for the weather, use the getWeather tool.\",\n    \"When the user explicitly asks for feedback or improvement suggestions on an existing saved document, use the requestSuggestions tool.\",\n    \"When the user asks for a structured research brief, comparison report, market scan, or decision memo, use createResearchReport so the answer renders as a report component.\",\n    \"For current research reports, call web_search first, then pass the grounded evidence into createResearchReport.\",\n    sandboxEnabled\n      ? \"Sandbox is already enabled for this chat. Use bash, readFile, writeFile, and skill for coding, repo-local execution, file inspection, and skill-backed work when relevant.\"\n      : \"Sandbox is currently disabled for this chat. If the user asks for coding, repo-local execution, shell commands, file edits, or any other execution-heavy task, call enableSandbox once before attempting that work. After sandbox approval, do not call enableSandbox again in the same turn.\",\n    sandboxEnabled && input?.sandboxContextText\n      ? input.sandboxContextText\n      : null,\n  ]\n    .filter(Boolean)\n    .join(\" \");\n}\n\nexport function getUltraChatbotAgentSuggestionSystemPrompt() {\n  return [\n    \"You are a writing assistant.\",\n    \"Review the document and return up to 5 concrete sentence-level suggestions.\",\n    \"Each suggestion must preserve the user's intent, explain why it helps, and include the original text plus the suggested rewrite.\",\n  ].join(\" \");\n}\n\nexport function getUltraChatbotAgentDocumentRewriteSystemPrompt() {\n  return [\n    \"You are rewriting a saved document inside the Ultra Chatbot Agent workspace.\",\n    \"Return only the revised document content.\",\n    \"Preserve the user's intent while applying the requested rewrite.\",\n    \"Keep the result concrete, coherent, and ready to save as the next version.\",\n  ].join(\" \");\n}\n",
      "type": "registry:lib",
      "target": "@lib/ultra-chatbot-agent/server/prompts.ts"
    },
    {
      "path": "registry/ultra-chatbot-agent/lib/ultra-chatbot-agent/server/providers.ts",
      "content": "import { createOpenAI } from \"@ai-sdk/openai\";\n\nimport {\n  createAiGateway,\n  getAiGatewayConfig,\n} from \"./env\";\n\nimport {\n  getUltraChatbotAgentDefaultModel,\n  isUltraChatbotAgentModelId,\n} from \"./models\";\n\ntype DemoEnv = Record<string, string | undefined>;\n\nexport interface UltraChatbotAgentProvider {\n  gateway: ReturnType<typeof createAiGateway>;\n  hostedToolsGateway: ReturnType<typeof createOpenAI>;\n  resolveModelId: (selectedChatModel?: string) => string;\n}\n\nexport const ULTRA_CHATBOT_AGENT_PROVIDER_OPTIONS = {\n  openai: {\n    reasoningEffort: \"medium\",\n    reasoningSummary: \"auto\",\n    textVerbosity: \"low\",\n  },\n} as const;\n\nconst trailingSlashPattern = /\\/$/;\nconst v3AiSuffixPattern = /\\/v3\\/ai$/;\n\nfunction resolveOpenAICompatibleBaseURL(baseURL: string) {\n  const normalizedBaseURL = baseURL.replace(trailingSlashPattern, \"\");\n\n  if (normalizedBaseURL.endsWith(\"/v1\")) {\n    return normalizedBaseURL;\n  }\n\n  if (normalizedBaseURL.endsWith(\"/v3/ai\")) {\n    return normalizedBaseURL.replace(v3AiSuffixPattern, \"/v1\");\n  }\n\n  throw new Error(\n    `Ultra chatbot agent expects AI_GATEWAY_BASE_URL to end with /v3/ai or /v1. Received: ${baseURL}`\n  );\n}\n\nexport function resolveUltraChatbotAgentSelectedModelId(input: {\n  env: DemoEnv;\n  selectedChatModel?: string;\n}) {\n  if (\n    input.selectedChatModel &&\n    isUltraChatbotAgentModelId(input.selectedChatModel)\n  ) {\n    return input.selectedChatModel;\n  }\n\n  const { chatModel } = getAiGatewayConfig(input.env);\n\n  if (isUltraChatbotAgentModelId(chatModel)) {\n    return chatModel;\n  }\n\n  return getUltraChatbotAgentDefaultModel();\n}\n\nexport function createUltraChatbotAgentProvider(\n  env: DemoEnv\n): UltraChatbotAgentProvider {\n  const gateway = createAiGateway(env);\n  const { apiKey, baseURL } = getAiGatewayConfig(env);\n  const hostedToolsGateway = createOpenAI({\n    apiKey,\n    baseURL: resolveOpenAICompatibleBaseURL(baseURL),\n    name: \"gateway-openai\",\n  });\n\n  return {\n    gateway,\n    hostedToolsGateway,\n    resolveModelId(selectedChatModel?: string) {\n      return resolveUltraChatbotAgentSelectedModelId({\n        env,\n        selectedChatModel,\n      });\n    },\n  };\n}\n",
      "type": "registry:lib",
      "target": "@lib/ultra-chatbot-agent/server/providers.ts"
    },
    {
      "path": "registry/ultra-chatbot-agent/lib/ultra-chatbot-agent/server/request-suggestions.ts",
      "content": "import { generateObject, tool } from \"ai\";\nimport { z } from \"zod\";\n\nimport { createUltraChatbotAgentDocumentStore } from \"./document-store\";\nimport {\n  findUltraChatbotAgentTargetDocument,\n  getUltraChatbotAgentDocumentLookupError,\n} from \"./document-target\";\nimport { getUltraChatbotAgentSuggestionSystemPrompt } from \"./prompts\";\nimport { createUltraChatbotAgentSuggestionStore } from \"./suggestion-store\";\n\nconst suggestionShapeSchema = z.object({\n  description: z.string().trim().min(1),\n  originalText: z.string().trim().min(1),\n  suggestedText: z.string().trim().min(1),\n});\n\nconst requestSuggestionsResultSchema = z.object({\n  suggestions: z.array(suggestionShapeSchema).max(5),\n});\n\nconst requestSuggestionsInputSchema = z.object({\n  documentId: z.string().uuid().optional(),\n  documentTitle: z.string().trim().min(1).optional(),\n});\n\nexport function createUltraChatbotAgentRequestSuggestionsTool(input: {\n  chatId: string;\n  model: Parameters<typeof generateObject>[0][\"model\"];\n  visitorId: string;\n}) {\n  return tool({\n    description:\n      \"Request writing suggestions for an existing saved document. Use this only when the user explicitly asks to improve or review a document they already created in this chat workspace.\",\n    inputSchema: requestSuggestionsInputSchema,\n    execute: async ({ documentId, documentTitle }) => {\n      const documentStore = createUltraChatbotAgentDocumentStore();\n      const latestDocuments = await documentStore.listLatestDocumentsForChat({\n        chatId: input.chatId,\n        limit: 24,\n        visitorId: input.visitorId,\n      });\n\n      const matchedDocument = findUltraChatbotAgentTargetDocument({\n        documentId,\n        documentTitle,\n        latestDocuments,\n      });\n\n      if (!matchedDocument?.content?.trim()) {\n        return {\n          error: getUltraChatbotAgentDocumentLookupError({\n            documentId,\n            documentTitle,\n            documentsCount: latestDocuments.length,\n          }),\n        };\n      }\n\n      const response = await generateObject({\n        model: input.model,\n        prompt: matchedDocument.content,\n        schema: requestSuggestionsResultSchema,\n        system: getUltraChatbotAgentSuggestionSystemPrompt(),\n      });\n\n      const savedSuggestions =\n        await createUltraChatbotAgentSuggestionStore().replaceSuggestionsForDocumentVersion(\n          {\n            documentCreatedAt: matchedDocument.createdAt,\n            documentId: matchedDocument.id,\n            suggestions: response.object.suggestions,\n            visitorId: input.visitorId,\n          }\n        );\n\n      return {\n        id: matchedDocument.id,\n        kind: matchedDocument.kind,\n        suggestionCount: savedSuggestions.length,\n        title: matchedDocument.title,\n      };\n    },\n  });\n}\n",
      "type": "registry:lib",
      "target": "@lib/ultra-chatbot-agent/server/request-suggestions.ts"
    },
    {
      "path": "registry/ultra-chatbot-agent/lib/ultra-chatbot-agent/server/research-report.ts",
      "content": "import { generateObject, tool } from \"ai\";\nimport { z } from \"zod\";\n\nconst researchReportInputSchema = z.object({\n  audience: z.string().trim().min(1).optional(),\n  evidence: z.string().trim().min(1).optional(),\n  sources: z\n    .array(\n      z.object({\n        title: z.string().trim().min(1),\n        url: z.string().trim().min(1),\n      })\n    )\n    .max(12)\n    .optional()\n    .describe(\n      \"Exact source records from prior web_search results. Preserve these URLs instead of inventing new ones.\"\n    ),\n  topic: z.string().trim().min(1),\n});\n\nconst researchReportSourceSchema = z.object({\n  title: z.string().trim().min(1),\n  url: z\n    .string()\n    .trim()\n    .min(1)\n    .describe(\"A source URL or stable source locator from the evidence.\"),\n});\n\nexport const researchReportSchema = z.object({\n  executiveSummary: z.string().trim().min(1),\n  keyFindings: z.array(z.string().trim().min(1)).min(1).max(6),\n  recommendations: z.array(z.string().trim().min(1)).min(1).max(5),\n  risks: z.array(z.string().trim().min(1)).max(5),\n  sources: z.array(researchReportSourceSchema).max(8),\n  title: z.string().trim().min(1),\n  topic: z.string().trim().min(1),\n});\n\nfunction buildResearchReportPrompt(\n  input: z.infer<typeof researchReportInputSchema>\n) {\n  return [\n    `Topic: ${input.topic}`,\n    input.audience ? `Audience: ${input.audience}` : null,\n    input.evidence ? `Evidence:\\n${input.evidence}` : null,\n    input.sources && input.sources.length > 0\n      ? `Exact sources:\\n${input.sources\n          .map((source) => `- ${source.title}: ${source.url}`)\n          .join(\"\\n\")}`\n      : null,\n  ]\n    .filter(Boolean)\n    .join(\"\\n\\n\");\n}\n\nfunction normalizeInputSources(\n  sources: NonNullable<z.infer<typeof researchReportInputSchema>[\"sources\"]>\n) {\n  const seenUrls = new Set<string>();\n\n  return sources\n    .map((source) => ({\n      title: source.title.trim(),\n      url: source.url.trim(),\n    }))\n    .filter((source) => {\n      if (!(source.title && source.url) || seenUrls.has(source.url)) {\n        return false;\n      }\n\n      seenUrls.add(source.url);\n      return true;\n    })\n    .slice(0, 8);\n}\n\nexport function createUltraChatbotAgentResearchReportTool(input: {\n  model: Parameters<typeof generateObject>[0][\"model\"];\n}) {\n  return tool({\n    description:\n      \"Create a structured research report object from a topic and available evidence. Use this when the user asks for a research brief, comparison report, market scan, or decision memo that should render as a structured component. If web_search was called first, pass its exact sources array in sources.\",\n    inputSchema: researchReportInputSchema,\n    execute: async (toolInput) => {\n      const suppliedSources = normalizeInputSources(toolInput.sources ?? []);\n      const response = await generateObject({\n        model: input.model,\n        prompt: buildResearchReportPrompt(toolInput),\n        schema: researchReportSchema,\n        system: [\n          \"You create a structured research report for the Ultra Chatbot Agent.\",\n          \"Keep findings concrete, decision-oriented, and grounded in the provided evidence.\",\n          \"If the evidence includes URLs, preserve the most relevant sources in the sources array.\",\n          \"If the topic requires current facts, the main agent should call web_search before this tool and pass the evidence here.\",\n        ].join(\" \"),\n      });\n\n      return {\n        ...response.object,\n        kind: \"research-report\" as const,\n        sources:\n          suppliedSources.length > 0\n            ? suppliedSources\n            : response.object.sources,\n      };\n    },\n  });\n}\n",
      "type": "registry:lib",
      "target": "@lib/ultra-chatbot-agent/server/research-report.ts"
    },
    {
      "path": "registry/ultra-chatbot-agent/lib/ultra-chatbot-agent/server/route-backed-chat-replay.ts",
      "content": "import type { UIMessage } from \"ai\";\n\nexport type RouteBackedChatRequestTrigger =\n  | \"regenerate-message\"\n  | \"submit-message\";\n\ninterface PrepareRouteBackedChatReplayInput {\n  incomingMessage: UIMessage;\n  messageId?: string;\n  persistedMessages: UIMessage[];\n  trigger?: RouteBackedChatRequestTrigger;\n}\n\ninterface PreparedRouteBackedChatReplay {\n  messages: UIMessage[];\n  trimAfterMessageId: string | null;\n}\n\nfunction findReplayUserIndex(input: PrepareRouteBackedChatReplayInput) {\n  if (input.trigger !== \"regenerate-message\") {\n    return -1;\n  }\n\n  if (input.messageId) {\n    const targetIndex = input.persistedMessages.findIndex(\n      (message) => message.id === input.messageId\n    );\n\n    if (targetIndex >= 0) {\n      if (input.persistedMessages[targetIndex]?.role === \"user\") {\n        return targetIndex;\n      }\n\n      for (let index = targetIndex - 1; index >= 0; index -= 1) {\n        if (input.persistedMessages[index]?.role === \"user\") {\n          return index;\n        }\n      }\n    }\n  }\n\n  return input.persistedMessages.findIndex(\n    (message) => message.id === input.incomingMessage.id\n  );\n}\n\nexport function prepareRouteBackedChatReplay(\n  input: PrepareRouteBackedChatReplayInput\n): PreparedRouteBackedChatReplay {\n  const replayUserIndex = findReplayUserIndex(input);\n\n  if (replayUserIndex >= 0) {\n    return {\n      messages: input.persistedMessages\n        .slice(0, replayUserIndex)\n        .concat(input.incomingMessage),\n      trimAfterMessageId: input.persistedMessages[replayUserIndex]?.id ?? null,\n    };\n  }\n\n  const incomingMessageIndex = input.persistedMessages.findIndex(\n    (message) => message.id === input.incomingMessage.id\n  );\n\n  if (incomingMessageIndex >= 0) {\n    return {\n      messages: input.persistedMessages\n        .slice(0, incomingMessageIndex)\n        .concat(input.incomingMessage),\n      trimAfterMessageId: null,\n    };\n  }\n\n  return {\n    messages: [...input.persistedMessages, input.incomingMessage],\n    trimAfterMessageId: null,\n  };\n}\n",
      "type": "registry:lib",
      "target": "@lib/ultra-chatbot-agent/server/route-backed-chat-replay.ts"
    },
    {
      "path": "registry/ultra-chatbot-agent/lib/ultra-chatbot-agent/server/route-owner.ts",
      "content": "const appEnv = {\n  // biome-ignore lint/style/noProcessEnv: Registry source installs into consumer apps without this repo's env wrapper.\n  NODE_ENV: process.env.NODE_ENV,\n};\n\nexport interface VisitorOwnerContext {\n  isNewVisitor: boolean;\n  shouldSetCookie: boolean;\n  visitorId: string;\n}\n\nexport interface VisitorOwner {\n  appendVisitorCookie(\n    response: Response,\n    visitor: VisitorOwnerContext\n  ): Response;\n  buildVisitorCookie(visitorId: string): string;\n  handleOwnedRequest(\n    request: Request,\n    handler: (\n      request: Request,\n      visitor: VisitorOwnerContext\n    ) => Promise<Response>\n  ): Promise<Response>;\n  readVisitorIdFromCookieHeader(cookieHeader: string | null): string | null;\n  resolveVisitor(request: Request): VisitorOwnerContext;\n}\n\nexport function createVisitorOwner({\n  cookieName,\n  createVisitorId = () => crypto.randomUUID(),\n  isValidVisitorId = (visitorId) => visitorId.trim().length > 0,\n  maxAgeSeconds,\n  secure = appEnv.NODE_ENV === \"production\",\n}: {\n  cookieName: string;\n  createVisitorId?: () => string;\n  isValidVisitorId?: (visitorId: string) => boolean;\n  maxAgeSeconds: number;\n  secure?: boolean;\n}): VisitorOwner {\n  function readVisitorIdFromCookieHeader(cookieHeader: string | null) {\n    const visitorId = getCookieValue(cookieHeader, cookieName);\n\n    if (typeof visitorId === \"string\" && isValidVisitorId(visitorId)) {\n      return visitorId.trim();\n    }\n\n    return null;\n  }\n\n  function resolveVisitor(request: Request): VisitorOwnerContext {\n    const visitorId = readVisitorIdFromCookieHeader(\n      request.headers.get(\"cookie\")\n    );\n\n    if (visitorId) {\n      return {\n        isNewVisitor: false,\n        shouldSetCookie: false,\n        visitorId,\n      };\n    }\n\n    return {\n      isNewVisitor: true,\n      shouldSetCookie: true,\n      visitorId: createVisitorId(),\n    };\n  }\n\n  function buildVisitorCookie(visitorId: string) {\n    return [\n      `${cookieName}=${encodeURIComponent(visitorId)}`,\n      \"Path=/\",\n      \"HttpOnly\",\n      \"SameSite=Lax\",\n      secure ? \"Secure\" : null,\n      `Max-Age=${maxAgeSeconds}`,\n    ]\n      .filter((part): part is string => typeof part === \"string\")\n      .join(\"; \");\n  }\n\n  function appendVisitorCookie(\n    response: Response,\n    visitor: VisitorOwnerContext\n  ) {\n    if (visitor.shouldSetCookie) {\n      response.headers.append(\n        \"set-cookie\",\n        buildVisitorCookie(visitor.visitorId)\n      );\n    }\n\n    return response;\n  }\n\n  async function handleOwnedRequest(\n    request: Request,\n    handler: (\n      request: Request,\n      visitor: VisitorOwnerContext\n    ) => Promise<Response>\n  ) {\n    const visitor = resolveVisitor(request);\n    const response = await handler(request, visitor);\n    return appendVisitorCookie(response, visitor);\n  }\n\n  return {\n    appendVisitorCookie,\n    buildVisitorCookie,\n    handleOwnedRequest,\n    readVisitorIdFromCookieHeader,\n    resolveVisitor,\n  };\n}\n\nfunction getCookieValue(cookieHeader: string | null, name: string) {\n  if (!cookieHeader) {\n    return null;\n  }\n\n  const cookies = cookieHeader\n    .split(\";\")\n    .map((part) => part.trim())\n    .filter((part) => part.length > 0);\n  const prefix = `${name}=`;\n  const cookie = cookies.find((part) => part.startsWith(prefix));\n\n  if (!cookie) {\n    return null;\n  }\n\n  return decodeURIComponent(cookie.slice(prefix.length));\n}\n",
      "type": "registry:lib",
      "target": "@lib/ultra-chatbot-agent/server/route-owner.ts"
    },
    {
      "path": "registry/ultra-chatbot-agent/lib/ultra-chatbot-agent/server/runtime.ts",
      "content": "import {\n  convertToModelMessages,\n  createIdGenerator,\n  generateId,\n  isFileUIPart,\n  stepCountIs,\n  ToolLoopAgent,\n  UI_MESSAGE_STREAM_HEADERS,\n  type UIMessage,\n} from \"ai\";\nimport Redis from \"ioredis\";\nimport { after } from \"next/dist/server/after\";\nimport { createResumableStreamContext } from \"resumable-stream/ioredis\";\n\nimport { getUltraChatbotAgentAppEnv } from \"./env\";\n\nconst appEnv = getUltraChatbotAgentAppEnv();\nimport { getAiGatewaySetupState } from \"./env\";\nimport {\n  prepareRouteBackedChatReplay,\n  type RouteBackedChatRequestTrigger,\n} from \"./route-backed-chat-replay\";\nimport { getDatabaseSetupState } from \"./env\";\n\nimport {\n  isUltraChatbotAgentAcceptedUploadMediaType,\n  ultraChatbotAgentUnsupportedAttachmentMediaTypeError,\n} from \"../attachment-config\";\nimport { getUltraChatbotAgentDefaultCapabilities } from \"./capabilities\";\nimport { projectUltraChatbotAgentHistoryForModel } from \"./chat-history\";\nimport {\n  createUltraChatbotAgentChatStore,\n  getUltraChatbotAgentChatNotFoundError,\n} from \"./chat-store\";\nimport { createUltraChatbotAgentCreateDocumentTool } from \"./create-document\";\nimport { createUltraChatbotAgentEditDocumentTool } from \"./edit-document\";\nimport { createUltraChatbotAgentEnableSandboxTool } from \"./enable-sandbox\";\nimport { createUltraChatbotAgentGetWeatherTool } from \"./get-weather\";\nimport {\n  getUltraChatbotAgentModelCatalog,\n  isUltraChatbotAgentModelId,\n  resolveUltraChatbotAgentDefaultModel,\n} from \"./models\";\nimport { createUltraChatbotAgentProjectDocsMcpToolbox } from \"./project-docs-mcp\";\nimport { getUltraChatbotAgentSystemPrompt } from \"./prompts\";\nimport {\n  createUltraChatbotAgentProvider,\n  ULTRA_CHATBOT_AGENT_PROVIDER_OPTIONS,\n} from \"./providers\";\nimport { createUltraChatbotAgentRequestSuggestionsTool } from \"./request-suggestions\";\nimport { createUltraChatbotAgentResearchReportTool } from \"./research-report\";\nimport { createUltraChatbotAgentSandboxToolbox } from \"./sandbox-tools\";\nimport { createUltraChatbotAgentSearchKnowledgeBaseTool } from \"./search-knowledge-base\";\nimport { createUltraChatbotAgentUpdateDocumentTool } from \"./update-document\";\nimport { createUltraChatbotAgentWebSearchTool } from \"./web-search\";\n\nconst invalidRequestBodyError =\n  'Expected a JSON body with a non-empty \"id\" string, \"message\" object, \"selectedChatModel\" string, and \"selectedVisibilityType\".';\nconst ultraChatbotAgentSearchToolName = \"web_search\";\n\ntype DemoEnv = Record<string, string | undefined>;\n\ninterface UltraChatbotAgentRequestBody {\n  id?: string;\n  message?: UIMessage;\n  messageId?: string;\n  selectedChatModel?: string;\n  selectedVisibilityType?: \"private\" | \"public\";\n  trigger?: RouteBackedChatRequestTrigger;\n}\n\nexport interface UltraChatbotAgentRuntimeState {\n  chatModel: string;\n  isChatAvailable: boolean;\n  models: ReturnType<typeof getUltraChatbotAgentModelCatalog>;\n  nodeVersion: string;\n  resumeRequiresRedis: boolean;\n  setupMessage: string | null;\n  statusLabel: \"Ready\" | \"Setup required\";\n}\n\nfunction getRedisSetupIssue(env: DemoEnv) {\n  return env.REDIS_URL\n    ? null\n    : \"REDIS_URL is missing. Ultra-chatbot-agent resume requires Redis-backed resumable streams.\";\n}\n\nexport function getUltraChatbotAgentRuntimeState(\n  env: DemoEnv = appEnv\n): UltraChatbotAgentRuntimeState {\n  const gatewaySetup = getAiGatewaySetupState(env);\n  const databaseSetup = getDatabaseSetupState(env);\n  const issues = [\n    ...gatewaySetup.issues,\n    ...databaseSetup.issues.map(\n      () =>\n        \"DATABASE_URL is missing. Ultra-chatbot-agent storage requires a writable Postgres database.\"\n    ),\n  ];\n  const redisIssue = getRedisSetupIssue(env);\n\n  if (redisIssue) {\n    issues.push(redisIssue);\n  }\n\n  return {\n    chatModel: resolveUltraChatbotAgentDefaultModel(\n      gatewaySetup.config.chatModel\n    ),\n    isChatAvailable: issues.length === 0,\n    models: getUltraChatbotAgentModelCatalog(),\n    nodeVersion: gatewaySetup.nodeVersion,\n    resumeRequiresRedis: true,\n    setupMessage: issues.length > 0 ? issues.join(\" \") : null,\n    statusLabel: issues.length === 0 ? \"Ready\" : \"Setup required\",\n  };\n}\n\nfunction readRequestBody(body: unknown) {\n  const {\n    id,\n    message,\n    messageId,\n    selectedChatModel,\n    selectedVisibilityType,\n    trigger,\n  } = (body ?? {}) as UltraChatbotAgentRequestBody;\n\n  if (\n    typeof id !== \"string\" ||\n    id.trim().length === 0 ||\n    !message ||\n    typeof message !== \"object\" ||\n    (messageId !== undefined && typeof messageId !== \"string\") ||\n    typeof selectedChatModel !== \"string\" ||\n    (selectedVisibilityType !== \"private\" &&\n      selectedVisibilityType !== \"public\") ||\n    (trigger !== undefined &&\n      trigger !== \"submit-message\" &&\n      trigger !== \"regenerate-message\")\n  ) {\n    throw new Error(invalidRequestBodyError);\n  }\n\n  return {\n    id: id.trim(),\n    message,\n    messageId:\n      typeof messageId === \"string\" && messageId.trim().length > 0\n        ? messageId.trim()\n        : undefined,\n    selectedChatModel,\n    selectedVisibilityType,\n    trigger,\n  };\n}\n\nfunction hasUnsupportedAttachment(message: UIMessage) {\n  return message.parts.some(\n    (part) =>\n      isFileUIPart(part) &&\n      !isUltraChatbotAgentAcceptedUploadMediaType(part.mediaType ?? \"\")\n  );\n}\n\nfunction getErrorMessage(error: unknown, fallback: string) {\n  return error instanceof Error ? error.message : fallback;\n}\n\nlet ultraChatbotAgentStreamContext:\n  | ReturnType<typeof createResumableStreamContext>\n  | undefined;\nlet ultraChatbotAgentStreamPublisher: Redis | undefined;\nlet ultraChatbotAgentStreamSubscriber: Redis | undefined;\n\nfunction getRedisUrl(env: DemoEnv) {\n  if (!env.REDIS_URL) {\n    throw new Error(\n      \"REDIS_URL is missing. Ultra-chatbot-agent resume requires Redis-backed resumable streams.\"\n    );\n  }\n\n  return env.REDIS_URL;\n}\n\nfunction getStreamContext(env: DemoEnv) {\n  if (!ultraChatbotAgentStreamPublisher) {\n    ultraChatbotAgentStreamPublisher = new Redis(getRedisUrl(env), {\n      enableReadyCheck: false,\n    });\n  }\n\n  if (!ultraChatbotAgentStreamSubscriber) {\n    ultraChatbotAgentStreamSubscriber = new Redis(getRedisUrl(env), {\n      enableReadyCheck: false,\n    });\n  }\n\n  ultraChatbotAgentStreamContext ??= createResumableStreamContext({\n    publisher: ultraChatbotAgentStreamPublisher,\n    subscriber: ultraChatbotAgentStreamSubscriber,\n    waitUntil: after,\n  });\n\n  return ultraChatbotAgentStreamContext;\n}\n\nasync function saveUltraChatbotAgentReplayStart(input: {\n  chatId: string;\n  message: UIMessage;\n  replay: ReturnType<typeof prepareRouteBackedChatReplay>;\n  selectedChatModel: string;\n  selectedVisibilityType: \"private\" | \"public\";\n  store: ReturnType<typeof createUltraChatbotAgentChatStore>;\n  visitorId: string;\n}) {\n  if (input.replay.trimAfterMessageId) {\n    await input.store.deleteMessagesAfterMessage({\n      chatId: input.chatId,\n      messageId: input.replay.trimAfterMessageId,\n      visitorId: input.visitorId,\n    });\n  }\n\n  await input.store.saveIncomingUserMessage({\n    chatId: input.chatId,\n    message: input.message,\n    selectedChatModel: input.selectedChatModel,\n    selectedVisibilityType: input.selectedVisibilityType,\n    visitorId: input.visitorId,\n  });\n}\n\nexport async function handleUltraChatbotAgentChatRequest(\n  request: Request,\n  viewer: { visitorId: string },\n  env: DemoEnv = appEnv\n) {\n  const runtimeState = getUltraChatbotAgentRuntimeState(env);\n\n  if (!runtimeState.isChatAvailable) {\n    return Response.json(\n      {\n        error: runtimeState.setupMessage,\n      },\n      { status: 500 }\n    );\n  }\n\n  let body: unknown;\n\n  try {\n    body = await request.json();\n  } catch {\n    return Response.json(\n      {\n        error: \"Expected a valid JSON request body.\",\n      },\n      { status: 400 }\n    );\n  }\n\n  let input: {\n    id: string;\n    message: UIMessage;\n    messageId?: string;\n    selectedChatModel: string;\n    selectedVisibilityType: \"private\" | \"public\";\n    trigger?: RouteBackedChatRequestTrigger;\n  };\n\n  try {\n    input = readRequestBody(body);\n  } catch (error) {\n    if (error instanceof Error && error.message === invalidRequestBodyError) {\n      return Response.json({ error: error.message }, { status: 400 });\n    }\n\n    throw error;\n  }\n\n  if (!isUltraChatbotAgentModelId(input.selectedChatModel)) {\n    return Response.json(\n      {\n        error: `Unsupported selectedChatModel \"${input.selectedChatModel}\".`,\n      },\n      { status: 400 }\n    );\n  }\n\n  if (hasUnsupportedAttachment(input.message)) {\n    return Response.json(\n      {\n        error: ultraChatbotAgentUnsupportedAttachmentMediaTypeError,\n      },\n      { status: 400 }\n    );\n  }\n\n  const store = createUltraChatbotAgentChatStore();\n  const existingSession = await store.loadChatSession(\n    input.id,\n    viewer.visitorId\n  );\n  const capabilities =\n    existingSession?.chat.capabilities ??\n    getUltraChatbotAgentDefaultCapabilities();\n  const replay = prepareRouteBackedChatReplay({\n    incomingMessage: input.message,\n    messageId: input.messageId,\n    persistedMessages: existingSession?.messages ?? [],\n    trigger: input.trigger,\n  });\n  const originalMessages = replay.messages;\n  const replayableMessages =\n    projectUltraChatbotAgentHistoryForModel(originalMessages);\n\n  try {\n    await saveUltraChatbotAgentReplayStart({\n      chatId: input.id,\n      message: input.message,\n      replay,\n      selectedChatModel: input.selectedChatModel,\n      selectedVisibilityType: input.selectedVisibilityType,\n      store,\n      visitorId: viewer.visitorId,\n    });\n  } catch (error) {\n    if (\n      error instanceof Error &&\n      error.message === getUltraChatbotAgentChatNotFoundError(input.id)\n    ) {\n      return Response.json({ error: error.message }, { status: 404 });\n    }\n\n    throw error;\n  }\n\n  const provider = createUltraChatbotAgentProvider(env);\n  const modelId = provider.resolveModelId(input.selectedChatModel);\n  const projectDocsMcpToolbox =\n    await createUltraChatbotAgentProjectDocsMcpToolbox({\n      origin: new URL(request.url).origin,\n    });\n  let closedProjectDocsMcp = false;\n\n  async function closeProjectDocsMcpToolbox() {\n    if (closedProjectDocsMcp) {\n      return;\n    }\n\n    closedProjectDocsMcp = true;\n    await projectDocsMcpToolbox.close();\n  }\n\n  let sandboxToolbox: Awaited<\n    ReturnType<typeof createUltraChatbotAgentSandboxToolbox>\n  > | null = null;\n\n  if (capabilities.sandboxEnabled) {\n    try {\n      sandboxToolbox = await createUltraChatbotAgentSandboxToolbox({\n        chatId: input.id,\n        env,\n        visitorId: viewer.visitorId,\n      });\n    } catch (error) {\n      await closeProjectDocsMcpToolbox();\n\n      return Response.json(\n        {\n          error: getErrorMessage(\n            error,\n            \"Failed to prepare Ultra sandbox tools.\"\n          ),\n        },\n        { status: 500 }\n      );\n    }\n  }\n\n  try {\n    const agent = new ToolLoopAgent({\n      instructions: getUltraChatbotAgentSystemPrompt({\n        capabilities,\n        sandboxContextText: sandboxToolbox?.contextText,\n      }),\n      model: provider.gateway(modelId),\n      providerOptions: ULTRA_CHATBOT_AGENT_PROVIDER_OPTIONS,\n      stopWhen: stepCountIs(20),\n      tools: {\n        ...projectDocsMcpToolbox.tools,\n        ...(sandboxToolbox?.tools ?? {}),\n        createDocument: createUltraChatbotAgentCreateDocumentTool({\n          chatId: input.id,\n          visitorId: viewer.visitorId,\n        }),\n        editDocument: createUltraChatbotAgentEditDocumentTool({\n          chatId: input.id,\n          visitorId: viewer.visitorId,\n        }),\n        ...(capabilities.sandboxEnabled\n          ? {}\n          : {\n              enableSandbox: createUltraChatbotAgentEnableSandboxTool({\n                chatId: input.id,\n                visitorId: viewer.visitorId,\n              }),\n            }),\n        getWeather: createUltraChatbotAgentGetWeatherTool(),\n        searchKnowledgeBase: createUltraChatbotAgentSearchKnowledgeBaseTool(),\n        requestSuggestions: createUltraChatbotAgentRequestSuggestionsTool({\n          chatId: input.id,\n          model: provider.gateway(modelId),\n          visitorId: viewer.visitorId,\n        }),\n        createResearchReport: createUltraChatbotAgentResearchReportTool({\n          model: provider.gateway(modelId),\n        }),\n        [ultraChatbotAgentSearchToolName]: createUltraChatbotAgentWebSearchTool(\n          {\n            model: provider.hostedToolsGateway(modelId),\n            webSearchTool: provider.hostedToolsGateway.tools.webSearch({\n              searchContextSize: \"medium\",\n            }),\n          }\n        ),\n        updateDocument: createUltraChatbotAgentUpdateDocumentTool({\n          chatId: input.id,\n          model: provider.gateway(modelId),\n          visitorId: viewer.visitorId,\n        }),\n      },\n    });\n    const result = await agent.stream({\n      messages: await convertToModelMessages(replayableMessages),\n    });\n\n    return result.toUIMessageStreamResponse({\n      consumeSseStream: async ({ stream }) => {\n        const streamId = generateId();\n        await getStreamContext(env).createNewResumableStream(\n          streamId,\n          () => stream\n        );\n        await store.setActiveStream({\n          activeStreamId: streamId,\n          chatId: input.id,\n          visitorId: viewer.visitorId,\n        });\n      },\n      generateMessageId: createIdGenerator({\n        prefix: \"ua-msg\",\n        size: 16,\n      }),\n      onFinish: async ({ isAborted, messages }) => {\n        try {\n          if (isAborted) {\n            return;\n          }\n\n          await store.saveFinishedMessages({\n            chatId: input.id,\n            messages,\n            visitorId: viewer.visitorId,\n          });\n        } finally {\n          await closeProjectDocsMcpToolbox();\n        }\n      },\n      originalMessages,\n      sendReasoning: true,\n      sendSources: true,\n    });\n  } catch (error) {\n    await closeProjectDocsMcpToolbox();\n    throw error;\n  }\n}\n\nexport async function handleUltraChatbotAgentStreamResumeRequest(\n  chatId: string,\n  viewer: { visitorId: string }\n) {\n  const session = await createUltraChatbotAgentChatStore().loadChatSession(\n    chatId,\n    viewer.visitorId\n  );\n\n  if (!session) {\n    return Response.json(\n      {\n        error: getUltraChatbotAgentChatNotFoundError(chatId),\n      },\n      { status: 404 }\n    );\n  }\n\n  if (!session.chat.activeStreamId) {\n    return new Response(null, { status: 204 });\n  }\n\n  return new Response(\n    await getStreamContext(appEnv).resumeExistingStream(\n      session.chat.activeStreamId\n    ),\n    {\n      headers: UI_MESSAGE_STREAM_HEADERS,\n    }\n  );\n}\n\nexport async function handleUltraChatbotAgentSessionRequest(\n  chatId: string,\n  viewer: { visitorId: string }\n) {\n  const session = await createUltraChatbotAgentChatStore().loadChatSession(\n    chatId,\n    viewer.visitorId\n  );\n\n  if (!session) {\n    return Response.json(\n      {\n        error: getUltraChatbotAgentChatNotFoundError(chatId),\n      },\n      { status: 404 }\n    );\n  }\n\n  return Response.json(session);\n}\n",
      "type": "registry:lib",
      "target": "@lib/ultra-chatbot-agent/server/runtime.ts"
    },
    {
      "path": "registry/ultra-chatbot-agent/lib/ultra-chatbot-agent/server/sandbox-tools.ts",
      "content": "import { readFile } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport type { ToolSet } from \"ai\";\nimport type { SkillsAgentEnv } from \"@/lib/ultra-chatbot-agent/skills-agent/server/env\";\nimport {\n  discoverWorkspaceSkills,\n  SKILLS_AGENT_WORKSPACE_ROOT,\n} from \"@/lib/ultra-chatbot-agent/skills-agent/server/local-skill-catalog\";\nimport {\n  createSkillsAgentOfficialTools,\n  type SkillsAgentOfficialTools,\n} from \"@/lib/ultra-chatbot-agent/skills-agent/server/official-tools\";\nimport {\n  getSharedSkillsAgentSessionRegistry,\n  SANDBOX_ARTIFACTS_ROOT,\n  SANDBOX_PROJECT_ROOT,\n} from \"@/lib/ultra-chatbot-agent/skills-agent/server/sandbox\";\n\nexport interface UltraChatbotAgentSandboxToolbox {\n  availableSkills: SkillsAgentOfficialTools[\"availableSkills\"];\n  contextText: string;\n  tools: ToolSet;\n}\n\ninterface CreateUltraChatbotAgentSandboxToolboxDependencies {\n  createOfficialTools?: typeof createSkillsAgentOfficialTools;\n  discoverSkills?: typeof discoverWorkspaceSkills;\n  getSessionRegistry?: typeof getSharedSkillsAgentSessionRegistry;\n  readAgentsFile?: typeof readFile;\n  workspaceRoot?: string;\n}\n\nconst defaultAgentsContent = [\n  \"# Ultra Chatbot Agent Consumer Workspace\",\n  \"\",\n  \"This project can install repo-local skills under `.agents/skills`.\",\n  \"Use loaded skill instructions as the source of truth, keep generated drafts under `artifacts/` unless a skill names a canonical path, and inspect optional repository files before reading them.\",\n].join(\"\\n\");\n\nfunction isMissingFileError(error: unknown) {\n  return (\n    typeof error === \"object\" &&\n    error !== null &&\n    \"code\" in error &&\n    error.code === \"ENOENT\"\n  );\n}\n\nasync function readAgentsContent(\n  agentsPath: string,\n  readAgentsFile: typeof readFile\n) {\n  try {\n    return await readAgentsFile(agentsPath, \"utf-8\");\n  } catch (error) {\n    if (isMissingFileError(error)) {\n      return defaultAgentsContent;\n    }\n\n    throw error;\n  }\n}\n\nfunction formatAvailableSkills(\n  skills: SkillsAgentOfficialTools[\"availableSkills\"]\n) {\n  if (skills.length === 0) {\n    return \"No workspace skills are available.\";\n  }\n\n  return skills\n    .map((skill) => `- ${skill.name}: ${skill.description}`)\n    .join(\"\\n\");\n}\n\nexport function getUltraChatbotAgentSandboxSessionId(chatId: string) {\n  return `ultra-chatbot-agent-${chatId}`;\n}\n\nexport async function createUltraChatbotAgentSandboxToolbox(\n  {\n    chatId,\n    env,\n  }: {\n    chatId: string;\n    env?: SkillsAgentEnv;\n    visitorId: string;\n  },\n  dependencies: CreateUltraChatbotAgentSandboxToolboxDependencies = {}\n): Promise<UltraChatbotAgentSandboxToolbox> {\n  const workspaceRoot =\n    dependencies.workspaceRoot ?? SKILLS_AGENT_WORKSPACE_ROOT;\n  const readAgentsFile = dependencies.readAgentsFile ?? readFile;\n  const createOfficialTools =\n    dependencies.createOfficialTools ?? createSkillsAgentOfficialTools;\n  const getSessionRegistry =\n    dependencies.getSessionRegistry ?? getSharedSkillsAgentSessionRegistry;\n  const discoverSkills = dependencies.discoverSkills ?? discoverWorkspaceSkills;\n  const agentsContent = await readAgentsContent(\n    path.join(workspaceRoot, \"AGENTS.md\"),\n    readAgentsFile\n  );\n  const session = (await getSessionRegistry(env)).getSession(\n    getUltraChatbotAgentSandboxSessionId(chatId)\n  );\n  const skills = await discoverSkills([\n    path.join(workspaceRoot, \".agents/skills\"),\n  ]);\n  await session.writeFile(\"AGENTS.md\", agentsContent);\n  const toolset = await createOfficialTools({\n    agentsContent,\n    projectRoot: SANDBOX_PROJECT_ROOT,\n    session,\n    skills,\n  });\n\n  return {\n    availableSkills: toolset.availableSkills,\n    contextText: [\n      `Sandbox project root: ${SANDBOX_PROJECT_ROOT}`,\n      `Sandbox artifacts root: ${SANDBOX_ARTIFACTS_ROOT}`,\n      `Available skills:\\n${formatAvailableSkills(toolset.availableSkills)}`,\n    ].join(\"\\n\"),\n    tools: toolset.tools as ToolSet,\n  };\n}\n",
      "type": "registry:lib",
      "target": "@lib/ultra-chatbot-agent/server/sandbox-tools.ts"
    },
    {
      "path": "registry/ultra-chatbot-agent/lib/ultra-chatbot-agent/server/schema.ts",
      "content": "import { sql } from \"drizzle-orm\";\nimport {\n  boolean,\n  foreignKey,\n  index,\n  jsonb,\n  pgTable,\n  primaryKey,\n  text,\n  timestamp,\n  uniqueIndex,\n  uuid,\n  varchar,\n} from \"drizzle-orm/pg-core\";\n\nexport const ultraChatbotAgentVisibilityTypes = [\"public\", \"private\"] as const;\nexport const ultraChatbotAgentDocumentKinds = [\n  \"text\",\n  \"code\",\n  \"image\",\n  \"sheet\",\n] as const;\n\nexport const ultraChatbotAgentChats = pgTable(\n  \"ultra_chatbot_agent_chats\",\n  {\n    id: uuid(\"id\").defaultRandom().primaryKey(),\n    visitorId: varchar(\"visitor_id\", { length: 191 }).notNull(),\n    title: text(\"title\").notNull(),\n    selectedChatModel: varchar(\"selected_chat_model\", {\n      length: 191,\n    }).notNull(),\n    visibility: varchar(\"visibility\", {\n      enum: ultraChatbotAgentVisibilityTypes,\n      length: 32,\n    })\n      .notNull()\n      .default(\"private\"),\n    activeStreamId: varchar(\"active_stream_id\", { length: 191 }),\n    capabilities: jsonb(\"capabilities\")\n      .notNull()\n      .default(sql`'{\"sandboxEnabled\": false}'::jsonb`),\n    createdAt: timestamp(\"created_at\", { withTimezone: true })\n      .defaultNow()\n      .notNull(),\n    updatedAt: timestamp(\"updated_at\", { withTimezone: true })\n      .defaultNow()\n      .notNull(),\n  },\n  (table) => ({\n    visitorUpdatedIndex: index(\n      \"ultra_chatbot_agent_chats_visitor_updated_idx\"\n    ).on(table.visitorId, table.updatedAt),\n  })\n);\n\nexport const ultraChatbotAgentMessages = pgTable(\n  \"ultra_chatbot_agent_messages\",\n  {\n    id: uuid(\"id\").defaultRandom().primaryKey(),\n    chatId: uuid(\"chat_id\").notNull(),\n    messageId: varchar(\"message_id\", { length: 191 }).notNull(),\n    role: varchar(\"role\", { length: 32 }).notNull(),\n    parts: jsonb(\"parts\").notNull(),\n    attachments: jsonb(\"attachments\").notNull(),\n    createdAt: timestamp(\"created_at\", { withTimezone: true })\n      .defaultNow()\n      .notNull(),\n  },\n  (table) => ({\n    chatCreatedIndex: index(\"ultra_chatbot_agent_messages_chat_created_idx\").on(\n      table.chatId,\n      table.createdAt\n    ),\n    chatMessageIdIndex: uniqueIndex(\n      \"ultra_chatbot_agent_messages_chat_message_id_idx\"\n    ).on(table.chatId, table.messageId),\n    chatForeignKey: foreignKey({\n      columns: [table.chatId],\n      foreignColumns: [ultraChatbotAgentChats.id],\n      name: \"ultra_chatbot_agent_messages_chat_fk\",\n    }).onDelete(\"cascade\"),\n  })\n);\n\nexport const ultraChatbotAgentVotes = pgTable(\n  \"ultra_chatbot_agent_votes\",\n  {\n    chatId: uuid(\"chat_id\").notNull(),\n    messageId: varchar(\"message_id\", { length: 191 }).notNull(),\n    visitorId: varchar(\"visitor_id\", { length: 191 }).notNull(),\n    isUpvoted: boolean(\"is_upvoted\").notNull(),\n  },\n  (table) => ({\n    primaryKey: primaryKey({\n      columns: [table.chatId, table.messageId, table.visitorId],\n      name: \"ultra_chatbot_agent_votes_pk\",\n    }),\n  })\n);\n\nexport const ultraChatbotAgentDocuments = pgTable(\n  \"ultra_chatbot_agent_documents\",\n  {\n    id: uuid(\"id\").notNull().defaultRandom(),\n    chatId: uuid(\"chat_id\"),\n    visitorId: varchar(\"visitor_id\", { length: 191 }).notNull(),\n    createdAt: timestamp(\"created_at\", { withTimezone: true }).notNull(),\n    title: text(\"title\").notNull(),\n    content: text(\"content\"),\n    kind: varchar(\"kind\", {\n      enum: ultraChatbotAgentDocumentKinds,\n      length: 32,\n    })\n      .notNull()\n      .default(\"text\"),\n  },\n  (table) => ({\n    primaryKey: primaryKey({\n      columns: [table.id, table.createdAt],\n      name: \"ultra_chatbot_agent_documents_pk\",\n    }),\n    chatCreatedIndex: index(\n      \"ultra_chatbot_agent_documents_chat_created_idx\"\n    ).on(table.chatId, table.createdAt),\n    chatForeignKey: foreignKey({\n      columns: [table.chatId],\n      foreignColumns: [ultraChatbotAgentChats.id],\n      name: \"ultra_chatbot_agent_documents_chat_fk\",\n    }).onDelete(\"cascade\"),\n  })\n);\n\nexport const ultraChatbotAgentSuggestions = pgTable(\n  \"ultra_chatbot_agent_suggestions\",\n  {\n    id: uuid(\"id\").notNull().defaultRandom(),\n    documentId: uuid(\"document_id\").notNull(),\n    documentCreatedAt: timestamp(\"document_created_at\", {\n      withTimezone: true,\n    }).notNull(),\n    visitorId: varchar(\"visitor_id\", { length: 191 }).notNull(),\n    originalText: text(\"original_text\").notNull(),\n    suggestedText: text(\"suggested_text\").notNull(),\n    description: text(\"description\"),\n    createdAt: timestamp(\"created_at\", { withTimezone: true }).notNull(),\n  },\n  (table) => ({\n    primaryKey: primaryKey({\n      columns: [table.id],\n      name: \"ultra_chatbot_agent_suggestions_pk\",\n    }),\n    documentForeignKey: foreignKey({\n      columns: [table.documentId, table.documentCreatedAt],\n      foreignColumns: [\n        ultraChatbotAgentDocuments.id,\n        ultraChatbotAgentDocuments.createdAt,\n      ],\n      name: \"ultra_chatbot_agent_suggestions_document_fk\",\n    }).onDelete(\"cascade\"),\n  })\n);\n\nexport const ultraChatbotAgentStreams = pgTable(\n  \"ultra_chatbot_agent_streams\",\n  {\n    id: uuid(\"id\").notNull().defaultRandom(),\n    chatId: uuid(\"chat_id\").notNull(),\n    createdAt: timestamp(\"created_at\", { withTimezone: true }).notNull(),\n  },\n  (table) => ({\n    primaryKey: primaryKey({\n      columns: [table.id],\n      name: \"ultra_chatbot_agent_streams_pk\",\n    }),\n    chatForeignKey: foreignKey({\n      columns: [table.chatId],\n      foreignColumns: [ultraChatbotAgentChats.id],\n      name: \"ultra_chatbot_agent_streams_chat_fk\",\n    }).onDelete(\"cascade\"),\n  })\n);\n\n\nexport const ultraChatbotAgentSchema = {\n  ultraChatbotAgentChats,\n  ultraChatbotAgentDocuments,\n  ultraChatbotAgentMessages,\n  ultraChatbotAgentStreams,\n  ultraChatbotAgentSuggestions,\n  ultraChatbotAgentVotes,\n} as const;\n",
      "type": "registry:lib",
      "target": "@lib/ultra-chatbot-agent/server/schema.ts"
    },
    {
      "path": "registry/ultra-chatbot-agent/lib/ultra-chatbot-agent/server/search-knowledge-base.ts",
      "content": "import { tool } from \"ai\";\nimport { z } from \"zod\";\n\nimport {\n  findRelevantContent,\n  type RagToolResult,\n} from \"@/lib/ultra-chatbot-agent/rag-chatbot/retrieval\";\n\nimport { ultraChatbotAgentKnowledgeSource } from \"../knowledge-source\";\n\nconst searchKnowledgeBaseInputSchema = z.object({\n  query: z\n    .string()\n    .trim()\n    .min(1)\n    .describe(\n      \"The user question or retrieval query for the preindexed PDF knowledge base.\"\n    ),\n});\n\nfunction toRenderableSources(result: RagToolResult) {\n  return result.sources.map((source) => ({\n    title: source.citationLabel,\n    url: source.documentUrl,\n  }));\n}\n\nfunction buildSourceAwareFallbackQuery(query: string) {\n  return [\n    query,\n    ultraChatbotAgentKnowledgeSource.title,\n    \"NASA logotype seal symbol typography color red black visual identity design guidelines\",\n  ].join(\" \");\n}\n\nasync function retrieveWithSourceAwareFallback(input: {\n  query: string;\n  retrieve: (query: string) => Promise<RagToolResult>;\n}) {\n  const retrievalQueries = [input.query];\n  const primaryResult = await input.retrieve(input.query);\n\n  if (primaryResult.sources.length > 0) {\n    return {\n      result: primaryResult,\n      retrievalQueries,\n    };\n  }\n\n  const fallbackQuery = buildSourceAwareFallbackQuery(input.query);\n  retrievalQueries.push(fallbackQuery);\n\n  return {\n    result: await input.retrieve(fallbackQuery),\n    retrievalQueries,\n  };\n}\n\nexport function createUltraChatbotAgentSearchKnowledgeBaseTool(\n  dependencies: {\n    findRelevantContent?: (query: string) => Promise<RagToolResult>;\n  } = {}\n) {\n  const retrieve = dependencies.findRelevantContent ?? findRelevantContent;\n\n  return tool({\n    description:\n      \"Search the preindexed PDF knowledge base and return grounded snippets plus citations for document questions. The active source is the NASA Graphics Standards Manual, so concrete retrieval terms include logotype, seal, typography, color, red, black, symbol, and visual identity.\",\n    inputSchema: searchKnowledgeBaseInputSchema,\n    execute: async ({ query }) => {\n      const { result, retrievalQueries } =\n        await retrieveWithSourceAwareFallback({\n          query,\n          retrieve,\n        });\n\n      return {\n        answerable: result.answerable,\n        knowledgeSource: ultraChatbotAgentKnowledgeSource,\n        message: result.message,\n        query,\n        retrievalQueries,\n        snippets: result.sources.map((source) => ({\n          citationLabel: source.citationLabel,\n          content: source.content,\n          documentUrl: source.documentUrl,\n          pageLabel: source.pageLabel,\n          sectionTitle: source.sectionTitle,\n          similarity: source.similarity,\n        })),\n        sources: toRenderableSources(result),\n      };\n    },\n  });\n}\n",
      "type": "registry:lib",
      "target": "@lib/ultra-chatbot-agent/server/search-knowledge-base.ts"
    },
    {
      "path": "registry/ultra-chatbot-agent/lib/ultra-chatbot-agent/server/session.ts",
      "content": "import { cookies } from \"next/headers\";\n\nimport {\n  createUltraChatbotAgentChatStore,\n  type UltraChatbotAgentChatSession,\n  type UltraChatbotAgentHistoryPage,\n} from \"./chat-store\";\nimport { ultraChatbotAgentVisitorCookieName } from \"./viewer-context\";\n\nexport interface UltraChatbotAgentScreenData {\n  draftChatId: string;\n  initialHistoryPage: UltraChatbotAgentHistoryPage;\n  initialSession: UltraChatbotAgentChatSession | null;\n  visitorId: string | null;\n}\n\nexport async function loadUltraChatbotAgentScreenData(input: {\n  chatId?: string | null;\n}) {\n  const draftChatId = crypto.randomUUID();\n  const cookieStore = await cookies();\n  const visitorId =\n    cookieStore.get(ultraChatbotAgentVisitorCookieName)?.value ?? null;\n\n  if (!visitorId) {\n    return {\n      draftChatId,\n      initialHistoryPage: {\n        chats: [],\n        hasMore: false,\n      },\n      initialSession: null,\n      visitorId: null,\n    } satisfies UltraChatbotAgentScreenData;\n  }\n\n  const store = createUltraChatbotAgentChatStore();\n  const [initialSession, initialHistoryPage] = await Promise.all([\n    input.chatId\n      ? store.loadChatSession(input.chatId, visitorId)\n      : Promise.resolve(null),\n    store.listChatsForVisitorPage({\n      endingBefore: null,\n      limit: 10,\n      startingAfter: null,\n      visitorId,\n    }),\n  ]);\n\n  return {\n    draftChatId,\n    initialHistoryPage,\n    initialSession,\n    visitorId,\n  } satisfies UltraChatbotAgentScreenData;\n}\n",
      "type": "registry:lib",
      "target": "@lib/ultra-chatbot-agent/server/session.ts"
    },
    {
      "path": "registry/ultra-chatbot-agent/lib/ultra-chatbot-agent/server/suggestion-store.ts",
      "content": "import { and, asc, eq } from \"drizzle-orm\";\n\nimport { loadUltraChatbotAgentDatabase } from \"./database\";\n\nexport interface UltraChatbotAgentSuggestionRecord {\n  createdAt: string;\n  description: string | null;\n  documentCreatedAt: string;\n  documentId: string;\n  id: string;\n  originalText: string;\n  suggestedText: string;\n  visitorId: string;\n}\n\ninterface UltraChatbotAgentSuggestionDatabaseModule {\n  database: typeof import(\"./database\")[\"database\"];\n  ultraChatbotAgentSuggestions: typeof import(\"./database\")[\"ultraChatbotAgentSuggestions\"];\n}\n\nfunction toIsoString(value: Date | string) {\n  return value instanceof Date ? value.toISOString() : value;\n}\n\nfunction normalizeSuggestionRecord(record: {\n  createdAt: Date | string;\n  description: string | null;\n  documentCreatedAt: Date | string;\n  documentId: string;\n  id: string;\n  originalText: string;\n  suggestedText: string;\n  visitorId: string;\n}) {\n  return {\n    createdAt: toIsoString(record.createdAt),\n    description: record.description,\n    documentCreatedAt: toIsoString(record.documentCreatedAt),\n    documentId: record.documentId,\n    id: record.id,\n    originalText: record.originalText,\n    suggestedText: record.suggestedText,\n    visitorId: record.visitorId,\n  } satisfies UltraChatbotAgentSuggestionRecord;\n}\n\nasync function loadUltraChatbotAgentSuggestionDatabase(): Promise<UltraChatbotAgentSuggestionDatabaseModule> {\n  const databaseModule = await loadUltraChatbotAgentDatabase();\n\n  return {\n    database: databaseModule.database,\n    ultraChatbotAgentSuggestions: databaseModule.ultraChatbotAgentSuggestions,\n  };\n}\n\nexport function createUltraChatbotAgentSuggestionStore() {\n  return {\n    async listSuggestionsForDocumentVersion(input: {\n      documentCreatedAt: string;\n      documentId: string;\n      visitorId: string;\n    }) {\n      const { database, ultraChatbotAgentSuggestions } =\n        await loadUltraChatbotAgentSuggestionDatabase();\n      const rows = await database\n        .select()\n        .from(ultraChatbotAgentSuggestions)\n        .where(\n          and(\n            eq(ultraChatbotAgentSuggestions.documentId, input.documentId),\n            eq(\n              ultraChatbotAgentSuggestions.documentCreatedAt,\n              new Date(input.documentCreatedAt)\n            ),\n            eq(ultraChatbotAgentSuggestions.visitorId, input.visitorId)\n          )\n        )\n        .orderBy(asc(ultraChatbotAgentSuggestions.createdAt));\n\n      return rows.map(normalizeSuggestionRecord);\n    },\n    async replaceSuggestionsForDocumentVersion(input: {\n      documentCreatedAt: string;\n      documentId: string;\n      suggestions: Array<{\n        description: string | null;\n        originalText: string;\n        suggestedText: string;\n      }>;\n      visitorId: string;\n    }) {\n      const { database, ultraChatbotAgentSuggestions } =\n        await loadUltraChatbotAgentSuggestionDatabase();\n\n      await database\n        .delete(ultraChatbotAgentSuggestions)\n        .where(\n          and(\n            eq(ultraChatbotAgentSuggestions.documentId, input.documentId),\n            eq(\n              ultraChatbotAgentSuggestions.documentCreatedAt,\n              new Date(input.documentCreatedAt)\n            ),\n            eq(ultraChatbotAgentSuggestions.visitorId, input.visitorId)\n          )\n        );\n\n      if (input.suggestions.length === 0) {\n        return [] satisfies UltraChatbotAgentSuggestionRecord[];\n      }\n\n      const createdAt = new Date();\n      const rows = await database\n        .insert(ultraChatbotAgentSuggestions)\n        .values(\n          input.suggestions.map((suggestion) => ({\n            createdAt,\n            description: suggestion.description,\n            documentCreatedAt: new Date(input.documentCreatedAt),\n            documentId: input.documentId,\n            id: crypto.randomUUID(),\n            originalText: suggestion.originalText,\n            suggestedText: suggestion.suggestedText,\n            visitorId: input.visitorId,\n          }))\n        )\n        .returning();\n\n      return rows.map(normalizeSuggestionRecord);\n    },\n  };\n}\n",
      "type": "registry:lib",
      "target": "@lib/ultra-chatbot-agent/server/suggestion-store.ts"
    },
    {
      "path": "registry/ultra-chatbot-agent/lib/ultra-chatbot-agent/server/suggestions.ts",
      "content": "import { createUltraChatbotAgentDocumentStore } from \"./document-store\";\nimport { createUltraChatbotAgentSuggestionStore } from \"./suggestion-store\";\n\nfunction getUltraChatbotAgentSuggestionNotFoundError(documentId: string) {\n  return `No ultra-chatbot-agent document found for ${documentId}.`;\n}\n\nexport async function handleUltraChatbotAgentSuggestionsRequest(\n  request: Request,\n  viewer: { visitorId: string }\n) {\n  if (request.method !== \"GET\") {\n    return Response.json({ error: \"Method not allowed.\" }, { status: 405 });\n  }\n\n  const url = new URL(request.url);\n  const chatId = url.searchParams.get(\"chatId\");\n  const documentId = url.searchParams.get(\"id\");\n\n  if (!chatId) {\n    return Response.json(\n      { error: 'Expected the \"chatId\" search parameter.' },\n      { status: 400 }\n    );\n  }\n\n  if (!documentId) {\n    return Response.json(\n      { error: 'Expected the \"id\" search parameter.' },\n      { status: 400 }\n    );\n  }\n\n  const latestDocument =\n    await createUltraChatbotAgentDocumentStore().loadLatestDocument({\n      chatId,\n      documentId,\n      visitorId: viewer.visitorId,\n    });\n\n  if (!latestDocument) {\n    return Response.json(\n      { error: getUltraChatbotAgentSuggestionNotFoundError(documentId) },\n      { status: 404 }\n    );\n  }\n\n  return Response.json(\n    await createUltraChatbotAgentSuggestionStore().listSuggestionsForDocumentVersion(\n      {\n        documentCreatedAt: latestDocument.createdAt,\n        documentId,\n        visitorId: viewer.visitorId,\n      }\n    )\n  );\n}\n",
      "type": "registry:lib",
      "target": "@lib/ultra-chatbot-agent/server/suggestions.ts"
    },
    {
      "path": "registry/ultra-chatbot-agent/lib/ultra-chatbot-agent/server/update-document.ts",
      "content": "import { generateText, tool } from \"ai\";\nimport { z } from \"zod\";\n\nimport { createUltraChatbotAgentDocumentStore } from \"./document-store\";\nimport {\n  findUltraChatbotAgentTargetDocument,\n  getUltraChatbotAgentDocumentLookupError,\n} from \"./document-target\";\nimport { getUltraChatbotAgentDocumentRewriteSystemPrompt } from \"./prompts\";\n\nconst updateDocumentInputSchema = z.object({\n  description: z.string().trim().min(1),\n  documentId: z.string().uuid().optional(),\n  documentTitle: z.string().trim().min(1).optional(),\n});\n\nexport function createUltraChatbotAgentUpdateDocumentTool(input: {\n  chatId: string;\n  model: Parameters<typeof generateText>[0][\"model\"];\n  visitorId: string;\n}) {\n  return tool({\n    description:\n      \"Rewrite an existing saved document more broadly from a change description. Use this when the user asks for a larger refresh or major rewrite, and prefer editDocument for small exact replacements.\",\n    inputSchema: updateDocumentInputSchema,\n    execute: async ({ description, documentId, documentTitle }) => {\n      const documentStore = createUltraChatbotAgentDocumentStore();\n      const latestDocuments = await documentStore.listLatestDocumentsForChat({\n        chatId: input.chatId,\n        limit: 24,\n        visitorId: input.visitorId,\n      });\n      const matchedDocument = findUltraChatbotAgentTargetDocument({\n        documentId,\n        documentTitle,\n        latestDocuments,\n      });\n\n      if (!matchedDocument?.content) {\n        return {\n          error: getUltraChatbotAgentDocumentLookupError({\n            documentId,\n            documentTitle,\n            documentsCount: latestDocuments.length,\n          }),\n        };\n      }\n\n      const response = await generateText({\n        model: input.model,\n        prompt: [\n          `Document title: ${matchedDocument.title}`,\n          `Document kind: ${matchedDocument.kind}`,\n          `Requested rewrite: ${description}`,\n          \"Current content:\",\n          matchedDocument.content,\n        ].join(\"\\n\\n\"),\n        system: getUltraChatbotAgentDocumentRewriteSystemPrompt(),\n      });\n\n      const nextContent = response.text.trim();\n\n      if (!nextContent) {\n        throw new Error(\"The model returned an empty document rewrite.\");\n      }\n\n      const savedDocument = await documentStore.saveDocument({\n        chatId: input.chatId,\n        content: nextContent,\n        documentId: matchedDocument.id,\n        kind: matchedDocument.kind,\n        title: matchedDocument.title,\n        visitorId: input.visitorId,\n      });\n\n      return {\n        id: savedDocument.id,\n        kind: savedDocument.kind,\n        title: savedDocument.title,\n      };\n    },\n  });\n}\n",
      "type": "registry:lib",
      "target": "@lib/ultra-chatbot-agent/server/update-document.ts"
    },
    {
      "path": "registry/ultra-chatbot-agent/lib/ultra-chatbot-agent/server/upload.ts",
      "content": "import { put } from \"@vercel/blob\";\nimport { z } from \"zod\";\n\nimport { getUltraChatbotAgentAppEnv } from \"./env\";\n\nconst appEnv = getUltraChatbotAgentAppEnv();\nimport { getVercelBlobToken } from \"./env\";\n\nimport {\n  isUltraChatbotAgentAcceptedUploadMediaType,\n  ultraChatbotAgentAcceptedUploadMediaTypes,\n  ultraChatbotAgentMaxUploadBytes,\n} from \"../attachment-config\";\nimport {\n  buildUltraChatbotAgentDailyUploadPrefix,\n  buildUltraChatbotAgentUploadPath,\n  formatUltraChatbotAgentUploadDateBucket,\n  readUltraChatbotAgentBlobUsageForPrefix,\n  validateUltraChatbotAgentBlobPathSegment,\n} from \"./blob-storage\";\n\nconst uploadFileSchema = z.object({\n  chatId: z.string().trim().min(1, {\n    message: \"A chat id is required.\",\n  }),\n  file: z\n    .instanceof(Blob)\n    .refine((file) => file.size <= ultraChatbotAgentMaxUploadBytes, {\n      message: \"File size should be less than 5MB.\",\n    })\n    .refine((file) => isUltraChatbotAgentAcceptedUploadMediaType(file.type), {\n      message: \"File type should be PDF, JPEG, or PNG.\",\n    }),\n});\n\nconst ultraChatbotAgentDailyUploadFileLimit = 20;\nconst ultraChatbotAgentDailyUploadBytesLimit = 50 * 1024 * 1024;\n\nexport interface UltraChatbotAgentUploadEnv {\n  BLOB_READ_WRITE_TOKEN?: string;\n}\n\nexport interface UltraChatbotAgentUploadViewer {\n  visitorId: string;\n}\n\nexport interface UltraChatbotAgentUploadOptions {\n  now?: Date;\n}\n\nexport function getUltraChatbotAgentAcceptedUploadMediaTypes() {\n  return [...ultraChatbotAgentAcceptedUploadMediaTypes];\n}\n\nfunction sanitizeFilename(filename: string) {\n  const trimmed = filename.trim();\n\n  if (trimmed.length === 0) {\n    return \"attachment\";\n  }\n\n  return trimmed.replace(/[^a-zA-Z0-9._-]/g, \"_\");\n}\n\nexport async function handleUltraChatbotAgentFileUploadRequest(\n  request: Request,\n  viewer: UltraChatbotAgentUploadViewer,\n  env: UltraChatbotAgentUploadEnv = appEnv,\n  options: UltraChatbotAgentUploadOptions = {}\n) {\n  if (request.body === null) {\n    return Response.json({ error: \"Request body is empty.\" }, { status: 400 });\n  }\n\n  let formData: FormData;\n\n  try {\n    formData = await request.formData();\n  } catch {\n    return Response.json(\n      { error: \"Failed to process the upload request.\" },\n      { status: 500 }\n    );\n  }\n\n  const maybeFile = formData.get(\"file\");\n  const maybeChatId = formData.get(\"chatId\");\n\n  if (!(maybeFile instanceof Blob)) {\n    return Response.json({ error: \"No file uploaded.\" }, { status: 400 });\n  }\n\n  if (typeof maybeChatId !== \"string\") {\n    return Response.json({ error: \"A chat id is required.\" }, { status: 400 });\n  }\n\n  const validatedUpload = uploadFileSchema.safeParse({\n    chatId: maybeChatId,\n    file: maybeFile,\n  });\n\n  if (!validatedUpload.success) {\n    return Response.json(\n      {\n        error: validatedUpload.error.issues\n          .map((issue) => issue.message)\n          .join(\" \"),\n      },\n      { status: 400 }\n    );\n  }\n\n  let token: string;\n\n  try {\n    token = getVercelBlobToken(env);\n  } catch (error) {\n    return Response.json(\n      {\n        error:\n          error instanceof Error\n            ? error.message\n            : \"Blob storage is not configured.\",\n      },\n      { status: 500 }\n    );\n  }\n\n  const file = validatedUpload.data.file;\n  const dateBucket = formatUltraChatbotAgentUploadDateBucket(\n    options.now ?? new Date()\n  );\n  let visitorId: string;\n  let chatId: string;\n\n  try {\n    visitorId = validateUltraChatbotAgentBlobPathSegment(\n      viewer.visitorId,\n      \"Visitor id\"\n    );\n    chatId = validateUltraChatbotAgentBlobPathSegment(\n      validatedUpload.data.chatId,\n      \"Chat id\"\n    );\n  } catch (error) {\n    return Response.json(\n      {\n        error:\n          error instanceof Error\n            ? error.message\n            : \"Invalid upload ownership path.\",\n      },\n      { status: 400 }\n    );\n  }\n\n  const dailyPrefix = buildUltraChatbotAgentDailyUploadPrefix({\n    dateBucket,\n    visitorId,\n  });\n\n  try {\n    const dailyUsage = await readUltraChatbotAgentBlobUsageForPrefix({\n      prefix: dailyPrefix,\n      token,\n    });\n\n    if (\n      dailyUsage.fileCount >= ultraChatbotAgentDailyUploadFileLimit ||\n      dailyUsage.totalBytes + file.size > ultraChatbotAgentDailyUploadBytesLimit\n    ) {\n      return Response.json(\n        { error: \"Daily upload quota exceeded.\" },\n        { status: 429 }\n      );\n    }\n  } catch {\n    return Response.json(\n      { error: \"Upload quota check failed.\" },\n      { status: 500 }\n    );\n  }\n\n  const filename =\n    maybeFile instanceof File && maybeFile.name ? maybeFile.name : \"attachment\";\n  const pathname = buildUltraChatbotAgentUploadPath({\n    chatId,\n    dateBucket,\n    filename: sanitizeFilename(filename),\n    uploadId: crypto.randomUUID(),\n    visitorId,\n  });\n\n  try {\n    const uploadedBlob = await put(pathname, file, {\n      access: \"public\",\n      token,\n    });\n\n    return Response.json({\n      contentType: uploadedBlob.contentType,\n      pathname: uploadedBlob.pathname,\n      size: file.size,\n      url: uploadedBlob.url,\n    });\n  } catch {\n    return Response.json({ error: \"Upload failed.\" }, { status: 500 });\n  }\n}\n",
      "type": "registry:lib",
      "target": "@lib/ultra-chatbot-agent/server/upload.ts"
    },
    {
      "path": "registry/ultra-chatbot-agent/lib/ultra-chatbot-agent/server/viewer-context.ts",
      "content": "import { createVisitorOwner } from \"./route-owner\";\n\nexport const ultraChatbotAgentVisitorCookieName = \"ua_visitor_id\";\nconst ultraChatbotAgentVisitorOwner = createVisitorOwner({\n  cookieName: ultraChatbotAgentVisitorCookieName,\n  maxAgeSeconds: 60 * 60 * 24 * 30,\n});\n\nexport const handleUltraChatbotAgentVisitorRequest =\n  ultraChatbotAgentVisitorOwner.handleOwnedRequest;\n",
      "type": "registry:lib",
      "target": "@lib/ultra-chatbot-agent/server/viewer-context.ts"
    },
    {
      "path": "registry/ultra-chatbot-agent/lib/ultra-chatbot-agent/server/visibility.ts",
      "content": "import { z } from \"zod\";\n\nimport { createUltraChatbotAgentChatStore } from \"./chat-store\";\n\nconst ultraChatbotAgentVisibilitySchema = z.object({\n  visibility: z.enum([\"private\", \"public\"]),\n});\n\nexport async function handleUltraChatbotAgentVisibilityPatchRequest(\n  request: Request,\n  viewer: { chatId: string; visitorId: string }\n) {\n  const parsedBody = ultraChatbotAgentVisibilitySchema.safeParse(\n    await request.json()\n  );\n\n  if (!parsedBody.success) {\n    return Response.json(\n      {\n        error: \"A valid visibility value is required.\",\n      },\n      { status: 400 }\n    );\n  }\n\n  const chatStore = createUltraChatbotAgentChatStore();\n  const session = await chatStore.loadChatSession(\n    viewer.chatId,\n    viewer.visitorId\n  );\n\n  if (!session) {\n    return Response.json(\n      {\n        error: \"Chat not found for this visitor.\",\n      },\n      { status: 404 }\n    );\n  }\n\n  const updatedChat = await chatStore.setChatVisibility({\n    chatId: viewer.chatId,\n    visibility: parsedBody.data.visibility,\n    visitorId: viewer.visitorId,\n  });\n\n  return Response.json(updatedChat, { status: 200 });\n}\n",
      "type": "registry:lib",
      "target": "@lib/ultra-chatbot-agent/server/visibility.ts"
    },
    {
      "path": "registry/ultra-chatbot-agent/lib/ultra-chatbot-agent/server/votes.ts",
      "content": "import { z } from \"zod\";\n\nimport { createUltraChatbotAgentChatStore } from \"./chat-store\";\n\nconst ultraChatbotAgentVoteSchema = z.object({\n  chatId: z.string().uuid(),\n  messageId: z.string().min(1),\n  type: z.enum([\"up\", \"down\", \"clear\"]),\n});\n\nfunction notFoundResponse() {\n  return Response.json(\n    {\n      error: \"Chat not found for this visitor.\",\n    },\n    { status: 404 }\n  );\n}\n\nexport async function handleUltraChatbotAgentVoteListRequest(\n  request: Request,\n  viewer: { visitorId: string }\n) {\n  const chatId = new URL(request.url).searchParams.get(\"chatId\");\n\n  if (!chatId) {\n    return Response.json(\n      {\n        error: \"Parameter chatId is required.\",\n      },\n      { status: 400 }\n    );\n  }\n\n  const chatStore = createUltraChatbotAgentChatStore();\n  const session = await chatStore.loadChatSession(chatId, viewer.visitorId);\n\n  if (!session) {\n    return notFoundResponse();\n  }\n\n  const votes = await chatStore.listVotesForChat({\n    chatId,\n    visitorId: viewer.visitorId,\n  });\n\n  return Response.json(votes, { status: 200 });\n}\n\nexport async function handleUltraChatbotAgentVotePatchRequest(\n  request: Request,\n  viewer: { visitorId: string }\n) {\n  const parsedBody = ultraChatbotAgentVoteSchema.safeParse(\n    await request.json()\n  );\n\n  if (!parsedBody.success) {\n    return Response.json(\n      {\n        error: \"Parameters chatId, messageId, and type are required.\",\n      },\n      { status: 400 }\n    );\n  }\n\n  const chatStore = createUltraChatbotAgentChatStore();\n  const session = await chatStore.loadChatSession(\n    parsedBody.data.chatId,\n    viewer.visitorId\n  );\n\n  if (!session) {\n    return notFoundResponse();\n  }\n\n  if (parsedBody.data.type === \"clear\") {\n    await chatStore.deleteVote({\n      chatId: parsedBody.data.chatId,\n      messageId: parsedBody.data.messageId,\n      visitorId: viewer.visitorId,\n    });\n  } else {\n    await chatStore.saveVote({\n      chatId: parsedBody.data.chatId,\n      isUpvoted: parsedBody.data.type === \"up\",\n      messageId: parsedBody.data.messageId,\n      visitorId: viewer.visitorId,\n    });\n  }\n\n  return new Response(\"Message voted\", { status: 200 });\n}\n",
      "type": "registry:lib",
      "target": "@lib/ultra-chatbot-agent/server/votes.ts"
    },
    {
      "path": "registry/ultra-chatbot-agent/lib/ultra-chatbot-agent/server/web-search.ts",
      "content": "import { generateText, stepCountIs, tool } from \"ai\";\nimport { z } from \"zod\";\n\nconst webSearchInputSchema = z.object({\n  query: z.string().trim().min(1),\n});\n\ninterface UltraChatbotAgentWebSearchToolSource {\n  title: string;\n  url: string;\n}\n\ninterface UltraChatbotAgentGeneratedSource {\n  title?: string | null;\n  type?: string | null;\n  url?: string | null;\n}\n\nconst markdownLinkPattern = /\\[([^\\]]+)\\]\\((https?:\\/\\/[^\\s)]+)\\)/g;\nconst explicitUrlPattern = /\\bhttps?:\\/\\/[^\\s)]+/g;\n\nfunction normalizeCitationUrl(value: string) {\n  return value.trim().replace(/[),.;]+$/g, \"\");\n}\n\nfunction normalizeSearchSource(\n  source: UltraChatbotAgentGeneratedSource\n): UltraChatbotAgentWebSearchToolSource | null {\n  if (source.type !== \"source\") {\n    return null;\n  }\n\n  const url = typeof source.url === \"string\" ? source.url.trim() : \"\";\n\n  if (!url) {\n    return null;\n  }\n\n  return {\n    title:\n      typeof source.title === \"string\" && source.title.trim().length > 0\n        ? source.title.trim()\n        : url,\n    url,\n  };\n}\n\nfunction collectSourcesFromSummary(summary: string) {\n  const sources = new Map<string, UltraChatbotAgentWebSearchToolSource>();\n\n  for (const match of summary.matchAll(markdownLinkPattern)) {\n    const [, label, rawUrl] = match;\n\n    if (typeof rawUrl !== \"string\") {\n      continue;\n    }\n\n    const url = normalizeCitationUrl(rawUrl);\n    const title = typeof label === \"string\" ? label.trim() : \"\";\n\n    if (!url) {\n      continue;\n    }\n\n    sources.set(url, {\n      title: title || url,\n      url,\n    });\n  }\n\n  for (const match of summary.matchAll(explicitUrlPattern)) {\n    const url = normalizeCitationUrl(match[0]);\n\n    if (!url || sources.has(url)) {\n      continue;\n    }\n\n    sources.set(url, {\n      title: url.replace(/^https?:\\/\\//, \"\"),\n      url,\n    });\n  }\n\n  return [...sources.values()];\n}\n\nexport function createUltraChatbotAgentWebSearchTool(input: {\n  model: Parameters<typeof generateText>[0][\"model\"];\n  webSearchTool: NonNullable<\n    Parameters<typeof generateText>[0][\"tools\"]\n  >[string];\n}) {\n  return tool({\n    description:\n      \"Search the public web for current information and return grounded notes plus concrete sources.\",\n    inputSchema: webSearchInputSchema,\n    execute: async ({ query }) => {\n      const result = await generateText({\n        model: input.model,\n        prompt: query,\n        stopWhen: stepCountIs(5),\n        system: [\n          \"You are the Ultra agent's research helper.\",\n          \"Use web_search when the query needs current or external facts.\",\n          \"Return a short synthesis grounded in the search results.\",\n          \"Cite at least two concrete sources when enough evidence is available.\",\n        ].join(\" \"),\n        tools: {\n          web_search: input.webSearchTool,\n        },\n      });\n\n      const summary = result.text.trim();\n      const explicitSources = result.sources\n        .map(normalizeSearchSource)\n        .filter(\n          (source): source is UltraChatbotAgentWebSearchToolSource =>\n            source !== null\n        );\n\n      return {\n        query,\n        summary,\n        sources:\n          explicitSources.length > 0\n            ? explicitSources\n            : collectSourcesFromSummary(summary),\n      };\n    },\n  });\n}\n",
      "type": "registry:lib",
      "target": "@lib/ultra-chatbot-agent/server/web-search.ts"
    },
    {
      "path": "registry/ultra-chatbot-agent/lib/ultra-chatbot-agent/skills-agent/server/chat.ts",
      "content": "import {\n  createAgentUIStreamResponse,\n  stepCountIs,\n  ToolLoopAgent,\n  type UIMessage,\n} from \"ai\";\nimport { z } from \"zod\";\nimport { createSkillsAgentGateway, getSkillsAgentEnv } from \"./env\";\nimport {\n  resolveSkillsAgentChatModel,\n  SKILLS_AGENT_PROVIDER_OPTIONS,\n} from \"./model\";\nimport type { SkillMetadata } from \"./skill-catalog\";\nimport { createSkillsAgentWorkspace } from \"./workspace\";\n\ntype DemoEnv = Record<string, string | undefined>;\n\nconst skillOptionSchema = z.object({\n  description: z.string(),\n  name: z.string(),\n  path: z.string(),\n});\n\nconst skillsAgentCallOptionsSchema = z.object({\n  skills: z.array(skillOptionSchema).min(1),\n});\n\ntype SkillsAgentCallOptions = z.infer<typeof skillsAgentCallOptionsSchema>;\n\nexport const skillsAgentInstructions = [\n  \"You are the skills-agent demo for a product and engineering team.\",\n  \"Treat the visible skill catalog as lightweight metadata. Load the full SKILL.md only when you decide a skill is needed.\",\n  \"Choose a skill based on the visible catalog descriptions when the user's request matches one of them.\",\n  \"Use readFile, writeFile, and bash inside the active sandbox workspace when the skill instructions require repository context or generated artifacts.\",\n  \"Follow the loaded skill's filesystem conventions. grill-with-docs owns repository paths like CONTEXT.md and docs/adr/, while artifacts/ is for standalone drafts when a skill does not specify a canonical location.\",\n  \"Repository files such as CONTEXT.md or docs/adr/ may not exist yet. Use bash checks like test -f or ls before reading optional files, and create them lazily when the loaded skill calls for that.\",\n  \"Keep the final answer concise and report any artifact paths that were created or updated.\",\n].join(\" \");\n\nexport async function streamSkillsAgent(\n  messages: UIMessage[],\n  {\n    env = getSkillsAgentEnv(),\n    sessionId,\n    skills,\n  }: {\n    env?: DemoEnv;\n    sessionId: string;\n    skills: SkillMetadata[];\n  }\n) {\n  const gateway = createSkillsAgentGateway(env);\n  const chatModel = resolveSkillsAgentChatModel(env);\n  const workspace = await createSkillsAgentWorkspace({\n    env,\n    sessionId,\n    skills,\n  });\n\n  const agent = new ToolLoopAgent<SkillsAgentCallOptions>({\n    instructions: skillsAgentInstructions,\n    model: gateway(chatModel),\n    prepareCall: (call) => ({\n      ...call,\n      instructions: [\n        skillsAgentInstructions,\n        `Visible skill catalog:\\n${workspace.visibleSkillCatalogText}`,\n        `Sandbox project root: ${workspace.projectRoot}`,\n        `Default artifacts root: ${workspace.artifactsRoot}`,\n      ].join(\"\\n\\n\"),\n    }),\n    providerOptions: SKILLS_AGENT_PROVIDER_OPTIONS,\n    callOptionsSchema: skillsAgentCallOptionsSchema,\n    stopWhen: stepCountIs(20),\n    tools: workspace.toolset.tools,\n  });\n\n  return await createAgentUIStreamResponse({\n    agent,\n    options: {\n      skills: workspace.visibleSkillCatalog,\n    },\n    sendReasoning: true,\n    uiMessages: messages,\n  });\n}\n",
      "type": "registry:lib",
      "target": "@lib/ultra-chatbot-agent/skills-agent/server/chat.ts"
    },
    {
      "path": "registry/ultra-chatbot-agent/lib/ultra-chatbot-agent/skills-agent/server/env-source.ts",
      "content": "export function getSkillsAgentAppEnv() {\n  // biome-ignore lint/style/noProcessEnv: Registry source installs into consumer apps without this repo's env wrapper.\n  return process.env;\n}\n",
      "type": "registry:lib",
      "target": "@lib/ultra-chatbot-agent/skills-agent/server/env-source.ts"
    },
    {
      "path": "registry/ultra-chatbot-agent/lib/ultra-chatbot-agent/skills-agent/server/env.ts",
      "content": "import { getSkillsAgentAppEnv } from \"./env-source\";\nimport {\n  buildAiGatewayContractSetupState,\n  createAiGatewayFromContract,\n  readAiGatewayContractConfig,\n  type AiGatewayContractConfig,\n  type AiGatewayContractSetupState,\n  type AiGatewayEnvRecord,\n  type AiGatewaySetupConfig,\n} from \"@/lib/ai-gateway/contract\";\n\nexport const DEFAULT_SKILLS_AGENT_CHAT_MODEL = \"openai/gpt-5-mini\";\nexport const SKILLS_AGENT_SANDBOX_ENVIRONMENT_LABEL =\n  \"Node 24 + uv Python 3.13\";\n\nexport type SkillsAgentEnv = AiGatewayEnvRecord;\n\nexport type SkillsAgentConfig = AiGatewayContractConfig;\n\nexport interface SkillsAgentSandboxTokenCredentials {\n  projectId: string;\n  teamId: string;\n  token: string;\n}\n\nexport interface SkillsAgentSandboxSetupState {\n  authMode: \"local\" | \"oidc\" | \"token\";\n  isReady: boolean;\n  issues: string[];\n  providerLabel: \"Local sandbox\" | \"Vercel Sandbox\";\n  runtime: \"node24\";\n}\n\nexport interface SkillsAgentSetupState\n  extends AiGatewayContractSetupState<AiGatewaySetupConfig> {\n  sandboxProvider: SkillsAgentSandboxSetupState[\"providerLabel\"];\n}\n\nexport type SkillsAgentGateway = ReturnType<typeof createAiGatewayFromContract>;\n\nconst skillsAgentContract = {\n  defaultChatModel: DEFAULT_SKILLS_AGENT_CHAT_MODEL,\n  missingApiKeyError:\n    \"Missing AI_GATEWAY_API_KEY. Add it to .env.local before using the skills agent.\",\n  missingApiKeyIssue: \"AI_GATEWAY_API_KEY is missing. The demo can render, but skills-agent chat requests will fail until it is configured.\",\n} as const;\n\nexport function getSkillsAgentEnv(): SkillsAgentEnv {\n  return getSkillsAgentAppEnv();\n}\n\nexport function getSkillsAgentConfig(\n  env: SkillsAgentEnv = getSkillsAgentEnv()\n): SkillsAgentConfig {\n  return readAiGatewayContractConfig(env, skillsAgentContract);\n}\n\nexport function hasSkillsAgentSandboxTokenCredentials(env: SkillsAgentEnv) {\n  return Boolean(\n    env.VERCEL_PROJECT_ID && env.VERCEL_TEAM_ID && env.VERCEL_TOKEN\n  );\n}\n\nexport function getSkillsAgentSandboxTokenCredentials(\n  env: SkillsAgentEnv = getSkillsAgentEnv()\n): SkillsAgentSandboxTokenCredentials {\n  const { VERCEL_PROJECT_ID, VERCEL_TEAM_ID, VERCEL_TOKEN } = env;\n\n  if (!(VERCEL_PROJECT_ID && VERCEL_TEAM_ID && VERCEL_TOKEN)) {\n    throw new Error(\n      \"Vercel Sandbox token credentials are incomplete. Expected VERCEL_TOKEN, VERCEL_TEAM_ID, and VERCEL_PROJECT_ID.\"\n    );\n  }\n\n  return {\n    projectId: VERCEL_PROJECT_ID,\n    teamId: VERCEL_TEAM_ID,\n    token: VERCEL_TOKEN,\n  };\n}\n\nexport function getSkillsAgentSandboxSetupState(\n  env: SkillsAgentEnv = getSkillsAgentEnv()\n): SkillsAgentSandboxSetupState {\n  const issues: string[] = [];\n  const hasOidc = Boolean(env.VERCEL_OIDC_TOKEN);\n  const hasAnyTokenCredential = Boolean(\n    env.VERCEL_PROJECT_ID || env.VERCEL_TEAM_ID || env.VERCEL_TOKEN\n  );\n  let authMode: SkillsAgentSandboxSetupState[\"authMode\"] = \"local\";\n\n  if (hasOidc) {\n    authMode = \"oidc\";\n  } else if (hasSkillsAgentSandboxTokenCredentials(env)) {\n    authMode = \"token\";\n  }\n\n  if (\n    authMode === \"local\" &&\n    hasAnyTokenCredential &&\n    !hasSkillsAgentSandboxTokenCredentials(env)\n  ) {\n    issues.push(\n      \"Vercel Sandbox token credentials are incomplete. Set VERCEL_TOKEN, VERCEL_TEAM_ID, and VERCEL_PROJECT_ID together, or remove them to use local sandbox mode.\"\n    );\n  }\n\n  return {\n    authMode,\n    isReady: issues.length === 0,\n    issues,\n    providerLabel: authMode === \"local\" ? \"Local sandbox\" : \"Vercel Sandbox\",\n    runtime: \"node24\",\n  };\n}\n\nexport function getSkillsAgentSetupState(\n  env: SkillsAgentEnv = getSkillsAgentEnv()\n): SkillsAgentSetupState {\n  const sandboxSetup = getSkillsAgentSandboxSetupState(env);\n  const gatewaySetup = buildAiGatewayContractSetupState(env, {\n    ...skillsAgentContract,\n    getAdditionalIssues: () => sandboxSetup.issues,\n  });\n\n  return {\n    ...gatewaySetup,\n    sandboxProvider: sandboxSetup.providerLabel,\n  };\n}\n\nexport function createSkillsAgentGateway(\n  env: SkillsAgentEnv = getSkillsAgentEnv()\n): SkillsAgentGateway {\n  return createAiGatewayFromContract(env, skillsAgentContract);\n}\n",
      "type": "registry:lib",
      "target": "@lib/ultra-chatbot-agent/skills-agent/server/env.ts"
    },
    {
      "path": "registry/ultra-chatbot-agent/lib/ultra-chatbot-agent/skills-agent/server/local-sandbox.ts",
      "content": "import { spawn } from \"node:child_process\";\nimport {\n  mkdir,\n  readdir,\n  readFile,\n  rm,\n  stat,\n  writeFile,\n} from \"node:fs/promises\";\nimport { tmpdir } from \"node:os\";\nimport path from \"node:path\";\n\nimport type { SkillsAgentSessionRegistry } from \"./sandbox\";\nimport {\n  discoverSkills,\n  type LoadedSkill,\n  loadSkill,\n  type SkillMetadata,\n  type SkillsSandbox,\n} from \"./skill-catalog\";\n\nconst SANDBOX_PROJECT_ROOT = \"/vercel/sandbox/project\";\nconst SANDBOX_SKILLS_ROOT = `${SANDBOX_PROJECT_ROOT}/.agents/skills`;\nconst localSandboxRoot = path.join(\n  /*turbopackIgnore: true*/ tmpdir(),\n  \"ai-sdk-skills-agent\"\n);\nconst commandTimeoutMs = 30_000;\nconst maxOutputBytes = 64 * 1024;\n\ninterface SessionEntry {\n  activeSkillDirectory: string | null;\n  hydratedSkillNames: Set<string>;\n}\n\nfunction sanitizeSessionId(sessionId: string) {\n  return sessionId.replace(/[^a-zA-Z0-9_-]/g, \"_\").slice(0, 96);\n}\n\nfunction getSessionRoot(sessionId: string) {\n  return path.join(\n    /*turbopackIgnore: true*/ localSandboxRoot,\n    sanitizeSessionId(sessionId)\n  );\n}\n\nfunction isInsideDirectory(parent: string, child: string) {\n  const relativePath = path.relative(parent, child);\n\n  return (\n    relativePath === \"\" ||\n    (!relativePath.startsWith(\"..\") && !path.isAbsolute(relativePath))\n  );\n}\n\nfunction toProjectRelativePath(projectRoot: string, targetPath: string) {\n  if (targetPath.startsWith(SANDBOX_PROJECT_ROOT)) {\n    return path.relative(SANDBOX_PROJECT_ROOT, targetPath) || \".\";\n  }\n\n  if (path.isAbsolute(targetPath)) {\n    if (!isInsideDirectory(projectRoot, targetPath)) {\n      throw new Error(`Sandbox path escapes the local workspace: ${targetPath}`);\n    }\n\n    return path.relative(projectRoot, targetPath) || \".\";\n  }\n\n  return targetPath;\n}\n\nfunction resolveLocalPath(projectRoot: string, targetPath: string) {\n  const resolvedPath = path.resolve(\n    /*turbopackIgnore: true*/ projectRoot,\n    toProjectRelativePath(projectRoot, targetPath)\n  );\n\n  if (!isInsideDirectory(projectRoot, resolvedPath)) {\n    throw new Error(`Sandbox path escapes the local workspace: ${targetPath}`);\n  }\n\n  return resolvedPath;\n}\n\nfunction resolveSessionPath(\n  entry: SessionEntry,\n  projectRoot: string,\n  targetPath: string\n) {\n  if (\n    entry.activeSkillDirectory &&\n    (targetPath === \".\" ||\n      targetPath.startsWith(\"./\") ||\n      targetPath.startsWith(\"../\"))\n  ) {\n    return path.resolve(\n      /*turbopackIgnore: true*/ entry.activeSkillDirectory,\n      targetPath\n    );\n  }\n\n  return resolveLocalPath(projectRoot, targetPath);\n}\n\nasync function pathExists(localPath: string) {\n  try {\n    await stat(/*turbopackIgnore: true*/ localPath);\n    return true;\n  } catch (error) {\n    if (\n      typeof error === \"object\" &&\n      error !== null &&\n      \"code\" in error &&\n      error.code === \"ENOENT\"\n    ) {\n      return false;\n    }\n\n    throw error;\n  }\n}\n\nfunction appendBounded(current: string, chunk: Buffer) {\n  const next = current + chunk.toString(\"utf-8\");\n\n  return next.length > maxOutputBytes ? next.slice(-maxOutputBytes) : next;\n}\n\nfunction runLocalCommand(command: string, cwd: string) {\n  return new Promise<{\n    command: string;\n    exitCode: number;\n    stderr: string;\n    stdout: string;\n  }>((resolve, reject) => {\n    let stdout = \"\";\n    let stderr = \"\";\n    let timedOut = false;\n    const child = spawn(\"bash\", [\"-lc\", command], {\n      cwd,\n      env: process.env,\n    });\n    const timeout = setTimeout(() => {\n      timedOut = true;\n      child.kill(\"SIGTERM\");\n    }, commandTimeoutMs);\n\n    child.stdout.on(\"data\", (chunk: Buffer) => {\n      stdout = appendBounded(stdout, chunk);\n    });\n    child.stderr.on(\"data\", (chunk: Buffer) => {\n      stderr = appendBounded(stderr, chunk);\n    });\n    child.on(\"error\", (error) => {\n      clearTimeout(timeout);\n      reject(error);\n    });\n    child.on(\"close\", (code) => {\n      clearTimeout(timeout);\n      resolve({\n        command,\n        exitCode: timedOut ? 124 : (code ?? 1),\n        stderr: timedOut\n          ? [stderr, `Command timed out after ${commandTimeoutMs}ms.`]\n              .filter(Boolean)\n              .join(\"\\n\")\n          : stderr,\n        stdout,\n      });\n    });\n  });\n}\n\nclass LocalSkillCatalogSource implements SkillsSandbox {\n  readdir(directory: string, opts?: { withFileTypes?: boolean }) {\n    if (opts?.withFileTypes) {\n      return readdir(/*turbopackIgnore: true*/ directory, {\n        withFileTypes: true,\n      });\n    }\n\n    return readdir(/*turbopackIgnore: true*/ directory);\n  }\n\n  readFile(filePath: string, encoding: BufferEncoding | \"utf-8\") {\n    return readFile(/*turbopackIgnore: true*/ filePath, encoding);\n  }\n\n  exec() {\n    return Promise.resolve({\n      stderr: \"\",\n      stdout: \"\",\n    });\n  }\n}\n\nasync function copyLocalPathToLocalSandbox(\n  localPath: string,\n  sandboxPath: string\n) {\n  const entry = await stat(/*turbopackIgnore: true*/ localPath);\n\n  if (entry.isDirectory()) {\n    await writeDirectory(sandboxPath);\n\n    for (const child of await readdir(/*turbopackIgnore: true*/ localPath)) {\n      await copyLocalPathToLocalSandbox(\n        path.join(/*turbopackIgnore: true*/ localPath, child),\n        path.join(/*turbopackIgnore: true*/ sandboxPath, child)\n      );\n    }\n\n    return;\n  }\n\n  await writeDirectory(path.dirname(sandboxPath));\n  await writeFile(\n    /*turbopackIgnore: true*/ sandboxPath,\n    await readFile(/*turbopackIgnore: true*/ localPath)\n  );\n}\n\nasync function writeDirectory(directory: string) {\n  await mkdir(/*turbopackIgnore: true*/ directory, { recursive: true });\n}\n\nfunction mergeSkillCatalog(\n  primarySkills: SkillMetadata[],\n  fallbackSkills: SkillMetadata[]\n) {\n  const seen = new Set<string>();\n  const mergedSkills: SkillMetadata[] = [];\n\n  for (const skill of [...primarySkills, ...fallbackSkills]) {\n    const key = skill.name.toLowerCase();\n\n    if (seen.has(key)) {\n      continue;\n    }\n\n    seen.add(key);\n    mergedSkills.push(skill);\n  }\n\n  return mergedSkills;\n}\n\nfunction findSkill(skills: SkillMetadata[], name: string) {\n  return skills.find(\n    (candidate) => candidate.name.toLowerCase() === name.toLowerCase()\n  );\n}\n\nfunction toLocalSandboxSkillPath(projectRoot: string, skill: SkillMetadata) {\n  return path.join(\n    /*turbopackIgnore: true*/ projectRoot,\n    \".agents/skills\",\n    path.basename(skill.path)\n  );\n}\n\nfunction isSandboxSkillPath(projectRoot: string, skillPath: string) {\n  return (\n    skillPath.startsWith(SANDBOX_SKILLS_ROOT) ||\n    isInsideDirectory(\n      path.join(/*turbopackIgnore: true*/ projectRoot, \".agents/skills\"),\n      skillPath\n    )\n  );\n}\n\nexport function createLocalSkillsAgentSessionRegistry(): SkillsAgentSessionRegistry {\n  const entries = new Map<string, SessionEntry>();\n\n  const getOrCreateEntry = (sessionId: string) => {\n    const existingEntry = entries.get(sessionId);\n\n    if (existingEntry) {\n      return existingEntry;\n    }\n\n    const createdEntry: SessionEntry = {\n      activeSkillDirectory: null,\n      hydratedSkillNames: new Set<string>(),\n    };\n    entries.set(sessionId, createdEntry);\n    return createdEntry;\n  };\n\n  const discoverLocalSkills = async (projectRoot: string) => {\n    const skillsDirectory = path.join(\n      /*turbopackIgnore: true*/ projectRoot,\n      \".agents/skills\"\n    );\n\n    if (!(await pathExists(skillsDirectory))) {\n      return [];\n    }\n\n    return discoverSkills(new LocalSkillCatalogSource(), [skillsDirectory]);\n  };\n\n  const ensureSkillHydrated = async (\n    entry: SessionEntry,\n    projectRoot: string,\n    skill: SkillMetadata\n  ) => {\n    if (entry.hydratedSkillNames.has(skill.name)) {\n      return;\n    }\n\n    const sandboxPath = toLocalSandboxSkillPath(projectRoot, skill);\n\n    if (!isSandboxSkillPath(projectRoot, skill.path)) {\n      await copyLocalPathToLocalSandbox(skill.path, sandboxPath);\n    }\n\n    entry.hydratedSkillNames.add(skill.name);\n  };\n\n  return {\n    getSession(sessionId) {\n      const entry = getOrCreateEntry(sessionId);\n      const sessionRoot = getSessionRoot(sessionId);\n      const projectRoot = path.join(\n        /*turbopackIgnore: true*/ sessionRoot,\n        \"project\"\n      );\n      const artifactsRoot = path.join(\n        /*turbopackIgnore: true*/ projectRoot,\n        \"artifacts\"\n      );\n\n      return {\n        get activeSkillDirectory() {\n          return entry.activeSkillDirectory;\n        },\n        artifactsRoot,\n        async discoverSkills(skills) {\n          return mergeSkillCatalog(await discoverLocalSkills(projectRoot), skills);\n        },\n        async listSkillFiles(skillDirectory) {\n          const resolvedDirectory = resolveLocalPath(projectRoot, skillDirectory);\n\n          if (!(await pathExists(resolvedDirectory))) {\n            return [];\n          }\n\n          const files: string[] = [];\n          const walk = async (directory: string) => {\n            for (const child of await readdir(\n              /*turbopackIgnore: true*/ directory,\n              {\n                withFileTypes: true,\n              }\n            )) {\n              const childPath = path.join(\n                /*turbopackIgnore: true*/ directory,\n                child.name\n              );\n\n              if (child.isDirectory()) {\n                await walk(childPath);\n                continue;\n              }\n\n              if (child.isFile() && child.name !== \"SKILL.md\") {\n                files.push(path.relative(resolvedDirectory, childPath));\n              }\n            }\n          };\n\n          await walk(resolvedDirectory);\n          return files.sort((left, right) => left.localeCompare(right));\n        },\n        async loadSkill(skills, name): Promise<LoadedSkill> {\n          const availableSkills = mergeSkillCatalog(\n            await discoverLocalSkills(projectRoot),\n            skills\n          );\n          const skill = findSkill(availableSkills, name);\n\n          if (!skill) {\n            throw new Error(`Skill \"${name}\" was not found in the catalog.`);\n          }\n\n          await ensureSkillHydrated(entry, projectRoot, skill);\n\n          const localSkillPath = toLocalSandboxSkillPath(projectRoot, skill);\n          const loadedSkill = await loadSkill(\n            new LocalSkillCatalogSource(),\n            [\n              {\n                ...skill,\n                path: localSkillPath,\n              },\n            ],\n            name\n          );\n\n          entry.activeSkillDirectory = loadedSkill.skillDirectory;\n          return loadedSkill;\n        },\n        async pathExists(targetPath) {\n          return pathExists(resolveSessionPath(entry, projectRoot, targetPath));\n        },\n        projectRoot,\n        async readFile(targetPath) {\n          return readFile(\n            /*turbopackIgnore: true*/ resolveSessionPath(\n              entry,\n              projectRoot,\n              targetPath\n            ),\n            \"utf-8\"\n          );\n        },\n        async runCommand(command) {\n          await writeDirectory(projectRoot);\n          await writeDirectory(artifactsRoot);\n\n          return runLocalCommand(command, projectRoot);\n        },\n        sessionId,\n        async stop() {\n          await rm(/*turbopackIgnore: true*/ sessionRoot, {\n            force: true,\n            recursive: true,\n          });\n        },\n        async writeFile(targetPath, content) {\n          const resolvedPath = resolveSessionPath(\n            entry,\n            projectRoot,\n            targetPath\n          );\n\n          await writeDirectory(path.dirname(resolvedPath));\n          await writeFile(\n            /*turbopackIgnore: true*/ resolvedPath,\n            content,\n            \"utf-8\"\n          );\n\n          return {\n            bytes: Buffer.byteLength(content, \"utf-8\"),\n            path: resolvedPath,\n          };\n        },\n      };\n    },\n    async stopSession(sessionId) {\n      await rm(/*turbopackIgnore: true*/ getSessionRoot(sessionId), {\n        force: true,\n        recursive: true,\n      });\n      entries.delete(sessionId);\n    },\n  };\n}\n",
      "type": "registry:lib",
      "target": "@lib/ultra-chatbot-agent/skills-agent/server/local-sandbox.ts"
    },
    {
      "path": "registry/ultra-chatbot-agent/lib/ultra-chatbot-agent/skills-agent/server/local-skill-catalog.ts",
      "content": "import { readdir, readFile } from \"node:fs/promises\";\nimport path from \"node:path\";\n\nimport {\n  discoverSkills,\n  type SkillMetadata,\n  type SkillsSandbox,\n} from \"./skill-catalog\";\n\nexport const PRIMARY_SKILL_NAMES = [\n  \"grill-with-docs\",\n  \"skill-creator\",\n] as const;\n\nexport const SKILLS_AGENT_WORKSPACE_ROOT = process.cwd();\nconst DEFAULT_SKILLS_DIRECTORY = path.join(\n  SKILLS_AGENT_WORKSPACE_ROOT,\n  \".agents/skills\"\n);\n\nclass LocalSkillCatalogSource implements SkillsSandbox {\n  exec() {\n    return Promise.resolve({\n      stderr: \"\",\n      stdout: \"\",\n    });\n  }\n\n  readdir(directory: string, opts?: { withFileTypes?: boolean }) {\n    if (opts?.withFileTypes) {\n      return readdir(directory, { withFileTypes: true });\n    }\n\n    return readdir(directory);\n  }\n\n  readFile(filePath: string, encoding: BufferEncoding | \"utf-8\") {\n    return readFile(filePath, encoding);\n  }\n}\n\nexport async function discoverWorkspaceSkills(\n  directories: string[] = [DEFAULT_SKILLS_DIRECTORY]\n): Promise<SkillMetadata[]> {\n  const skills = await discoverSkills(\n    new LocalSkillCatalogSource(),\n    directories\n  );\n  const allowedNames = new Set(PRIMARY_SKILL_NAMES);\n\n  return skills.filter((skill) => allowedNames.has(skill.name as never));\n}\n",
      "type": "registry:lib",
      "target": "@lib/ultra-chatbot-agent/skills-agent/server/local-skill-catalog.ts"
    },
    {
      "path": "registry/ultra-chatbot-agent/lib/ultra-chatbot-agent/skills-agent/server/model.ts",
      "content": "import { getSkillsAgentEnv } from \"./env\";\n\nconst DEFAULT_SKILLS_AGENT_CHAT_MODEL = \"openai/gpt-5-mini\";\n\nexport const SKILLS_AGENT_PROVIDER_OPTIONS = {\n  openai: {\n    reasoningEffort: \"medium\",\n    reasoningSummary: \"auto\",\n  },\n} as const;\n\ntype SkillsAgentEnv = Record<string, string | undefined>;\n\nexport function resolveSkillsAgentChatModel(\n  env: SkillsAgentEnv = getSkillsAgentEnv()\n): string {\n  return env.AI_GATEWAY_CHAT_MODEL || DEFAULT_SKILLS_AGENT_CHAT_MODEL;\n}\n",
      "type": "registry:lib",
      "target": "@lib/ultra-chatbot-agent/skills-agent/server/model.ts"
    },
    {
      "path": "registry/ultra-chatbot-agent/lib/ultra-chatbot-agent/skills-agent/server/official-tools.ts",
      "content": "import { type ToolSet, tool } from \"ai\";\nimport { z } from \"zod\";\n\nimport type { SkillsAgentSession } from \"./sandbox\";\nimport type { SkillMetadata } from \"./skill-catalog\";\n\nconst SKILL_NAME_ALIAS_SEPARATOR_PATTERN = /[\\s_]+/g;\nconst REPEATED_DASH_PATTERN = /-+/g;\n\nconst SKILLS_AGENT_TOOL_PROMPT = [\n  \"Use bash to explore the sandbox workspace and inspect files after loading a skill.\",\n  \"Use readFile and writeFile for direct file access.\",\n  \"Load a skill before using sandbox tools that depend on repository context.\",\n].join(\" \");\n\nexport interface SkillsAgentOfficialTools {\n  availableSkills: Pick<SkillMetadata, \"description\" | \"name\">[];\n  primedFiles: { content: string; path: string }[];\n  tools: ToolSet;\n}\n\ninterface SkillToolSuccessResult {\n  files: string[];\n  instructions: string;\n  skill: {\n    description: string;\n    name: string;\n    path: string;\n  };\n  success: true;\n}\n\nfunction normalizeSkillLookupKey(skillName: string) {\n  return skillName\n    .trim()\n    .toLowerCase()\n    .replace(SKILL_NAME_ALIAS_SEPARATOR_PATTERN, \"-\")\n    .replace(REPEATED_DASH_PATTERN, \"-\");\n}\n\nfunction resolveSkillNameAlias(\n  availableSkills: SkillMetadata[],\n  requestedSkillName: string\n) {\n  const trimmedSkillName = requestedSkillName.trim();\n  const exactMatch = availableSkills.find(\n    (skill) => skill.name === trimmedSkillName\n  );\n\n  if (exactMatch) {\n    return exactMatch.name;\n  }\n\n  const normalizedRequestedSkillName =\n    normalizeSkillLookupKey(trimmedSkillName);\n  const normalizedMatch = availableSkills.find(\n    (skill) =>\n      normalizeSkillLookupKey(skill.name) === normalizedRequestedSkillName\n  );\n\n  return normalizedMatch?.name ?? trimmedSkillName;\n}\n\nfunction findSkillByName(skills: SkillMetadata[], name: string) {\n  return skills.find(\n    (skill) => skill.name.toLowerCase() === name.toLowerCase()\n  );\n}\n\nfunction createSkillToolError(error: unknown) {\n  return {\n    error: error instanceof Error ? error.message : String(error),\n    success: false,\n  };\n}\n\nasync function createLoadedSkillToolResult({\n  availableSkills,\n  loadedSkill,\n  session,\n}: {\n  availableSkills: SkillMetadata[];\n  loadedSkill: Awaited<ReturnType<SkillsAgentSession[\"loadSkill\"]>>;\n  session: SkillsAgentSession;\n}): Promise<SkillToolSuccessResult> {\n  const metadata = findSkillByName(availableSkills, loadedSkill.name);\n\n  if (!metadata) {\n    throw new Error(`Loaded skill \"${loadedSkill.name}\" is missing metadata.`);\n  }\n\n  return {\n    files: await session.listSkillFiles(loadedSkill.skillDirectory),\n    instructions: loadedSkill.content,\n    skill: {\n      description: metadata.description,\n      name: loadedSkill.name,\n      path: loadedSkill.skillDirectory,\n    },\n    success: true,\n  };\n}\n\nfunction buildPrimedFiles({\n  agentsContent,\n  projectRoot,\n}: {\n  agentsContent: string;\n  projectRoot: string;\n}) {\n  return [\n    {\n      content: agentsContent,\n      path: `${projectRoot}/AGENTS.md`,\n    },\n  ];\n}\n\nexport async function createSkillsAgentOfficialTools({\n  agentsContent,\n  projectRoot,\n  session,\n  skills,\n}: {\n  agentsContent: string;\n  projectRoot: string;\n  session: SkillsAgentSession;\n  skills: SkillMetadata[];\n}): Promise<SkillsAgentOfficialTools> {\n  return {\n    availableSkills: skills.map(({ description, name }) => ({\n      description,\n      name,\n    })),\n    primedFiles: buildPrimedFiles({\n      agentsContent,\n      projectRoot,\n    }),\n    tools: {\n      bash: tool({\n        description: [\n          `Run a shell command inside the sandbox workspace at ${projectRoot}.`,\n          SKILLS_AGENT_TOOL_PROMPT,\n        ].join(\"\\n\\n\"),\n        execute: ({ command }) => session.runCommand(command),\n        inputSchema: z.object({\n          command: z\n            .string()\n            .min(1)\n            .describe(\"Shell command to run from the sandbox project root.\"),\n        }),\n      }),\n      readFile: tool({\n        description: \"Read a UTF-8 file from the sandbox workspace.\",\n        execute: async ({ path }) => ({\n          content: await session.readFile(path),\n          path,\n        }),\n        inputSchema: z.object({\n          path: z\n            .string()\n            .min(1)\n            .describe(\n              \"File path to read, relative to the sandbox project root unless absolute.\"\n            ),\n        }),\n      }),\n      skill: tool({\n        description:\n          \"Load full SKILL.md instructions from the visible skills catalog.\",\n        execute: async ({ skillName }) => {\n          try {\n            const availableSkills = await session.discoverSkills(skills);\n            const canonicalSkillName = resolveSkillNameAlias(\n              availableSkills,\n              skillName\n            );\n            const loadedSkill = await session.loadSkill(\n              availableSkills,\n              canonicalSkillName\n            );\n\n            return createLoadedSkillToolResult({\n              availableSkills,\n              loadedSkill,\n              session,\n            });\n          } catch (error) {\n            return createSkillToolError(error);\n          }\n        },\n        inputSchema: z.object({\n          skillName: z\n            .string()\n            .min(1)\n            .describe(\"Name of the skill to load from the visible catalog.\"),\n        }),\n      }),\n      writeFile: tool({\n        description: \"Write a UTF-8 file into the sandbox workspace.\",\n        execute: ({ content, path }) => session.writeFile(path, content),\n        inputSchema: z.object({\n          content: z.string().describe(\"UTF-8 file content to write.\"),\n          path: z\n            .string()\n            .min(1)\n            .describe(\n              \"File path to write, relative to the sandbox project root unless absolute.\"\n            ),\n        }),\n      }),\n    },\n  };\n}\n",
      "type": "registry:lib",
      "target": "@lib/ultra-chatbot-agent/skills-agent/server/official-tools.ts"
    },
    {
      "path": "registry/ultra-chatbot-agent/lib/ultra-chatbot-agent/skills-agent/server/request.ts",
      "content": "import { type UIMessage, validateUIMessages } from \"ai\";\n\nimport { getSkillsAgentEnv } from \"./env\";\nimport { discoverWorkspaceSkills } from \"./local-skill-catalog\";\nimport { getSkillsAgentRuntimeState } from \"./runtime\";\nimport type { SkillMetadata } from \"./skill-catalog\";\n\ntype DemoEnv = Record<string, string | undefined>;\n\ninterface SkillsAgentRequestBody {\n  id?: string;\n  messages?: UIMessage[];\n}\n\ninterface StreamSkillsAgentOptions {\n  sessionId: string;\n  skills: SkillMetadata[];\n}\n\ninterface SkillsAgentRequestDependencies {\n  discoverSkills: () => Promise<SkillMetadata[]>;\n  streamSkillsAgent: (\n    messages: UIMessage[],\n    options: StreamSkillsAgentOptions\n  ) => Promise<Response> | Response;\n}\n\nconst invalidUiMessagesError =\n  'Expected each \"messages\" entry to match the UIMessage format.';\nconst invalidChatIdError =\n  'Expected a JSON body with an \"id\" string and a \"messages\" array.';\n\nasync function readSkillsAgentRequest(\n  body: unknown\n): Promise<{ messages: UIMessage[]; sessionId: string }> {\n  const { id, messages } = (body ?? {}) as SkillsAgentRequestBody;\n\n  if (!(typeof id === \"string\" && id.length > 0 && Array.isArray(messages))) {\n    throw new Error(invalidChatIdError);\n  }\n\n  try {\n    return {\n      messages: await validateUIMessages({ messages }),\n      sessionId: id,\n    };\n  } catch {\n    throw new Error(invalidUiMessagesError);\n  }\n}\n\nexport async function handleSkillsAgentRequest(\n  request: Request,\n  env: DemoEnv = getSkillsAgentEnv(),\n  dependencies: Partial<SkillsAgentRequestDependencies> = {\n    discoverSkills: discoverWorkspaceSkills,\n    streamSkillsAgent: async (messages, options) => {\n      const { streamSkillsAgent } = await import(\"./chat\");\n\n      return streamSkillsAgent(messages, {\n        env,\n        sessionId: options.sessionId,\n        skills: options.skills,\n      });\n    },\n  }\n) {\n  const discoverSkills = dependencies.discoverSkills ?? discoverWorkspaceSkills;\n  const streamAgent =\n    dependencies.streamSkillsAgent ??\n    (async (messages: UIMessage[], options: StreamSkillsAgentOptions) => {\n      const { streamSkillsAgent } = await import(\"./chat\");\n\n      return streamSkillsAgent(messages, {\n        env,\n        sessionId: options.sessionId,\n        skills: options.skills,\n      });\n    });\n  const runtimeState = await getSkillsAgentRuntimeState(env, {\n    discoverSkills,\n  });\n\n  if (!runtimeState.isChatAvailable) {\n    return Response.json(\n      {\n        error: runtimeState.setupMessage,\n      },\n      { status: 500 }\n    );\n  }\n\n  try {\n    const { messages, sessionId } = await readSkillsAgentRequest(\n      await request.json()\n    );\n    const skills = await discoverSkills();\n\n    return streamAgent(messages, { sessionId, skills });\n  } catch (error) {\n    if (\n      error instanceof Error &&\n      [invalidUiMessagesError, invalidChatIdError].includes(error.message)\n    ) {\n      return Response.json(\n        {\n          error: error.message,\n        },\n        { status: 400 }\n      );\n    }\n\n    throw error;\n  }\n}\n",
      "type": "registry:lib",
      "target": "@lib/ultra-chatbot-agent/skills-agent/server/request.ts"
    },
    {
      "path": "registry/ultra-chatbot-agent/lib/ultra-chatbot-agent/skills-agent/server/runtime.ts",
      "content": "import {\n  getSkillsAgentEnv,\n  getSkillsAgentSetupState,\n  SKILLS_AGENT_SANDBOX_ENVIRONMENT_LABEL,\n} from \"./env\";\nimport { discoverWorkspaceSkills } from \"./local-skill-catalog\";\nimport type { SkillMetadata } from \"./skill-catalog\";\n\ntype DemoEnv = Record<string, string | undefined>;\n\nexport interface SkillsAgentRuntimeState {\n  availableSkills: Pick<SkillMetadata, \"description\" | \"name\">[];\n  chatModel: string;\n  environmentLabel: string;\n  isChatAvailable: boolean;\n  nodeVersion: string;\n  sandboxProvider: string;\n  setupMessage: string | null;\n  statusLabel: \"Ready\" | \"Setup required\";\n}\n\nexport async function getSkillsAgentRuntimeState(\n  env: DemoEnv = getSkillsAgentEnv(),\n  dependencies: {\n    discoverSkills: () => Promise<SkillMetadata[]>;\n  } = {\n    discoverSkills: discoverWorkspaceSkills,\n  }\n): Promise<SkillsAgentRuntimeState> {\n  const setup = getSkillsAgentSetupState(env);\n  const skills = await dependencies.discoverSkills();\n  const issues = [...setup.issues];\n\n  if (skills.length === 0) {\n    issues.push(\n      'No primary skills were found under \".agents/skills\". The demo expects grill-with-docs and skill-creator.'\n    );\n  }\n\n  return {\n    availableSkills: skills.map(({ description, name }) => ({\n      description,\n      name,\n    })),\n    chatModel: setup.config.chatModel,\n    environmentLabel: SKILLS_AGENT_SANDBOX_ENVIRONMENT_LABEL,\n    isChatAvailable: issues.length === 0,\n    nodeVersion: setup.nodeVersion,\n    sandboxProvider: setup.sandboxProvider,\n    setupMessage: issues.length > 0 ? issues.join(\" \") : null,\n    statusLabel: issues.length === 0 ? \"Ready\" : \"Setup required\",\n  };\n}\n",
      "type": "registry:lib",
      "target": "@lib/ultra-chatbot-agent/skills-agent/server/runtime.ts"
    },
    {
      "path": "registry/ultra-chatbot-agent/lib/ultra-chatbot-agent/skills-agent/server/sandbox.ts",
      "content": "import path, { posix as posixPath } from \"node:path\";\nimport type {\n  VercelSandboxFactory,\n  VercelSandboxHandle,\n  VercelSandboxSession,\n  VercelSandboxSessionRegistry,\n} from \"./vercel-sandbox\";\nimport {\n  getSkillsAgentEnv,\n  getSkillsAgentSandboxSetupState as getSkillsAgentSandboxSetup,\n  type SkillsAgentEnv,\n} from \"./env\";\nimport { SKILLS_AGENT_WORKSPACE_ROOT } from \"./local-skill-catalog\";\nimport {\n  type LoadedSkill,\n  loadSkill,\n  parseSkillMetadata,\n  type SkillMetadata,\n} from \"./skill-catalog\";\n\nexport type SkillsAgentSandboxHandle = VercelSandboxHandle;\nexport type SandboxFactory = VercelSandboxFactory;\nexport const SANDBOX_PROJECT_ROOT = \"/vercel/sandbox/project\";\nexport const SANDBOX_SKILLS_ROOT = `${SANDBOX_PROJECT_ROOT}/.agents/skills`;\nexport const SANDBOX_ARTIFACTS_ROOT = `${SANDBOX_PROJECT_ROOT}/artifacts`;\nexport const SANDBOX_AGENTS_FILE = `${SANDBOX_PROJECT_ROOT}/AGENTS.md`;\n\nexport interface SkillsAgentSession extends VercelSandboxSession {\n  readonly activeSkillDirectory: string | null;\n  discoverSkills(skills: SkillMetadata[]): Promise<SkillMetadata[]>;\n  listSkillFiles(skillDirectory: string): Promise<string[]>;\n  loadSkill(skills: SkillMetadata[], name: string): Promise<LoadedSkill>;\n}\n\nexport interface SkillsAgentSessionRegistry {\n  getSession(sessionId: string): SkillsAgentSession;\n  stopSession(sessionId: string): Promise<void>;\n}\n\ninterface SessionEntry {\n  activeSkillDirectory: string | null;\n  hydratedSkillNames: Set<string>;\n}\n\nfunction resolveSessionPath(entry: SessionEntry, targetPath: string) {\n  if (targetPath.startsWith(\"/\")) {\n    return targetPath;\n  }\n\n  if (\n    entry.activeSkillDirectory &&\n    (targetPath === \".\" ||\n      targetPath.startsWith(\"./\") ||\n      targetPath.startsWith(\"../\"))\n  ) {\n    return posixPath.normalize(\n      posixPath.join(entry.activeSkillDirectory, targetPath)\n    );\n  }\n\n  return targetPath;\n}\n\nfunction shellQuote(value: string) {\n  return `'${value.replace(/'/g, `'\"'\"'`)}'`;\n}\n\nfunction isSandboxSkillPath(skillPath: string) {\n  return (\n    skillPath === SANDBOX_SKILLS_ROOT ||\n    skillPath.startsWith(`${SANDBOX_SKILLS_ROOT}/`)\n  );\n}\n\nfunction createSandboxSkillSource(baseSession: VercelSandboxSession) {\n  return {\n    exec: async () => ({\n      stderr: \"\",\n      stdout: \"\",\n    }),\n    readdir: async () => [],\n    readFile: (filePath: string, encoding: BufferEncoding | \"utf-8\") =>\n      baseSession.readFile(filePath).then((content) => {\n        if (encoding !== \"utf-8\") {\n          throw new Error(`Unexpected encoding ${encoding}`);\n        }\n\n        return content;\n      }),\n  };\n}\n\nfunction mergeSkillCatalog(\n  primarySkills: SkillMetadata[],\n  fallbackSkills: SkillMetadata[]\n) {\n  const seen = new Set<string>();\n  const mergedSkills: SkillMetadata[] = [];\n\n  for (const skill of [...primarySkills, ...fallbackSkills]) {\n    const key = skill.name.toLowerCase();\n\n    if (seen.has(key)) {\n      continue;\n    }\n\n    seen.add(key);\n    mergedSkills.push(skill);\n  }\n\n  return mergedSkills;\n}\n\nfunction findSkill(skills: SkillMetadata[], name: string) {\n  return skills.find(\n    (candidate) => candidate.name.toLowerCase() === name.toLowerCase()\n  );\n}\n\nasync function discoverSandboxSkills(baseSession: VercelSandboxSession) {\n  const command = [\n    `if [ -d ${shellQuote(SANDBOX_SKILLS_ROOT)} ]; then`,\n    `find ${shellQuote(SANDBOX_SKILLS_ROOT)} -mindepth 2 -maxdepth 2 -type f -name 'SKILL.md' -print`,\n    \"fi\",\n  ].join(\"\\n\");\n  const result = await baseSession.runCommand(command);\n\n  if (result.exitCode !== 0) {\n    throw new Error(\n      `Sandbox skill discovery failed. ${result.stderr.trim() || result.stdout.trim() || result.command}`\n    );\n  }\n\n  const seen = new Set<string>();\n  const skills: SkillMetadata[] = [];\n  const skillFiles = result.stdout\n    .split(/\\r?\\n/)\n    .map((line) => line.trim())\n    .filter(Boolean)\n    .sort((left, right) => left.localeCompare(right));\n\n  for (const skillFile of skillFiles) {\n    const content = await baseSession.readFile(skillFile);\n    const metadata = parseSkillMetadata(content);\n\n    if (!metadata) {\n      continue;\n    }\n\n    const key = metadata.name.toLowerCase();\n\n    if (seen.has(key)) {\n      continue;\n    }\n\n    seen.add(key);\n    skills.push({\n      ...metadata,\n      path: posixPath.dirname(skillFile),\n    });\n  }\n\n  return skills;\n}\n\nasync function listSandboxSkillFiles(\n  baseSession: VercelSandboxSession,\n  skillDirectory: string\n) {\n  const command = [\n    `if [ -d ${shellQuote(skillDirectory)} ]; then`,\n    `cd ${shellQuote(skillDirectory)} && find . -type f ! -path './SKILL.md' -print | sed 's#^./##' | sort`,\n    \"fi\",\n  ].join(\"\\n\");\n  const result = await baseSession.runCommand(command);\n\n  if (result.exitCode !== 0) {\n    throw new Error(\n      `Sandbox skill file listing failed for ${skillDirectory}. ${result.stderr.trim() || result.stdout.trim() || result.command}`\n    );\n  }\n\n  return result.stdout\n    .split(/\\r?\\n/)\n    .map((line) => line.trim())\n    .filter(Boolean);\n}\n\nexport const getSkillsAgentSandboxSetupState = getSkillsAgentSandboxSetup;\n\nexport async function createSkillsAgentSandbox(\n  sessionId: string,\n  env: SkillsAgentEnv = getSkillsAgentEnv(),\n  options: { ports?: number[] } = {}\n): Promise<SkillsAgentSandboxHandle> {\n  const { createVercelSandbox } = await import(\"./vercel-sandbox\");\n\n  return createVercelSandbox(sessionId, env, options);\n}\n\nexport async function createSkillsAgentSessionRegistry({\n  createSandbox,\n  workspaceRoot = SKILLS_AGENT_WORKSPACE_ROOT,\n}: {\n  createSandbox: SandboxFactory;\n  workspaceRoot?: string;\n}): Promise<SkillsAgentSessionRegistry> {\n  const { copyLocalPathToSandbox, createVercelSandboxSessionRegistry } =\n    await import(\"./vercel-sandbox\");\n  const baseRegistry: VercelSandboxSessionRegistry =\n    createVercelSandboxSessionRegistry({\n      createSandbox,\n      workspaceRoot,\n    });\n  const entries = new Map<string, SessionEntry>();\n\n  const getOrCreateEntry = (sessionId: string) => {\n    const existingEntry = entries.get(sessionId);\n\n    if (existingEntry) {\n      return existingEntry;\n    }\n\n    const createdEntry: SessionEntry = {\n      activeSkillDirectory: null,\n      hydratedSkillNames: new Set<string>(),\n    };\n    entries.set(sessionId, createdEntry);\n    return createdEntry;\n  };\n\n  const ensureSkillHydrated = async (\n    entry: SessionEntry,\n    sessionId: string,\n    skill: SkillMetadata\n  ) => {\n    if (entry.hydratedSkillNames.has(skill.name)) {\n      return;\n    }\n\n    const sandbox = await baseRegistry.getSandbox(sessionId);\n    await copyLocalPathToSandbox(\n      sandbox,\n      skill.path,\n      posixPath.join(SANDBOX_SKILLS_ROOT, path.basename(skill.path))\n    );\n    entry.hydratedSkillNames.add(skill.name);\n  };\n\n  return {\n    getSession(sessionId) {\n      const entry = getOrCreateEntry(sessionId);\n      const baseSession = baseRegistry.getSession(sessionId);\n\n      return {\n        get activeSkillDirectory() {\n          return entry.activeSkillDirectory;\n        },\n        artifactsRoot: SANDBOX_ARTIFACTS_ROOT,\n        async discoverSkills(skills) {\n          return mergeSkillCatalog(\n            await discoverSandboxSkills(baseSession),\n            skills\n          );\n        },\n        listSkillFiles(skillDirectory) {\n          return listSandboxSkillFiles(baseSession, skillDirectory);\n        },\n        projectRoot: SANDBOX_PROJECT_ROOT,\n        sessionId,\n        async loadSkill(skills, name) {\n          const availableSkills = mergeSkillCatalog(\n            await discoverSandboxSkills(baseSession),\n            skills\n          );\n          const skill = findSkill(availableSkills, name);\n\n          if (!skill) {\n            throw new Error(`Skill \"${name}\" was not found in the catalog.`);\n          }\n\n          if (!isSandboxSkillPath(skill.path)) {\n            await ensureSkillHydrated(entry, sessionId, skill);\n          }\n\n          return loadSkill(\n            createSandboxSkillSource(baseSession),\n            [\n              {\n                ...skill,\n                path: isSandboxSkillPath(skill.path)\n                  ? skill.path\n                  : posixPath.join(\n                      SANDBOX_SKILLS_ROOT,\n                      path.basename(skill.path)\n                    ),\n              },\n            ],\n            name\n          ).then((loadedSkill) => {\n            entry.activeSkillDirectory = loadedSkill.skillDirectory;\n            return loadedSkill;\n          });\n        },\n        pathExists(targetPath) {\n          return baseSession.pathExists(resolveSessionPath(entry, targetPath));\n        },\n        readFile(targetPath) {\n          return baseSession.readFile(resolveSessionPath(entry, targetPath));\n        },\n        runCommand(command) {\n          return baseSession.runCommand(command);\n        },\n        stop() {\n          return baseRegistry.stopSession(sessionId);\n        },\n        writeFile(targetPath, content) {\n          return baseSession.writeFile(\n            resolveSessionPath(entry, targetPath),\n            content\n          );\n        },\n      };\n    },\n    async stopSession(sessionId) {\n      await baseRegistry.stopSession(sessionId);\n      entries.delete(sessionId);\n    },\n  };\n}\n\nlet sharedRegistry: SkillsAgentSessionRegistry | null = null;\nlet sharedRegistryMode:\n  | ReturnType<typeof getSkillsAgentSandboxSetup>[\"authMode\"]\n  | null = null;\n\nexport async function getSharedSkillsAgentSessionRegistry(\n  env: SkillsAgentEnv = getSkillsAgentEnv()\n) {\n  const setupState = getSkillsAgentSandboxSetup(env);\n\n  if (!sharedRegistry || sharedRegistryMode !== setupState.authMode) {\n    if (setupState.authMode === \"local\") {\n      const { createLocalSkillsAgentSessionRegistry } = await import(\n        \"./local-sandbox\"\n      );\n\n      sharedRegistry = createLocalSkillsAgentSessionRegistry();\n    } else {\n      const { createVercelSandbox } = await import(\"./vercel-sandbox\");\n\n      sharedRegistry = await createSkillsAgentSessionRegistry({\n        createSandbox: (sessionId) => createVercelSandbox(sessionId, env),\n      });\n    }\n\n    sharedRegistryMode = setupState.authMode;\n  }\n\n  return sharedRegistry;\n}\n",
      "type": "registry:lib",
      "target": "@lib/ultra-chatbot-agent/skills-agent/server/sandbox.ts"
    },
    {
      "path": "registry/ultra-chatbot-agent/lib/ultra-chatbot-agent/skills-agent/server/skill-catalog.ts",
      "content": "interface SkillDirent {\n  isDirectory(): boolean;\n  name: string;\n}\n\nexport interface SkillsSandbox {\n  exec(command: string): Promise<{ stderr: string; stdout: string }>;\n  readdir(\n    path: string,\n    opts?: { withFileTypes?: boolean }\n  ): Promise<SkillDirent[] | string[]>;\n  readFile(path: string, encoding: BufferEncoding | \"utf-8\"): Promise<string>;\n}\n\nexport interface SkillMetadata {\n  description: string;\n  name: string;\n  path: string;\n}\n\nexport interface LoadedSkill {\n  content: string;\n  name: string;\n  skillDirectory: string;\n}\n\nconst frontmatterPattern = /^---\\r?\\n([\\s\\S]*?)\\r?\\n---\\r?\\n?/;\nconst trailingSlashPattern = /\\/$/;\nconst frontmatterDescriptionPattern = /^description:\\s*(.+)$/m;\nconst frontmatterNamePattern = /^name:\\s*(.+)$/m;\n\nfunction joinPath(directory: string, name: string) {\n  return `${directory.replace(trailingSlashPattern, \"\")}/${name}`;\n}\n\nexport function parseSkillMetadata(\n  content: string\n): Omit<SkillMetadata, \"path\"> | null {\n  const match = content.match(frontmatterPattern);\n\n  if (!match) {\n    return null;\n  }\n\n  const frontmatter = match[1] ?? \"\";\n  const name = frontmatter.match(frontmatterNamePattern)?.[1]?.trim();\n  const description = frontmatter\n    .match(frontmatterDescriptionPattern)?.[1]\n    ?.trim();\n\n  if (!(name && description)) {\n    return null;\n  }\n\n  return {\n    description,\n    name,\n  };\n}\n\nexport function stripFrontmatter(content: string) {\n  const match = content.match(frontmatterPattern);\n\n  return match ? content.slice(match[0].length).trim() : content.trim();\n}\n\nexport async function discoverSkills(\n  sandbox: SkillsSandbox,\n  directories: string[]\n): Promise<SkillMetadata[]> {\n  const seen = new Set<string>();\n  const skills: SkillMetadata[] = [];\n\n  for (const directory of directories) {\n    const entries = (await sandbox.readdir(directory, {\n      withFileTypes: true,\n    })) as SkillDirent[];\n\n    for (const entry of entries) {\n      if (!entry.isDirectory()) {\n        continue;\n      }\n\n      const skillDirectory = joinPath(directory, entry.name);\n      const skillFile = joinPath(skillDirectory, \"SKILL.md\");\n\n      try {\n        const content = await sandbox.readFile(skillFile, \"utf-8\");\n        const metadata = parseSkillMetadata(content);\n\n        if (!metadata || seen.has(metadata.name.toLowerCase())) {\n          continue;\n        }\n\n        seen.add(metadata.name.toLowerCase());\n        skills.push({\n          ...metadata,\n          path: skillDirectory,\n        });\n      } catch {\n        // Ignore directories without a readable SKILL.md.\n      }\n    }\n  }\n\n  return skills.sort((left, right) => left.name.localeCompare(right.name));\n}\n\nexport async function loadSkill(\n  sandbox: SkillsSandbox,\n  skills: SkillMetadata[],\n  name: string\n): Promise<LoadedSkill> {\n  const skill = skills.find(\n    (candidate) => candidate.name.toLowerCase() === name.toLowerCase()\n  );\n\n  if (!skill) {\n    throw new Error(`Skill \"${name}\" was not found in the catalog.`);\n  }\n\n  const content = await sandbox.readFile(\n    joinPath(skill.path, \"SKILL.md\"),\n    \"utf-8\"\n  );\n\n  return {\n    content: stripFrontmatter(content),\n    name: skill.name,\n    skillDirectory: skill.path,\n  };\n}\n",
      "type": "registry:lib",
      "target": "@lib/ultra-chatbot-agent/skills-agent/server/skill-catalog.ts"
    },
    {
      "path": "registry/ultra-chatbot-agent/lib/ultra-chatbot-agent/skills-agent/server/vercel-sandbox.ts",
      "content": "import { readdir, readFile, stat } from \"node:fs/promises\";\nimport path, { posix as posixPath } from \"node:path\";\nimport { Sandbox } from \"@vercel/sandbox\";\nimport {\n  getSkillsAgentEnv,\n  getSkillsAgentSandboxSetupState,\n  getSkillsAgentSandboxTokenCredentials,\n  type SkillsAgentEnv,\n} from \"./env\";\n\nexport const VERCEL_SANDBOX_WORKSPACE_ROOT = process.cwd();\nexport const VERCEL_SANDBOX_PROJECT_ROOT = \"/vercel/sandbox/project\";\nexport const VERCEL_SANDBOX_SKILLS_ROOT =\n  `${VERCEL_SANDBOX_PROJECT_ROOT}/.agents/skills`;\nexport const VERCEL_SANDBOX_ARTIFACTS_ROOT =\n  `${VERCEL_SANDBOX_PROJECT_ROOT}/artifacts`;\nexport const VERCEL_SANDBOX_AGENTS_FILE =\n  `${VERCEL_SANDBOX_PROJECT_ROOT}/AGENTS.md`;\nconst VERCEL_SANDBOX_PYTHON_VERSION = \"3.13\";\nconst VERCEL_SANDBOX_PYTHON_PROJECT_NAME = \"skills-agent-sandbox\";\n\ninterface SandboxFs {\n  exists(path: string): Promise<boolean>;\n  mkdir(path: string, options?: { recursive?: boolean }): Promise<void>;\n  readFile(path: string, encoding: BufferEncoding | \"utf-8\"): Promise<string>;\n  writeFile(\n    path: string,\n    content: string | Uint8Array,\n    encoding?: BufferEncoding | \"utf-8\"\n  ): Promise<void>;\n}\n\nexport interface VercelSandboxHandle {\n  fs: SandboxFs;\n  runCommand(options: {\n    args: string[];\n    cmd: string;\n    cwd: string;\n    sudo?: boolean;\n  }): Promise<{\n    exitCode: number;\n    stderr(): Promise<string>;\n    stdout(): Promise<string>;\n  }>;\n  stop(): Promise<unknown>;\n}\n\nexport type VercelSandboxFactory = (\n  sessionId: string\n) => Promise<VercelSandboxHandle>;\n\nexport interface VercelSandboxSession {\n  readonly artifactsRoot: string;\n  pathExists(targetPath: string): Promise<boolean>;\n  readonly projectRoot: string;\n  readFile(targetPath: string): Promise<string>;\n  runCommand(command: string): Promise<{\n    command: string;\n    exitCode: number;\n    stderr: string;\n    stdout: string;\n  }>;\n  readonly sessionId: string;\n  stop(): Promise<void>;\n  writeFile(\n    targetPath: string,\n    content: string\n  ): Promise<{\n    bytes: number;\n    path: string;\n  }>;\n}\n\nexport interface VercelSandboxSessionRegistry {\n  getSandbox(sessionId: string): Promise<VercelSandboxHandle>;\n  getSession(sessionId: string): VercelSandboxSession;\n  stopSession(sessionId: string): Promise<void>;\n}\n\ninterface SessionEntry {\n  sandboxPromise: Promise<VercelSandboxHandle> | null;\n  seededBaseWorkspace: boolean;\n}\n\nexport async function createVercelSandbox(\n  sessionId: string,\n  env: SkillsAgentEnv = getSkillsAgentEnv(),\n  options: {\n    ports?: number[];\n  } = {}\n): Promise<VercelSandboxHandle> {\n  const setupState = getSkillsAgentSandboxSetupState(env);\n\n  if (!setupState.isReady) {\n    throw new Error(setupState.issues.join(\" \"));\n  }\n\n  const baseOptions = {\n    name: sessionId,\n    persistent: true,\n    ports: options.ports,\n    runtime: setupState.runtime,\n    timeout: 300_000,\n  };\n\n  if (setupState.authMode === \"oidc\") {\n    try {\n      return (await Sandbox.get({\n        name: sessionId,\n      })) as unknown as VercelSandboxHandle;\n    } catch {\n      return (await Sandbox.create(\n        baseOptions\n      )) as unknown as VercelSandboxHandle;\n    }\n  }\n\n  const tokenCredentials = getSkillsAgentSandboxTokenCredentials(env);\n\n  try {\n    return (await Sandbox.get({\n      name: sessionId,\n      projectId: tokenCredentials.projectId,\n      teamId: tokenCredentials.teamId,\n      token: tokenCredentials.token,\n    })) as unknown as VercelSandboxHandle;\n  } catch {\n    return (await Sandbox.create({\n      ...baseOptions,\n      projectId: tokenCredentials.projectId,\n      teamId: tokenCredentials.teamId,\n      token: tokenCredentials.token,\n    })) as unknown as VercelSandboxHandle;\n  }\n}\n\nasync function pathExists(localPath: string) {\n  try {\n    await stat(localPath);\n    return true;\n  } catch (error) {\n    if (\n      typeof error === \"object\" &&\n      error !== null &&\n      \"code\" in error &&\n      error.code === \"ENOENT\"\n    ) {\n      return false;\n    }\n\n    throw error;\n  }\n}\n\nexport async function copyLocalPathToSandbox(\n  sandbox: VercelSandboxHandle,\n  localPath: string,\n  remotePath: string\n) {\n  const entry = await stat(localPath);\n\n  if (entry.isDirectory()) {\n    await sandbox.fs.mkdir(remotePath, { recursive: true });\n\n    for (const child of await readdir(localPath)) {\n      await copyLocalPathToSandbox(\n        sandbox,\n        path.join(localPath, child),\n        posixPath.join(remotePath, child)\n      );\n    }\n\n    return;\n  }\n\n  await sandbox.fs.mkdir(posixPath.dirname(remotePath), { recursive: true });\n  await sandbox.fs.writeFile(remotePath, await readFile(localPath));\n}\n\nfunction resolveSandboxPath(targetPath: string) {\n  if (targetPath.startsWith(\"/\")) {\n    return targetPath;\n  }\n\n  return posixPath.join(VERCEL_SANDBOX_PROJECT_ROOT, targetPath);\n}\n\nfunction shellQuote(value: string) {\n  return `'${value.replace(/'/g, `'\"'\"'`)}'`;\n}\n\nfunction formatSandboxError(error: unknown) {\n  if (!(error instanceof Error)) {\n    return String(error);\n  }\n\n  const details = [\n    \"response\" in error &&\n    typeof error.response === \"object\" &&\n    error.response &&\n    \"status\" in error.response &&\n    typeof error.response.status === \"number\"\n      ? `HTTP ${error.response.status}`\n      : null,\n    \"sandboxName\" in error && typeof error.sandboxName === \"string\"\n      ? `sandbox=${error.sandboxName}`\n      : null,\n    \"sessionId\" in error && typeof error.sessionId === \"string\"\n      ? `session=${error.sessionId}`\n      : null,\n    \"json\" in error && typeof error.json === \"object\" && error.json\n      ? `json=${JSON.stringify(error.json)}`\n      : null,\n    \"text\" in error && typeof error.text === \"string\" && error.text\n      ? `text=${error.text}`\n      : null,\n    error.message ? `message=${error.message}` : null,\n  ].filter(Boolean);\n\n  return details.join(\" | \") || error.message;\n}\n\nfunction createSandboxOperationError({\n  action,\n  error,\n  target,\n}: {\n  action: string;\n  error: unknown;\n  target: string;\n}) {\n  return new Error(\n    `Sandbox ${action} failed for ${target}. ${formatSandboxError(error)}`\n  );\n}\n\nfunction buildUvInstallCommand() {\n  return [\n    \"set -euo pipefail\",\n    \"if ! command -v curl >/dev/null 2>&1; then\",\n    \"  dnf install -y curl\",\n    \"fi\",\n    \"if ! command -v uv >/dev/null 2>&1; then\",\n    \"  curl --retry 1 --retry-delay 1 -LsSf https://astral.sh/uv/install.sh | env UV_UNMANAGED_INSTALL=/usr/local/bin sh\",\n    \"fi\",\n    \"uv --version\",\n  ].join(\"\\n\");\n}\n\nfunction buildPythonProjectBootstrapCommand() {\n  return [\n    \"set -euo pipefail\",\n    \"command -v uv >/dev/null 2>&1 || { echo 'uv is not available after sandbox bootstrap.' >&2; exit 127; }\",\n    \"if [ -f pyproject.toml ] && [ -f .python-version ] && [ -d .venv ]; then\",\n    \"  uv --version\",\n    \"  exit 0\",\n    \"fi\",\n    `uv python install ${VERCEL_SANDBOX_PYTHON_VERSION}`,\n    \"if [ ! -f pyproject.toml ]; then\",\n    `  uv init --bare --name ${VERCEL_SANDBOX_PYTHON_PROJECT_NAME} --python ${VERCEL_SANDBOX_PYTHON_VERSION}`,\n    \"fi\",\n    `uv python pin ${VERCEL_SANDBOX_PYTHON_VERSION}`,\n    `uv venv .venv --python ${VERCEL_SANDBOX_PYTHON_VERSION} --allow-existing`,\n    \"uv run python --version\",\n  ].join(\"\\n\");\n}\n\nasync function runSandboxBootstrapCommand({\n  action,\n  command,\n  sandbox,\n  sudo,\n}: {\n  action: string;\n  command: string;\n  sandbox: VercelSandboxHandle;\n  sudo?: boolean;\n}) {\n  let result: Awaited<ReturnType<VercelSandboxHandle[\"runCommand\"]>>;\n\n  try {\n    const commandOptions: Parameters<VercelSandboxHandle[\"runCommand\"]>[0] = {\n      args: [\"-lc\", command],\n      cmd: \"bash\",\n      cwd: VERCEL_SANDBOX_PROJECT_ROOT,\n    };\n\n    if (sudo) {\n      commandOptions.sudo = true;\n    }\n\n    result = await sandbox.runCommand(commandOptions);\n  } catch (error) {\n    throw createSandboxOperationError({\n      action,\n      error,\n      target: VERCEL_SANDBOX_PROJECT_ROOT,\n    });\n  }\n\n  if (result.exitCode === 0) {\n    return;\n  }\n\n  const stderr = (await result.stderr()).trim();\n  const stdout = (await result.stdout()).trim();\n\n  throw new Error(\n    `Sandbox ${action} failed for ${VERCEL_SANDBOX_PROJECT_ROOT}. ${stderr || stdout || command}`\n  );\n}\n\nasync function bootstrapSandboxPythonProject(sandbox: VercelSandboxHandle) {\n  await runSandboxBootstrapCommand({\n    action: \"uv install\",\n    command: buildUvInstallCommand(),\n    sandbox,\n    sudo: true,\n  });\n  await runSandboxBootstrapCommand({\n    action: \"Python project bootstrap\",\n    command: buildPythonProjectBootstrapCommand(),\n    sandbox,\n  });\n}\n\nasync function writeSandboxFile(\n  sandbox: VercelSandboxHandle,\n  resolvedPath: string,\n  content: string\n) {\n  try {\n    await sandbox.fs.mkdir(posixPath.dirname(resolvedPath), {\n      recursive: true,\n    });\n    await sandbox.fs.writeFile(resolvedPath, content, \"utf-8\");\n    return;\n  } catch (primaryError) {\n    const base64Content = Buffer.from(content, \"utf-8\").toString(\"base64\");\n    const fallbackCommand = [\n      `mkdir -p ${shellQuote(posixPath.dirname(resolvedPath))}`,\n      `base64 -d > ${shellQuote(resolvedPath)} <<'__SKILLS_AGENT_CONTENT__'`,\n      base64Content,\n      \"__SKILLS_AGENT_CONTENT__\",\n    ].join(\"\\n\");\n\n    let fallbackResult: Awaited<ReturnType<VercelSandboxHandle[\"runCommand\"]>>;\n\n    try {\n      fallbackResult = await sandbox.runCommand({\n        args: [\"-lc\", fallbackCommand],\n        cmd: \"bash\",\n        cwd: VERCEL_SANDBOX_PROJECT_ROOT,\n      });\n    } catch (fallbackError) {\n      throw new Error(\n        [\n          `Sandbox writeFile failed for ${resolvedPath}.`,\n          `File API error: ${formatSandboxError(primaryError)}`,\n          `Shell fallback error: ${formatSandboxError(fallbackError)}`,\n        ].join(\" \")\n      );\n    }\n\n    if (fallbackResult.exitCode === 0) {\n      return;\n    }\n\n    throw new Error(\n      [\n        \"Failed to write a sandbox file.\",\n        `File API error: ${formatSandboxError(primaryError)}`,\n        `Shell fallback error: ${(await fallbackResult.stderr()).trim() || \"unknown error\"}`,\n      ].join(\" \")\n    );\n  }\n}\n\nexport function createVercelSandboxSessionRegistry({\n  createSandbox,\n  workspaceRoot = VERCEL_SANDBOX_WORKSPACE_ROOT,\n}: {\n  createSandbox: VercelSandboxFactory;\n  workspaceRoot?: string;\n}): VercelSandboxSessionRegistry {\n  const entries = new Map<string, SessionEntry>();\n\n  const stopSession = async (sessionId: string) => {\n    const entry = entries.get(sessionId);\n\n    if (!entry) {\n      return;\n    }\n\n    if (!entry.sandboxPromise) {\n      entries.delete(sessionId);\n      return;\n    }\n\n    let sandbox: VercelSandboxHandle;\n\n    try {\n      sandbox = await entry.sandboxPromise;\n    } catch {\n      entries.delete(sessionId);\n      return;\n    }\n\n    await sandbox.stop();\n    entries.delete(sessionId);\n  };\n\n  const getOrCreateEntry = (sessionId: string) => {\n    const existingEntry = entries.get(sessionId);\n\n    if (existingEntry) {\n      return existingEntry;\n    }\n\n    const createdEntry: SessionEntry = {\n      sandboxPromise: null,\n      seededBaseWorkspace: false,\n    };\n    entries.set(sessionId, createdEntry);\n    return createdEntry;\n  };\n\n  const ensureSandbox = async (sessionId: string, entry: SessionEntry) => {\n    if (!entry.sandboxPromise) {\n      entry.sandboxPromise = (async () => {\n        let lastError: unknown;\n\n        for (let attempt = 0; attempt < 2; attempt += 1) {\n          try {\n            const sandbox = await createSandbox(sessionId);\n\n            if (!entry.seededBaseWorkspace) {\n              await sandbox.fs.mkdir(VERCEL_SANDBOX_PROJECT_ROOT, {\n                recursive: true,\n              });\n              await sandbox.fs.mkdir(VERCEL_SANDBOX_SKILLS_ROOT, {\n                recursive: true,\n              });\n              await sandbox.fs.mkdir(VERCEL_SANDBOX_ARTIFACTS_ROOT, {\n                recursive: true,\n              });\n\n              const agentsPath = path.join(\n                /*turbopackIgnore: true*/ workspaceRoot,\n                \"AGENTS.md\"\n              );\n\n              if (await pathExists(agentsPath)) {\n                await copyLocalPathToSandbox(\n                  sandbox,\n                  agentsPath,\n                  VERCEL_SANDBOX_AGENTS_FILE\n                );\n              }\n\n              await bootstrapSandboxPythonProject(sandbox);\n              entry.seededBaseWorkspace = true;\n            }\n\n            return sandbox;\n          } catch (error) {\n            lastError = error;\n            entry.seededBaseWorkspace = false;\n          }\n        }\n\n        throw lastError;\n      })().catch((error) => {\n        entry.sandboxPromise = null;\n        throw error;\n      });\n    }\n\n    return await entry.sandboxPromise;\n  };\n\n  return {\n    getSandbox(sessionId) {\n      const entry = getOrCreateEntry(sessionId);\n      return ensureSandbox(sessionId, entry);\n    },\n    getSession(sessionId) {\n      const entry = getOrCreateEntry(sessionId);\n\n      return {\n        artifactsRoot: VERCEL_SANDBOX_ARTIFACTS_ROOT,\n        projectRoot: VERCEL_SANDBOX_PROJECT_ROOT,\n        sessionId,\n        async pathExists(targetPath) {\n          const sandbox = await ensureSandbox(sessionId, entry);\n          return sandbox.fs.exists(resolveSandboxPath(targetPath));\n        },\n        async readFile(targetPath) {\n          const sandbox = await ensureSandbox(sessionId, entry);\n          const resolvedPath = resolveSandboxPath(targetPath);\n\n          try {\n            return await sandbox.fs.readFile(resolvedPath, \"utf-8\");\n          } catch (error) {\n            throw createSandboxOperationError({\n              action: \"readFile\",\n              error,\n              target: resolvedPath,\n            });\n          }\n        },\n        async runCommand(command) {\n          const sandbox = await ensureSandbox(sessionId, entry);\n          let result: Awaited<ReturnType<VercelSandboxHandle[\"runCommand\"]>>;\n\n          try {\n            result = await sandbox.runCommand({\n              args: [\"-lc\", command],\n              cmd: \"bash\",\n              cwd: VERCEL_SANDBOX_PROJECT_ROOT,\n            });\n          } catch (error) {\n            throw createSandboxOperationError({\n              action: \"bash\",\n              error,\n              target: command,\n            });\n          }\n\n          return {\n            command,\n            exitCode: result.exitCode,\n            stderr: await result.stderr(),\n            stdout: await result.stdout(),\n          };\n        },\n        async stop() {\n          await stopSession(sessionId);\n        },\n        async writeFile(targetPath, content) {\n          const sandbox = await ensureSandbox(sessionId, entry);\n          const resolvedPath = resolveSandboxPath(targetPath);\n\n          await writeSandboxFile(sandbox, resolvedPath, content);\n\n          return {\n            bytes: Buffer.byteLength(content, \"utf-8\"),\n            path: resolvedPath,\n          };\n        },\n      };\n    },\n    stopSession,\n  };\n}\n\nlet sharedRegistry: VercelSandboxSessionRegistry | null = null;\n\nexport function getSharedVercelSandboxSessionRegistry(\n  env: SkillsAgentEnv = getSkillsAgentEnv()\n) {\n  sharedRegistry ??= createVercelSandboxSessionRegistry({\n    createSandbox: (sessionId) => createVercelSandbox(sessionId, env),\n  });\n\n  return sharedRegistry;\n}\n",
      "type": "registry:lib",
      "target": "@lib/ultra-chatbot-agent/skills-agent/server/vercel-sandbox.ts"
    },
    {
      "path": "registry/ultra-chatbot-agent/lib/ultra-chatbot-agent/skills-agent/server/workspace.ts",
      "content": "import { readFile } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { getSkillsAgentEnv } from \"./env\";\nimport { SKILLS_AGENT_WORKSPACE_ROOT } from \"./local-skill-catalog\";\nimport {\n  createSkillsAgentOfficialTools,\n  type SkillsAgentOfficialTools,\n} from \"./official-tools\";\nimport {\n  getSharedSkillsAgentSessionRegistry,\n  type SkillsAgentSession,\n} from \"./sandbox\";\nimport type { SkillMetadata } from \"./skill-catalog\";\n\ntype DemoEnv = Record<string, string | undefined>;\n\nexport interface VisibleSkillMetadata {\n  description: string;\n  name: string;\n  path: string;\n}\n\nexport interface SkillsAgentWorkspace {\n  readonly artifactsRoot: string;\n  readonly projectRoot: string;\n  readonly session: SkillsAgentSession;\n  readonly toolset: SkillsAgentOfficialTools;\n  readonly visibleSkillCatalog: VisibleSkillMetadata[];\n  readonly visibleSkillCatalogText: string;\n}\n\nexport const SKILLS_AGENT_AGENTS_PATH = path.join(\n  SKILLS_AGENT_WORKSPACE_ROOT,\n  \"AGENTS.md\"\n);\nexport const SKILLS_AGENT_SKILLS_DIRECTORY = path.join(\n  SKILLS_AGENT_WORKSPACE_ROOT,\n  \".agents/skills\"\n);\nconst defaultAgentsContent = [\n  \"# Skills Agent Consumer Workspace\",\n  \"\",\n  \"This project can install repo-local skills under `.agents/skills`.\",\n  \"Use loaded skill instructions as the source of truth, keep generated drafts under `artifacts/` unless a skill names a canonical path, and inspect optional repository files before reading them.\",\n].join(\"\\n\");\n\nfunction isMissingFileError(error: unknown) {\n  return (\n    typeof error === \"object\" &&\n    error !== null &&\n    \"code\" in error &&\n    error.code === \"ENOENT\"\n  );\n}\n\nasync function readAgentsContent(readAgentsFile: typeof readFile) {\n  try {\n    return await readAgentsFile(SKILLS_AGENT_AGENTS_PATH, \"utf-8\");\n  } catch (error) {\n    if (isMissingFileError(error)) {\n      return defaultAgentsContent;\n    }\n\n    throw error;\n  }\n}\n\nexport function toVisibleSkillCatalog(\n  skills: SkillMetadata[]\n): VisibleSkillMetadata[] {\n  return skills.map(({ description, name, path }) => ({\n    description,\n    name,\n    path,\n  }));\n}\n\nexport function formatVisibleSkillCatalog(skills: VisibleSkillMetadata[]) {\n  return skills\n    .map(\n      (skill) => `- ${skill.name}: ${skill.description} (path: ${skill.path})`\n    )\n    .join(\"\\n\");\n}\n\nexport async function createSkillsAgentWorkspace(\n  {\n    env = getSkillsAgentEnv(),\n    sessionId,\n    skills,\n  }: {\n    env?: DemoEnv;\n    sessionId: string;\n    skills: SkillMetadata[];\n  },\n  dependencies: {\n    createOfficialTools?: typeof createSkillsAgentOfficialTools;\n    readAgentsFile?: typeof readFile;\n  } = {}\n): Promise<SkillsAgentWorkspace> {\n  const readAgentsFile = dependencies.readAgentsFile ?? readFile;\n  const createOfficialTools =\n    dependencies.createOfficialTools ?? createSkillsAgentOfficialTools;\n  const session = (\n    await getSharedSkillsAgentSessionRegistry(env)\n  ).getSession(sessionId);\n  const visibleSkillCatalog = toVisibleSkillCatalog(skills);\n  const agentsContent = await readAgentsContent(readAgentsFile);\n  await session.writeFile(\"AGENTS.md\", agentsContent);\n  const toolset = await createOfficialTools({\n    agentsContent,\n    projectRoot: session.projectRoot,\n    session,\n    skills,\n  });\n\n  return {\n    artifactsRoot: session.artifactsRoot,\n    projectRoot: session.projectRoot,\n    session,\n    toolset,\n    visibleSkillCatalog,\n    visibleSkillCatalogText: formatVisibleSkillCatalog(visibleSkillCatalog),\n  };\n}\n",
      "type": "registry:lib",
      "target": "@lib/ultra-chatbot-agent/skills-agent/server/workspace.ts"
    }
  ],
  "envVars": {
    "AI_GATEWAY_API_KEY": "",
    "AI_GATEWAY_BASE_URL": "https://ai-gateway.vercel.sh/v3/ai",
    "AI_GATEWAY_CHAT_MODEL": "openai/gpt-5-mini",
    "AI_GATEWAY_EMBEDDING_MODEL": "openai/text-embedding-3-small",
    "BLOB_READ_WRITE_TOKEN": "",
    "CRON_SECRET": "",
    "DATABASE_URL": "",
    "REDIS_URL": "",
    "VERCEL_OIDC_TOKEN": "",
    "VERCEL_PROJECT_ID": "",
    "VERCEL_TEAM_ID": "",
    "VERCEL_TOKEN": ""
  },
  "docs": "Install into a Next.js App Router project initialized with shadcn/ui. This item adds the complete Ultra Chatbot Agent page, route-backed APIs, model selector, history, votes, document artifacts, Blob upload route, RAG-backed knowledge search, Project Docs MCP route, gated sandbox tools, cleanup cron, Drizzle schema, and setup notes. Configure AI Gateway, Postgres, and Redis for the main chat happy path; add Blob and Vercel Sandbox credentials for those capability families.",
  "type": "registry:block"
}
