@tangle-network/agent-app 0.45.31 → 0.45.33
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{DesignCanvas-RSWQCXM3.js → DesignCanvas-EODMDQMT.js} +2 -2
- package/dist/{DesignCanvasEditor-XZMGIST7.js → DesignCanvasEditor-DRRZ6ZD3.js} +4 -4
- package/dist/{InvitationsPanel-CJ7DR4F7.js → InvitationsPanel-BATXZHY4.js} +2 -2
- package/dist/{MembersPanel-BB4OD2KB.js → MembersPanel-XNLF3SA4.js} +2 -2
- package/dist/assistant/index.js +11 -10
- package/dist/assistant/index.js.map +1 -1
- package/dist/chat-react/index.js +1 -1
- package/dist/{chunk-YIQHOAHN.js → chunk-3UBAO3N5.js} +3 -3
- package/dist/{chunk-YIQHOAHN.js.map → chunk-3UBAO3N5.js.map} +1 -1
- package/dist/{chunk-PURHUAOR.js → chunk-556RB3XI.js} +5 -5
- package/dist/chunk-556RB3XI.js.map +1 -0
- package/dist/{chunk-NEJ7OMQS.js → chunk-5LC2VXH5.js} +102 -4
- package/dist/chunk-5LC2VXH5.js.map +1 -0
- package/dist/{chunk-HZ4WCA37.js → chunk-5MZXWPFN.js} +9 -9
- package/dist/chunk-5MZXWPFN.js.map +1 -0
- package/dist/{chunk-FNM7JYME.js → chunk-5N434BGF.js} +17 -17
- package/dist/chunk-5N434BGF.js.map +1 -0
- package/dist/{chunk-5SXS3YAB.js → chunk-7MV23XBW.js} +2 -2
- package/dist/chunk-7MV23XBW.js.map +1 -0
- package/dist/{chunk-S564OFTL.js → chunk-F4XLTVQ3.js} +3 -3
- package/dist/{chunk-S564OFTL.js.map → chunk-F4XLTVQ3.js.map} +1 -1
- package/dist/{chunk-LSQR6HM6.js → chunk-IN6LNZ7O.js} +35 -34
- package/dist/chunk-IN6LNZ7O.js.map +1 -0
- package/dist/{chunk-L7FHRORY.js → chunk-SFSR3CAH.js} +180 -40
- package/dist/chunk-SFSR3CAH.js.map +1 -0
- package/dist/{chunk-QGOU3VXH.js → chunk-UXRJAUJL.js} +3 -3
- package/dist/{chunk-IFYJDX3J.js → chunk-XHD4Y54K.js} +47 -46
- package/dist/chunk-XHD4Y54K.js.map +1 -0
- package/dist/design-canvas-react/index.js +8 -8
- package/dist/design-canvas-react/index.js.map +1 -1
- package/dist/design-canvas-react/lazy.js +1 -1
- package/dist/spend/cli.js +1 -1
- package/dist/spend/index.d.ts +212 -7
- package/dist/spend/index.js +5 -1
- package/dist/spend/index.js.map +1 -1
- package/dist/studio-react/index.js +27 -27
- package/dist/studio-react/index.js.map +1 -1
- package/dist/teams-react/index.js +2 -2
- package/dist/teams-react/lazy.js +2 -2
- package/dist/theme/index.js +1 -1
- package/dist/theme/tailwind-preset.d.ts +5 -1
- package/dist/theme/tailwind-preset.js +11 -2
- package/dist/theme/tailwind-preset.js.map +1 -1
- package/dist/theme/tokens.css +126 -79
- package/dist/web-react/async/index.js +1 -1
- package/dist/web-react/index.d.ts +60 -2
- package/dist/web-react/index.js +13 -3
- package/dist/work-product-react/index.js +3 -3
- package/dist/work-product-react/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-5SXS3YAB.js.map +0 -1
- package/dist/chunk-FNM7JYME.js.map +0 -1
- package/dist/chunk-HZ4WCA37.js.map +0 -1
- package/dist/chunk-IFYJDX3J.js.map +0 -1
- package/dist/chunk-L7FHRORY.js.map +0 -1
- package/dist/chunk-LSQR6HM6.js.map +0 -1
- package/dist/chunk-NEJ7OMQS.js.map +0 -1
- package/dist/chunk-PURHUAOR.js.map +0 -1
- /package/dist/{DesignCanvas-RSWQCXM3.js.map → DesignCanvas-EODMDQMT.js.map} +0 -0
- /package/dist/{DesignCanvasEditor-XZMGIST7.js.map → DesignCanvasEditor-DRRZ6ZD3.js.map} +0 -0
- /package/dist/{InvitationsPanel-CJ7DR4F7.js.map → InvitationsPanel-BATXZHY4.js.map} +0 -0
- /package/dist/{MembersPanel-BB4OD2KB.js.map → MembersPanel-XNLF3SA4.js.map} +0 -0
- /package/dist/{chunk-QGOU3VXH.js.map → chunk-UXRJAUJL.js.map} +0 -0
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/web-react/provider-logo.tsx","../src/web-react/controls.tsx","../src/web-react/use-composer-attachments.ts","../src/web-react/agent-session-controls.tsx"],"sourcesContent":["/**\n * Provider brand marks — real logo path data (simple-icons / SVG Logos, both\n * CC0) inlined so the picker shows actual provider identity instead of\n * colored-initial monograms. Providers without a usable mark fall back to a\n * tinted monogram chip. Aliases (z-ai/zai, moonshot/moonshotai, deepseek_ai)\n * normalize to one entry.\n */\n\nimport type { ReactNode } from 'react'\n\ninterface LogoDef {\n viewBox: string\n fill: string\n paths: string[]\n}\n\nconst LOGOS: Record<string, LogoDef> = {\n anthropic: { viewBox: '0 0 24 24', fill: '#D97757', paths: [\"M17.3041 3.541h-3.6718l6.696 16.918H24Zm-10.6082 0L0 20.459h3.7442l1.3693-3.5527h7.0052l1.3693 3.5528h3.7442L10.5363 3.5409Zm-.3712 10.2232 2.2914-5.9456 2.2914 5.9456Z\"] },\n google: { viewBox: '0 0 24 24', fill: '#8E75B2', paths: [\"M11.04 19.32Q12 21.51 12 24q0-2.49.93-4.68.96-2.19 2.58-3.81t3.81-2.55Q21.51 12 24 12q-2.49 0-4.68-.93a12.3 12.3 0 0 1-3.81-2.58 12.3 12.3 0 0 1-2.58-3.81Q12 2.49 12 0q0 2.49-.96 4.68-.93 2.19-2.55 3.81a12.3 12.3 0 0 1-3.81 2.58Q2.49 12 0 12q2.49 0 4.68.96 2.19.93 3.81 2.55t2.55 3.81\"] },\n deepseek: { viewBox: '0 0 24 24', fill: '#4D6BFE', paths: [\"M23.748 4.651c-.254-.124-.364.113-.512.233-.051.04-.094.09-.137.137-.372.397-.806.657-1.373.626-.829-.046-1.537.214-2.163.848-.133-.782-.575-1.248-1.247-1.548-.352-.155-.708-.311-.955-.65-.172-.24-.219-.509-.305-.774-.055-.16-.11-.323-.293-.35-.2-.031-.278.136-.356.276-.313.572-.434 1.202-.422 1.84.027 1.436.633 2.58 1.838 3.393.137.094.172.187.129.323-.082.28-.18.553-.266.833-.055.179-.137.218-.328.14a5.5 5.5 0 0 1-1.737-1.179c-.857-.828-1.631-1.743-2.597-2.46a12 12 0 0 0-.689-.47c-.985-.957.13-1.743.387-1.836.27-.098.094-.433-.778-.428-.872.003-1.67.295-2.687.685a3 3 0 0 1-.465.136 9.6 9.6 0 0 0-2.883-.101c-1.885.21-3.39 1.1-4.497 2.622C.082 8.776-.231 10.854.152 13.02c.403 2.284 1.568 4.175 3.36 5.653 1.857 1.533 3.997 2.284 6.438 2.14 1.482-.085 3.132-.284 4.994-1.86.47.234.962.328 1.78.398.629.058 1.235-.031 1.705-.129.735-.155.684-.836.418-.961-2.155-1.004-1.682-.595-2.112-.926 1.095-1.295 2.768-3.598 3.284-6.733.05-.346.115-.834.108-1.114-.004-.171.035-.238.23-.257a4.2 4.2 0 0 0 1.545-.475c1.397-.763 1.96-2.016 2.093-3.517.02-.23-.004-.467-.247-.588M11.58 18.168c-2.088-1.642-3.101-2.183-3.52-2.16-.39.024-.32.472-.234.763.09.288.207.487.371.74.114.167.192.416-.113.603-.673.416-1.842-.14-1.897-.168-1.361-.801-2.5-1.86-3.301-3.306-.775-1.393-1.225-2.888-1.299-4.482-.02-.385.094-.522.477-.592a4.7 4.7 0 0 1 1.53-.038c2.131.311 3.946 1.264 5.467 2.774.868.86 1.525 1.887 2.202 2.89.72 1.066 1.494 2.082 2.48 2.915.348.291.626.513.892.677-.802.09-2.14.109-3.055-.615zm1.001-6.44a.306.306 0 0 1 .415-.287.3.3 0 0 1 .113.074.3.3 0 0 1 .086.214c0 .17-.136.307-.308.307a.303.303 0 0 1-.306-.307m3.11 1.596c-.2.081-.4.151-.591.16a1.25 1.25 0 0 1-.798-.254c-.274-.23-.47-.358-.551-.758a1.7 1.7 0 0 1 .015-.588c.07-.327-.007-.537-.238-.727-.188-.156-.426-.199-.689-.199a.6.6 0 0 1-.254-.078.253.253 0 0 1-.114-.358 1 1 0 0 1 .192-.21c.356-.202.767-.136 1.146.016.352.144.618.408 1.001.782.392.451.462.576.685.915.176.264.336.536.446.848.066.194-.02.353-.25.45\"] },\n mistral: { viewBox: '0 0 24 24', fill: '#FA520F', paths: [\"M17.143 3.429v3.428h-3.429v3.429h-3.428V6.857H6.857V3.43H3.43v13.714H0v3.428h10.286v-3.428H6.857v-3.429h3.429v3.429h3.429v-3.429h3.428v3.429h-3.428v3.428H24v-3.428h-3.43V3.429z\"] },\n xai: { viewBox: '0 0 24 24', fill: '#000000', paths: [\"M14.234 10.162 22.977 0h-2.072l-7.591 8.824L7.251 0H.258l9.168 13.343L.258 24H2.33l8.016-9.318L16.749 24h6.993zm-2.837 3.299-.929-1.329L3.076 1.56h3.182l5.965 8.532.929 1.329 7.754 11.09h-3.182z\"] },\n nvidia: { viewBox: '0 0 24 24', fill: '#76B900', paths: [\"M8.948 8.798v-1.43a6.7 6.7 0 0 1 .424-.018c3.922-.124 6.493 3.374 6.493 3.374s-2.774 3.851-5.75 3.851c-.398 0-.787-.062-1.158-.185v-4.346c1.528.185 1.837.857 2.747 2.385l2.04-1.714s-1.492-1.952-4-1.952a6.016 6.016 0 0 0-.796.035m0-4.735v2.138l.424-.027c5.45-.185 9.01 4.47 9.01 4.47s-4.08 4.964-8.33 4.964c-.37 0-.733-.035-1.095-.097v1.325c.3.035.61.062.91.062 3.957 0 6.82-2.023 9.593-4.408.459.371 2.34 1.263 2.73 1.652-2.633 2.208-8.772 3.984-12.253 3.984-.335 0-.653-.018-.971-.053v1.864H24V4.063zm0 10.326v1.131c-3.657-.654-4.673-4.46-4.673-4.46s1.758-1.944 4.673-2.262v1.237H8.94c-1.528-.186-2.73 1.245-2.73 1.245s.68 2.412 2.739 3.11M2.456 10.9s2.164-3.197 6.5-3.533V6.201C4.153 6.59 0 10.653 0 10.653s2.35 6.802 8.948 7.42v-1.237c-4.84-.6-6.492-5.936-6.492-5.936z\"] },\n meta: { viewBox: '0 0 24 24', fill: '#0467DF', paths: [\"M6.915 4.03c-1.968 0-3.683 1.28-4.871 3.113C.704 9.208 0 11.883 0 14.449c0 .706.07 1.369.21 1.973a6.624 6.624 0 0 0 .265.86 5.297 5.297 0 0 0 .371.761c.696 1.159 1.818 1.927 3.593 1.927 1.497 0 2.633-.671 3.965-2.444.76-1.012 1.144-1.626 2.663-4.32l.756-1.339.186-.325c.061.1.121.196.183.3l2.152 3.595c.724 1.21 1.665 2.556 2.47 3.314 1.046.987 1.992 1.22 3.06 1.22 1.075 0 1.876-.355 2.455-.843a3.743 3.743 0 0 0 .81-.973c.542-.939.861-2.127.861-3.745 0-2.72-.681-5.357-2.084-7.45-1.282-1.912-2.957-2.93-4.716-2.93-1.047 0-2.088.467-3.053 1.308-.652.57-1.257 1.29-1.82 2.05-.69-.875-1.335-1.547-1.958-2.056-1.182-.966-2.315-1.303-3.454-1.303zm10.16 2.053c1.147 0 2.188.758 2.992 1.999 1.132 1.748 1.647 4.195 1.647 6.4 0 1.548-.368 2.9-1.839 2.9-.58 0-1.027-.23-1.664-1.004-.496-.601-1.343-1.878-2.832-4.358l-.617-1.028a44.908 44.908 0 0 0-1.255-1.98c.07-.109.141-.224.211-.327 1.12-1.667 2.118-2.602 3.358-2.602zm-10.201.553c1.265 0 2.058.791 2.675 1.446.307.327.737.871 1.234 1.579l-1.02 1.566c-.757 1.163-1.882 3.017-2.837 4.338-1.191 1.649-1.81 1.817-2.486 1.817-.524 0-1.038-.237-1.383-.794-.263-.426-.464-1.13-.464-2.046 0-2.221.63-4.535 1.66-6.088.454-.687.964-1.226 1.533-1.533a2.264 2.264 0 0 1 1.088-.285z\"] },\n moonshotai: { viewBox: '0 0 24 24', fill: '#16191E', paths: [\"m1.053 16.91 9.538 2.55a21 20.981 0 0 0 .06 2.031l5.956 1.592a12 11.99 0 0 1-15.554-6.172m-1.02-5.79 11.352 3.035a21 20.981 0 0 0-.469 2.01l10.817 2.89a12 11.99 0 0 1-1.845 2.004L.658 15.918a12 11.99 0 0 1-.625-4.796m1.593-5.146L13.573 9.17a21 20.981 0 0 0-1.01 1.874l11.297 3.02a21 20.981 0 0 1-.67 2.362l-11.55-3.087L.125 10.26a12 11.99 0 0 1 1.499-4.285ZM6.067 1.58l11.285 3.016a21 20.981 0 0 0-1.688 1.719l7.824 2.091a21 20.981 0 0 1 .513 2.664L2.107 5.218a12 11.99 0 0 1 3.96-3.638M21.68 4.866 7.222 1.003A12 11.99 0 0 1 21.68 4.866\"] },\n openai: { viewBox: '0 0 256 260', fill: '#10A37F', paths: [\"M239.184 106.203a64.72 64.72 0 0 0-5.576-53.103C219.452 28.459 191 15.784 163.213 21.74A65.586 65.586 0 0 0 52.096 45.22a64.72 64.72 0 0 0-43.23 31.36c-14.31 24.602-11.061 55.634 8.033 76.74a64.67 64.67 0 0 0 5.525 53.102c14.174 24.65 42.644 37.324 70.446 31.36a64.72 64.72 0 0 0 48.754 21.744c28.481.025 53.714-18.361 62.414-45.481a64.77 64.77 0 0 0 43.229-31.36c14.137-24.558 10.875-55.423-8.083-76.483m-97.56 136.338a48.4 48.4 0 0 1-31.105-11.255l1.535-.87l51.67-29.825a8.6 8.6 0 0 0 4.247-7.367v-72.85l21.845 12.636c.218.111.37.32.409.563v60.367c-.056 26.818-21.783 48.545-48.601 48.601M37.158 197.93a48.35 48.35 0 0 1-5.781-32.589l1.534.921l51.722 29.826a8.34 8.34 0 0 0 8.441 0l63.181-36.425v25.221a.87.87 0 0 1-.358.665l-52.335 30.184c-23.257 13.398-52.97 5.431-66.404-17.803M23.549 85.38a48.5 48.5 0 0 1 25.58-21.333v61.39a8.29 8.29 0 0 0 4.195 7.316l62.874 36.272l-21.845 12.636a.82.82 0 0 1-.767 0L41.353 151.53c-23.211-13.454-31.171-43.144-17.804-66.405zm179.466 41.695l-63.08-36.63L161.73 77.86a.82.82 0 0 1 .768 0l52.233 30.184a48.6 48.6 0 0 1-7.316 87.635v-61.391a8.54 8.54 0 0 0-4.4-7.213m21.742-32.69l-1.535-.922l-51.619-30.081a8.39 8.39 0 0 0-8.492 0L99.98 99.808V74.587a.72.72 0 0 1 .307-.665l52.233-30.133a48.652 48.652 0 0 1 72.236 50.391zM88.061 139.097l-21.845-12.585a.87.87 0 0 1-.41-.614V65.685a48.652 48.652 0 0 1 79.757-37.346l-1.535.87l-51.67 29.825a8.6 8.6 0 0 0-4.246 7.367zm11.868-25.58L128.067 97.3l28.188 16.218v32.434l-28.086 16.218l-28.188-16.218z\"] },\n}\n\nconst ALIASES: Record<string, string> = {\n moonshot: 'moonshotai',\n deepseek_ai: 'deepseek',\n 'x-ai': 'xai',\n 'meta-llama': 'meta',\n}\n\nconst MONOGRAM: Record<string, { bg: string; fg: string }> = {\n cohere: { bg: '#fae8ff', fg: '#c026d3' },\n groq: { bg: '#fce7f3', fg: '#db2777' },\n cerebras: { bg: '#ccfbf1', fg: '#0d9488' },\n zai: { bg: '#ede9fe', fg: '#7c3aed' },\n 'z-ai': { bg: '#ede9fe', fg: '#7c3aed' },\n tuner: { bg: '#dbeafe', fg: '#2563eb' },\n}\n\nexport interface ProviderLogoProps {\n provider?: string\n size?: number\n}\n\n/** Real brand mark when we have one; tinted monogram otherwise. */\nexport function ProviderLogo({ provider, size = 16 }: ProviderLogoProps): ReactNode {\n const key = ALIASES[provider ?? ''] ?? provider ?? ''\n const logo = LOGOS[key]\n if (logo) {\n return (\n <svg width={size} height={size} viewBox={logo.viewBox} role=\"img\" aria-label={key}>\n {logo.paths.map((d, i) => (\n <path key={i} d={d} fill={logo.fill} />\n ))}\n </svg>\n )\n }\n const mono = MONOGRAM[key] ?? { bg: '#f3f4f6', fg: '#6b7280' }\n return (\n <svg width={size} height={size} viewBox=\"0 0 16 16\" role=\"img\" aria-label={key || 'model'}>\n <rect width=\"16\" height=\"16\" rx=\"4\" fill={mono.bg} />\n <text x=\"8\" y=\"11.6\" textAnchor=\"middle\" fill={mono.fg} fontSize=\"9\" fontWeight=\"700\" fontFamily=\"system-ui, sans-serif\">\n {(key || '?').charAt(0).toUpperCase()}\n </text>\n </svg>\n )\n}\n","/**\n * Shared chat-shell control primitives — the LEAF that both the web-react barrel\n * (`./index`) and the composer children (`./agent-session-controls`,\n * `./seat-paywall`) import directly, so neither child has to reach back through\n * the barrel (which would re-create an import cycle). The barrel re-exports the\n * public names (`usePopover`, `usePending`, `ModelPicker`, `EffortPicker`, …)\n * unchanged, so the published export surface is identical.\n *\n * Styling contract matches the rest of `web-react`: Tailwind classes against the\n * shared design tokens; the glyphs are inline SVGs, no icon-library dependency.\n */\n\nimport { useEffect, useMemo, useRef, useState, type ReactNode } from 'react'\nimport { ProviderLogo } from './provider-logo'\nimport type { CatalogModel } from '../runtime/model-catalog'\n\n// ── shared glyphs (no icon-library dependency) ────────────────────────────\n\nexport function ChevronDown({ className }: { className?: string }) {\n return (\n <svg className={className} viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden>\n <path d=\"m6 9 6 6 6-6\" />\n </svg>\n )\n}\n\nfunction SearchGlyph({ className }: { className?: string }) {\n return (\n <svg className={className} viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden>\n <circle cx=\"11\" cy=\"11\" r=\"8\" />\n <path d=\"m21 21-4.3-4.3\" />\n </svg>\n )\n}\n\nfunction SparkleGlyph({ className }: { className?: string }) {\n return (\n <svg className={className} viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden>\n <path d=\"M12 3v3m0 12v3M3 12h3m12 0h3M5.6 5.6l2.1 2.1m8.6 8.6 2.1 2.1m0-12.8-2.1 2.1M7.7 16.3l-2.1 2.1\" />\n </svg>\n )\n}\n\n/**\n * Keyboard + pointer model for a trigger-and-popover pair, dependency-free.\n * Outside-mousedown and Escape both close; Escape also returns focus to the\n * trigger so keyboard users aren't dropped at the top of the document. The\n * returned `triggerProps` carry the ARIA contract (`aria-haspopup`/\n * `aria-expanded`); spread them onto the trigger button.\n */\nexport function usePopover(open: boolean, setOpen: (open: boolean) => void) {\n const containerRef = useRef<HTMLDivElement>(null)\n const triggerRef = useRef<HTMLButtonElement>(null)\n\n useEffect(() => {\n if (!open) return\n function onMouseDown(e: MouseEvent) {\n if (containerRef.current && !containerRef.current.contains(e.target as Node)) setOpen(false)\n }\n function onKeyDown(e: KeyboardEvent) {\n if (e.key === 'Escape') {\n setOpen(false)\n triggerRef.current?.focus()\n }\n }\n document.addEventListener('mousedown', onMouseDown)\n document.addEventListener('keydown', onKeyDown)\n return () => {\n document.removeEventListener('mousedown', onMouseDown)\n document.removeEventListener('keydown', onKeyDown)\n }\n }, [open, setOpen])\n\n return {\n containerRef,\n triggerRef,\n triggerProps: {\n ref: triggerRef,\n 'aria-haspopup': true as const,\n 'aria-expanded': open,\n },\n }\n}\n\n/**\n * Focus treatment for a row inside a popover panel.\n *\n * The ring itself now comes from the `:focus-visible` floor in tokens.css, so\n * this no longer restates a width or a colour. What it still has to say is\n * WHERE the ring is drawn: a popover option is a full-width row inside a panel\n * that clips its own corners (`overflow-hidden rounded-xl`), and an outward\n * ring on the first or last row is clipped away by that panel. Pulling the\n * offset negative draws the same ring just inside the row instead.\n */\nexport const POPOVER_OPTION_FOCUS = 'focus-visible:[outline-offset:-2px]'\n\n/**\n * Guard an async action against double-submit. `run` ignores re-entrant calls\n * while a promise is in flight and flips `pending` so the caller can disable\n * the control — the fix for double-charge / double-approve on a slow network.\n * Settles (success or throw) before clearing, and no-ops state updates after\n * unmount.\n */\nexport function usePending(): { pending: boolean; run: (action: () => void | Promise<void>) => void } {\n const [pending, setPending] = useState(false)\n const inFlight = useRef(false)\n const mounted = useRef(true)\n useEffect(() => {\n mounted.current = true\n return () => {\n mounted.current = false\n }\n }, [])\n const run = (action: () => void | Promise<void>) => {\n if (inFlight.current) return\n let result: void | Promise<void>\n try {\n result = action()\n } catch {\n return\n }\n if (!(result instanceof Promise)) return\n inFlight.current = true\n setPending(true)\n void result.finally(() => {\n inFlight.current = false\n if (mounted.current) setPending(false)\n })\n }\n return { pending, run }\n}\n\n// ── ModelPicker ───────────────────────────────────────────────────────────\n\nexport interface ModelPickerProps {\n value: string\n onChange: (id: string) => void\n /** Catalogue models — from `GET`ing the app's catalogue route (see\n * `runtime/model-catalog`), plus any product-specific entries appended. */\n models: CatalogModel[]\n loading?: boolean\n /** Render a provider logo/badge; default is a generic sparkle. */\n renderProviderBadge?: (provider: string) => ReactNode\n /** Section label for `featured` models. */\n recommendedLabel?: string\n /** Pin a labeled section to the TOP of the list (above Recommended) for the\n * models a product wants surfaced first — e.g. a tuner app's own fine-tuned\n * models (`{ label: 'Your Fine-Tuned Models', match: (m) => m.provider === 'tuner' }`).\n * Matching models are shown only in this section, not duplicated below. */\n priorityGroup?: {\n label: string\n match: (model: CatalogModel) => boolean\n }\n}\n\nfunction formatPrice(p?: string): string | undefined {\n if (!p) return undefined\n const n = Number(p)\n if (isNaN(n) || n === 0) return undefined\n const perM = n * 1_000_000\n return perM >= 1 ? `$${perM.toFixed(0)}/M` : `$${perM.toFixed(2)}/M`\n}\n\nfunction formatContext(len?: number): string | undefined {\n if (!len) return undefined\n if (len >= 1_000_000) return `${(len / 1_000_000).toFixed(1)}M ctx`\n if (len >= 1_000) return `${Math.round(len / 1_000)}K ctx`\n return `${len} ctx`\n}\n\nfunction SectionHeader({ children }: { children: ReactNode }) {\n return (\n <div className=\"px-3 pb-1 pt-3 text-xs font-semibold uppercase tracking-wide text-muted-foreground\">\n {children}\n </div>\n )\n}\n\nfunction ModelRow({\n model,\n selected,\n onSelect,\n renderProviderBadge,\n}: {\n model: CatalogModel\n selected: boolean\n onSelect: () => void\n renderProviderBadge?: (provider: string) => ReactNode\n}) {\n const price = formatPrice(model.pricing?.prompt)\n const ctx = formatContext(model.contextLength)\n return (\n <button\n type=\"button\"\n onClick={onSelect}\n className={`flex w-full items-center gap-2.5 rounded-md px-3 py-2.5 text-left text-sm transition ${POPOVER_OPTION_FOCUS} ${\n selected ? 'bg-primary/10 font-medium' : 'hover:bg-accent/30'\n }`}\n >\n {renderProviderBadge ? renderProviderBadge(model.provider) : <ProviderLogo provider={model.provider} size={16} />}\n <span className=\"truncate\">{model.name}</span>\n {!model.supportsTools && (\n <span className=\"shrink-0 rounded bg-muted/60 px-1.5 py-0.5 text-[10px] font-medium text-muted-foreground\">\n no tools\n </span>\n )}\n <span className=\"ml-auto flex shrink-0 items-center gap-2 text-xs text-muted-foreground\">\n {ctx && <span>{ctx}</span>}\n {price && <span>{price}</span>}\n </span>\n </button>\n )\n}\n\n/**\n * Searchable model picker pill + popover: a featured/recommended section\n * first, then per-provider groups in catalogue order (the server already\n * sorts providers by tier).\n *\n * This is the CANONICAL ecosystem model picker (see \"UI chrome ownership\n * (picker canon)\" in AGENTS.md). sandbox-ui's `dashboard/ModelPicker` is\n * legacy — deprecated, frozen, removed at sandbox-ui's next major; new code\n * belongs here.\n */\nexport function ModelPicker({ value, onChange, models, loading, renderProviderBadge, recommendedLabel = 'Recommended', priorityGroup }: ModelPickerProps) {\n const [open, setOpen] = useState(false)\n const [query, setQuery] = useState('')\n const { containerRef, triggerProps } = usePopover(open, setOpen)\n const inputRef = useRef<HTMLInputElement>(null)\n const popoverRef = useRef<HTMLDivElement>(null)\n\n useEffect(() => {\n if (open) inputRef.current?.focus()\n }, [open])\n\n // Keep the wide popover inside the viewport. It is left-anchored under the\n // trigger, so inside a narrow RIGHT-docked shell (the assistant drawer near\n // its minimum width) the 420px card runs past the window's right edge and\n // the price/context column clips. Shift it left just enough to fit — a no-op\n // whenever it already fits (the common case), so anchoring is unchanged.\n useEffect(() => {\n if (!open) return\n const el = popoverRef.current\n if (!el) return\n const overflowRight = el.getBoundingClientRect().right - (window.innerWidth - 16)\n el.style.transform = overflowRight > 0 ? `translateX(-${Math.ceil(overflowRight)}px)` : ''\n }, [open])\n\n const selected = models.find((m) => m.id === value)\n\n const filtered = useMemo(() => {\n const q = query.trim().toLowerCase()\n if (!q) return null\n return models.filter(\n (m) =>\n m.id.toLowerCase().includes(q) ||\n m.name.toLowerCase().includes(q) ||\n (m.description?.toLowerCase() ?? '').includes(q) ||\n m.provider.toLowerCase().includes(q),\n )\n }, [models, query])\n\n const sections = useMemo(() => {\n const isPriority = priorityGroup ? (m: CatalogModel) => priorityGroup.match(m) : () => false\n const priority = priorityGroup ? models.filter(isPriority) : []\n const recommended = models.filter((m) => m.featured && !isPriority(m))\n const byProvider: Array<{ provider: string; items: CatalogModel[] }> = []\n for (const m of models) {\n if (m.featured || isPriority(m)) continue\n const last = byProvider[byProvider.length - 1]\n if (last && last.provider === m.provider) last.items.push(m)\n else byProvider.push({ provider: m.provider, items: [m] })\n }\n return { priority, recommended, byProvider }\n }, [models, priorityGroup])\n\n const select = (id: string) => {\n onChange(id)\n setOpen(false)\n setQuery('')\n }\n\n return (\n <div ref={containerRef} className=\"relative inline-flex\">\n <button\n type=\"button\"\n {...triggerProps}\n onClick={() => setOpen(!open)}\n className=\"inline-flex items-center gap-1.5 rounded-full border border-border bg-card px-3 py-1.5 text-sm font-medium text-foreground transition hover:bg-accent/30\"\n >\n {selected ? (renderProviderBadge ? renderProviderBadge(selected.provider) : <ProviderLogo provider={selected.provider} size={16} />) : <SparkleGlyph className=\"h-3.5 w-3.5 text-muted-foreground\" />}\n <span className=\"max-w-[160px] truncate\">{selected?.name ?? value}</span>\n <ChevronDown className=\"h-3.5 w-3.5 text-muted-foreground\" />\n </button>\n\n {open && (\n <div ref={popoverRef} className=\"absolute bottom-full left-0 z-50 mb-2 w-[420px] max-w-[calc(100vw-2rem)] overflow-hidden rounded-xl border border-border bg-card shadow-lg\">\n <div className=\"border-b border-border px-3 py-2\">\n <div className=\"flex items-center gap-2 rounded-lg border border-border bg-background px-3 py-2\">\n <SearchGlyph className=\"h-3.5 w-3.5 text-muted-foreground\" />\n <input\n ref={inputRef}\n type=\"text\"\n value={query}\n onChange={(e) => setQuery(e.target.value)}\n placeholder=\"Search models...\"\n className=\"flex-1 bg-transparent text-sm placeholder:text-muted-foreground\"\n />\n </div>\n </div>\n <div className=\"max-h-[400px] overflow-y-auto p-1 pb-2\">\n {loading && <div className=\"px-3 py-4 text-center text-sm text-muted-foreground\">Loading models...</div>}\n {!loading && filtered && (\n <>\n {filtered.length === 0 && (\n <div className=\"px-3 py-4 text-center text-sm text-muted-foreground\">No models match your search</div>\n )}\n {filtered.map((m) => (\n <ModelRow key={m.id} model={m} selected={m.id === value} onSelect={() => select(m.id)} renderProviderBadge={renderProviderBadge} />\n ))}\n </>\n )}\n {!loading && !filtered && models.length === 0 && (\n <div className=\"px-3 py-4 text-center text-sm text-muted-foreground\">No models available</div>\n )}\n {!loading && !filtered && models.length > 0 && (\n <>\n {priorityGroup && sections.priority.length > 0 && (\n <>\n <SectionHeader>{priorityGroup.label}</SectionHeader>\n {sections.priority.map((m) => (\n <ModelRow key={m.id} model={m} selected={m.id === value} onSelect={() => select(m.id)} renderProviderBadge={renderProviderBadge} />\n ))}\n </>\n )}\n {sections.recommended.length > 0 && (\n <>\n <SectionHeader>{recommendedLabel}</SectionHeader>\n {sections.recommended.map((m) => (\n <ModelRow key={m.id} model={m} selected={m.id === value} onSelect={() => select(m.id)} renderProviderBadge={renderProviderBadge} />\n ))}\n </>\n )}\n {sections.byProvider.map((g) => (\n <div key={g.provider}>\n <SectionHeader>{g.provider}</SectionHeader>\n {g.items.map((m) => (\n <ModelRow key={m.id} model={m} selected={m.id === value} onSelect={() => select(m.id)} renderProviderBadge={renderProviderBadge} />\n ))}\n </div>\n ))}\n </>\n )}\n </div>\n </div>\n )}\n </div>\n )\n}\n\n// ── EffortPicker ──────────────────────────────────────────────────────────\n\n/** One reasoning-budget level: the engine `id` is unchanged (the value the\n * product sends to the loop); only the user-facing `label` is renamed to the\n * plainer \"how hard should it think\" vocabulary from docs/product-surfaces.md.\n * `low`→Quick, `medium`→Standard, `high`→Extended. The mapping is overridable\n * via `EffortPickerProps.levels`, so a product can relabel without losing the\n * ids the runtime expects. */\nexport interface EffortLevel {\n id: string\n label: string\n}\n\nexport const DEFAULT_EFFORT_LEVELS: readonly EffortLevel[] = [\n { id: 'off', label: 'Off' },\n { id: 'low', label: 'Quick' },\n { id: 'medium', label: 'Standard' },\n { id: 'high', label: 'Extended' },\n]\n\nexport interface EffortPickerProps {\n value: string\n onChange: (id: string) => void\n /** Selectable levels (engine id + user-facing label). Defaults to the plain\n * \"Thinking\" vocabulary; override to relabel without changing the ids the\n * runtime receives. */\n levels?: readonly EffortLevel[]\n /** Prefix shown before the active level on the pill — the \"what is this\"\n * context the bare value lacked. Default \"Thinking\". Pass '' to hide it. */\n label?: string\n}\n\n/** Thinking-budget selector pill, styled to match {@link ModelPicker}. Show\n * it only when the selected model `supportsReasoning`. \"Thinking\" is the\n * plain-English name for what was internally called \"effort\".\n *\n * The CANONICAL ecosystem effort picker — sandbox-ui's reasoning menu (inside\n * its `chat/AgentSessionControls`) is legacy and frozen. */\nexport function EffortPicker({ value, onChange, levels = DEFAULT_EFFORT_LEVELS, label = 'Thinking' }: EffortPickerProps) {\n const [open, setOpen] = useState(false)\n const { containerRef, triggerProps } = usePopover(open, setOpen)\n const selected = levels.find((l) => l.id === value) ?? levels[2] ?? levels[0]\n\n return (\n <div ref={containerRef} className=\"relative inline-flex\">\n <button\n type=\"button\"\n {...triggerProps}\n onClick={() => setOpen(!open)}\n title={label ? `${label} — how hard the agent reasons before answering` : 'Reasoning effort'}\n className=\"inline-flex min-h-[36px] items-center gap-1.5 rounded-full border border-border bg-card px-3 py-1.5 text-sm font-medium text-foreground transition hover:bg-accent/30\"\n >\n <SparkleGlyph className=\"h-3.5 w-3.5 text-muted-foreground\" />\n <span>\n {label ? <span className=\"text-muted-foreground\">{label}: </span> : null}\n {selected?.label}\n </span>\n <ChevronDown className=\"h-3.5 w-3.5 text-muted-foreground\" />\n </button>\n {open && (\n <div role=\"menu\" className=\"absolute bottom-full left-0 z-50 mb-2 w-40 overflow-hidden rounded-xl border border-border bg-card p-1 shadow-lg\">\n {levels.map((l) => (\n <button\n key={l.id}\n type=\"button\"\n role=\"menuitemradio\"\n aria-checked={l.id === value}\n onClick={() => {\n onChange(l.id)\n setOpen(false)\n }}\n className={`flex min-h-[40px] w-full items-center rounded-md px-3 py-2 text-left text-sm transition ${POPOVER_OPTION_FOCUS} ${\n l.id === value ? 'bg-primary/10 font-medium' : 'hover:bg-accent/30'\n }`}\n >\n {l.label}\n </button>\n ))}\n </div>\n )}\n </div>\n )\n}\n","/**\n * `useComposerAttachments` — the composer's staged-upload lifecycle: validate\n * selected/dropped/pasted files against the shared limits (the SAME\n * `sniffBinary`/`checkAttachmentType`/size-cap vocabulary the store-backed\n * upload route enforces server-side, `../chat-routes/attachment-validation`\n * + `../chat-routes/binary-sniff`), upload each accepted file with one POST\n * request per file (so a single failure never poisons the batch), and track\n * every file's status so a host composer can render chips and gate sending.\n *\n * Ported from gtm-agent's `src/components/composer-attachments.tsx`\n * (gtm#584/#592/#593 hardened the sniff gate and batch semantics this leans\n * on), de-gtm-ified:\n * - the hardcoded `/api/vault/upload?workspaceId=` URL becomes\n * `uploadUrl`/`buildUploadRequest` (the latter wins — it hands back both\n * the URL and a `RequestInit` override, e.g. an auth header);\n * - `sonner` toasts become `onReject` (client pre-validation, never hits the\n * network) and `onError` (a request that reached the server and failed);\n * - the sandbox-ui `validateComposerFiles` import becomes a small\n * accept-list matcher re-implemented locally (`isAcceptedFileType`,\n * mirroring its `accept`-string matching byte-for-byte) — this module\n * stays free of the sandbox-ui peer;\n * - the response is expected to be `{ files: ChatAttachmentInput[] }` (full\n * server-authoritative descriptors — size/mediaType/kind — not gtm's\n * `{path, name}`), so `references` is a verbatim pass-through with no\n * client recompute;\n * - `workspaceId`'s truthiness gate becomes `enabled` (default `true`).\n *\n * Import-free beyond React + the browser-safe `/chat-routes` validation core:\n * this module ships through `/web-react` into client bundles\n * (`tests/browser-safe-subpaths.test.ts` walks the graph), so nothing here\n * may reach a Node builtin, `sandbox-ui`, or an engine package.\n */\n\nimport { useCallback, useEffect, useMemo, useRef, useState } from 'react'\nimport type { ChatAttachmentInput, ChatAttachmentKind } from './chat-stream'\nimport type { ComposerFile } from './chat-composer'\nimport {\n ATTACHMENT_ACCEPT,\n ATTACHMENT_MAX_COUNT,\n MAX_ATTACHMENT_TOTAL_BYTES,\n MAX_BINARY_ATTACHMENT_BYTES,\n MAX_TEXT_ATTACHMENT_BYTES,\n attachmentSizeErrorMessage,\n attachmentTotalSizeErrorMessage,\n checkAttachmentType,\n sanitizeAttachmentFileName,\n} from '../chat-routes/attachment-validation'\nimport { sniffBinary } from '../chat-routes/binary-sniff'\n\nexport { ATTACHMENT_ACCEPT } from '../chat-routes/attachment-validation'\n\n/** One staged file and its upload lifecycle. `file` is retained so a failed\n * upload can be retried without re-selecting; `previewUrl` is an object URL\n * for image thumbnails and must be revoked when the entry leaves the queue.\n * `reference` is the server's authoritative descriptor once the upload\n * lands — stored verbatim, never recomputed client-side. */\ninterface StagedAttachment {\n id: string\n file: File\n name: string\n size: number\n status: 'pending' | 'uploading' | 'ready' | 'error'\n reference?: ChatAttachmentInput\n previewUrl?: string\n errorMessage?: string\n}\n\n/** Define options for configuring file upload behavior and handling in a composer component */\nexport interface UseComposerAttachmentsOptions {\n /** Simple upload target: every file POSTs here. Ignored when\n * `buildUploadRequest` is provided. */\n uploadUrl?: string\n /** Full request-building seam (auth headers, per-file routing, …) — wins\n * over `uploadUrl` when both are set. */\n buildUploadRequest?: (args: { file: File; name: string; form: FormData }) => {\n url: string\n init?: Omit<RequestInit, 'body' | 'signal'>\n }\n /** Client pre-validation rejections — a file that never reaches the\n * network (bad type, over a size cap, over count, disallowed kind). */\n onReject?: (reason: string, file?: File) => void\n /** A file that reached the upload endpoint and failed (HTTP error,\n * transport error, malformed response). */\n onError?: (reason: string) => void\n limits?: {\n maxCount?: number\n maxBinaryBytes?: number\n maxTextBytes?: number\n maxTotalBytes?: number\n }\n /** Attachment kinds accepted, checked against the sniffed content's\n * mime. Default: both (`['image', 'file']` — i.e. no restriction). */\n allowedKinds?: ChatAttachmentKind[]\n /** `<input accept>`-style gate for the file picker/drop/paste path.\n * Default {@link ATTACHMENT_ACCEPT}. */\n accept?: string\n /** When `false`, `addFiles` rejects every call via `onReject` (and\n * `blockReason` explains why) instead of staging anything — the\n * replacement for gtm's `workspaceId`-truthiness gate (e.g. no workspace\n * loaded yet). Default `true`. */\n enabled?: boolean\n}\n\n/** Provide staged file chips, ready attachments, and methods to add, retry, or drop composer files */\nexport interface UseComposerAttachmentsResult {\n /** Chip models for `ChatComposer`'s `pendingFiles` prop, one per staged\n * file — `kind` is always `'file'` (agent-app's `ComposerFile.kind`\n * discriminates file-vs-folder chips, not attachment media type). */\n composerFiles: ComposerFile[]\n /** Ready-to-send attachment descriptors — only files whose upload\n * succeeded, straight from the server's response (no recompute). Feed\n * this into `ChatTurnRequestPayload.attachments`. */\n references: ChatAttachmentInput[]\n /** Validate + stage + upload the given files, one request per file. */\n addFiles: (files: File[] | FileList) => Promise<void>\n /** Re-upload a failed entry using its retained `File`. */\n retry: (id: string) => void\n /** Drop one staged entry, aborting its upload and revoking its preview. */\n removeAttachment: (id: string) => void\n /** Forget every staged entry (call after a successful send). */\n clear: () => void\n /** True while any file is still pending or uploading. */\n hasPending: boolean\n /** True while any file failed to upload. */\n hasError: boolean\n /** Why a send is blocked, or `null` when the queue is clean. */\n blockReason: string | null\n}\n\nfunction newId(): string {\n const cryptoObject = globalThis.crypto\n if (typeof cryptoObject?.randomUUID === 'function') return cryptoObject.randomUUID()\n return `att-${Date.now()}-${Math.random().toString(36).slice(2)}`\n}\n\n/** Suffix a name (`report.pdf` → `report-2.pdf`) until it's unused. The\n * server writes to a name-derived store path, so identical names would\n * overwrite. The suffix stays inside the store-path charset (see\n * `sanitizeAttachmentFileName`). Ported byte-for-byte from gtm's\n * `dedupeName`. */\nfunction dedupeName(name: string, taken: Set<string>): string {\n if (!taken.has(name)) return name\n const dot = name.lastIndexOf('.')\n const base = dot > 0 ? name.slice(0, dot) : name\n const ext = dot > 0 ? name.slice(dot) : ''\n let n = 2\n let candidate = `${base}-${n}${ext}`\n while (taken.has(candidate)) {\n n += 1\n candidate = `${base}-${n}${ext}`\n }\n return candidate\n}\n\n/** `image/*` → `'image'`, everything else → `'file'`. Deliberately\n * reimplemented here (not imported from `../chat-store/parts`, which pulls\n * the drizzle-adjacent `/chat-store` barrel): this module must stay reachable\n * from a browser bundle with only the `/chat-routes` validation core as a\n * dependency. */\nfunction kindForMime(mime: string): ChatAttachmentKind {\n return mime.startsWith('image/') ? 'image' : 'file'\n}\n\n/** `<input accept>`-style matcher: extension (`.pdf`), wildcard mime\n * (`image/*`), or exact mime. Reimplemented locally (NOT imported from\n * `@tangle-network/sandbox-ui`'s `validateComposerFiles`/`isAcceptedType`) so\n * this module has no sandbox-ui dependency; the matching semantics are kept\n * identical so a rejection reads the same either side of the fence. */\nfunction isAcceptedFileType(file: File, accept: string): boolean {\n const patterns = accept.split(',').map((p) => p.trim()).filter((p) => p.length > 0)\n if (patterns.length === 0) return true\n const name = file.name.toLowerCase()\n const type = (file.type || '').toLowerCase()\n return patterns.some((pattern) => {\n const lower = pattern.toLowerCase()\n if (lower.startsWith('.')) return name.endsWith(lower)\n if (lower.endsWith('/*')) return type.startsWith(lower.slice(0, -1))\n return type === lower\n })\n}\n\n/** Pull a human-readable message out of the upload endpoint's error body.\n * Ported from gtm's `parseUploadError`: handles both `{ error: string }`\n * (size/count/access errors) and `{ error: { message } }` (the\n * `createAttachmentUploadRoute` envelope, `{error:{code,message,path?}}`). */\nasync function parseUploadError(res: Response): Promise<string> {\n const detail = await res.json().catch(() => null)\n if (detail && typeof detail === 'object' && 'error' in detail) {\n const error = (detail as { error: unknown }).error\n if (typeof error === 'string' && error) return error\n if (error && typeof error === 'object' && 'message' in error) {\n const message = (error as { message: unknown }).message\n if (typeof message === 'string' && message) return message\n }\n }\n return `Upload failed (${res.status})`\n}\n\n/** Shown when neither `uploadUrl` nor `buildUploadRequest` is configured\n * while `enabled` — a product wiring bug, not a user-facing rejection, so it\n * lands each affected entry in `error` (with `onError`) rather than blocking\n * `addFiles` outright via `onReject`: the files still stage and can be\n * retried once the product fixes its config, instead of silently vanishing. */\nconst NO_UPLOAD_TARGET_MESSAGE = 'No upload destination configured (pass uploadUrl or buildUploadRequest)'\n\n/**\n * Owns the composer's attachment lifecycle: validate selected/dropped/pasted\n * files against the shared limits, upload each accepted file to the\n * product's store (one request per file), and track every file's status so\n * the composer can render chips and gate sending.\n *\n * Failures surface loud — a rejected file calls `onReject` and is never\n * uploaded; a failed upload calls `onError` and leaves an error chip the user\n * can retry or remove. `references` only ever contains files whose upload the\n * server actually confirmed.\n */\nexport function useComposerAttachments(\n options: UseComposerAttachmentsOptions,\n): UseComposerAttachmentsResult {\n // Latest options, read from inside stable callbacks — avoids re-creating\n // `addFiles`/`upload` (and therefore breaking referential stability for\n // effects a host might hang off them) every time a caller passes a fresh\n // options object literal.\n const optionsRef = useRef(options)\n optionsRef.current = options\n\n const [staged, setStagedState] = useState<StagedAttachment[]>([])\n // Mirror of `staged` kept in lockstep so dedupe/aggregate-cap/abort read\n // current values synchronously (setState callbacks alone can't answer\n // \"what's staged right now\" mid-validation).\n const stagedRef = useRef<StagedAttachment[]>([])\n const controllersRef = useRef<Map<string, AbortController>>(new Map())\n\n // Post-unmount calls reduce to a React no-op setState; the refs they touch\n // die with the instance.\n const setStaged = useCallback(\n (updater: StagedAttachment[] | ((prev: StagedAttachment[]) => StagedAttachment[])) => {\n const next =\n typeof updater === 'function'\n ? (updater as (prev: StagedAttachment[]) => StagedAttachment[])(stagedRef.current)\n : updater\n stagedRef.current = next\n setStagedState(next)\n },\n [],\n )\n\n const upload = useCallback(\n async (id: string, file: File, name: string) => {\n const opts = optionsRef.current\n setStaged((prev) =>\n prev.map((s) => (s.id === id ? { ...s, status: 'uploading', errorMessage: undefined } : s)),\n )\n const controller = new AbortController()\n controllersRef.current.set(id, controller)\n const form = new FormData()\n form.append('file', file, name)\n\n const request = opts.buildUploadRequest\n ? opts.buildUploadRequest({ file, name, form })\n : opts.uploadUrl\n ? { url: opts.uploadUrl }\n : null\n\n if (!request) {\n setStaged((prev) =>\n prev.map((s) =>\n s.id === id ? { ...s, status: 'error', errorMessage: NO_UPLOAD_TARGET_MESSAGE } : s,\n ),\n )\n opts.onError?.(NO_UPLOAD_TARGET_MESSAGE)\n controllersRef.current.delete(id)\n return\n }\n\n try {\n const res = await fetch(request.url, {\n method: 'POST',\n credentials: 'same-origin',\n ...request.init,\n body: form,\n signal: controller.signal,\n })\n if (!res.ok) {\n const message = await parseUploadError(res)\n setStaged((prev) =>\n prev.map((s) => (s.id === id ? { ...s, status: 'error', errorMessage: message } : s)),\n )\n opts.onError?.(message)\n return\n }\n const data = (await res.json()) as { files?: ChatAttachmentInput[] }\n const uploaded = data.files?.[0]\n if (!uploaded) {\n const message = 'Upload returned no file'\n setStaged((prev) =>\n prev.map((s) => (s.id === id ? { ...s, status: 'error', errorMessage: message } : s)),\n )\n opts.onError?.(message)\n return\n }\n setStaged((prev) =>\n prev.map((s) => (s.id === id ? { ...s, status: 'ready', reference: uploaded } : s)),\n )\n } catch (err) {\n if ((err as Error).name === 'AbortError') return // silent removal — see removeAttachment/clear\n const message =\n err instanceof Error && err.message ? err.message : 'Upload failed — check your connection'\n setStaged((prev) =>\n prev.map((s) => (s.id === id ? { ...s, status: 'error', errorMessage: message } : s)),\n )\n opts.onError?.(message)\n } finally {\n controllersRef.current.delete(id)\n }\n },\n [setStaged],\n )\n\n const addFiles = useCallback(\n async (files: File[] | FileList) => {\n const opts = optionsRef.current\n const enabled = opts.enabled ?? true\n if (!enabled) {\n opts.onReject?.('Attachments are disabled')\n return\n }\n\n const accept = opts.accept ?? ATTACHMENT_ACCEPT\n const maxCount = opts.limits?.maxCount ?? ATTACHMENT_MAX_COUNT\n const maxBinaryBytes = opts.limits?.maxBinaryBytes ?? MAX_BINARY_ATTACHMENT_BYTES\n const maxTextBytes = opts.limits?.maxTextBytes ?? MAX_TEXT_ATTACHMENT_BYTES\n const maxTotalBytes = opts.limits?.maxTotalBytes ?? MAX_ATTACHMENT_TOTAL_BYTES\n const allowedKinds = opts.allowedKinds ?? (['image', 'file'] as ChatAttachmentKind[])\n\n const list = Array.isArray(files) ? files : Array.from(files)\n\n // Pass 1: accept-list + count cap, mirroring sandbox-ui's\n // `validateComposerFiles` semantics (accept checked before count, count\n // checked against currently-staged + already-accepted-this-batch).\n const currentCount = stagedRef.current.length\n const countAccepted: File[] = []\n for (const file of list) {\n if (!isAcceptedFileType(file, accept)) {\n opts.onReject?.(`\"${file.name}\" is not an accepted file type (${accept}).`, file)\n continue\n }\n if (currentCount + countAccepted.length >= maxCount) {\n opts.onReject?.(`\"${file.name}\" was not added — the ${maxCount}-file limit is already reached.`, file)\n continue\n }\n countAccepted.push(file)\n }\n\n // Pass 2: real content sniff + type gate + per-kind size cap +\n // allowed-kinds gate — the SAME checks the server enforces, so a\n // rejection never differs depending on which side classified the bytes\n // first. Nothing here ever reaches the network.\n const sizeAccepted: File[] = []\n for (const file of countAccepted) {\n const bytes = new Uint8Array(await file.arrayBuffer())\n const sniff = sniffBinary(bytes)\n const typeCheck = checkAttachmentType(file.name, sniff)\n if (!typeCheck.succeeded) {\n opts.onReject?.(typeCheck.message, file)\n continue\n }\n const limit = sniff.binary ? maxBinaryBytes : maxTextBytes\n if (file.size > limit) {\n opts.onReject?.(attachmentSizeErrorMessage(file.name, file.size, limit), file)\n continue\n }\n const mediaType = sniff.mime ?? file.type ?? ''\n const kind = kindForMime(mediaType)\n if (!allowedKinds.includes(kind)) {\n opts.onReject?.(`\"${file.name}\" is a ${kind} attachment, which isn't accepted here`, file)\n continue\n }\n sizeAccepted.push(file)\n }\n\n // Pass 3: running aggregate cap across this batch + everything already\n // staged (any status) — a partial batch can still land.\n const accepted: File[] = []\n let totalBytes = stagedRef.current.reduce((total, s) => total + s.size, 0)\n for (const file of sizeAccepted) {\n const nextTotalBytes = totalBytes + file.size\n if (nextTotalBytes > maxTotalBytes) {\n opts.onReject?.(attachmentTotalSizeErrorMessage(nextTotalBytes, maxTotalBytes), file)\n continue\n }\n accepted.push(file)\n totalBytes = nextTotalBytes\n }\n if (accepted.length === 0) return\n\n // Stage under the name the server will actually store, so the chip and\n // the message's attachment references never diverge.\n const taken = new Set(stagedRef.current.map((s) => s.name))\n const entries: StagedAttachment[] = accepted.map((file) => {\n const name = dedupeName(sanitizeAttachmentFileName(file.name), taken)\n taken.add(name)\n return {\n id: newId(),\n file,\n name,\n size: file.size,\n status: 'pending',\n previewUrl: file.type.startsWith('image/') ? URL.createObjectURL(file) : undefined,\n }\n })\n setStaged((prev) => [...prev, ...entries])\n for (const entry of entries) void upload(entry.id, entry.file, entry.name)\n },\n [setStaged, upload],\n )\n\n const retry = useCallback(\n (id: string) => {\n const entry = stagedRef.current.find((s) => s.id === id)\n if (!entry) return\n void upload(entry.id, entry.file, entry.name)\n },\n [upload],\n )\n\n const removeAttachment = useCallback(\n (id: string) => {\n controllersRef.current.get(id)?.abort()\n controllersRef.current.delete(id)\n const entry = stagedRef.current.find((s) => s.id === id)\n if (entry?.previewUrl) URL.revokeObjectURL(entry.previewUrl)\n setStaged((prev) => prev.filter((s) => s.id !== id))\n },\n [setStaged],\n )\n\n const clear = useCallback(() => {\n for (const controller of controllersRef.current.values()) controller.abort()\n controllersRef.current.clear()\n for (const entry of stagedRef.current) {\n if (entry.previewUrl) URL.revokeObjectURL(entry.previewUrl)\n }\n setStaged([])\n }, [setStaged])\n\n useEffect(\n () => () => {\n for (const controller of controllersRef.current.values()) controller.abort()\n controllersRef.current.clear()\n for (const entry of stagedRef.current) {\n if (entry.previewUrl) URL.revokeObjectURL(entry.previewUrl)\n }\n },\n [],\n )\n\n const composerFiles = useMemo<ComposerFile[]>(\n () =>\n staged.map((s) => ({\n id: s.id,\n name: s.name,\n size: s.size,\n kind: 'file' as const,\n status: s.status,\n })),\n [staged],\n )\n\n const references = useMemo<ChatAttachmentInput[]>(\n () =>\n staged\n .filter((s): s is StagedAttachment & { reference: ChatAttachmentInput } => s.status === 'ready' && !!s.reference)\n .map((s) => s.reference),\n [staged],\n )\n\n const hasPending = useMemo(\n () => staged.some((s) => s.status === 'pending' || s.status === 'uploading'),\n [staged],\n )\n const hasError = useMemo(() => staged.some((s) => s.status === 'error'), [staged])\n const enabled = options.enabled ?? true\n const blockReason = !enabled\n ? 'Attachments are disabled'\n : hasPending\n ? 'Attachments are still uploading'\n : hasError\n ? 'Remove failed attachments to send'\n : null\n\n return {\n composerFiles,\n references,\n addFiles,\n retry,\n removeAttachment,\n clear,\n hasPending,\n hasError,\n blockReason,\n }\n}\n","/**\n * `AgentSessionControls` — the CANONICAL model + harness + reasoning-effort\n * cluster a chat composer docks (see \"UI chrome ownership (picker canon)\" in\n * AGENTS.md). One component so every product's two composers (and every\n * product) share the same control surface and harness↔model coherence policy.\n *\n * PICKER CANON. The model menu below IS `/web-react`'s `ModelPicker` and the\n * thinking-budget pill IS `EffortPicker` — the canonical ecosystem pickers.\n * sandbox-ui's `dashboard/ModelPicker` and the model menu inside sandbox-ui's\n * `chat/AgentSessionControls` are legacy (deprecated, frozen, removed at\n * sandbox-ui's next major), and the `/chat-react` `ComposerAgentControls`\n * adapter that rendered sandbox-ui's strip is REMOVED — a surface that still\n * renders the sandbox-ui strip is showing the old design; migrate it\n * (props mapping in `docs/ui-picker-canon.md`).\n *\n * Dependency-free beyond React by design: `/web-react` must not force the\n * optional sandbox-ui peer, so this component — the canonical one — can never\n * require it.\n *\n * Two layouts, additive — the default preserves the prior hand-rolled behavior:\n * - `layout=\"inline\"` (default): model, harness, and effort sit side by side as\n * pills. This is the original arrangement; existing call sites that mounted\n * `ModelPicker` + a harness picker + `EffortPicker` in a row get the same UI.\n * - `layout=\"compact\"`: the model picker stays inline and visible; the agent\n * backend (\"harness\") and reasoning-effort controls — internal jargon a user\n * rarely needs — tuck behind a single gear popover with plain-English copy.\n *\n * Harness ↔ model coherence is identical in both layouts, via the substrate's\n * snap helpers (`@tangle-network/agent-app/harness`): changing the harness snaps\n * an incompatible model to that harness's best catalog option; changing the\n * model switches to the model's native harness. Catalog model ids are canonical\n * (\"provider/model\"), which is exactly what the snap helpers expect — no id\n * translation is needed here.\n *\n * Dependency-free beyond React: inline SVG glyphs, CSS-var / Tailwind tokens the\n * app shell defines. The harness picker is rendered inline so this needs no\n * sandbox-ui dependency.\n */\n\nimport { useMemo, useState, type ReactNode } from 'react'\nimport {\n snapHarnessToModel,\n snapModelToHarness,\n type Harness,\n} from '../harness'\nimport type { CatalogModel } from '../runtime/model-catalog'\nimport { ModelPicker, EffortPicker, usePopover } from './controls'\n\n/** Plain-English labels for the harnesses a product is likely to expose. Unknown\n * ids fall back to the raw value so a new backend still renders a usable label. */\nconst HARNESS_LABELS: Partial<Record<Harness, string>> = {\n opencode: 'OpenCode (any model)',\n 'claude-code': 'Claude Code (Anthropic)',\n codex: 'Codex (OpenAI)',\n 'kimi-code': 'Kimi (Moonshot)',\n amp: 'Amp',\n 'factory-droids': 'Factory Droids',\n cursor: 'Cursor',\n hermes: 'Hermes',\n forge: 'Forge',\n pi: 'Pi',\n openclaw: 'OpenClaw',\n acp: 'ACP',\n 'cli-base': 'CLI',\n}\n\nfunction harnessLabel(h: Harness): string {\n return HARNESS_LABELS[h] ?? h\n}\n\nfunction ChevronDown({ className }: { className?: string }) {\n return (\n <svg className={className} viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden>\n <path d=\"m6 9 6 6 6-6\" />\n </svg>\n )\n}\n\nfunction GearGlyph({ className }: { className?: string }) {\n return (\n <svg className={className} viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden>\n <circle cx=\"12\" cy=\"12\" r=\"3\" />\n <path d=\"M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1Z\" />\n </svg>\n )\n}\n\n/** Tailwind utilities for keyboard-visible focus on popover options + triggers. */\nconst FOCUS_RING =\n ''\n\n/** Pill-styled harness picker — inline, no sandbox-ui dependency. */\nfunction HarnessPicker({\n value,\n onChange,\n available,\n}: {\n value: Harness\n onChange: (h: Harness) => void\n available?: ReadonlyArray<Harness>\n}) {\n const [open, setOpen] = useState(false)\n const { containerRef, triggerProps } = usePopover(open, setOpen)\n const options = available ?? (Object.keys(HARNESS_LABELS) as Harness[])\n return (\n <div ref={containerRef} className=\"relative inline-flex\">\n <button\n type=\"button\"\n {...triggerProps}\n onClick={() => setOpen(!open)}\n title=\"Agent backend\"\n className={`inline-flex w-full items-center justify-between gap-1.5 rounded-lg border border-border bg-card px-3 py-1.5 text-sm font-medium text-foreground transition hover:bg-accent/30 ${FOCUS_RING}`}\n >\n <span className=\"truncate\">{harnessLabel(value)}</span>\n <ChevronDown className=\"h-3.5 w-3.5 text-muted-foreground\" />\n </button>\n {open && (\n <div role=\"menu\" className=\"absolute bottom-full left-0 z-50 mb-2 max-h-64 w-full min-w-[220px] overflow-y-auto rounded-xl border border-border bg-card p-1 shadow-lg\">\n {options.map((h) => (\n <button\n key={h}\n type=\"button\"\n role=\"menuitemradio\"\n aria-checked={h === value}\n onClick={() => {\n onChange(h)\n setOpen(false)\n }}\n className={`flex w-full items-center rounded-md px-3 py-2 text-left text-sm transition ${FOCUS_RING} ${\n h === value ? 'bg-primary/10 font-medium' : 'hover:bg-accent/30'\n }`}\n >\n {harnessLabel(h)}\n </button>\n ))}\n </div>\n )}\n </div>\n )\n}\n\nexport interface AgentSessionControlsProps {\n /** Catalog models — canonical provider-prefixed ids. */\n models: CatalogModel[]\n modelsLoading?: boolean\n /** Selected canonical model id. */\n model: string\n onModelChange(modelId: string): void\n /** Current harness; harness↔model coherence is enforced on every change. */\n harness: Harness\n onHarnessChange(harness: Harness): void\n /** Harnesses to offer; defaults to the labeled set. */\n availableHarnesses?: ReadonlyArray<Harness>\n /** Reasoning-effort value + setter. Shown only when the selected model\n * `supportsReasoning`, matching `EffortPicker`'s guidance. */\n effort: string\n onEffortChange(effort: string): void\n /**\n * `inline` (default): model, harness, effort side by side — the prior\n * behavior. `compact`: model inline, harness + effort behind a gear popover.\n */\n layout?: 'inline' | 'compact'\n /** Hide the harness control entirely (single-harness products). */\n showHarness?: boolean\n renderProviderBadge?: (provider: string) => ReactNode\n className?: string\n}\n\n/**\n * Apply the harness↔model coherence policy and emit the resulting change(s).\n * Returned from a hook-free helper so both layouts share one implementation.\n */\nfunction useCoherentHandlers(props: AgentSessionControlsProps) {\n const { model, models, harness, onModelChange, onHarnessChange } = props\n const canonicalIds = useMemo(() => models.map((m) => m.id), [models])\n\n const onModel = (next: string) => {\n onModelChange(next)\n const nextHarness = snapHarnessToModel(harness, next)\n if (nextHarness !== harness) onHarnessChange(nextHarness)\n }\n\n const onHarness = (next: Harness) => {\n onHarnessChange(next)\n const snapped = snapModelToHarness(next, model, canonicalIds)\n if (snapped !== model) onModelChange(snapped)\n }\n\n return { onModel, onHarness }\n}\n\nexport function AgentSessionControls(props: AgentSessionControlsProps) {\n const {\n models,\n modelsLoading,\n model,\n harness,\n availableHarnesses,\n effort,\n onEffortChange,\n layout = 'inline',\n showHarness = true,\n renderProviderBadge,\n className,\n } = props\n const { onModel, onHarness } = useCoherentHandlers(props)\n const [open, setOpen] = useState(false)\n const { containerRef: popoverRef, triggerProps } = usePopover(open, setOpen)\n\n const selectedModel = models.find((m) => m.id === model)\n const showEffort = selectedModel?.supportsReasoning ?? true\n\n const modelPicker = (\n <ModelPicker\n value={model}\n onChange={onModel}\n models={models}\n loading={modelsLoading}\n renderProviderBadge={renderProviderBadge}\n />\n )\n\n if (layout === 'inline') {\n return (\n <div className={`flex items-center gap-1.5 ${className ?? ''}`}>\n {modelPicker}\n {showHarness && (\n <HarnessPicker value={harness} onChange={onHarness} available={availableHarnesses} />\n )}\n {showEffort && <EffortPicker value={effort} onChange={onEffortChange} />}\n </div>\n )\n }\n\n // compact: model inline; harness + effort behind a gear popover.\n const hasAdvanced = showHarness || showEffort\n return (\n <div className={`flex items-center gap-1.5 ${className ?? ''}`}>\n {modelPicker}\n {hasAdvanced && (\n <div ref={popoverRef} className=\"relative inline-flex\">\n <button\n type=\"button\"\n {...triggerProps}\n onClick={() => setOpen(!open)}\n title=\"Model settings — pick the agent backend and how hard it thinks\"\n className={`flex h-8 w-8 items-center justify-center rounded-full text-muted-foreground transition-colors hover:bg-muted hover:text-foreground data-[state=open]:bg-muted ${FOCUS_RING}`}\n data-state={open ? 'open' : 'closed'}\n >\n <GearGlyph className=\"h-4 w-4\" />\n </button>\n {open && (\n <div className=\"absolute bottom-full left-0 z-50 mb-2 w-72 space-y-3 rounded-xl border border-border bg-card p-3 shadow-lg\">\n {showHarness && (\n <div className=\"space-y-1.5\">\n <p className=\"text-xs font-medium text-foreground\">Agent backend</p>\n <HarnessPicker value={harness} onChange={onHarness} available={availableHarnesses} />\n <p className=\"text-[11px] leading-snug text-muted-foreground\">\n The engine that runs the agent. Switching it keeps your model choice compatible.\n </p>\n </div>\n )}\n {showEffort && (\n <div className=\"space-y-1.5\">\n <p className=\"text-xs font-medium text-foreground\">Thinking</p>\n <EffortPicker value={effort} onChange={onEffortChange} label=\"\" />\n <p className=\"text-[11px] leading-snug text-muted-foreground\">\n How hard the agent thinks before answering. Higher is slower but more thorough.\n </p>\n </div>\n )}\n </div>\n )}\n </div>\n )}\n </div>\n )\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAyDU,cAON,YAPM;AAzCV,IAAM,QAAiC;AAAA,EACrC,WAAW,EAAE,SAAS,aAAa,MAAM,WAAW,OAAO,CAAC,0KAA0K,EAAE;AAAA,EACxO,QAAQ,EAAE,SAAS,aAAa,MAAM,WAAW,OAAO,CAAC,8RAA8R,EAAE;AAAA,EACzV,UAAU,EAAE,SAAS,aAAa,MAAM,WAAW,OAAO,CAAC,w7DAAw7D,EAAE;AAAA,EACr/D,SAAS,EAAE,SAAS,aAAa,MAAM,WAAW,OAAO,CAAC,kLAAkL,EAAE;AAAA,EAC9O,KAAK,EAAE,SAAS,aAAa,MAAM,WAAW,OAAO,CAAC,oMAAoM,EAAE;AAAA,EAC5P,QAAQ,EAAE,SAAS,aAAa,MAAM,WAAW,OAAO,CAAC,qwBAAqwB,EAAE;AAAA,EACh0B,MAAM,EAAE,SAAS,aAAa,MAAM,WAAW,OAAO,CAAC,ksCAAksC,EAAE;AAAA,EAC3vC,YAAY,EAAE,SAAS,aAAa,MAAM,WAAW,OAAO,CAAC,2hBAA2hB,EAAE;AAAA,EAC1lB,QAAQ,EAAE,SAAS,eAAe,MAAM,WAAW,OAAO,CAAC,28CAA28C,EAAE;AAC1gD;AAEA,IAAM,UAAkC;AAAA,EACtC,UAAU;AAAA,EACV,aAAa;AAAA,EACb,QAAQ;AAAA,EACR,cAAc;AAChB;AAEA,IAAM,WAAuD;AAAA,EAC3D,QAAQ,EAAE,IAAI,WAAW,IAAI,UAAU;AAAA,EACvC,MAAM,EAAE,IAAI,WAAW,IAAI,UAAU;AAAA,EACrC,UAAU,EAAE,IAAI,WAAW,IAAI,UAAU;AAAA,EACzC,KAAK,EAAE,IAAI,WAAW,IAAI,UAAU;AAAA,EACpC,QAAQ,EAAE,IAAI,WAAW,IAAI,UAAU;AAAA,EACvC,OAAO,EAAE,IAAI,WAAW,IAAI,UAAU;AACxC;AAQO,SAAS,aAAa,EAAE,UAAU,OAAO,GAAG,GAAiC;AAClF,QAAM,MAAM,QAAQ,YAAY,EAAE,KAAK,YAAY;AACnD,QAAM,OAAO,MAAM,GAAG;AACtB,MAAI,MAAM;AACR,WACE,oBAAC,SAAI,OAAO,MAAM,QAAQ,MAAM,SAAS,KAAK,SAAS,MAAK,OAAM,cAAY,KAC3E,eAAK,MAAM,IAAI,CAAC,GAAG,MAClB,oBAAC,UAAa,GAAM,MAAM,KAAK,QAApB,CAA0B,CACtC,GACH;AAAA,EAEJ;AACA,QAAM,OAAO,SAAS,GAAG,KAAK,EAAE,IAAI,WAAW,IAAI,UAAU;AAC7D,SACE,qBAAC,SAAI,OAAO,MAAM,QAAQ,MAAM,SAAQ,aAAY,MAAK,OAAM,cAAY,OAAO,SAChF;AAAA,wBAAC,UAAK,OAAM,MAAK,QAAO,MAAK,IAAG,KAAI,MAAM,KAAK,IAAI;AAAA,IACnD,oBAAC,UAAK,GAAE,KAAI,GAAE,QAAO,YAAW,UAAS,MAAM,KAAK,IAAI,UAAS,KAAI,YAAW,OAAM,YAAW,yBAC7F,kBAAO,KAAK,OAAO,CAAC,EAAE,YAAY,GACtC;AAAA,KACF;AAEJ;;;AC3DA,SAAS,WAAW,SAAS,QAAQ,gBAAgC;AAS/D,SAoSQ,UApSR,OAAAA,MAOF,QAAAC,aAPE;AAHC,SAAS,YAAY,EAAE,UAAU,GAA2B;AACjE,SACE,gBAAAD,KAAC,SAAI,WAAsB,SAAQ,aAAY,MAAK,QAAO,QAAO,gBAAe,aAAY,KAAI,eAAc,SAAQ,gBAAe,SAAQ,eAAW,MACvJ,0BAAAA,KAAC,UAAK,GAAE,gBAAe,GACzB;AAEJ;AAEA,SAAS,YAAY,EAAE,UAAU,GAA2B;AAC1D,SACE,gBAAAC,MAAC,SAAI,WAAsB,SAAQ,aAAY,MAAK,QAAO,QAAO,gBAAe,aAAY,KAAI,eAAc,SAAQ,gBAAe,SAAQ,eAAW,MACvJ;AAAA,oBAAAD,KAAC,YAAO,IAAG,MAAK,IAAG,MAAK,GAAE,KAAI;AAAA,IAC9B,gBAAAA,KAAC,UAAK,GAAE,kBAAiB;AAAA,KAC3B;AAEJ;AAEA,SAAS,aAAa,EAAE,UAAU,GAA2B;AAC3D,SACE,gBAAAA,KAAC,SAAI,WAAsB,SAAQ,aAAY,MAAK,QAAO,QAAO,gBAAe,aAAY,KAAI,eAAc,SAAQ,gBAAe,SAAQ,eAAW,MACvJ,0BAAAA,KAAC,UAAK,GAAE,iGAAgG,GAC1G;AAEJ;AASO,SAAS,WAAW,MAAe,SAAkC;AAC1E,QAAM,eAAe,OAAuB,IAAI;AAChD,QAAM,aAAa,OAA0B,IAAI;AAEjD,YAAU,MAAM;AACd,QAAI,CAAC,KAAM;AACX,aAAS,YAAY,GAAe;AAClC,UAAI,aAAa,WAAW,CAAC,aAAa,QAAQ,SAAS,EAAE,MAAc,EAAG,SAAQ,KAAK;AAAA,IAC7F;AACA,aAAS,UAAU,GAAkB;AACnC,UAAI,EAAE,QAAQ,UAAU;AACtB,gBAAQ,KAAK;AACb,mBAAW,SAAS,MAAM;AAAA,MAC5B;AAAA,IACF;AACA,aAAS,iBAAiB,aAAa,WAAW;AAClD,aAAS,iBAAiB,WAAW,SAAS;AAC9C,WAAO,MAAM;AACX,eAAS,oBAAoB,aAAa,WAAW;AACrD,eAAS,oBAAoB,WAAW,SAAS;AAAA,IACnD;AAAA,EACF,GAAG,CAAC,MAAM,OAAO,CAAC;AAElB,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,cAAc;AAAA,MACZ,KAAK;AAAA,MACL,iBAAiB;AAAA,MACjB,iBAAiB;AAAA,IACnB;AAAA,EACF;AACF;AAYO,IAAM,uBAAuB;AAS7B,SAAS,aAAsF;AACpG,QAAM,CAAC,SAAS,UAAU,IAAI,SAAS,KAAK;AAC5C,QAAM,WAAW,OAAO,KAAK;AAC7B,QAAM,UAAU,OAAO,IAAI;AAC3B,YAAU,MAAM;AACd,YAAQ,UAAU;AAClB,WAAO,MAAM;AACX,cAAQ,UAAU;AAAA,IACpB;AAAA,EACF,GAAG,CAAC,CAAC;AACL,QAAM,MAAM,CAAC,WAAuC;AAClD,QAAI,SAAS,QAAS;AACtB,QAAI;AACJ,QAAI;AACF,eAAS,OAAO;AAAA,IAClB,QAAQ;AACN;AAAA,IACF;AACA,QAAI,EAAE,kBAAkB,SAAU;AAClC,aAAS,UAAU;AACnB,eAAW,IAAI;AACf,SAAK,OAAO,QAAQ,MAAM;AACxB,eAAS,UAAU;AACnB,UAAI,QAAQ,QAAS,YAAW,KAAK;AAAA,IACvC,CAAC;AAAA,EACH;AACA,SAAO,EAAE,SAAS,IAAI;AACxB;AAyBA,SAAS,YAAY,GAAgC;AACnD,MAAI,CAAC,EAAG,QAAO;AACf,QAAM,IAAI,OAAO,CAAC;AAClB,MAAI,MAAM,CAAC,KAAK,MAAM,EAAG,QAAO;AAChC,QAAM,OAAO,IAAI;AACjB,SAAO,QAAQ,IAAI,IAAI,KAAK,QAAQ,CAAC,CAAC,OAAO,IAAI,KAAK,QAAQ,CAAC,CAAC;AAClE;AAEA,SAAS,cAAc,KAAkC;AACvD,MAAI,CAAC,IAAK,QAAO;AACjB,MAAI,OAAO,IAAW,QAAO,IAAI,MAAM,KAAW,QAAQ,CAAC,CAAC;AAC5D,MAAI,OAAO,IAAO,QAAO,GAAG,KAAK,MAAM,MAAM,GAAK,CAAC;AACnD,SAAO,GAAG,GAAG;AACf;AAEA,SAAS,cAAc,EAAE,SAAS,GAA4B;AAC5D,SACE,gBAAAA,KAAC,SAAI,WAAU,sFACZ,UACH;AAEJ;AAEA,SAAS,SAAS;AAAA,EAChB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAKG;AACD,QAAM,QAAQ,YAAY,MAAM,SAAS,MAAM;AAC/C,QAAM,MAAM,cAAc,MAAM,aAAa;AAC7C,SACE,gBAAAC;AAAA,IAAC;AAAA;AAAA,MACC,MAAK;AAAA,MACL,SAAS;AAAA,MACT,WAAW,wFAAwF,oBAAoB,IACrH,WAAW,8BAA8B,oBAC3C;AAAA,MAEC;AAAA,8BAAsB,oBAAoB,MAAM,QAAQ,IAAI,gBAAAD,KAAC,gBAAa,UAAU,MAAM,UAAU,MAAM,IAAI;AAAA,QAC/G,gBAAAA,KAAC,UAAK,WAAU,YAAY,gBAAM,MAAK;AAAA,QACtC,CAAC,MAAM,iBACN,gBAAAA,KAAC,UAAK,WAAU,4FAA2F,sBAE3G;AAAA,QAEF,gBAAAC,MAAC,UAAK,WAAU,0EACb;AAAA,iBAAO,gBAAAD,KAAC,UAAM,eAAI;AAAA,UAClB,SAAS,gBAAAA,KAAC,UAAM,iBAAM;AAAA,WACzB;AAAA;AAAA;AAAA,EACF;AAEJ;AAYO,SAAS,YAAY,EAAE,OAAO,UAAU,QAAQ,SAAS,qBAAqB,mBAAmB,eAAe,cAAc,GAAqB;AACxJ,QAAM,CAAC,MAAM,OAAO,IAAI,SAAS,KAAK;AACtC,QAAM,CAAC,OAAO,QAAQ,IAAI,SAAS,EAAE;AACrC,QAAM,EAAE,cAAc,aAAa,IAAI,WAAW,MAAM,OAAO;AAC/D,QAAM,WAAW,OAAyB,IAAI;AAC9C,QAAM,aAAa,OAAuB,IAAI;AAE9C,YAAU,MAAM;AACd,QAAI,KAAM,UAAS,SAAS,MAAM;AAAA,EACpC,GAAG,CAAC,IAAI,CAAC;AAOT,YAAU,MAAM;AACd,QAAI,CAAC,KAAM;AACX,UAAM,KAAK,WAAW;AACtB,QAAI,CAAC,GAAI;AACT,UAAM,gBAAgB,GAAG,sBAAsB,EAAE,SAAS,OAAO,aAAa;AAC9E,OAAG,MAAM,YAAY,gBAAgB,IAAI,eAAe,KAAK,KAAK,aAAa,CAAC,QAAQ;AAAA,EAC1F,GAAG,CAAC,IAAI,CAAC;AAET,QAAM,WAAW,OAAO,KAAK,CAAC,MAAM,EAAE,OAAO,KAAK;AAElD,QAAM,WAAW,QAAQ,MAAM;AAC7B,UAAM,IAAI,MAAM,KAAK,EAAE,YAAY;AACnC,QAAI,CAAC,EAAG,QAAO;AACf,WAAO,OAAO;AAAA,MACZ,CAAC,MACC,EAAE,GAAG,YAAY,EAAE,SAAS,CAAC,KAC7B,EAAE,KAAK,YAAY,EAAE,SAAS,CAAC,MAC9B,EAAE,aAAa,YAAY,KAAK,IAAI,SAAS,CAAC,KAC/C,EAAE,SAAS,YAAY,EAAE,SAAS,CAAC;AAAA,IACvC;AAAA,EACF,GAAG,CAAC,QAAQ,KAAK,CAAC;AAElB,QAAM,WAAW,QAAQ,MAAM;AAC7B,UAAM,aAAa,gBAAgB,CAAC,MAAoB,cAAc,MAAM,CAAC,IAAI,MAAM;AACvF,UAAM,WAAW,gBAAgB,OAAO,OAAO,UAAU,IAAI,CAAC;AAC9D,UAAM,cAAc,OAAO,OAAO,CAAC,MAAM,EAAE,YAAY,CAAC,WAAW,CAAC,CAAC;AACrE,UAAM,aAAiE,CAAC;AACxE,eAAW,KAAK,QAAQ;AACtB,UAAI,EAAE,YAAY,WAAW,CAAC,EAAG;AACjC,YAAM,OAAO,WAAW,WAAW,SAAS,CAAC;AAC7C,UAAI,QAAQ,KAAK,aAAa,EAAE,SAAU,MAAK,MAAM,KAAK,CAAC;AAAA,UACtD,YAAW,KAAK,EAAE,UAAU,EAAE,UAAU,OAAO,CAAC,CAAC,EAAE,CAAC;AAAA,IAC3D;AACA,WAAO,EAAE,UAAU,aAAa,WAAW;AAAA,EAC7C,GAAG,CAAC,QAAQ,aAAa,CAAC;AAE1B,QAAM,SAAS,CAAC,OAAe;AAC7B,aAAS,EAAE;AACX,YAAQ,KAAK;AACb,aAAS,EAAE;AAAA,EACb;AAEA,SACE,gBAAAC,MAAC,SAAI,KAAK,cAAc,WAAU,wBAChC;AAAA,oBAAAA;AAAA,MAAC;AAAA;AAAA,QACC,MAAK;AAAA,QACJ,GAAG;AAAA,QACJ,SAAS,MAAM,QAAQ,CAAC,IAAI;AAAA,QAC5B,WAAU;AAAA,QAET;AAAA,qBAAY,sBAAsB,oBAAoB,SAAS,QAAQ,IAAI,gBAAAD,KAAC,gBAAa,UAAU,SAAS,UAAU,MAAM,IAAI,IAAM,gBAAAA,KAAC,gBAAa,WAAU,qCAAoC;AAAA,UACnM,gBAAAA,KAAC,UAAK,WAAU,0BAA0B,oBAAU,QAAQ,OAAM;AAAA,UAClE,gBAAAA,KAAC,eAAY,WAAU,qCAAoC;AAAA;AAAA;AAAA,IAC7D;AAAA,IAEC,QACC,gBAAAC,MAAC,SAAI,KAAK,YAAY,WAAU,8IAC9B;AAAA,sBAAAD,KAAC,SAAI,WAAU,oCACb,0BAAAC,MAAC,SAAI,WAAU,mFACb;AAAA,wBAAAD,KAAC,eAAY,WAAU,qCAAoC;AAAA,QAC3D,gBAAAA;AAAA,UAAC;AAAA;AAAA,YACC,KAAK;AAAA,YACL,MAAK;AAAA,YACL,OAAO;AAAA,YACP,UAAU,CAAC,MAAM,SAAS,EAAE,OAAO,KAAK;AAAA,YACxC,aAAY;AAAA,YACZ,WAAU;AAAA;AAAA,QACZ;AAAA,SACF,GACF;AAAA,MACA,gBAAAC,MAAC,SAAI,WAAU,0CACZ;AAAA,mBAAW,gBAAAD,KAAC,SAAI,WAAU,uDAAsD,+BAAiB;AAAA,QACjG,CAAC,WAAW,YACX,gBAAAC,MAAA,YACG;AAAA,mBAAS,WAAW,KACnB,gBAAAD,KAAC,SAAI,WAAU,uDAAsD,yCAA2B;AAAA,UAEjG,SAAS,IAAI,CAAC,MACb,gBAAAA,KAAC,YAAoB,OAAO,GAAG,UAAU,EAAE,OAAO,OAAO,UAAU,MAAM,OAAO,EAAE,EAAE,GAAG,uBAAxE,EAAE,EAAgH,CAClI;AAAA,WACH;AAAA,QAED,CAAC,WAAW,CAAC,YAAY,OAAO,WAAW,KAC1C,gBAAAA,KAAC,SAAI,WAAU,uDAAsD,iCAAmB;AAAA,QAEzF,CAAC,WAAW,CAAC,YAAY,OAAO,SAAS,KACxC,gBAAAC,MAAA,YACG;AAAA,2BAAiB,SAAS,SAAS,SAAS,KAC3C,gBAAAA,MAAA,YACE;AAAA,4BAAAD,KAAC,iBAAe,wBAAc,OAAM;AAAA,YACnC,SAAS,SAAS,IAAI,CAAC,MACtB,gBAAAA,KAAC,YAAoB,OAAO,GAAG,UAAU,EAAE,OAAO,OAAO,UAAU,MAAM,OAAO,EAAE,EAAE,GAAG,uBAAxE,EAAE,EAAgH,CAClI;AAAA,aACH;AAAA,UAED,SAAS,YAAY,SAAS,KAC7B,gBAAAC,MAAA,YACE;AAAA,4BAAAD,KAAC,iBAAe,4BAAiB;AAAA,YAChC,SAAS,YAAY,IAAI,CAAC,MACzB,gBAAAA,KAAC,YAAoB,OAAO,GAAG,UAAU,EAAE,OAAO,OAAO,UAAU,MAAM,OAAO,EAAE,EAAE,GAAG,uBAAxE,EAAE,EAAgH,CAClI;AAAA,aACH;AAAA,UAED,SAAS,WAAW,IAAI,CAAC,MACxB,gBAAAC,MAAC,SACC;AAAA,4BAAAD,KAAC,iBAAe,YAAE,UAAS;AAAA,YAC1B,EAAE,MAAM,IAAI,CAAC,MACZ,gBAAAA,KAAC,YAAoB,OAAO,GAAG,UAAU,EAAE,OAAO,OAAO,UAAU,MAAM,OAAO,EAAE,EAAE,GAAG,uBAAxE,EAAE,EAAgH,CAClI;AAAA,eAJO,EAAE,QAKZ,CACD;AAAA,WACH;AAAA,SAEJ;AAAA,OACF;AAAA,KAEJ;AAEJ;AAeO,IAAM,wBAAgD;AAAA,EAC3D,EAAE,IAAI,OAAO,OAAO,MAAM;AAAA,EAC1B,EAAE,IAAI,OAAO,OAAO,QAAQ;AAAA,EAC5B,EAAE,IAAI,UAAU,OAAO,WAAW;AAAA,EAClC,EAAE,IAAI,QAAQ,OAAO,WAAW;AAClC;AAoBO,SAAS,aAAa,EAAE,OAAO,UAAU,SAAS,uBAAuB,QAAQ,WAAW,GAAsB;AACvH,QAAM,CAAC,MAAM,OAAO,IAAI,SAAS,KAAK;AACtC,QAAM,EAAE,cAAc,aAAa,IAAI,WAAW,MAAM,OAAO;AAC/D,QAAM,WAAW,OAAO,KAAK,CAAC,MAAM,EAAE,OAAO,KAAK,KAAK,OAAO,CAAC,KAAK,OAAO,CAAC;AAE5E,SACE,gBAAAC,MAAC,SAAI,KAAK,cAAc,WAAU,wBAChC;AAAA,oBAAAA;AAAA,MAAC;AAAA;AAAA,QACC,MAAK;AAAA,QACJ,GAAG;AAAA,QACJ,SAAS,MAAM,QAAQ,CAAC,IAAI;AAAA,QAC5B,OAAO,QAAQ,GAAG,KAAK,wDAAmD;AAAA,QAC1E,WAAU;AAAA,QAEV;AAAA,0BAAAD,KAAC,gBAAa,WAAU,qCAAoC;AAAA,UAC5D,gBAAAC,MAAC,UACE;AAAA,oBAAQ,gBAAAA,MAAC,UAAK,WAAU,yBAAyB;AAAA;AAAA,cAAM;AAAA,eAAE,IAAU;AAAA,YACnE,UAAU;AAAA,aACb;AAAA,UACA,gBAAAD,KAAC,eAAY,WAAU,qCAAoC;AAAA;AAAA;AAAA,IAC7D;AAAA,IACC,QACC,gBAAAA,KAAC,SAAI,MAAK,QAAO,WAAU,oHACxB,iBAAO,IAAI,CAAC,MACX,gBAAAA;AAAA,MAAC;AAAA;AAAA,QAEC,MAAK;AAAA,QACL,MAAK;AAAA,QACL,gBAAc,EAAE,OAAO;AAAA,QACvB,SAAS,MAAM;AACb,mBAAS,EAAE,EAAE;AACb,kBAAQ,KAAK;AAAA,QACf;AAAA,QACA,WAAW,2FAA2F,oBAAoB,IACxH,EAAE,OAAO,QAAQ,8BAA8B,oBACjD;AAAA,QAEC,YAAE;AAAA;AAAA,MAZE,EAAE;AAAA,IAaT,CACD,GACH;AAAA,KAEJ;AAEJ;;;ACzZA,SAAS,aAAa,aAAAE,YAAW,WAAAC,UAAS,UAAAC,SAAQ,YAAAC,iBAAgB;AAgGlE,SAAS,QAAgB;AACvB,QAAM,eAAe,WAAW;AAChC,MAAI,OAAO,cAAc,eAAe,WAAY,QAAO,aAAa,WAAW;AACnF,SAAO,OAAO,KAAK,IAAI,CAAC,IAAI,KAAK,OAAO,EAAE,SAAS,EAAE,EAAE,MAAM,CAAC,CAAC;AACjE;AAOA,SAAS,WAAW,MAAc,OAA4B;AAC5D,MAAI,CAAC,MAAM,IAAI,IAAI,EAAG,QAAO;AAC7B,QAAM,MAAM,KAAK,YAAY,GAAG;AAChC,QAAM,OAAO,MAAM,IAAI,KAAK,MAAM,GAAG,GAAG,IAAI;AAC5C,QAAM,MAAM,MAAM,IAAI,KAAK,MAAM,GAAG,IAAI;AACxC,MAAI,IAAI;AACR,MAAI,YAAY,GAAG,IAAI,IAAI,CAAC,GAAG,GAAG;AAClC,SAAO,MAAM,IAAI,SAAS,GAAG;AAC3B,SAAK;AACL,gBAAY,GAAG,IAAI,IAAI,CAAC,GAAG,GAAG;AAAA,EAChC;AACA,SAAO;AACT;AAOA,SAAS,YAAY,MAAkC;AACrD,SAAO,KAAK,WAAW,QAAQ,IAAI,UAAU;AAC/C;AAOA,SAAS,mBAAmB,MAAY,QAAyB;AAC/D,QAAM,WAAW,OAAO,MAAM,GAAG,EAAE,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO,CAAC,MAAM,EAAE,SAAS,CAAC;AAClF,MAAI,SAAS,WAAW,EAAG,QAAO;AAClC,QAAM,OAAO,KAAK,KAAK,YAAY;AACnC,QAAM,QAAQ,KAAK,QAAQ,IAAI,YAAY;AAC3C,SAAO,SAAS,KAAK,CAAC,YAAY;AAChC,UAAM,QAAQ,QAAQ,YAAY;AAClC,QAAI,MAAM,WAAW,GAAG,EAAG,QAAO,KAAK,SAAS,KAAK;AACrD,QAAI,MAAM,SAAS,IAAI,EAAG,QAAO,KAAK,WAAW,MAAM,MAAM,GAAG,EAAE,CAAC;AACnE,WAAO,SAAS;AAAA,EAClB,CAAC;AACH;AAMA,eAAe,iBAAiB,KAAgC;AAC9D,QAAM,SAAS,MAAM,IAAI,KAAK,EAAE,MAAM,MAAM,IAAI;AAChD,MAAI,UAAU,OAAO,WAAW,YAAY,WAAW,QAAQ;AAC7D,UAAM,QAAS,OAA8B;AAC7C,QAAI,OAAO,UAAU,YAAY,MAAO,QAAO;AAC/C,QAAI,SAAS,OAAO,UAAU,YAAY,aAAa,OAAO;AAC5D,YAAM,UAAW,MAA+B;AAChD,UAAI,OAAO,YAAY,YAAY,QAAS,QAAO;AAAA,IACrD;AAAA,EACF;AACA,SAAO,kBAAkB,IAAI,MAAM;AACrC;AAOA,IAAM,2BAA2B;AAa1B,SAAS,uBACd,SAC8B;AAK9B,QAAM,aAAaC,QAAO,OAAO;AACjC,aAAW,UAAU;AAErB,QAAM,CAAC,QAAQ,cAAc,IAAIC,UAA6B,CAAC,CAAC;AAIhE,QAAM,YAAYD,QAA2B,CAAC,CAAC;AAC/C,QAAM,iBAAiBA,QAAqC,oBAAI,IAAI,CAAC;AAIrE,QAAM,YAAY;AAAA,IAChB,CAAC,YAAqF;AACpF,YAAM,OACJ,OAAO,YAAY,aACd,QAA6D,UAAU,OAAO,IAC/E;AACN,gBAAU,UAAU;AACpB,qBAAe,IAAI;AAAA,IACrB;AAAA,IACA,CAAC;AAAA,EACH;AAEA,QAAM,SAAS;AAAA,IACb,OAAO,IAAY,MAAY,SAAiB;AAC9C,YAAM,OAAO,WAAW;AACxB;AAAA,QAAU,CAAC,SACT,KAAK,IAAI,CAAC,MAAO,EAAE,OAAO,KAAK,EAAE,GAAG,GAAG,QAAQ,aAAa,cAAc,OAAU,IAAI,CAAE;AAAA,MAC5F;AACA,YAAM,aAAa,IAAI,gBAAgB;AACvC,qBAAe,QAAQ,IAAI,IAAI,UAAU;AACzC,YAAM,OAAO,IAAI,SAAS;AAC1B,WAAK,OAAO,QAAQ,MAAM,IAAI;AAE9B,YAAM,UAAU,KAAK,qBACjB,KAAK,mBAAmB,EAAE,MAAM,MAAM,KAAK,CAAC,IAC5C,KAAK,YACH,EAAE,KAAK,KAAK,UAAU,IACtB;AAEN,UAAI,CAAC,SAAS;AACZ;AAAA,UAAU,CAAC,SACT,KAAK;AAAA,YAAI,CAAC,MACR,EAAE,OAAO,KAAK,EAAE,GAAG,GAAG,QAAQ,SAAS,cAAc,yBAAyB,IAAI;AAAA,UACpF;AAAA,QACF;AACA,aAAK,UAAU,wBAAwB;AACvC,uBAAe,QAAQ,OAAO,EAAE;AAChC;AAAA,MACF;AAEA,UAAI;AACF,cAAM,MAAM,MAAM,MAAM,QAAQ,KAAK;AAAA,UACnC,QAAQ;AAAA,UACR,aAAa;AAAA,UACb,GAAG,QAAQ;AAAA,UACX,MAAM;AAAA,UACN,QAAQ,WAAW;AAAA,QACrB,CAAC;AACD,YAAI,CAAC,IAAI,IAAI;AACX,gBAAM,UAAU,MAAM,iBAAiB,GAAG;AAC1C;AAAA,YAAU,CAAC,SACT,KAAK,IAAI,CAAC,MAAO,EAAE,OAAO,KAAK,EAAE,GAAG,GAAG,QAAQ,SAAS,cAAc,QAAQ,IAAI,CAAE;AAAA,UACtF;AACA,eAAK,UAAU,OAAO;AACtB;AAAA,QACF;AACA,cAAM,OAAQ,MAAM,IAAI,KAAK;AAC7B,cAAM,WAAW,KAAK,QAAQ,CAAC;AAC/B,YAAI,CAAC,UAAU;AACb,gBAAM,UAAU;AAChB;AAAA,YAAU,CAAC,SACT,KAAK,IAAI,CAAC,MAAO,EAAE,OAAO,KAAK,EAAE,GAAG,GAAG,QAAQ,SAAS,cAAc,QAAQ,IAAI,CAAE;AAAA,UACtF;AACA,eAAK,UAAU,OAAO;AACtB;AAAA,QACF;AACA;AAAA,UAAU,CAAC,SACT,KAAK,IAAI,CAAC,MAAO,EAAE,OAAO,KAAK,EAAE,GAAG,GAAG,QAAQ,SAAS,WAAW,SAAS,IAAI,CAAE;AAAA,QACpF;AAAA,MACF,SAAS,KAAK;AACZ,YAAK,IAAc,SAAS,aAAc;AAC1C,cAAM,UACJ,eAAe,SAAS,IAAI,UAAU,IAAI,UAAU;AACtD;AAAA,UAAU,CAAC,SACT,KAAK,IAAI,CAAC,MAAO,EAAE,OAAO,KAAK,EAAE,GAAG,GAAG,QAAQ,SAAS,cAAc,QAAQ,IAAI,CAAE;AAAA,QACtF;AACA,aAAK,UAAU,OAAO;AAAA,MACxB,UAAE;AACA,uBAAe,QAAQ,OAAO,EAAE;AAAA,MAClC;AAAA,IACF;AAAA,IACA,CAAC,SAAS;AAAA,EACZ;AAEA,QAAM,WAAW;AAAA,IACf,OAAO,UAA6B;AAClC,YAAM,OAAO,WAAW;AACxB,YAAME,WAAU,KAAK,WAAW;AAChC,UAAI,CAACA,UAAS;AACZ,aAAK,WAAW,0BAA0B;AAC1C;AAAA,MACF;AAEA,YAAM,SAAS,KAAK,UAAU;AAC9B,YAAM,WAAW,KAAK,QAAQ,YAAY;AAC1C,YAAM,iBAAiB,KAAK,QAAQ,kBAAkB;AACtD,YAAM,eAAe,KAAK,QAAQ,gBAAgB;AAClD,YAAM,gBAAgB,KAAK,QAAQ,iBAAiB;AACpD,YAAM,eAAe,KAAK,gBAAiB,CAAC,SAAS,MAAM;AAE3D,YAAM,OAAO,MAAM,QAAQ,KAAK,IAAI,QAAQ,MAAM,KAAK,KAAK;AAK5D,YAAM,eAAe,UAAU,QAAQ;AACvC,YAAM,gBAAwB,CAAC;AAC/B,iBAAW,QAAQ,MAAM;AACvB,YAAI,CAAC,mBAAmB,MAAM,MAAM,GAAG;AACrC,eAAK,WAAW,IAAI,KAAK,IAAI,mCAAmC,MAAM,MAAM,IAAI;AAChF;AAAA,QACF;AACA,YAAI,eAAe,cAAc,UAAU,UAAU;AACnD,eAAK,WAAW,IAAI,KAAK,IAAI,8BAAyB,QAAQ,mCAAmC,IAAI;AACrG;AAAA,QACF;AACA,sBAAc,KAAK,IAAI;AAAA,MACzB;AAMA,YAAM,eAAuB,CAAC;AAC9B,iBAAW,QAAQ,eAAe;AAChC,cAAM,QAAQ,IAAI,WAAW,MAAM,KAAK,YAAY,CAAC;AACrD,cAAM,QAAQ,YAAY,KAAK;AAC/B,cAAM,YAAY,oBAAoB,KAAK,MAAM,KAAK;AACtD,YAAI,CAAC,UAAU,WAAW;AACxB,eAAK,WAAW,UAAU,SAAS,IAAI;AACvC;AAAA,QACF;AACA,cAAM,QAAQ,MAAM,SAAS,iBAAiB;AAC9C,YAAI,KAAK,OAAO,OAAO;AACrB,eAAK,WAAW,2BAA2B,KAAK,MAAM,KAAK,MAAM,KAAK,GAAG,IAAI;AAC7E;AAAA,QACF;AACA,cAAM,YAAY,MAAM,QAAQ,KAAK,QAAQ;AAC7C,cAAM,OAAO,YAAY,SAAS;AAClC,YAAI,CAAC,aAAa,SAAS,IAAI,GAAG;AAChC,eAAK,WAAW,IAAI,KAAK,IAAI,UAAU,IAAI,0CAA0C,IAAI;AACzF;AAAA,QACF;AACA,qBAAa,KAAK,IAAI;AAAA,MACxB;AAIA,YAAM,WAAmB,CAAC;AAC1B,UAAI,aAAa,UAAU,QAAQ,OAAO,CAAC,OAAO,MAAM,QAAQ,EAAE,MAAM,CAAC;AACzE,iBAAW,QAAQ,cAAc;AAC/B,cAAM,iBAAiB,aAAa,KAAK;AACzC,YAAI,iBAAiB,eAAe;AAClC,eAAK,WAAW,gCAAgC,gBAAgB,aAAa,GAAG,IAAI;AACpF;AAAA,QACF;AACA,iBAAS,KAAK,IAAI;AAClB,qBAAa;AAAA,MACf;AACA,UAAI,SAAS,WAAW,EAAG;AAI3B,YAAM,QAAQ,IAAI,IAAI,UAAU,QAAQ,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC;AAC1D,YAAM,UAA8B,SAAS,IAAI,CAAC,SAAS;AACzD,cAAM,OAAO,WAAW,2BAA2B,KAAK,IAAI,GAAG,KAAK;AACpE,cAAM,IAAI,IAAI;AACd,eAAO;AAAA,UACL,IAAI,MAAM;AAAA,UACV;AAAA,UACA;AAAA,UACA,MAAM,KAAK;AAAA,UACX,QAAQ;AAAA,UACR,YAAY,KAAK,KAAK,WAAW,QAAQ,IAAI,IAAI,gBAAgB,IAAI,IAAI;AAAA,QAC3E;AAAA,MACF,CAAC;AACD,gBAAU,CAAC,SAAS,CAAC,GAAG,MAAM,GAAG,OAAO,CAAC;AACzC,iBAAW,SAAS,QAAS,MAAK,OAAO,MAAM,IAAI,MAAM,MAAM,MAAM,IAAI;AAAA,IAC3E;AAAA,IACA,CAAC,WAAW,MAAM;AAAA,EACpB;AAEA,QAAM,QAAQ;AAAA,IACZ,CAAC,OAAe;AACd,YAAM,QAAQ,UAAU,QAAQ,KAAK,CAAC,MAAM,EAAE,OAAO,EAAE;AACvD,UAAI,CAAC,MAAO;AACZ,WAAK,OAAO,MAAM,IAAI,MAAM,MAAM,MAAM,IAAI;AAAA,IAC9C;AAAA,IACA,CAAC,MAAM;AAAA,EACT;AAEA,QAAM,mBAAmB;AAAA,IACvB,CAAC,OAAe;AACd,qBAAe,QAAQ,IAAI,EAAE,GAAG,MAAM;AACtC,qBAAe,QAAQ,OAAO,EAAE;AAChC,YAAM,QAAQ,UAAU,QAAQ,KAAK,CAAC,MAAM,EAAE,OAAO,EAAE;AACvD,UAAI,OAAO,WAAY,KAAI,gBAAgB,MAAM,UAAU;AAC3D,gBAAU,CAAC,SAAS,KAAK,OAAO,CAAC,MAAM,EAAE,OAAO,EAAE,CAAC;AAAA,IACrD;AAAA,IACA,CAAC,SAAS;AAAA,EACZ;AAEA,QAAM,QAAQ,YAAY,MAAM;AAC9B,eAAW,cAAc,eAAe,QAAQ,OAAO,EAAG,YAAW,MAAM;AAC3E,mBAAe,QAAQ,MAAM;AAC7B,eAAW,SAAS,UAAU,SAAS;AACrC,UAAI,MAAM,WAAY,KAAI,gBAAgB,MAAM,UAAU;AAAA,IAC5D;AACA,cAAU,CAAC,CAAC;AAAA,EACd,GAAG,CAAC,SAAS,CAAC;AAEd,EAAAC;AAAA,IACE,MAAM,MAAM;AACV,iBAAW,cAAc,eAAe,QAAQ,OAAO,EAAG,YAAW,MAAM;AAC3E,qBAAe,QAAQ,MAAM;AAC7B,iBAAW,SAAS,UAAU,SAAS;AACrC,YAAI,MAAM,WAAY,KAAI,gBAAgB,MAAM,UAAU;AAAA,MAC5D;AAAA,IACF;AAAA,IACA,CAAC;AAAA,EACH;AAEA,QAAM,gBAAgBC;AAAA,IACpB,MACE,OAAO,IAAI,CAAC,OAAO;AAAA,MACjB,IAAI,EAAE;AAAA,MACN,MAAM,EAAE;AAAA,MACR,MAAM,EAAE;AAAA,MACR,MAAM;AAAA,MACN,QAAQ,EAAE;AAAA,IACZ,EAAE;AAAA,IACJ,CAAC,MAAM;AAAA,EACT;AAEA,QAAM,aAAaA;AAAA,IACjB,MACE,OACG,OAAO,CAAC,MAAkE,EAAE,WAAW,WAAW,CAAC,CAAC,EAAE,SAAS,EAC/G,IAAI,CAAC,MAAM,EAAE,SAAS;AAAA,IAC3B,CAAC,MAAM;AAAA,EACT;AAEA,QAAM,aAAaA;AAAA,IACjB,MAAM,OAAO,KAAK,CAAC,MAAM,EAAE,WAAW,aAAa,EAAE,WAAW,WAAW;AAAA,IAC3E,CAAC,MAAM;AAAA,EACT;AACA,QAAM,WAAWA,SAAQ,MAAM,OAAO,KAAK,CAAC,MAAM,EAAE,WAAW,OAAO,GAAG,CAAC,MAAM,CAAC;AACjF,QAAM,UAAU,QAAQ,WAAW;AACnC,QAAM,cAAc,CAAC,UACjB,6BACA,aACE,oCACA,WACE,sCACA;AAER,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;;;AC/cA,SAAS,WAAAC,UAAS,YAAAC,iBAAgC;AAkC5C,gBAAAC,MAOF,QAAAC,aAPE;AAvBN,IAAM,iBAAmD;AAAA,EACvD,UAAU;AAAA,EACV,eAAe;AAAA,EACf,OAAO;AAAA,EACP,aAAa;AAAA,EACb,KAAK;AAAA,EACL,kBAAkB;AAAA,EAClB,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,OAAO;AAAA,EACP,IAAI;AAAA,EACJ,UAAU;AAAA,EACV,KAAK;AAAA,EACL,YAAY;AACd;AAEA,SAAS,aAAa,GAAoB;AACxC,SAAO,eAAe,CAAC,KAAK;AAC9B;AAEA,SAASC,aAAY,EAAE,UAAU,GAA2B;AAC1D,SACE,gBAAAF,KAAC,SAAI,WAAsB,SAAQ,aAAY,MAAK,QAAO,QAAO,gBAAe,aAAY,KAAI,eAAc,SAAQ,gBAAe,SAAQ,eAAW,MACvJ,0BAAAA,KAAC,UAAK,GAAE,gBAAe,GACzB;AAEJ;AAEA,SAAS,UAAU,EAAE,UAAU,GAA2B;AACxD,SACE,gBAAAC,MAAC,SAAI,WAAsB,SAAQ,aAAY,MAAK,QAAO,QAAO,gBAAe,aAAY,KAAI,eAAc,SAAQ,gBAAe,SAAQ,eAAW,MACvJ;AAAA,oBAAAD,KAAC,YAAO,IAAG,MAAK,IAAG,MAAK,GAAE,KAAI;AAAA,IAC9B,gBAAAA,KAAC,UAAK,GAAE,knBAAinB;AAAA,KAC3nB;AAEJ;AAGA,IAAM,aACJ;AAGF,SAAS,cAAc;AAAA,EACrB;AAAA,EACA;AAAA,EACA;AACF,GAIG;AACD,QAAM,CAAC,MAAM,OAAO,IAAIG,UAAS,KAAK;AACtC,QAAM,EAAE,cAAc,aAAa,IAAI,WAAW,MAAM,OAAO;AAC/D,QAAM,UAAU,aAAc,OAAO,KAAK,cAAc;AACxD,SACE,gBAAAF,MAAC,SAAI,KAAK,cAAc,WAAU,wBAChC;AAAA,oBAAAA;AAAA,MAAC;AAAA;AAAA,QACC,MAAK;AAAA,QACJ,GAAG;AAAA,QACJ,SAAS,MAAM,QAAQ,CAAC,IAAI;AAAA,QAC5B,OAAM;AAAA,QACN,WAAW,iLAAiL,UAAU;AAAA,QAEtM;AAAA,0BAAAD,KAAC,UAAK,WAAU,YAAY,uBAAa,KAAK,GAAE;AAAA,UAChD,gBAAAA,KAACE,cAAA,EAAY,WAAU,qCAAoC;AAAA;AAAA;AAAA,IAC7D;AAAA,IACC,QACC,gBAAAF,KAAC,SAAI,MAAK,QAAO,WAAU,6IACxB,kBAAQ,IAAI,CAAC,MACZ,gBAAAA;AAAA,MAAC;AAAA;AAAA,QAEC,MAAK;AAAA,QACL,MAAK;AAAA,QACL,gBAAc,MAAM;AAAA,QACpB,SAAS,MAAM;AACb,mBAAS,CAAC;AACV,kBAAQ,KAAK;AAAA,QACf;AAAA,QACA,WAAW,8EAA8E,UAAU,IACjG,MAAM,QAAQ,8BAA8B,oBAC9C;AAAA,QAEC,uBAAa,CAAC;AAAA;AAAA,MAZV;AAAA,IAaP,CACD,GACH;AAAA,KAEJ;AAEJ;AAiCA,SAAS,oBAAoB,OAAkC;AAC7D,QAAM,EAAE,OAAO,QAAQ,SAAS,eAAe,gBAAgB,IAAI;AACnE,QAAM,eAAeI,SAAQ,MAAM,OAAO,IAAI,CAAC,MAAM,EAAE,EAAE,GAAG,CAAC,MAAM,CAAC;AAEpE,QAAM,UAAU,CAAC,SAAiB;AAChC,kBAAc,IAAI;AAClB,UAAM,cAAc,mBAAmB,SAAS,IAAI;AACpD,QAAI,gBAAgB,QAAS,iBAAgB,WAAW;AAAA,EAC1D;AAEA,QAAM,YAAY,CAAC,SAAkB;AACnC,oBAAgB,IAAI;AACpB,UAAM,UAAU,mBAAmB,MAAM,OAAO,YAAY;AAC5D,QAAI,YAAY,MAAO,eAAc,OAAO;AAAA,EAC9C;AAEA,SAAO,EAAE,SAAS,UAAU;AAC9B;AAEO,SAAS,qBAAqB,OAAkC;AACrE,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,SAAS;AAAA,IACT,cAAc;AAAA,IACd;AAAA,IACA;AAAA,EACF,IAAI;AACJ,QAAM,EAAE,SAAS,UAAU,IAAI,oBAAoB,KAAK;AACxD,QAAM,CAAC,MAAM,OAAO,IAAID,UAAS,KAAK;AACtC,QAAM,EAAE,cAAc,YAAY,aAAa,IAAI,WAAW,MAAM,OAAO;AAE3E,QAAM,gBAAgB,OAAO,KAAK,CAAC,MAAM,EAAE,OAAO,KAAK;AACvD,QAAM,aAAa,eAAe,qBAAqB;AAEvD,QAAM,cACJ,gBAAAH;AAAA,IAAC;AAAA;AAAA,MACC,OAAO;AAAA,MACP,UAAU;AAAA,MACV;AAAA,MACA,SAAS;AAAA,MACT;AAAA;AAAA,EACF;AAGF,MAAI,WAAW,UAAU;AACvB,WACE,gBAAAC,MAAC,SAAI,WAAW,6BAA6B,aAAa,EAAE,IACzD;AAAA;AAAA,MACA,eACC,gBAAAD,KAAC,iBAAc,OAAO,SAAS,UAAU,WAAW,WAAW,oBAAoB;AAAA,MAEpF,cAAc,gBAAAA,KAAC,gBAAa,OAAO,QAAQ,UAAU,gBAAgB;AAAA,OACxE;AAAA,EAEJ;AAGA,QAAM,cAAc,eAAe;AACnC,SACE,gBAAAC,MAAC,SAAI,WAAW,6BAA6B,aAAa,EAAE,IACzD;AAAA;AAAA,IACA,eACC,gBAAAA,MAAC,SAAI,KAAK,YAAY,WAAU,wBAC9B;AAAA,sBAAAD;AAAA,QAAC;AAAA;AAAA,UACC,MAAK;AAAA,UACJ,GAAG;AAAA,UACJ,SAAS,MAAM,QAAQ,CAAC,IAAI;AAAA,UAC5B,OAAM;AAAA,UACN,WAAW,iKAAiK,UAAU;AAAA,UACtL,cAAY,OAAO,SAAS;AAAA,UAE5B,0BAAAA,KAAC,aAAU,WAAU,WAAU;AAAA;AAAA,MACjC;AAAA,MACC,QACC,gBAAAC,MAAC,SAAI,WAAU,8GACZ;AAAA,uBACC,gBAAAA,MAAC,SAAI,WAAU,eACb;AAAA,0BAAAD,KAAC,OAAE,WAAU,uCAAsC,2BAAa;AAAA,UAChE,gBAAAA,KAAC,iBAAc,OAAO,SAAS,UAAU,WAAW,WAAW,oBAAoB;AAAA,UACnF,gBAAAA,KAAC,OAAE,WAAU,kDAAiD,8FAE9D;AAAA,WACF;AAAA,QAED,cACC,gBAAAC,MAAC,SAAI,WAAU,eACb;AAAA,0BAAAD,KAAC,OAAE,WAAU,uCAAsC,sBAAQ;AAAA,UAC3D,gBAAAA,KAAC,gBAAa,OAAO,QAAQ,UAAU,gBAAgB,OAAM,IAAG;AAAA,UAChE,gBAAAA,KAAC,OAAE,WAAU,kDAAiD,6FAE9D;AAAA,WACF;AAAA,SAEJ;AAAA,OAEJ;AAAA,KAEJ;AAEJ;","names":["jsx","jsxs","useEffect","useMemo","useRef","useState","useRef","useState","enabled","useEffect","useMemo","useMemo","useState","jsx","jsxs","ChevronDown","useState","useMemo"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/theme/theme.ts"],"sourcesContent":["/**\n * Typed mirror of tokens.css for runtime/JS theming. The canonical source is\n * tokens.css (`import '@tangle-network/agent-app/styles'`); this module is for\n * apps that compute theme variables in JS, or read color values where CSS\n * custom properties cannot reach — notably Konva canvas render code, which\n * paints to a bitmap and cannot resolve `var(--…)`.\n *\n * Values are shadcn-style HSL channel triples (\"H S% L%\"); wrap with `color()`.\n *\n * The neutral triples are the RESOLVED form of tokens.css's `--neutral-*` ramp\n * (that file states them as `var(--neutral-NN-hsl)`; JS has no cascade to\n * resolve through, so they are inlined here). `tests/theme/tokens-contract.test.ts`\n * resolves the CSS one level and fails if this mirror has drifted.\n */\n\nexport interface AgentAppTheme {\n background: string\n foreground: string\n card: string\n cardForeground: string\n popover: string\n popoverForeground: string\n primary: string\n primaryForeground: string\n secondary: string\n secondaryForeground: string\n muted: string\n mutedForeground: string\n accent: string\n accentForeground: string\n destructive: string\n destructiveForeground: string\n border: string\n input: string\n ring: string\n success: string\n successForeground: string\n warning: string\n warningForeground: string\n /**\n * Warning as TEXT on a warning-TINTED surface, as a channel triple. Optional\n * so a consumer's existing `AgentAppTheme` literal still type-checks;\n * {@link themeToCssVars} falls back to `warningForeground`, which is what the\n * surface used before this token existed.\n */\n warningStrong?: string\n /** Full CSS color (not a triple) — the canvas/scene backdrop. */\n canvasBackdrop: string\n /**\n * Border tiers as FULL CSS colors (they are `color-mix()` results, not\n * triples). Optional so a consumer's existing `AgentAppTheme` literal still\n * type-checks; {@link themeToCssVars} falls back to full-strength\n * `hsl(var(--border))`, which can never erase an edge.\n */\n borderSoft?: string\n /** @see borderSoft */\n cardEdge?: string\n /** Konva render palette — full hex colors the bitmap canvas paints with\n * (it cannot resolve `var(--…)`). NOT emitted by themeToCssVars. */\n canvasRender: CanvasRenderPalette\n}\n\n/**\n * Colors the Konva design-canvas paints directly. Konva renders to a bitmap\n * and cannot read CSS custom properties, so these are full hex strings sourced\n * from the active theme and threaded through the canvas components.\n */\nexport interface CanvasRenderPalette {\n /** Grid line color (GridLayer). */\n grid: string\n /** Grid-snap guide line (SnapGuidesOverlay, kind 'grid'). */\n snapGrid: string\n /** Saved ruler-guide snap line (kind 'guide'). */\n snapGuide: string\n /** Page edge/center snap line (kinds 'page-edge'/'page-center'). */\n snapPage: string\n /** Element edge/center snap line (kinds 'element-edge'/'element-center'). */\n snapElement: string\n /** Transformer border + anchor stroke (SelectionLayer). */\n selectionStroke: string\n /** Transformer anchor fill (SelectionLayer). */\n selectionAnchorFill: string\n /** Video placeholder fill (ElementNode VideoNode). */\n placeholderFill: string\n /** Video placeholder stroke (ElementNode VideoNode). */\n placeholderStroke: string\n /** Broken/loading image placeholder fill (ElementNode ImageNode). */\n brokenFill: string\n /** Broken/loading image placeholder stroke (ElementNode ImageNode). */\n brokenStroke: string\n}\n\n/** Define a light color theme with specific background, foreground, and accent color values */\nexport const lightTheme: AgentAppTheme = {\n background: '0 0% 100%',\n foreground: '240.1 6.8% 5.5%',\n card: '239.6 17.2% 96.5%',\n cardForeground: '240.1 6.8% 5.5%',\n popover: '0 0% 100%',\n popoverForeground: '240.1 6.8% 5.5%',\n primary: '245 62% 57%',\n primaryForeground: '0 0% 100%',\n secondary: '239.6 8% 92.6%',\n secondaryForeground: '239.9 3.8% 10.7%',\n muted: '239.6 8% 92.6%',\n mutedForeground: '239.7 1.3% 38.2%',\n accent: '239.6 8% 92.6%',\n accentForeground: '239.9 3.8% 10.7%',\n destructive: '0 72% 41%',\n destructiveForeground: '0 0% 100%',\n border: '239.6 5.2% 88.7%',\n input: '239.6 5.2% 88.7%',\n ring: '245 62% 57%',\n success: '160 84% 26%',\n successForeground: '0 0% 100%',\n warning: '41 96% 38%',\n warningForeground: '38 92% 12%',\n warningStrong: '41 96% 28%',\n canvasBackdrop: 'hsl(239.6 5.2% 88.7%)',\n borderSoft: 'color-mix(in oklch, hsl(var(--border)) 40%, transparent)',\n cardEdge: 'color-mix(in oklch, hsl(var(--border)) 60%, transparent)',\n canvasRender: {\n grid: '#c0c0c0',\n snapGrid: '#a0a0a0',\n snapGuide: '#3b82f6',\n snapPage: '#f59e0b',\n snapElement: '#f43f5e',\n selectionStroke: '#00a1ff',\n selectionAnchorFill: '#ffffff',\n placeholderFill: '#1f2937',\n placeholderStroke: '#374151',\n brokenFill: '#e5e7eb',\n brokenStroke: '#9ca3af',\n },\n}\n\n/** Define a dark color scheme for the Agent app interface with specific background and foreground hues */\nexport const darkTheme: AgentAppTheme = {\n background: '240.1 6.8% 5.5%',\n foreground: '239.6 8% 92.6%',\n card: '240 4.9% 8%',\n cardForeground: '239.6 8% 92.6%',\n popover: '239.9 3.1% 13.5%',\n popoverForeground: '239.6 8% 92.6%',\n primary: '239 84% 74%',\n // Inverted, not copied from light: white on this fill measures 3.02:1.\n primaryForeground: '240.1 6.8% 5.5%',\n secondary: '239.9 3.8% 10.7%',\n secondaryForeground: '239.6 8% 92.6%',\n muted: '239.9 3.8% 10.7%',\n mutedForeground: '239.6 1.5% 62.5%',\n accent: '239.9 3.8% 10.7%',\n accentForeground: '239.6 8% 92.6%',\n destructive: '348 90% 68%',\n destructiveForeground: '239.9 3.1% 13.5%',\n border: '239.9 3.1% 13.5%',\n input: '239.9 3.8% 10.7%',\n ring: '239 84% 74%',\n success: '160 70% 52%',\n successForeground: '160 84% 10%',\n warning: '40 94% 56%',\n warningForeground: '38 92% 12%',\n warningStrong: '40 94% 56%',\n canvasBackdrop: 'hsl(240.1 6.8% 5.5%)',\n // The inversion: dark keeps both tiers at full strength (see tokens.css).\n borderSoft: 'hsl(var(--border))',\n cardEdge: 'hsl(var(--border))',\n canvasRender: {\n grid: '#3a3a3a',\n snapGrid: '#5a5a5a',\n snapGuide: '#3b82f6',\n snapPage: '#f59e0b',\n snapElement: '#f43f5e',\n selectionStroke: '#00a1ff',\n selectionAnchorFill: '#e5e7eb',\n placeholderFill: '#2a2f3a',\n placeholderStroke: '#3f4654',\n brokenFill: '#262b33',\n brokenStroke: '#4b5563',\n },\n}\n\n/**\n * Wrap a channel triple in `hsl()`; pass through values already in a color form.\n * `oklch(…)` / `oklab(…)` / `color-mix(…)` are recognised because the ramp is\n * authored in oklch and the border tiers are colour mixes — wrapping either in\n * `hsl()` yields an invalid colour that paints as nothing.\n */\nexport function themeColor(value: string): string {\n return /^(hsl|rgb|oklch|oklab|lch|lab|color|color-mix|#)/.test(value) ? value : `hsl(${value})`\n}\n\n/**\n * Map a theme to the full CSS-variable set (shadcn triples + canvas/sequences\n * aliases + canvas surface). Apply at runtime to scope a theme without loading\n * tokens.css: `Object.assign(el.style, themeToCssVars(darkTheme))`.\n */\nexport function themeToCssVars(theme: AgentAppTheme): Record<string, string> {\n return {\n '--background': theme.background,\n '--foreground': theme.foreground,\n '--card': theme.card,\n '--card-foreground': theme.cardForeground,\n '--popover': theme.popover,\n '--popover-foreground': theme.popoverForeground,\n '--primary': theme.primary,\n '--primary-foreground': theme.primaryForeground,\n '--secondary': theme.secondary,\n '--secondary-foreground': theme.secondaryForeground,\n '--muted': theme.muted,\n '--muted-foreground': theme.mutedForeground,\n '--accent': theme.accent,\n '--accent-foreground': theme.accentForeground,\n '--destructive': theme.destructive,\n '--destructive-foreground': theme.destructiveForeground,\n '--border': theme.border,\n '--input': theme.input,\n '--ring': theme.ring,\n '--success': theme.success,\n '--success-foreground': theme.successForeground,\n '--warning': theme.warning,\n '--warning-foreground': theme.warningForeground,\n // Falls back to the pairing the tinted surfaces used before this token\n // existed, so an unset value restores the previous look rather than an\n // unthemed one.\n '--warning-strong': theme.warningStrong ?? theme.warningForeground,\n '--bg-input': `hsl(${theme.card})`,\n '--text-primary': `hsl(${theme.foreground})`,\n '--text-secondary': `hsl(${theme.secondaryForeground})`,\n '--text-muted': `hsl(${theme.mutedForeground})`,\n '--text-danger': `hsl(${theme.destructive})`,\n '--border-default': `hsl(${theme.border})`,\n '--brand-primary': `hsl(${theme.primary})`,\n '--editor-selection-background': `hsl(${theme.primary})`,\n '--editor-selection-foreground': `hsl(${theme.background})`,\n '--canvas-backdrop': theme.canvasBackdrop,\n // Full strength is the safe default: an unset tier can under-draw an edge,\n // never erase one.\n '--border-soft': theme.borderSoft ?? `hsl(${theme.border})`,\n '--card-edge': theme.cardEdge ?? `hsl(${theme.border})`,\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/spend/types.ts","../src/spend/ceiling.ts","../src/spend/reference.ts","../src/spend/reconcile.ts","../src/spend/report.ts"],"sourcesContent":["/**\n * The vocabulary of consumer-side spend verification.\n *\n * Kept in its own module with ZERO imports so a product can type its storage\n * rows and its reconciliation config without pulling in `node:fs` through the\n * CLI half.\n *\n * The model in one paragraph: the platform's ledger is authoritative about what\n * was CHARGED. A product knows something the ledger does not — what it ASKED\n * for. Recording that second view, and diffing it against the first, is what\n * turns a platform billing defect from silent money into an alert. Nothing here\n * lets a product self-certify a charge away; the output is a discrepancy a human\n * disputes.\n */\n\n// ── the product's own view of a box's life ────────────────────────────────────\n\n/**\n * One box, as the PRODUCT understands it. Folded, not an append-only log: a\n * product runs one row per sandbox, and every field below is derived by a\n * monotonic fold (see `foldSpendBoxRecord`) so two concurrent writers cannot\n * produce a wrong answer, only a stale one.\n *\n * Timestamps are epoch ms throughout, matching the platform's own\n * `sandbox_meta.last_started_at`.\n */\nexport interface SpendBoxRecord {\n /** The platform's sandbox id — the join key to every settlement row. */\n readonly sandboxId: string\n /** The product's own tenancy unit, which the platform does not model. */\n readonly workspaceId: string\n /** First moment the product knew this box existed. */\n readonly createdAt: number\n /**\n * The idle timeout the product ASKED the platform for, seconds. This is the\n * width of the grace window between the last thing the product saw and the\n * moment the platform should have stopped billing.\n */\n readonly idleTimeoutSeconds: number\n /**\n * The maximum lifetime the product asked for, seconds, when it asked for one.\n * This is the strongest bound a product holds: the platform destroys the box\n * at `createdAt + maxLifetimeSeconds` regardless of what the product observed,\n * so it caps the ceiling even when nothing else can (see `computeExpectedCeiling`).\n */\n readonly maxLifetimeSeconds: number | null\n /** Latest moment the product OBSERVED the box doing work. */\n readonly lastActivityAt: number\n /**\n * Detached runs dispatched but never observed to finish, by run id.\n *\n * Non-empty means the product genuinely cannot bound this box from its own\n * observations: it handed the platform work and disconnected. The ceiling\n * degrades accordingly rather than pretending to a tightness it did not earn.\n */\n readonly openDetachedRunIds: readonly string[]\n /** When the product knows the box stopped. Cleared by later activity. */\n readonly stoppedAt: number | null\n /** When the product knows the box was deleted. Set once — a deleted id never returns. */\n readonly deletedAt: number | null\n /** Opaque product-column values, written verbatim and never read here. */\n readonly extras?: Record<string, unknown>\n}\n\n/**\n * A fold step. Every field states its own merge rule, so a SQL implementation\n * can apply it in one statement and reach the same record an in-memory\n * read-modify-write reaches.\n */\nexport interface SpendBoxPatch {\n /** Advance `lastActivityAt` to the max of stored and this. Never moves backward. */\n readonly observedActivityAt?: number\n /** Add a run id to `openDetachedRunIds` (set semantics — re-adding is a no-op). */\n readonly openDetachedRunAdd?: string\n /** Remove a run id from `openDetachedRunIds`. Removing an absent id is a no-op. */\n readonly openDetachedRunRemove?: string\n /**\n * Latest-wins. Activity observed AFTER a recorded stop clears it: a box that\n * worked after we thought it stopped is running again, and a stale stop would\n * make the ceiling too tight.\n */\n readonly stoppedAt?: number\n /** Set-once. A later delete observation does not move the first one. */\n readonly deletedAt?: number\n}\n\n// ── what bounds a box's billable time ─────────────────────────────────────────\n\n/** Which fact bounds a box's billable time, weakest last. */\nexport type CeilingBasis =\n /** The product observed deletion. Billing cannot run past a box that is gone. */\n | 'deleted'\n /** The product observed a stop. Billing should have closed there. */\n | 'stopped'\n /** No stop seen, but the platform destroys the box at its max lifetime. */\n | 'max-lifetime'\n /** No stop seen; the platform's idle timer is what should have closed billing. */\n | 'idle-timeout'\n /**\n * An unfinished detached run and no max lifetime — the product cannot bound\n * this box at all, so the ceiling degrades to the reconciliation instant.\n * A finding on this basis is weak evidence and says so.\n */\n | 'open-detached-run'\n\n/** The upper bound on one box's billable duration, and what earned it. */\nexport interface ExpectedCeiling {\n readonly sandboxId: string\n readonly basis: CeilingBasis\n /** The latest instant this box could still have been billable, epoch ms. */\n readonly horizonAt: number\n /** `horizonAt - createdAt + toleranceMs`. The upper bound on billable ms. */\n readonly ceilingMs: number\n readonly toleranceMs: number\n /**\n * False when the basis is `open-detached-run` — the ceiling then rests on the\n * reconciliation instant rather than on anything the product observed, so an\n * overage means the platform billed outside the box's own lifetime, not merely\n * longer than expected.\n */\n readonly bounded: boolean\n}\n\n// ── what the platform actually emitted ────────────────────────────────────────\n\n/**\n * One settled ledger row, in the shape the platform's `credit_transactions`\n * table stores it. The product supplies these through its own fetch (the\n * platform's credit-history API, an export, a mirror) — this package never\n * reaches for them, because the ledger is the counterparty's record and reading\n * it is the product's authenticated business.\n *\n * The product's fetch MUST scope rows to boxes it owns. Products bill to a\n * shared company key, so an unscoped fetch returns every sibling product's\n * settlements and every one of them is a correct `unknown-box` finding.\n */\nexport interface SettlementRow {\n /** The ledger row id, for the dispute. */\n readonly id: string\n /**\n * `sandbox:<kind>:<sandboxId>:<intervalStartMs>` — the platform's idempotency\n * key, and the only place the billed interval's START is recorded.\n */\n readonly referenceId: string | null\n /**\n * Signed nanodollars, exactly as the ledger stores it: negative is a charge,\n * positive is a credit or refund.\n */\n readonly amountNanoUsd: number\n /** `compute` | `refund` | `inference` | … */\n readonly type: string\n /** `sandbox` | `router` | … */\n readonly product: string | null\n /** `sandbox:<sandboxId>` — the platform's aggregation unit. */\n readonly groupKey: string | null\n /** Settlement instant, epoch ms. The product normalizes the stored text. */\n readonly createdAt: number\n readonly description: string | null\n /** Provider at-cost basis, unsigned nanodollars. Null when unattributed. */\n readonly costBasisNanoUsd: number | null\n /**\n * The billed duration, when the product's ledger view exposes it directly.\n * Null is the common case: the platform does not store duration on the row.\n */\n readonly billedMs: number | null\n}\n\n/** The parts of a settlement reference id, once parsed. */\nexport interface SettlementReference {\n /** `stop` | `compute` | `egress` | `gpu-lease` | anything the platform adds. */\n readonly kind: string\n /** For compute kinds, the sandbox id. For `gpu-lease`, the lease id. */\n readonly resourceId: string\n /** The interval's start, epoch ms. Null for kinds that carry no interval. */\n readonly intervalStartMs: number | null\n}\n\n/** How a settled duration was arrived at — every duration finding carries one. */\nexport type BilledDurationBasis =\n /** The ledger row carried the duration. Exact. */\n | 'reported'\n /** `amount ÷ the product's stated hourly rate`. Exact when the rate is right. */\n | 'rate'\n /**\n * `settledAt - intervalStart`. An UPPER bound, not the billed duration: a\n * correct settlement posted late by the platform's durable settlement queue\n * reads longer here than it billed. Findings on this basis say so.\n */\n | 'reference-span'\n /** No basis available — duration rules are skipped for this row. */\n | 'unknown'\n\n// ── findings ──────────────────────────────────────────────────────────────────\n\n/** The checks this reconciler runs. Each is individually skippable, by name. */\nexport type SpendCheckId =\n /** A settlement against a box the product has no record of ever asking for. */\n | 'unknown-box'\n /** A settled duration longer than the product's own upper bound allows. */\n | 'over-ceiling'\n /** A spend window far above the trailing median — the burst shape of a defect. */\n | 'velocity'\n /** The balance the product observes has gone below its floor. */\n | 'negative-balance'\n\nexport const SPEND_CHECKS: readonly SpendCheckId[] = [\n 'unknown-box',\n 'over-ceiling',\n 'velocity',\n 'negative-balance',\n]\n\n/**\n * One discrepancy, with every number the rule compared.\n *\n * Nullable fields are per-check and deliberately present-but-null rather than\n * absent: a reader scanning a JSON dump can tell \"this rule does not measure\n * that\" from \"that measurement is missing\".\n */\nexport interface SpendFinding {\n readonly check: SpendCheckId\n /** What is wrong, in one sentence, with the numbers in it. */\n readonly message: string\n /** What to do about it. A finding without a remedy is a complaint. */\n readonly remedy: string\n readonly sandboxId: string | null\n readonly workspaceId: string | null\n /** The ledger rows that evidence this finding — the dispute's exhibit list. */\n readonly referenceIds: readonly string[]\n /** Nanodollars this finding puts in question, unsigned. */\n readonly settledNanoUsd: number\n /** `over-ceiling` — the duration actually settled, and how that was derived. */\n readonly settledMs: number | null\n readonly durationBasis: BilledDurationBasis | null\n /** `over-ceiling` — the bound it broke, and what earned that bound. */\n readonly ceilingMs: number | null\n readonly overageMs: number | null\n readonly ceilingBasis: CeilingBasis | null\n /** `velocity` — the window, its trailing median, and the ratio between them. */\n readonly windowNanoUsd: number | null\n readonly trailingMedianNanoUsd: number | null\n readonly velocityRatio: number | null\n readonly windowStartAt: number | null\n /** `negative-balance` — the observed balance and the floor it broke. */\n readonly balanceNanoUsd: number | null\n readonly balanceFloorNanoUsd: number | null\n}\n\n/** What one reconciliation pass concluded. */\nexport interface SpendReport {\n /** True when nothing fired. `ok === findings.length === 0`. */\n readonly ok: boolean\n readonly findings: readonly SpendFinding[]\n readonly checksRun: readonly SpendCheckId[]\n /** Rows the pass read, including the ones no rule looked at. */\n readonly rowsExamined: number\n /** Distinct boxes those rows settled against. */\n readonly boxesExamined: number\n /** Total charged across every examined row, unsigned nanodollars. */\n readonly settledNanoUsd: number\n /** Total credited back across every examined row, unsigned nanodollars. */\n readonly creditedNanoUsd: number\n /** The instant the pass treated as \"now\". */\n readonly asOf: number\n}\n","import type { CeilingBasis, ExpectedCeiling, SpendBoxRecord } from './types'\n\n/**\n * Slack allowed between the product's bound and what the platform settled,\n * before an overage is called a discrepancy. 15 minutes.\n *\n * Not a guess: it is the platform's OWN staleness threshold for compute\n * settlement. Its runbook clears an incident when\n * `/health computeSettlement.oldestAgeSeconds` is \"back under 900\" — so 900 s is\n * the age the platform itself treats as normal settlement lag, and anything\n * inside it is drift the platform has already declared acceptable. Below that a\n * product would alert on the platform's ordinary queue behaviour; far above it\n * the tolerance starts eating the signal, because the idle window it must stay\n * well under is 3600 s in every shipped product.\n *\n * It is a caller parameter because a product that asks for a shorter idle\n * timeout must shrink this with it.\n */\nexport const DEFAULT_CEILING_TOLERANCE_MS = 900_000\n\nexport interface ComputeExpectedCeilingOptions {\n /** The instant the reconciliation treats as \"now\", epoch ms. */\n readonly asOf: number\n /** Slack before an overage counts. Default {@link DEFAULT_CEILING_TOLERANCE_MS}. */\n readonly toleranceMs?: number\n}\n\n/**\n * The upper bound on how long one box could honestly have been billable.\n *\n * The whole design constraint is that this must stay an UPPER bound under\n * everything the product cannot see. Three such blind spots exist, and they\n * pull in different directions:\n *\n * - **Platform-side suspends.** The platform can park a box the product never\n * hears about. That only ever REDUCES real billable time, so an upper bound\n * is unaffected and nothing here widens for it.\n * - **Detached runs.** The product dispatches work and disconnects. The box\n * keeps working — and billing — after the last activity the product saw, so\n * `lastActivityAt` understates the truth. An unfinished detached run\n * therefore abandons the activity-based bound entirely rather than reporting\n * a bound it cannot support.\n * - **Reconnects.** A browser or worker re-attaches and work resumes. This\n * needs no special case: a reconnect is recorded as activity, the fold takes\n * the max, and the horizon moves out on its own.\n *\n * The bound that rescues the detached case is `maxLifetimeSeconds`. The platform\n * destroys the box at `createdAt + maxLifetimeSeconds` no matter what anyone\n * observed, so a product that asks for one holds a hard bound that survives\n * every blind spot above. Both shipped products ask for 86 400 s, which is why\n * the incident — 124 to 268 hours settled against boxes with a 24-hour\n * lifetime — is detectable with no lifecycle bookkeeping at all.\n */\nexport function computeExpectedCeiling(\n record: SpendBoxRecord,\n options: ComputeExpectedCeilingOptions,\n): ExpectedCeiling {\n const toleranceMs = options.toleranceMs ?? DEFAULT_CEILING_TOLERANCE_MS\n const { asOf } = options\n\n let basis: CeilingBasis\n let horizonAt: number\n\n if (record.deletedAt !== null) {\n basis = 'deleted'\n horizonAt = record.deletedAt\n } else if (record.stoppedAt !== null) {\n basis = 'stopped'\n horizonAt = record.stoppedAt\n } else if (record.openDetachedRunIds.length > 0) {\n // Nothing the product observed bounds this box. Fall back to the\n // reconciliation instant, and say so through `bounded: false`.\n basis = 'open-detached-run'\n horizonAt = asOf\n } else {\n basis = 'idle-timeout'\n horizonAt = record.lastActivityAt + record.idleTimeoutSeconds * 1000\n }\n\n // The hard platform bound wins whenever it is tighter — including over an\n // unfinished detached run, which is what turns an unbounded box back into a\n // bounded one.\n if (record.maxLifetimeSeconds !== null) {\n const lifetimeHorizon = record.createdAt + record.maxLifetimeSeconds * 1000\n if (lifetimeHorizon < horizonAt) {\n basis = 'max-lifetime'\n horizonAt = lifetimeHorizon\n }\n }\n\n // Time that has not elapsed cannot have been billed, so an idle window\n // reaching into the future does not widen the bound. This only ever tightens\n // the ceiling, so it cannot mask a charge.\n if (horizonAt > asOf) horizonAt = asOf\n // A horizon before creation would produce a negative ceiling; a box is at\n // minimum billable for the instant it existed.\n if (horizonAt < record.createdAt) horizonAt = record.createdAt\n\n return {\n sandboxId: record.sandboxId,\n basis,\n horizonAt,\n ceilingMs: horizonAt - record.createdAt + toleranceMs,\n toleranceMs,\n bounded: basis !== 'open-detached-run',\n }\n}\n","import type { SettlementReference, SettlementRow } from './types'\n\n/**\n * Parse the platform's settlement idempotency key.\n *\n * The platform mints it as `sandbox:<kind>:<resourceId>:<intervalStart>`\n * (`d1-usage-service.ts`), where `intervalStart` is the interval cursor in epoch\n * ms — the SAME `last_started_at` the settlement subtracts from to get its\n * billed duration. That makes this string the only place a consumer can read the\n * billed interval's start, because the ledger row itself stores no duration.\n *\n * Kinds seen in production: `stop` (an interval closing), `compute` (a heartbeat\n * claim), `egress`, `gpu-lease`. `stop` deliberately covers both a settle and a\n * late stop racing over the same claim, so the two derive one reference id and\n * the ledger's uniqueness constraint makes the overlap safe.\n *\n * Returns null for anything that is not a sandbox reference — a router\n * inference row, a grant, a refund — rather than guessing.\n */\nexport function parseSettlementReference(referenceId: string | null | undefined): SettlementReference | null {\n if (!referenceId) return null\n const parts = referenceId.split(':')\n if (parts.length < 3 || parts[0] !== 'sandbox') return null\n const kind = parts[1]\n if (!kind) return null\n\n // The trailing segment is the interval cursor ONLY when it reads as an epoch\n // instant. `sandbox:gpu-lease:<leaseId>` has no cursor, and a resource id that\n // happened to contain a colon must not have its tail eaten as one.\n const tail = parts[parts.length - 1] as string\n const tailMs = /^\\d+$/.test(tail) ? Number(tail) : Number.NaN\n const hasCursor = parts.length >= 4 && Number.isSafeInteger(tailMs) && tailMs > 0\n\n const resourceId = hasCursor ? parts.slice(2, -1).join(':') : parts.slice(2).join(':')\n if (!resourceId) return null\n\n return { kind, resourceId, intervalStartMs: hasCursor ? tailMs : null }\n}\n\n/**\n * Read the sandbox id out of the platform's aggregation key, `sandbox:<id>`.\n *\n * Distinct from the reference id: `groupKey` is the unit a billing statement\n * groups by and is deliberately NOT unique per row, while `referenceId` is\n * unique per interval. A null group key means \"do not aggregate\" (grants,\n * top-ups, refunds, transfers) and is not an error.\n */\nexport function parseSandboxGroupKey(groupKey: string | null | undefined): string | null {\n if (!groupKey) return null\n const parts = groupKey.split(':')\n if (parts.length < 2 || parts[0] !== 'sandbox') return null\n const id = parts.slice(1).join(':')\n return id || null\n}\n\n/**\n * The sandbox a settlement row is attributable to.\n *\n * The reference id wins over the group key because it is the field the platform\n * dedups on, so it is the one guaranteed present and correct on a compute\n * settlement; the group key is the fallback for rows written before a producer\n * stamped a reference, and for kinds whose reference names something else (a GPU\n * lease id, not a box).\n */\nexport function settlementSandboxId(row: SettlementRow): string | null {\n const reference = parseSettlementReference(row.referenceId)\n if (reference && reference.intervalStartMs !== null) return reference.resourceId\n return parseSandboxGroupKey(row.groupKey) ?? (reference ? reference.resourceId : null)\n}\n\n/** True when a row is a charge (the ledger stores charges as negative amounts). */\nexport function isCharge(row: SettlementRow): boolean {\n return row.amountNanoUsd < 0\n}\n\n/** A charge's magnitude in unsigned nanodollars; 0 for credits. */\nexport function chargeNanoUsd(row: SettlementRow): number {\n return row.amountNanoUsd < 0 ? -row.amountNanoUsd : 0\n}\n","import { DEFAULT_CEILING_TOLERANCE_MS, computeExpectedCeiling } from './ceiling'\nimport { chargeNanoUsd, parseSettlementReference, settlementSandboxId } from './reference'\nimport type { SpendLedgerStorePort } from './store'\nimport {\n SPEND_CHECKS,\n type BilledDurationBasis,\n type CeilingBasis,\n type SettlementRow,\n type SpendBoxRecord,\n type SpendCheckId,\n type SpendFinding,\n type SpendReport,\n} from './types'\n\n/** How many nanodollars in one US dollar. The ledger's unit. */\nconst NANO_PER_USD = 1_000_000_000\nconst MS_PER_HOUR = 3_600_000\n\nexport interface VelocityOptions {\n /** Bucket width for a spend window, ms. Default 24 h. */\n readonly windowMs?: number\n /** Fire when a window exceeds this multiple of the trailing median. Default 5. */\n readonly multiple?: number\n /**\n * Windows of history required before a median means anything. Default 3.\n * Below this the rule stays silent, so a product's genuine first days of\n * usage are not reported as an anomaly.\n */\n readonly minTrailingWindows?: number\n /**\n * A window under this never fires, whatever the ratio. Default $1.00.\n *\n * Without a floor the rule is useless: a trailing median of a tenth of a cent\n * makes every ordinary day a 5x outlier. $1.00 is set from the incident's own\n * distribution — the smallest of the eight affected wallets took $1.98, and\n * the two rows in the same window that were GENUINE were sub-cent. So the\n * floor sits above the noise and below every real finding.\n */\n readonly minAbsoluteNanoUsd?: number\n}\n\nconst DEFAULT_VELOCITY: Required<VelocityOptions> = {\n windowMs: 86_400_000,\n multiple: 5,\n minTrailingWindows: 3,\n minAbsoluteNanoUsd: NANO_PER_USD,\n}\n\n/** The balance the product observes, and the floor it must not cross. */\nexport interface ObservedBalance {\n /** Signed nanodollars, as the platform reports it. */\n readonly nanoUsd: number\n /** Below this is a finding. Default 0. */\n readonly floorNanoUsd?: number\n}\n\n/**\n * A box's price, nanodollars per hour, used to derive an EXACT billed duration\n * from a charge. Return null when the product does not know the box's rate; the\n * reconciler then falls back to the reference span.\n */\nexport type BoxRateResolver = (record: SpendBoxRecord | null, sandboxId: string) => number | null | undefined\n\nexport interface ReconcileSpendOptions {\n /**\n * Settled ledger rows, supplied by the product's own authenticated fetch.\n *\n * MUST be scoped to boxes this product owns. Products bill to a shared company\n * key, so an unscoped fetch returns every sibling product's settlements and\n * every one is a correct — and useless — `unknown-box` finding.\n */\n readonly rows: readonly SettlementRow[]\n /** The product's expectation ledger. */\n readonly store: SpendLedgerStorePort\n /** Treated as \"now\". Default `Date.now()`. */\n readonly asOf?: number\n /** Ceiling slack. Default {@link DEFAULT_CEILING_TOLERANCE_MS}. */\n readonly toleranceMs?: number\n /** Box price, for the exact duration basis. A number applies to every box. */\n readonly nanoUsdPerHour?: number | BoxRateResolver\n /** Velocity tuning, or `false` to skip the rule. */\n readonly velocity?: VelocityOptions | false\n /** The workspace balance, when the product can see one. Omitted skips the rule. */\n readonly balance?: ObservedBalance\n /** Stamped onto findings so an alert names the tenant. */\n readonly workspaceId?: string\n /** Checks to leave out of this pass. */\n readonly skip?: readonly SpendCheckId[]\n}\n\nfunction usd(nano: number): string {\n return `$${(nano / NANO_PER_USD).toFixed(2)}`\n}\n\nfunction hours(ms: number): string {\n return `${(ms / MS_PER_HOUR).toFixed(1)}h`\n}\n\n/** Trust order, least trustworthy first — a mixed aggregate reports the weakest. */\nconst BASIS_TRUST: readonly BilledDurationBasis[] = ['unknown', 'reference-span', 'rate', 'reported']\n\nfunction weakestBasis(a: BilledDurationBasis, b: BilledDurationBasis): BilledDurationBasis {\n return BASIS_TRUST.indexOf(a) <= BASIS_TRUST.indexOf(b) ? a : b\n}\n\n/**\n * How long one settlement billed for, and how confidently we know it.\n *\n * The ledger row does not store a duration — this is the whole reason the check\n * is subtle. Three ways to recover it, best first:\n *\n * 1. `reported` — the product's ledger view exposed it. Exact.\n * 2. `rate` — `charge / pricePerHour`. Exact, because the platform computes the\n * charge as `(durationMs / 3_600_000) * costPerHour` and nothing else enters\n * it. Requires the product to know its box's price.\n * 3. `reference-span` — `settledAt - intervalStart`, both read off the row. An\n * UPPER bound, not the duration: a correct settlement delayed by the\n * platform's durable settlement queue reads longer here than it billed. It is\n * exact for the failure this module exists to catch, because billing \"up to\n * now\" makes the settlement instant and the interval end the same moment.\n */\nfunction resolveBilledMs(\n row: SettlementRow,\n ratePerHourNano: number | null,\n): { ms: number; basis: BilledDurationBasis } {\n if (row.billedMs !== null) return { ms: row.billedMs, basis: 'reported' }\n\n const charge = chargeNanoUsd(row)\n if (ratePerHourNano !== null && ratePerHourNano > 0 && charge > 0) {\n return { ms: (charge / ratePerHourNano) * MS_PER_HOUR, basis: 'rate' }\n }\n\n const reference = parseSettlementReference(row.referenceId)\n if (reference?.intervalStartMs != null && row.createdAt > reference.intervalStartMs) {\n return { ms: row.createdAt - reference.intervalStartMs, basis: 'reference-span' }\n }\n\n return { ms: 0, basis: 'unknown' }\n}\n\nfunction median(values: readonly number[]): number {\n if (values.length === 0) return 0\n const sorted = [...values].sort((a, b) => a - b)\n const mid = Math.floor(sorted.length / 2)\n if (sorted.length % 2 === 1) return sorted[mid] as number\n return (((sorted[mid - 1] as number) + (sorted[mid] as number)) / 2)\n}\n\nfunction emptyFinding(check: SpendCheckId): Omit<SpendFinding, 'message' | 'remedy'> {\n return {\n check,\n sandboxId: null,\n workspaceId: null,\n referenceIds: [],\n settledNanoUsd: 0,\n settledMs: null,\n durationBasis: null,\n ceilingMs: null,\n overageMs: null,\n ceilingBasis: null,\n windowNanoUsd: null,\n trailingMedianNanoUsd: null,\n velocityRatio: null,\n windowStartAt: null,\n balanceNanoUsd: null,\n balanceFloorNanoUsd: null,\n }\n}\n\n/**\n * Diff what the platform charged against what the product believes it asked for.\n *\n * Never disputes anything and never writes: the output is a report a human acts\n * on. The platform's ledger stays authoritative — this only ever produces the\n * evidence for a conversation with it.\n */\nexport async function reconcileSpend(options: ReconcileSpendOptions): Promise<SpendReport> {\n const asOf = options.asOf ?? Date.now()\n const toleranceMs = options.toleranceMs ?? DEFAULT_CEILING_TOLERANCE_MS\n const workspaceId = options.workspaceId ?? null\n const skip = new Set(options.skip ?? [])\n const checksRun = SPEND_CHECKS.filter((check) => !skip.has(check))\n const runs = (check: SpendCheckId): boolean => !skip.has(check)\n\n const findings: SpendFinding[] = []\n let settledNanoUsd = 0\n let creditedNanoUsd = 0\n\n // ── group charges by the box they are attributable to ──────────────────────\n const byBox = new Map<string, SettlementRow[]>()\n for (const row of options.rows) {\n if (row.amountNanoUsd < 0) settledNanoUsd += -row.amountNanoUsd\n else creditedNanoUsd += row.amountNanoUsd\n if (row.amountNanoUsd >= 0) continue\n const sandboxId = settlementSandboxId(row)\n if (!sandboxId) continue\n const bucket = byBox.get(sandboxId)\n if (bucket) bucket.push(row)\n else byBox.set(sandboxId, [row])\n }\n\n const rateOf = (record: SpendBoxRecord | null, sandboxId: string): number | null => {\n const rate = options.nanoUsdPerHour\n if (rate === undefined) return null\n if (typeof rate === 'number') return rate\n return rate(record, sandboxId) ?? null\n }\n\n for (const [sandboxId, rows] of byBox) {\n const record = await options.store.load(sandboxId)\n const referenceIds = rows.map((row) => row.referenceId ?? row.id)\n const charged = rows.reduce((sum, row) => sum + chargeNanoUsd(row), 0)\n\n // ── unknown-box ──────────────────────────────────────────────────────────\n if (!record) {\n if (runs('unknown-box')) {\n findings.push({\n ...emptyFinding('unknown-box'),\n sandboxId,\n workspaceId,\n referenceIds,\n settledNanoUsd: charged,\n message:\n `${usd(charged)} settled across ${rows.length} row(s) against sandbox ${sandboxId}, ` +\n 'which this product has no record of ever asking for.',\n remedy:\n 'Either the fetch is not scoped to this product\\'s own boxes (fix the scope — a shared ' +\n 'billing key returns every sibling product\\'s settlements), or a box was provisioned ' +\n 'outside the recorded seam, or the platform billed a box that is not ours. Identify ' +\n `which by looking up ${sandboxId} on the platform before disputing.`,\n })\n }\n continue\n }\n\n // ── over-ceiling ─────────────────────────────────────────────────────────\n if (!runs('over-ceiling')) continue\n\n const ceiling = computeExpectedCeiling(record, { asOf, toleranceMs })\n const ratePerHourNano = rateOf(record, sandboxId)\n\n let settledMs = 0\n let basis: BilledDurationBasis = 'reported'\n let anyMeasured = false\n for (const row of rows) {\n const resolved = resolveBilledMs(row, ratePerHourNano)\n if (resolved.basis === 'unknown') {\n basis = weakestBasis(basis, 'unknown')\n continue\n }\n anyMeasured = true\n settledMs += resolved.ms\n basis = weakestBasis(basis, resolved.basis)\n }\n if (!anyMeasured) continue\n if (settledMs <= ceiling.ceilingMs) continue\n\n const overageMs = settledMs - ceiling.ceilingMs\n const confidence = ceiling.bounded\n ? ''\n : ' The product could not bound this box from its own observations (an unfinished detached ' +\n 'run, and no max lifetime), so the ceiling rests on the reconciliation instant: this ' +\n 'settlement bills time outside the box\\'s own life, not merely more than expected.'\n const spanCaveat =\n basis === 'reference-span'\n ? ' Duration is derived from the settlement instant minus the interval start, which ' +\n 'overstates a settlement the platform merely posted late — confirm before disputing.'\n : ''\n\n findings.push({\n ...emptyFinding('over-ceiling'),\n sandboxId,\n workspaceId: record.workspaceId || workspaceId,\n referenceIds,\n settledNanoUsd: charged,\n settledMs,\n durationBasis: basis,\n ceilingMs: ceiling.ceilingMs,\n overageMs,\n ceilingBasis: ceiling.basis,\n message:\n `Sandbox ${sandboxId} settled ${hours(settledMs)} (${usd(charged)}) across ${rows.length} ` +\n `row(s), against an expected ceiling of ${hours(ceiling.ceilingMs)} — over by ` +\n `${hours(overageMs)}. Ceiling basis: ${ceiling.basis}; duration basis: ${basis}.` +\n confidence +\n spanCaveat,\n remedy:\n `Dispute ${referenceIds.join(', ')} against the platform ledger with both numbers. ` +\n (ceiling.basis === 'stopped' || ceiling.basis === 'deleted'\n ? 'The product recorded this box as no longer running before the billed time ended, so ' +\n 'either the stop did not take or the interval was settled at the wrong boundary.'\n : 'Freeze the open interval before anything deletes this box — deleting a box with an ' +\n 'open compute interval settles the whole gap at once.'),\n })\n }\n\n // ── velocity ───────────────────────────────────────────────────────────────\n if (runs('velocity') && options.velocity !== false) {\n const cfg = { ...DEFAULT_VELOCITY, ...(options.velocity ?? {}) }\n const buckets = new Map<number, { nano: number; references: string[] }>()\n for (const row of options.rows) {\n const charge = chargeNanoUsd(row)\n if (charge === 0) continue\n const bucketStart = Math.floor(row.createdAt / cfg.windowMs) * cfg.windowMs\n const bucket = buckets.get(bucketStart)\n if (bucket) {\n bucket.nano += charge\n bucket.references.push(row.referenceId ?? row.id)\n } else {\n buckets.set(bucketStart, { nano: charge, references: [row.referenceId ?? row.id] })\n }\n }\n\n const ordered = [...buckets.entries()].sort((a, b) => a[0] - b[0])\n for (let i = 0; i < ordered.length; i++) {\n const entry = ordered[i]\n if (!entry) continue\n const [windowStartAt, bucket] = entry\n if (i < cfg.minTrailingWindows) continue\n const trailing = ordered.slice(0, i).map(([, prior]) => prior.nano)\n const trailingMedian = median(trailing)\n const threshold = Math.max(trailingMedian * cfg.multiple, cfg.minAbsoluteNanoUsd)\n if (bucket.nano <= threshold) continue\n const ratio = trailingMedian > 0 ? bucket.nano / trailingMedian : Number.POSITIVE_INFINITY\n\n findings.push({\n ...emptyFinding('velocity'),\n workspaceId,\n referenceIds: bucket.references,\n settledNanoUsd: bucket.nano,\n windowNanoUsd: bucket.nano,\n trailingMedianNanoUsd: trailingMedian,\n velocityRatio: ratio,\n windowStartAt,\n message:\n `${usd(bucket.nano)} settled in the window starting ${new Date(windowStartAt).toISOString()} ` +\n `across ${bucket.references.length} row(s), against a trailing median of ` +\n `${usd(trailingMedian)} over ${trailing.length} prior window(s) — ` +\n `${Number.isFinite(ratio) ? `${ratio.toFixed(1)}x` : 'no prior spend to compare against'}, ` +\n `over the ${cfg.multiple}x threshold.`,\n remedy:\n 'A burst of this shape is what a settlement defect looks like from the consumer side: ' +\n 'long-dormant intervals cashed out at once. Check whether these rows carry interval ' +\n 'starts far older than the settlement instant before treating it as real usage.',\n })\n }\n }\n\n // ── negative-balance ───────────────────────────────────────────────────────\n if (runs('negative-balance') && options.balance) {\n const floor = options.balance.floorNanoUsd ?? 0\n if (options.balance.nanoUsd < floor) {\n findings.push({\n ...emptyFinding('negative-balance'),\n workspaceId,\n settledNanoUsd: Math.max(0, floor - options.balance.nanoUsd),\n balanceNanoUsd: options.balance.nanoUsd,\n balanceFloorNanoUsd: floor,\n message:\n `Observed balance ${usd(options.balance.nanoUsd)} is below the floor ${usd(floor)}.`,\n remedy:\n 'Stop provisioning new compute for this owner until the balance is explained. A negative ' +\n 'balance that nobody is watching is how a billing defect becomes settled money.',\n })\n }\n }\n\n return {\n ok: findings.length === 0,\n findings,\n checksRun,\n rowsExamined: options.rows.length,\n boxesExamined: byBox.size,\n settledNanoUsd,\n creditedNanoUsd,\n asOf,\n }\n}\n","import type { SpendFinding, SpendReport } from './types'\n\nconst NANO_PER_USD = 1_000_000_000\n\nfunction usd(nano: number | null): string {\n return nano === null ? '—' : `$${(nano / NANO_PER_USD).toFixed(2)}`\n}\n\n/**\n * Render a reconciliation for a human deciding whether to open a dispute.\n *\n * Every finding prints its numbers, not a summary of them: the reader's next\n * action is a conversation with the platform about specific reference ids, and a\n * report that made them re-derive the durations would just be re-read alongside\n * the raw rows anyway.\n */\nexport function formatSpendReport(report: SpendReport): string {\n const lines: string[] = []\n lines.push(\n `spend reconciliation — ${report.rowsExamined} row(s), ${report.boxesExamined} box(es), ` +\n `${usd(report.settledNanoUsd)} charged, ${usd(report.creditedNanoUsd)} credited, ` +\n `as of ${new Date(report.asOf).toISOString()}`,\n )\n lines.push(`checks: ${report.checksRun.join(', ') || '(none)'}`)\n\n if (report.ok) {\n lines.push('')\n lines.push('OK — no discrepancy between the product\\'s expectations and the settled ledger.')\n return lines.join('\\n')\n }\n\n lines.push('')\n lines.push(`${report.findings.length} finding(s):`)\n for (const finding of report.findings) {\n lines.push('')\n lines.push(` [${finding.check}] ${finding.message}`)\n for (const [label, value] of measuredFields(finding)) lines.push(` ${label}: ${value}`)\n if (finding.referenceIds.length > 0) {\n lines.push(` rows: ${finding.referenceIds.join(', ')}`)\n }\n lines.push(` → ${finding.remedy}`)\n }\n return lines.join('\\n')\n}\n\n/** Every measured field a finding carries, including the ones it left null. */\nfunction measuredFields(finding: SpendFinding): Array<[string, string]> {\n const ms = (value: number | null): string =>\n value === null ? '—' : `${(value / 3_600_000).toFixed(2)}h`\n return [\n ['sandbox', finding.sandboxId ?? '—'],\n ['workspace', finding.workspaceId ?? '—'],\n ['amount', usd(finding.settledNanoUsd)],\n ['settled', ms(finding.settledMs)],\n ['ceiling', ms(finding.ceilingMs)],\n ['overage', ms(finding.overageMs)],\n ['ceiling basis', finding.ceilingBasis ?? '—'],\n ['duration basis', finding.durationBasis ?? '—'],\n ['window', finding.windowStartAt === null ? '—' : new Date(finding.windowStartAt).toISOString()],\n ['window spend', usd(finding.windowNanoUsd)],\n ['trailing median', usd(finding.trailingMedianNanoUsd)],\n ['ratio', finding.velocityRatio === null ? '—' : `${finding.velocityRatio.toFixed(1)}x`],\n ['balance', usd(finding.balanceNanoUsd)],\n ['balance floor', usd(finding.balanceFloorNanoUsd)],\n ]\n}\n\n/** The report as a plain JSON value, for an alerting pipeline. */\nexport function spendReportToJson(report: SpendReport): string {\n return JSON.stringify(report, null, 2)\n}\n"],"mappings":"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","names":["NANO_PER_USD","usd"]}
|