@sentientui/mcp 0.18.0 → 0.18.2
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/README.md +2 -0
- package/dist/{chunk-PX6RCSB2.js → chunk-5TEQLWMW.js} +89 -25
- package/dist/index.cjs +89 -25
- package/dist/index.js +1 -1
- package/dist/lib.cjs +89 -25
- package/dist/lib.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -105,6 +105,8 @@ A sandboxed demo token is provisioned automatically — 10 calls/month, read-onl
|
|
|
105
105
|
| `get_cell_detail` | One cell's story: the generated option, why it was written (stored rationale), and how it performs vs the original |
|
|
106
106
|
| `generate_cell` | Queue AI generation of a version for one (visitor type, region) cell — brand-locked, live in under a minute (paid plan) |
|
|
107
107
|
|
|
108
|
+
> **Generated versions.** An `<Adaptive id="…">` that wraps your original as children (no `variants`) registers as a personalizable region once it is mounted and deployed. Its versions per visitor type are written in SentientUI — inspect and fill them with `get_cell_matrix`, `get_cell_detail`, and `generate_cell` — with no code change or redeploy.
|
|
109
|
+
>
|
|
108
110
|
> **Code-native vs no-code variants.** Variants you declare in your app (`<Adaptive variants={{…}}>`) register automatically the first time the SDK requests an assignment after you deploy — they go live immediately and need **no** `create_variant` call. Use `create_variant` only for **no-code** variants whose content is stored in SentientUI and rendered without a code change; these start as drafts you activate from the dashboard.
|
|
109
111
|
>
|
|
110
112
|
> **Optimizing a component?** Call `get_variant_brief` first. It returns the component's performance, audience, insights, and a data-sufficiency assessment — then your AI assistant writes a new on-brand variant directly into your code (which auto-registers on deploy). When there's no data yet, the brief falls back to best-practice priors for your project's context type so the assistant still makes a sensible change.
|
|
@@ -1789,8 +1789,9 @@ import { z as z13 } from "zod";
|
|
|
1789
1789
|
var GUIDE = `# SentientUI integration guide \u2014 the adaptive ladder
|
|
1790
1790
|
|
|
1791
1791
|
SentientUI adapts a site per visitor type (personas), learning from real conversions. Each
|
|
1792
|
-
project has its own persona vocabulary (dashboard -> Settings -> Personas
|
|
1793
|
-
|
|
1792
|
+
project has its own persona vocabulary (dashboard -> Settings -> Personas), and it starts
|
|
1793
|
+
EMPTY: every visitor is 'unknown' until a persona exists, and the optimizer learns across all
|
|
1794
|
+
traffic pooled in the meantime. Personas are DISCOVERED from behavior \u2014 or, far faster,
|
|
1794
1795
|
DECLARED by the app when it already knows the visitor's role: init({ persona: 'admin' }) /
|
|
1795
1796
|
<AdaptiveProvider persona=...> / window.sentient.persona. Declared beats inferred and serves
|
|
1796
1797
|
at full confidence; unrecognized values are ignored server-side and surfaced in the dashboard
|
|
@@ -1803,12 +1804,14 @@ time.
|
|
|
1803
1804
|
1. Run \`npx @sentientui/cli init\` (detects Next App/Pages, Vite, Remix, CRA; installs
|
|
1804
1805
|
@sentientui/react; writes .env.local; scaffolds an example). It does NOT edit your layout \u2014
|
|
1805
1806
|
it prints the wrap snippet for step 2.
|
|
1806
|
-
2. Wrap the root layout with <AdaptiveRoot apiKey
|
|
1807
|
-
other React apps use <AdaptiveProvider> from '@sentientui/react')
|
|
1808
|
-
suppressHydrationWarning to <html> \u2014 an inline script sets persona attributes
|
|
1809
|
-
Nothing adapts and nothing is tracked until this wrap is in place.
|
|
1810
|
-
|
|
1811
|
-
|
|
1807
|
+
2. Wrap the root layout with <AdaptiveRoot apiKey>{children}</AdaptiveRoot> (from
|
|
1808
|
+
'@sentientui/react/next'; other React apps use <AdaptiveProvider> from '@sentientui/react')
|
|
1809
|
+
and add suppressHydrationWarning to <html> \u2014 an inline script sets persona attributes
|
|
1810
|
+
pre-paint. Nothing adapts and nothing is tracked until this wrap is in place. Declare no
|
|
1811
|
+
components here: every <Adaptive> registers itself when it renders (see Rung 2).
|
|
1812
|
+
3. \`npm run dev\`, then open the app with \`?sentient_persona=a\` vs
|
|
1813
|
+
\`?sentient_persona=b\` to see it adapt \u2014 in keyless local mode any key drives the built-in
|
|
1814
|
+
heuristic. No API key needed. Against a real project the key must be one you declared.
|
|
1812
1815
|
4. To learn from real traffic: create a project at https://sentient-ui.com and set
|
|
1813
1816
|
NEXT_PUBLIC_SENTIENT_API_KEY=pk_... in .env.local.
|
|
1814
1817
|
|
|
@@ -1817,8 +1820,8 @@ time.
|
|
|
1817
1820
|
Persona attributes on <html> (zero declaration; the attribute carries the project's own
|
|
1818
1821
|
vocabulary keys \u2014 declared or inferred):
|
|
1819
1822
|
|
|
1820
|
-
html[data-sentient-persona='
|
|
1821
|
-
html[data-sentient-confidence='low'] .
|
|
1823
|
+
html[data-sentient-persona='admin'] .admin-tools { display: block; }
|
|
1824
|
+
html[data-sentient-confidence='low'] .admin-tools { display: none; }
|
|
1822
1825
|
|
|
1823
1826
|
Learned style tokens (element-scoped, SSR-safe):
|
|
1824
1827
|
|
|
@@ -1833,14 +1836,43 @@ prefers-reduced-motion: reduce override in CSS.
|
|
|
1833
1836
|
|
|
1834
1837
|
## Rung 2 \u2014 Swap (alternate content)
|
|
1835
1838
|
|
|
1839
|
+
Generated versions (start here) \u2014 wrap what the page shows today; the children are the
|
|
1840
|
+
original:
|
|
1841
|
+
|
|
1842
|
+
<Adaptive id="hero-cta" goal="signup_click">
|
|
1843
|
+
<a href="/signup">Start free trial</a>
|
|
1844
|
+
</Adaptive>
|
|
1845
|
+
|
|
1846
|
+
Mount + deploy registers the region. SentientUI writes versions per visitor type in the
|
|
1847
|
+
dashboard ("Who sees what") \u2014 no redeploy. The children render for holdout traffic, unknown
|
|
1848
|
+
visitor types, types with no version yet, and every error path. Mounted regions are requested
|
|
1849
|
+
in one batched call after mount: the first visit shows the original briefly, then swaps; return
|
|
1850
|
+
visits start from the last version. Keyless local mode renders originals only.
|
|
1851
|
+
|
|
1852
|
+
Optional server preload (Next.js App Router): registrySlotIds={['hero-cta']} for generated
|
|
1853
|
+
regions, components={[{ id, variantIds }]} for code variants, both on AdaptiveRoot \u2014 the first
|
|
1854
|
+
paint then shows the served version. List ONLY ids rendered on every page that AdaptiveRoot
|
|
1855
|
+
wraps (in app/layout.tsx, every route): a declared id is decided on every request, rendered or
|
|
1856
|
+
not, and those visits count as views that can never convert.
|
|
1857
|
+
|
|
1858
|
+
Pass
|
|
1859
|
+
onFormSubmit to allow form versions; set reportBaselineText={false} on regions wrapping
|
|
1860
|
+
personalized or account content. (Formerly <AdaptiveSlot>.)
|
|
1861
|
+
|
|
1862
|
+
Code variants \u2014 when you write the alternatives yourself:
|
|
1863
|
+
|
|
1864
|
+
<Adaptive id="buy-box" goal="buy_click" // goal REQUIRED
|
|
1865
|
+
variants={{ control: <CalmBuyBox/>, urgent: <UrgentBuyBox/> }} /> // first key = baseline
|
|
1866
|
+
|
|
1867
|
+
// hook form:
|
|
1836
1868
|
const { value, bind } = useAdaptive('buy-box', {
|
|
1837
1869
|
variants: { calm: <CalmBuyBox/>, urgent: <UrgentBuyBox/> }, // first key = baseline
|
|
1838
1870
|
goal: 'buy_click', // REQUIRED
|
|
1839
1871
|
});
|
|
1840
1872
|
return <div {...bind}>{value}</div>;
|
|
1841
1873
|
|
|
1842
|
-
|
|
1843
|
-
|
|
1874
|
+
Children or variants on one <Adaptive>, never both. With the hook, always attach bind \u2014 it
|
|
1875
|
+
wires exposure tracking and goal listeners. <AdaptiveText> swaps dashboard-managed text.
|
|
1844
1876
|
|
|
1845
1877
|
## Rung 3 \u2014 Reorder (structure)
|
|
1846
1878
|
|
|
@@ -1858,12 +1890,20 @@ their shared parent, and applies nothing if any selector drifts.
|
|
|
1858
1890
|
|
|
1859
1891
|
## No-code install order (snippet)
|
|
1860
1892
|
|
|
1861
|
-
|
|
1862
|
-
then the
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1893
|
+
Two tags in <head>, in this order: one inline <script> that assigns the window.sentient config
|
|
1894
|
+
and then, in the same tag after the ';', runs the inline pre-paint script; then the deferred
|
|
1895
|
+
loader. The pre-paint script reads window.sentient when it runs, so sharing the tag is safe. It
|
|
1896
|
+
is optional but recommended \u2014 the loader is deferred, so on a return visit the page can paint
|
|
1897
|
+
its original state before the loader runs; the inline script applies the last served decision
|
|
1898
|
+
from the visitor's own device first, with no network call and no cloak. Get the exact bytes from
|
|
1899
|
+
the dashboard's Install page, or from renderSnippetInstall({ config }) in
|
|
1900
|
+
'@sentientui/snippet/install' (renderSnippetPrePaintScript() for the script alone).
|
|
1901
|
+
|
|
1902
|
+
Strict Content-Security-Policy (script hashes): use the three-tag form instead \u2014 config, the
|
|
1903
|
+
pre-paint script alone, then the loader (renderSnippetInstall({ config, split: true })). The
|
|
1904
|
+
combined tag contains the site's config, so its hash differs per site; the split pre-paint tag is
|
|
1905
|
+
byte-identical everywhere, so one hash covers it. Existing three-tag and config + loader installs
|
|
1906
|
+
keep working.
|
|
1867
1907
|
|
|
1868
1908
|
## Testing the integration
|
|
1869
1909
|
|
|
@@ -2043,12 +2083,17 @@ var cellOutput = z15.object({
|
|
|
2043
2083
|
rationale: z15.string().nullable(),
|
|
2044
2084
|
error: z15.string().nullable(),
|
|
2045
2085
|
updatedAt: z15.string(),
|
|
2086
|
+
// These ride the API response too; omitting them from the schema made zod
|
|
2087
|
+
// STRIP them, so agents could never see refinement or retry state.
|
|
2088
|
+
refinedFrom: z15.string().nullable().optional().describe("Incumbent arm this cell is improving on \u2014 a pending cell with this set still SERVES the incumbent"),
|
|
2089
|
+
notBefore: z15.string().nullable().optional().describe("ISO time the queue will next consider this cell (retry backoff / daily-cap deferral); null when due now"),
|
|
2090
|
+
attempts: z15.number().optional().describe("Generation attempts so far"),
|
|
2046
2091
|
performance: z15.object({
|
|
2047
2092
|
shown: z15.number(),
|
|
2048
2093
|
conversions: z15.number(),
|
|
2049
2094
|
deltaPct: z15.number().nullable().describe("Signed % vs the original conversion rate"),
|
|
2050
2095
|
tier: z15.string().describe("early | no_baseline | winning | losing | flat")
|
|
2051
|
-
}).optional().describe("Present on live cells: the at-a-glance readout")
|
|
2096
|
+
}).optional().describe("Present on live cells (and, mid-refinement, the incumbent arm): the at-a-glance readout")
|
|
2052
2097
|
});
|
|
2053
2098
|
function registerCellTools(server, client) {
|
|
2054
2099
|
server.registerTool(
|
|
@@ -2058,7 +2103,15 @@ function registerCellTools(server, client) {
|
|
|
2058
2103
|
description: 'The "Who sees what" matrix: which visitor types (personas) have an AI-generated version live in each personalizable region (slot), with per-persona traffic share and the first-miss auto-fill setting. A cell with no row means that visitor type sees the original.' + UNTRUSTED_FIELDS_NOTE,
|
|
2059
2104
|
inputSchema: { projectId: projectIdSchema },
|
|
2060
2105
|
outputSchema: {
|
|
2061
|
-
slots: z15.array(
|
|
2106
|
+
slots: z15.array(
|
|
2107
|
+
z15.object({
|
|
2108
|
+
slotId: z15.string(),
|
|
2109
|
+
displayName: z15.string(),
|
|
2110
|
+
supportsForms: z15.boolean(),
|
|
2111
|
+
// API sends this; without it zod stripped the draft-vs-live signal.
|
|
2112
|
+
live: z15.boolean().optional().describe("False for an sdk-registered draft column \u2014 nothing serves until its first generation publishes it")
|
|
2113
|
+
})
|
|
2114
|
+
),
|
|
2062
2115
|
personas: z15.array(personaOutput),
|
|
2063
2116
|
cells: z15.array(cellOutput),
|
|
2064
2117
|
autoFill: z15.boolean(),
|
|
@@ -2072,13 +2125,19 @@ function registerCellTools(server, client) {
|
|
|
2072
2125
|
const data = await client.get(`/projects/${id}/cells`);
|
|
2073
2126
|
const byCell = new Map(data.cells.map((c) => [`${c.slotId}::${c.persona}`, c]));
|
|
2074
2127
|
const lines = [
|
|
2075
|
-
`Slots: ${data.slots.map((s) => untrusted(s.displayName)).join(", ") || "(none published)"}`,
|
|
2128
|
+
`Slots: ${data.slots.map((s) => `${untrusted(s.displayName)}${s.live === false ? " [draft]" : ""}`).join(", ") || "(none published)"}`,
|
|
2076
2129
|
`Auto-fill on first miss: ${data.autoFill ? "ON" : "off"} \xB7 persona source: ${data.personaSource}`
|
|
2077
2130
|
];
|
|
2078
2131
|
for (const p of data.personas) {
|
|
2079
2132
|
const states = data.slots.map((s) => {
|
|
2080
2133
|
const c = byCell.get(`${s.slotId}::${p.key}`);
|
|
2081
|
-
|
|
2134
|
+
let state = c ? c.status : "original";
|
|
2135
|
+
if (c && (c.status === "pending" || c.status === "generating") && c.refinedFrom) {
|
|
2136
|
+
state = `${c.status} (improving on ${untrusted(c.refinedFrom, 64)} \u2014 current version still live)`;
|
|
2137
|
+
} else if (c && c.status === "pending" && c.notBefore && Date.parse(c.notBefore) > Date.now()) {
|
|
2138
|
+
state = `pending (waiting until ${c.notBefore})`;
|
|
2139
|
+
}
|
|
2140
|
+
return `${untrusted(s.displayName, 40)}: ${state}`;
|
|
2082
2141
|
});
|
|
2083
2142
|
lines.push(`- ${untrusted(p.displayName)} (${Math.round(p.share * 100)}% of visitors) \u2014 ${states.join(" \xB7 ")}`);
|
|
2084
2143
|
}
|
|
@@ -2120,7 +2179,7 @@ function registerCellTools(server, client) {
|
|
|
2120
2179
|
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false }
|
|
2121
2180
|
},
|
|
2122
2181
|
withApiErrorGuidance(async ({ projectId, slotId, persona }) => {
|
|
2123
|
-
var _a, _b;
|
|
2182
|
+
var _a, _b, _c;
|
|
2124
2183
|
const id = encodeURIComponent(projectId);
|
|
2125
2184
|
const data = await client.get(`/projects/${id}/cells/detail?slotId=${encodeURIComponent(slotId)}&persona=${encodeURIComponent(persona)}`);
|
|
2126
2185
|
const lines = [];
|
|
@@ -2132,7 +2191,12 @@ function registerCellTools(server, client) {
|
|
|
2132
2191
|
lines.push("No generated version is live for this cell.");
|
|
2133
2192
|
}
|
|
2134
2193
|
if ((_a = data.cell) == null ? void 0 : _a.rationale) lines.push(`Why this exists: ${untrusted(data.cell.rationale, 500)}`);
|
|
2135
|
-
if ((_b = data.cell) == null ? void 0 : _b.
|
|
2194
|
+
if (((_b = data.cell) == null ? void 0 : _b.refinedFrom) && (data.cell.status === "pending" || data.cell.status === "generating")) {
|
|
2195
|
+
lines.push(
|
|
2196
|
+
`An improved take on ${untrusted(data.cell.refinedFrom, 64)} is being written \u2014 the current version keeps serving meanwhile.`
|
|
2197
|
+
);
|
|
2198
|
+
}
|
|
2199
|
+
if ((_c = data.cell) == null ? void 0 : _c.error) lines.push(`Last attempt failed: ${untrusted(data.cell.error, 300)}`);
|
|
2136
2200
|
lines.push(data.verdict);
|
|
2137
2201
|
if (data.performance) {
|
|
2138
2202
|
lines.push(`Shown ${data.performance.shown} times, ${data.performance.conversions} conversions.`);
|
|
@@ -2177,7 +2241,7 @@ function registerCellTools(server, client) {
|
|
|
2177
2241
|
}
|
|
2178
2242
|
|
|
2179
2243
|
// src/server.ts
|
|
2180
|
-
var PKG_VERSION = true ? "0.18.
|
|
2244
|
+
var PKG_VERSION = true ? "0.18.2" : "0.0.0-dev";
|
|
2181
2245
|
function createMcpServer(client) {
|
|
2182
2246
|
const server = new McpServer(
|
|
2183
2247
|
{
|
package/dist/index.cjs
CHANGED
|
@@ -1795,8 +1795,9 @@ var import_zod13 = require("zod");
|
|
|
1795
1795
|
var GUIDE = `# SentientUI integration guide \u2014 the adaptive ladder
|
|
1796
1796
|
|
|
1797
1797
|
SentientUI adapts a site per visitor type (personas), learning from real conversions. Each
|
|
1798
|
-
project has its own persona vocabulary (dashboard -> Settings -> Personas
|
|
1799
|
-
|
|
1798
|
+
project has its own persona vocabulary (dashboard -> Settings -> Personas), and it starts
|
|
1799
|
+
EMPTY: every visitor is 'unknown' until a persona exists, and the optimizer learns across all
|
|
1800
|
+
traffic pooled in the meantime. Personas are DISCOVERED from behavior \u2014 or, far faster,
|
|
1800
1801
|
DECLARED by the app when it already knows the visitor's role: init({ persona: 'admin' }) /
|
|
1801
1802
|
<AdaptiveProvider persona=...> / window.sentient.persona. Declared beats inferred and serves
|
|
1802
1803
|
at full confidence; unrecognized values are ignored server-side and surfaced in the dashboard
|
|
@@ -1809,12 +1810,14 @@ time.
|
|
|
1809
1810
|
1. Run \`npx @sentientui/cli init\` (detects Next App/Pages, Vite, Remix, CRA; installs
|
|
1810
1811
|
@sentientui/react; writes .env.local; scaffolds an example). It does NOT edit your layout \u2014
|
|
1811
1812
|
it prints the wrap snippet for step 2.
|
|
1812
|
-
2. Wrap the root layout with <AdaptiveRoot apiKey
|
|
1813
|
-
other React apps use <AdaptiveProvider> from '@sentientui/react')
|
|
1814
|
-
suppressHydrationWarning to <html> \u2014 an inline script sets persona attributes
|
|
1815
|
-
Nothing adapts and nothing is tracked until this wrap is in place.
|
|
1816
|
-
|
|
1817
|
-
|
|
1813
|
+
2. Wrap the root layout with <AdaptiveRoot apiKey>{children}</AdaptiveRoot> (from
|
|
1814
|
+
'@sentientui/react/next'; other React apps use <AdaptiveProvider> from '@sentientui/react')
|
|
1815
|
+
and add suppressHydrationWarning to <html> \u2014 an inline script sets persona attributes
|
|
1816
|
+
pre-paint. Nothing adapts and nothing is tracked until this wrap is in place. Declare no
|
|
1817
|
+
components here: every <Adaptive> registers itself when it renders (see Rung 2).
|
|
1818
|
+
3. \`npm run dev\`, then open the app with \`?sentient_persona=a\` vs
|
|
1819
|
+
\`?sentient_persona=b\` to see it adapt \u2014 in keyless local mode any key drives the built-in
|
|
1820
|
+
heuristic. No API key needed. Against a real project the key must be one you declared.
|
|
1818
1821
|
4. To learn from real traffic: create a project at https://sentient-ui.com and set
|
|
1819
1822
|
NEXT_PUBLIC_SENTIENT_API_KEY=pk_... in .env.local.
|
|
1820
1823
|
|
|
@@ -1823,8 +1826,8 @@ time.
|
|
|
1823
1826
|
Persona attributes on <html> (zero declaration; the attribute carries the project's own
|
|
1824
1827
|
vocabulary keys \u2014 declared or inferred):
|
|
1825
1828
|
|
|
1826
|
-
html[data-sentient-persona='
|
|
1827
|
-
html[data-sentient-confidence='low'] .
|
|
1829
|
+
html[data-sentient-persona='admin'] .admin-tools { display: block; }
|
|
1830
|
+
html[data-sentient-confidence='low'] .admin-tools { display: none; }
|
|
1828
1831
|
|
|
1829
1832
|
Learned style tokens (element-scoped, SSR-safe):
|
|
1830
1833
|
|
|
@@ -1839,14 +1842,43 @@ prefers-reduced-motion: reduce override in CSS.
|
|
|
1839
1842
|
|
|
1840
1843
|
## Rung 2 \u2014 Swap (alternate content)
|
|
1841
1844
|
|
|
1845
|
+
Generated versions (start here) \u2014 wrap what the page shows today; the children are the
|
|
1846
|
+
original:
|
|
1847
|
+
|
|
1848
|
+
<Adaptive id="hero-cta" goal="signup_click">
|
|
1849
|
+
<a href="/signup">Start free trial</a>
|
|
1850
|
+
</Adaptive>
|
|
1851
|
+
|
|
1852
|
+
Mount + deploy registers the region. SentientUI writes versions per visitor type in the
|
|
1853
|
+
dashboard ("Who sees what") \u2014 no redeploy. The children render for holdout traffic, unknown
|
|
1854
|
+
visitor types, types with no version yet, and every error path. Mounted regions are requested
|
|
1855
|
+
in one batched call after mount: the first visit shows the original briefly, then swaps; return
|
|
1856
|
+
visits start from the last version. Keyless local mode renders originals only.
|
|
1857
|
+
|
|
1858
|
+
Optional server preload (Next.js App Router): registrySlotIds={['hero-cta']} for generated
|
|
1859
|
+
regions, components={[{ id, variantIds }]} for code variants, both on AdaptiveRoot \u2014 the first
|
|
1860
|
+
paint then shows the served version. List ONLY ids rendered on every page that AdaptiveRoot
|
|
1861
|
+
wraps (in app/layout.tsx, every route): a declared id is decided on every request, rendered or
|
|
1862
|
+
not, and those visits count as views that can never convert.
|
|
1863
|
+
|
|
1864
|
+
Pass
|
|
1865
|
+
onFormSubmit to allow form versions; set reportBaselineText={false} on regions wrapping
|
|
1866
|
+
personalized or account content. (Formerly <AdaptiveSlot>.)
|
|
1867
|
+
|
|
1868
|
+
Code variants \u2014 when you write the alternatives yourself:
|
|
1869
|
+
|
|
1870
|
+
<Adaptive id="buy-box" goal="buy_click" // goal REQUIRED
|
|
1871
|
+
variants={{ control: <CalmBuyBox/>, urgent: <UrgentBuyBox/> }} /> // first key = baseline
|
|
1872
|
+
|
|
1873
|
+
// hook form:
|
|
1842
1874
|
const { value, bind } = useAdaptive('buy-box', {
|
|
1843
1875
|
variants: { calm: <CalmBuyBox/>, urgent: <UrgentBuyBox/> }, // first key = baseline
|
|
1844
1876
|
goal: 'buy_click', // REQUIRED
|
|
1845
1877
|
});
|
|
1846
1878
|
return <div {...bind}>{value}</div>;
|
|
1847
1879
|
|
|
1848
|
-
|
|
1849
|
-
|
|
1880
|
+
Children or variants on one <Adaptive>, never both. With the hook, always attach bind \u2014 it
|
|
1881
|
+
wires exposure tracking and goal listeners. <AdaptiveText> swaps dashboard-managed text.
|
|
1850
1882
|
|
|
1851
1883
|
## Rung 3 \u2014 Reorder (structure)
|
|
1852
1884
|
|
|
@@ -1864,12 +1896,20 @@ their shared parent, and applies nothing if any selector drifts.
|
|
|
1864
1896
|
|
|
1865
1897
|
## No-code install order (snippet)
|
|
1866
1898
|
|
|
1867
|
-
|
|
1868
|
-
then the
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1899
|
+
Two tags in <head>, in this order: one inline <script> that assigns the window.sentient config
|
|
1900
|
+
and then, in the same tag after the ';', runs the inline pre-paint script; then the deferred
|
|
1901
|
+
loader. The pre-paint script reads window.sentient when it runs, so sharing the tag is safe. It
|
|
1902
|
+
is optional but recommended \u2014 the loader is deferred, so on a return visit the page can paint
|
|
1903
|
+
its original state before the loader runs; the inline script applies the last served decision
|
|
1904
|
+
from the visitor's own device first, with no network call and no cloak. Get the exact bytes from
|
|
1905
|
+
the dashboard's Install page, or from renderSnippetInstall({ config }) in
|
|
1906
|
+
'@sentientui/snippet/install' (renderSnippetPrePaintScript() for the script alone).
|
|
1907
|
+
|
|
1908
|
+
Strict Content-Security-Policy (script hashes): use the three-tag form instead \u2014 config, the
|
|
1909
|
+
pre-paint script alone, then the loader (renderSnippetInstall({ config, split: true })). The
|
|
1910
|
+
combined tag contains the site's config, so its hash differs per site; the split pre-paint tag is
|
|
1911
|
+
byte-identical everywhere, so one hash covers it. Existing three-tag and config + loader installs
|
|
1912
|
+
keep working.
|
|
1873
1913
|
|
|
1874
1914
|
## Testing the integration
|
|
1875
1915
|
|
|
@@ -2049,12 +2089,17 @@ var cellOutput = import_zod15.z.object({
|
|
|
2049
2089
|
rationale: import_zod15.z.string().nullable(),
|
|
2050
2090
|
error: import_zod15.z.string().nullable(),
|
|
2051
2091
|
updatedAt: import_zod15.z.string(),
|
|
2092
|
+
// These ride the API response too; omitting them from the schema made zod
|
|
2093
|
+
// STRIP them, so agents could never see refinement or retry state.
|
|
2094
|
+
refinedFrom: import_zod15.z.string().nullable().optional().describe("Incumbent arm this cell is improving on \u2014 a pending cell with this set still SERVES the incumbent"),
|
|
2095
|
+
notBefore: import_zod15.z.string().nullable().optional().describe("ISO time the queue will next consider this cell (retry backoff / daily-cap deferral); null when due now"),
|
|
2096
|
+
attempts: import_zod15.z.number().optional().describe("Generation attempts so far"),
|
|
2052
2097
|
performance: import_zod15.z.object({
|
|
2053
2098
|
shown: import_zod15.z.number(),
|
|
2054
2099
|
conversions: import_zod15.z.number(),
|
|
2055
2100
|
deltaPct: import_zod15.z.number().nullable().describe("Signed % vs the original conversion rate"),
|
|
2056
2101
|
tier: import_zod15.z.string().describe("early | no_baseline | winning | losing | flat")
|
|
2057
|
-
}).optional().describe("Present on live cells: the at-a-glance readout")
|
|
2102
|
+
}).optional().describe("Present on live cells (and, mid-refinement, the incumbent arm): the at-a-glance readout")
|
|
2058
2103
|
});
|
|
2059
2104
|
function registerCellTools(server, client) {
|
|
2060
2105
|
server.registerTool(
|
|
@@ -2064,7 +2109,15 @@ function registerCellTools(server, client) {
|
|
|
2064
2109
|
description: 'The "Who sees what" matrix: which visitor types (personas) have an AI-generated version live in each personalizable region (slot), with per-persona traffic share and the first-miss auto-fill setting. A cell with no row means that visitor type sees the original.' + UNTRUSTED_FIELDS_NOTE,
|
|
2065
2110
|
inputSchema: { projectId: projectIdSchema },
|
|
2066
2111
|
outputSchema: {
|
|
2067
|
-
slots: import_zod15.z.array(
|
|
2112
|
+
slots: import_zod15.z.array(
|
|
2113
|
+
import_zod15.z.object({
|
|
2114
|
+
slotId: import_zod15.z.string(),
|
|
2115
|
+
displayName: import_zod15.z.string(),
|
|
2116
|
+
supportsForms: import_zod15.z.boolean(),
|
|
2117
|
+
// API sends this; without it zod stripped the draft-vs-live signal.
|
|
2118
|
+
live: import_zod15.z.boolean().optional().describe("False for an sdk-registered draft column \u2014 nothing serves until its first generation publishes it")
|
|
2119
|
+
})
|
|
2120
|
+
),
|
|
2068
2121
|
personas: import_zod15.z.array(personaOutput),
|
|
2069
2122
|
cells: import_zod15.z.array(cellOutput),
|
|
2070
2123
|
autoFill: import_zod15.z.boolean(),
|
|
@@ -2078,13 +2131,19 @@ function registerCellTools(server, client) {
|
|
|
2078
2131
|
const data = await client.get(`/projects/${id}/cells`);
|
|
2079
2132
|
const byCell = new Map(data.cells.map((c) => [`${c.slotId}::${c.persona}`, c]));
|
|
2080
2133
|
const lines = [
|
|
2081
|
-
`Slots: ${data.slots.map((s) => untrusted(s.displayName)).join(", ") || "(none published)"}`,
|
|
2134
|
+
`Slots: ${data.slots.map((s) => `${untrusted(s.displayName)}${s.live === false ? " [draft]" : ""}`).join(", ") || "(none published)"}`,
|
|
2082
2135
|
`Auto-fill on first miss: ${data.autoFill ? "ON" : "off"} \xB7 persona source: ${data.personaSource}`
|
|
2083
2136
|
];
|
|
2084
2137
|
for (const p of data.personas) {
|
|
2085
2138
|
const states = data.slots.map((s) => {
|
|
2086
2139
|
const c = byCell.get(`${s.slotId}::${p.key}`);
|
|
2087
|
-
|
|
2140
|
+
let state = c ? c.status : "original";
|
|
2141
|
+
if (c && (c.status === "pending" || c.status === "generating") && c.refinedFrom) {
|
|
2142
|
+
state = `${c.status} (improving on ${untrusted(c.refinedFrom, 64)} \u2014 current version still live)`;
|
|
2143
|
+
} else if (c && c.status === "pending" && c.notBefore && Date.parse(c.notBefore) > Date.now()) {
|
|
2144
|
+
state = `pending (waiting until ${c.notBefore})`;
|
|
2145
|
+
}
|
|
2146
|
+
return `${untrusted(s.displayName, 40)}: ${state}`;
|
|
2088
2147
|
});
|
|
2089
2148
|
lines.push(`- ${untrusted(p.displayName)} (${Math.round(p.share * 100)}% of visitors) \u2014 ${states.join(" \xB7 ")}`);
|
|
2090
2149
|
}
|
|
@@ -2126,7 +2185,7 @@ function registerCellTools(server, client) {
|
|
|
2126
2185
|
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false }
|
|
2127
2186
|
},
|
|
2128
2187
|
withApiErrorGuidance(async ({ projectId, slotId, persona }) => {
|
|
2129
|
-
var _a2, _b;
|
|
2188
|
+
var _a2, _b, _c;
|
|
2130
2189
|
const id = encodeURIComponent(projectId);
|
|
2131
2190
|
const data = await client.get(`/projects/${id}/cells/detail?slotId=${encodeURIComponent(slotId)}&persona=${encodeURIComponent(persona)}`);
|
|
2132
2191
|
const lines = [];
|
|
@@ -2138,7 +2197,12 @@ function registerCellTools(server, client) {
|
|
|
2138
2197
|
lines.push("No generated version is live for this cell.");
|
|
2139
2198
|
}
|
|
2140
2199
|
if ((_a2 = data.cell) == null ? void 0 : _a2.rationale) lines.push(`Why this exists: ${untrusted(data.cell.rationale, 500)}`);
|
|
2141
|
-
if ((_b = data.cell) == null ? void 0 : _b.
|
|
2200
|
+
if (((_b = data.cell) == null ? void 0 : _b.refinedFrom) && (data.cell.status === "pending" || data.cell.status === "generating")) {
|
|
2201
|
+
lines.push(
|
|
2202
|
+
`An improved take on ${untrusted(data.cell.refinedFrom, 64)} is being written \u2014 the current version keeps serving meanwhile.`
|
|
2203
|
+
);
|
|
2204
|
+
}
|
|
2205
|
+
if ((_c = data.cell) == null ? void 0 : _c.error) lines.push(`Last attempt failed: ${untrusted(data.cell.error, 300)}`);
|
|
2142
2206
|
lines.push(data.verdict);
|
|
2143
2207
|
if (data.performance) {
|
|
2144
2208
|
lines.push(`Shown ${data.performance.shown} times, ${data.performance.conversions} conversions.`);
|
|
@@ -2183,7 +2247,7 @@ function registerCellTools(server, client) {
|
|
|
2183
2247
|
}
|
|
2184
2248
|
|
|
2185
2249
|
// src/server.ts
|
|
2186
|
-
var PKG_VERSION = true ? "0.18.
|
|
2250
|
+
var PKG_VERSION = true ? "0.18.2" : "0.0.0-dev";
|
|
2187
2251
|
function createMcpServer(client) {
|
|
2188
2252
|
const server = new import_mcp.McpServer(
|
|
2189
2253
|
{
|
package/dist/index.js
CHANGED
package/dist/lib.cjs
CHANGED
|
@@ -1819,8 +1819,9 @@ var import_zod13 = require("zod");
|
|
|
1819
1819
|
var GUIDE = `# SentientUI integration guide \u2014 the adaptive ladder
|
|
1820
1820
|
|
|
1821
1821
|
SentientUI adapts a site per visitor type (personas), learning from real conversions. Each
|
|
1822
|
-
project has its own persona vocabulary (dashboard -> Settings -> Personas
|
|
1823
|
-
|
|
1822
|
+
project has its own persona vocabulary (dashboard -> Settings -> Personas), and it starts
|
|
1823
|
+
EMPTY: every visitor is 'unknown' until a persona exists, and the optimizer learns across all
|
|
1824
|
+
traffic pooled in the meantime. Personas are DISCOVERED from behavior \u2014 or, far faster,
|
|
1824
1825
|
DECLARED by the app when it already knows the visitor's role: init({ persona: 'admin' }) /
|
|
1825
1826
|
<AdaptiveProvider persona=...> / window.sentient.persona. Declared beats inferred and serves
|
|
1826
1827
|
at full confidence; unrecognized values are ignored server-side and surfaced in the dashboard
|
|
@@ -1833,12 +1834,14 @@ time.
|
|
|
1833
1834
|
1. Run \`npx @sentientui/cli init\` (detects Next App/Pages, Vite, Remix, CRA; installs
|
|
1834
1835
|
@sentientui/react; writes .env.local; scaffolds an example). It does NOT edit your layout \u2014
|
|
1835
1836
|
it prints the wrap snippet for step 2.
|
|
1836
|
-
2. Wrap the root layout with <AdaptiveRoot apiKey
|
|
1837
|
-
other React apps use <AdaptiveProvider> from '@sentientui/react')
|
|
1838
|
-
suppressHydrationWarning to <html> \u2014 an inline script sets persona attributes
|
|
1839
|
-
Nothing adapts and nothing is tracked until this wrap is in place.
|
|
1840
|
-
|
|
1841
|
-
|
|
1837
|
+
2. Wrap the root layout with <AdaptiveRoot apiKey>{children}</AdaptiveRoot> (from
|
|
1838
|
+
'@sentientui/react/next'; other React apps use <AdaptiveProvider> from '@sentientui/react')
|
|
1839
|
+
and add suppressHydrationWarning to <html> \u2014 an inline script sets persona attributes
|
|
1840
|
+
pre-paint. Nothing adapts and nothing is tracked until this wrap is in place. Declare no
|
|
1841
|
+
components here: every <Adaptive> registers itself when it renders (see Rung 2).
|
|
1842
|
+
3. \`npm run dev\`, then open the app with \`?sentient_persona=a\` vs
|
|
1843
|
+
\`?sentient_persona=b\` to see it adapt \u2014 in keyless local mode any key drives the built-in
|
|
1844
|
+
heuristic. No API key needed. Against a real project the key must be one you declared.
|
|
1842
1845
|
4. To learn from real traffic: create a project at https://sentient-ui.com and set
|
|
1843
1846
|
NEXT_PUBLIC_SENTIENT_API_KEY=pk_... in .env.local.
|
|
1844
1847
|
|
|
@@ -1847,8 +1850,8 @@ time.
|
|
|
1847
1850
|
Persona attributes on <html> (zero declaration; the attribute carries the project's own
|
|
1848
1851
|
vocabulary keys \u2014 declared or inferred):
|
|
1849
1852
|
|
|
1850
|
-
html[data-sentient-persona='
|
|
1851
|
-
html[data-sentient-confidence='low'] .
|
|
1853
|
+
html[data-sentient-persona='admin'] .admin-tools { display: block; }
|
|
1854
|
+
html[data-sentient-confidence='low'] .admin-tools { display: none; }
|
|
1852
1855
|
|
|
1853
1856
|
Learned style tokens (element-scoped, SSR-safe):
|
|
1854
1857
|
|
|
@@ -1863,14 +1866,43 @@ prefers-reduced-motion: reduce override in CSS.
|
|
|
1863
1866
|
|
|
1864
1867
|
## Rung 2 \u2014 Swap (alternate content)
|
|
1865
1868
|
|
|
1869
|
+
Generated versions (start here) \u2014 wrap what the page shows today; the children are the
|
|
1870
|
+
original:
|
|
1871
|
+
|
|
1872
|
+
<Adaptive id="hero-cta" goal="signup_click">
|
|
1873
|
+
<a href="/signup">Start free trial</a>
|
|
1874
|
+
</Adaptive>
|
|
1875
|
+
|
|
1876
|
+
Mount + deploy registers the region. SentientUI writes versions per visitor type in the
|
|
1877
|
+
dashboard ("Who sees what") \u2014 no redeploy. The children render for holdout traffic, unknown
|
|
1878
|
+
visitor types, types with no version yet, and every error path. Mounted regions are requested
|
|
1879
|
+
in one batched call after mount: the first visit shows the original briefly, then swaps; return
|
|
1880
|
+
visits start from the last version. Keyless local mode renders originals only.
|
|
1881
|
+
|
|
1882
|
+
Optional server preload (Next.js App Router): registrySlotIds={['hero-cta']} for generated
|
|
1883
|
+
regions, components={[{ id, variantIds }]} for code variants, both on AdaptiveRoot \u2014 the first
|
|
1884
|
+
paint then shows the served version. List ONLY ids rendered on every page that AdaptiveRoot
|
|
1885
|
+
wraps (in app/layout.tsx, every route): a declared id is decided on every request, rendered or
|
|
1886
|
+
not, and those visits count as views that can never convert.
|
|
1887
|
+
|
|
1888
|
+
Pass
|
|
1889
|
+
onFormSubmit to allow form versions; set reportBaselineText={false} on regions wrapping
|
|
1890
|
+
personalized or account content. (Formerly <AdaptiveSlot>.)
|
|
1891
|
+
|
|
1892
|
+
Code variants \u2014 when you write the alternatives yourself:
|
|
1893
|
+
|
|
1894
|
+
<Adaptive id="buy-box" goal="buy_click" // goal REQUIRED
|
|
1895
|
+
variants={{ control: <CalmBuyBox/>, urgent: <UrgentBuyBox/> }} /> // first key = baseline
|
|
1896
|
+
|
|
1897
|
+
// hook form:
|
|
1866
1898
|
const { value, bind } = useAdaptive('buy-box', {
|
|
1867
1899
|
variants: { calm: <CalmBuyBox/>, urgent: <UrgentBuyBox/> }, // first key = baseline
|
|
1868
1900
|
goal: 'buy_click', // REQUIRED
|
|
1869
1901
|
});
|
|
1870
1902
|
return <div {...bind}>{value}</div>;
|
|
1871
1903
|
|
|
1872
|
-
|
|
1873
|
-
|
|
1904
|
+
Children or variants on one <Adaptive>, never both. With the hook, always attach bind \u2014 it
|
|
1905
|
+
wires exposure tracking and goal listeners. <AdaptiveText> swaps dashboard-managed text.
|
|
1874
1906
|
|
|
1875
1907
|
## Rung 3 \u2014 Reorder (structure)
|
|
1876
1908
|
|
|
@@ -1888,12 +1920,20 @@ their shared parent, and applies nothing if any selector drifts.
|
|
|
1888
1920
|
|
|
1889
1921
|
## No-code install order (snippet)
|
|
1890
1922
|
|
|
1891
|
-
|
|
1892
|
-
then the
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1923
|
+
Two tags in <head>, in this order: one inline <script> that assigns the window.sentient config
|
|
1924
|
+
and then, in the same tag after the ';', runs the inline pre-paint script; then the deferred
|
|
1925
|
+
loader. The pre-paint script reads window.sentient when it runs, so sharing the tag is safe. It
|
|
1926
|
+
is optional but recommended \u2014 the loader is deferred, so on a return visit the page can paint
|
|
1927
|
+
its original state before the loader runs; the inline script applies the last served decision
|
|
1928
|
+
from the visitor's own device first, with no network call and no cloak. Get the exact bytes from
|
|
1929
|
+
the dashboard's Install page, or from renderSnippetInstall({ config }) in
|
|
1930
|
+
'@sentientui/snippet/install' (renderSnippetPrePaintScript() for the script alone).
|
|
1931
|
+
|
|
1932
|
+
Strict Content-Security-Policy (script hashes): use the three-tag form instead \u2014 config, the
|
|
1933
|
+
pre-paint script alone, then the loader (renderSnippetInstall({ config, split: true })). The
|
|
1934
|
+
combined tag contains the site's config, so its hash differs per site; the split pre-paint tag is
|
|
1935
|
+
byte-identical everywhere, so one hash covers it. Existing three-tag and config + loader installs
|
|
1936
|
+
keep working.
|
|
1897
1937
|
|
|
1898
1938
|
## Testing the integration
|
|
1899
1939
|
|
|
@@ -2073,12 +2113,17 @@ var cellOutput = import_zod15.z.object({
|
|
|
2073
2113
|
rationale: import_zod15.z.string().nullable(),
|
|
2074
2114
|
error: import_zod15.z.string().nullable(),
|
|
2075
2115
|
updatedAt: import_zod15.z.string(),
|
|
2116
|
+
// These ride the API response too; omitting them from the schema made zod
|
|
2117
|
+
// STRIP them, so agents could never see refinement or retry state.
|
|
2118
|
+
refinedFrom: import_zod15.z.string().nullable().optional().describe("Incumbent arm this cell is improving on \u2014 a pending cell with this set still SERVES the incumbent"),
|
|
2119
|
+
notBefore: import_zod15.z.string().nullable().optional().describe("ISO time the queue will next consider this cell (retry backoff / daily-cap deferral); null when due now"),
|
|
2120
|
+
attempts: import_zod15.z.number().optional().describe("Generation attempts so far"),
|
|
2076
2121
|
performance: import_zod15.z.object({
|
|
2077
2122
|
shown: import_zod15.z.number(),
|
|
2078
2123
|
conversions: import_zod15.z.number(),
|
|
2079
2124
|
deltaPct: import_zod15.z.number().nullable().describe("Signed % vs the original conversion rate"),
|
|
2080
2125
|
tier: import_zod15.z.string().describe("early | no_baseline | winning | losing | flat")
|
|
2081
|
-
}).optional().describe("Present on live cells: the at-a-glance readout")
|
|
2126
|
+
}).optional().describe("Present on live cells (and, mid-refinement, the incumbent arm): the at-a-glance readout")
|
|
2082
2127
|
});
|
|
2083
2128
|
function registerCellTools(server, client) {
|
|
2084
2129
|
server.registerTool(
|
|
@@ -2088,7 +2133,15 @@ function registerCellTools(server, client) {
|
|
|
2088
2133
|
description: 'The "Who sees what" matrix: which visitor types (personas) have an AI-generated version live in each personalizable region (slot), with per-persona traffic share and the first-miss auto-fill setting. A cell with no row means that visitor type sees the original.' + UNTRUSTED_FIELDS_NOTE,
|
|
2089
2134
|
inputSchema: { projectId: projectIdSchema },
|
|
2090
2135
|
outputSchema: {
|
|
2091
|
-
slots: import_zod15.z.array(
|
|
2136
|
+
slots: import_zod15.z.array(
|
|
2137
|
+
import_zod15.z.object({
|
|
2138
|
+
slotId: import_zod15.z.string(),
|
|
2139
|
+
displayName: import_zod15.z.string(),
|
|
2140
|
+
supportsForms: import_zod15.z.boolean(),
|
|
2141
|
+
// API sends this; without it zod stripped the draft-vs-live signal.
|
|
2142
|
+
live: import_zod15.z.boolean().optional().describe("False for an sdk-registered draft column \u2014 nothing serves until its first generation publishes it")
|
|
2143
|
+
})
|
|
2144
|
+
),
|
|
2092
2145
|
personas: import_zod15.z.array(personaOutput),
|
|
2093
2146
|
cells: import_zod15.z.array(cellOutput),
|
|
2094
2147
|
autoFill: import_zod15.z.boolean(),
|
|
@@ -2102,13 +2155,19 @@ function registerCellTools(server, client) {
|
|
|
2102
2155
|
const data = await client.get(`/projects/${id}/cells`);
|
|
2103
2156
|
const byCell = new Map(data.cells.map((c) => [`${c.slotId}::${c.persona}`, c]));
|
|
2104
2157
|
const lines = [
|
|
2105
|
-
`Slots: ${data.slots.map((s) => untrusted(s.displayName)).join(", ") || "(none published)"}`,
|
|
2158
|
+
`Slots: ${data.slots.map((s) => `${untrusted(s.displayName)}${s.live === false ? " [draft]" : ""}`).join(", ") || "(none published)"}`,
|
|
2106
2159
|
`Auto-fill on first miss: ${data.autoFill ? "ON" : "off"} \xB7 persona source: ${data.personaSource}`
|
|
2107
2160
|
];
|
|
2108
2161
|
for (const p of data.personas) {
|
|
2109
2162
|
const states = data.slots.map((s) => {
|
|
2110
2163
|
const c = byCell.get(`${s.slotId}::${p.key}`);
|
|
2111
|
-
|
|
2164
|
+
let state = c ? c.status : "original";
|
|
2165
|
+
if (c && (c.status === "pending" || c.status === "generating") && c.refinedFrom) {
|
|
2166
|
+
state = `${c.status} (improving on ${untrusted(c.refinedFrom, 64)} \u2014 current version still live)`;
|
|
2167
|
+
} else if (c && c.status === "pending" && c.notBefore && Date.parse(c.notBefore) > Date.now()) {
|
|
2168
|
+
state = `pending (waiting until ${c.notBefore})`;
|
|
2169
|
+
}
|
|
2170
|
+
return `${untrusted(s.displayName, 40)}: ${state}`;
|
|
2112
2171
|
});
|
|
2113
2172
|
lines.push(`- ${untrusted(p.displayName)} (${Math.round(p.share * 100)}% of visitors) \u2014 ${states.join(" \xB7 ")}`);
|
|
2114
2173
|
}
|
|
@@ -2150,7 +2209,7 @@ function registerCellTools(server, client) {
|
|
|
2150
2209
|
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false }
|
|
2151
2210
|
},
|
|
2152
2211
|
withApiErrorGuidance(async ({ projectId, slotId, persona }) => {
|
|
2153
|
-
var _a, _b;
|
|
2212
|
+
var _a, _b, _c;
|
|
2154
2213
|
const id = encodeURIComponent(projectId);
|
|
2155
2214
|
const data = await client.get(`/projects/${id}/cells/detail?slotId=${encodeURIComponent(slotId)}&persona=${encodeURIComponent(persona)}`);
|
|
2156
2215
|
const lines = [];
|
|
@@ -2162,7 +2221,12 @@ function registerCellTools(server, client) {
|
|
|
2162
2221
|
lines.push("No generated version is live for this cell.");
|
|
2163
2222
|
}
|
|
2164
2223
|
if ((_a = data.cell) == null ? void 0 : _a.rationale) lines.push(`Why this exists: ${untrusted(data.cell.rationale, 500)}`);
|
|
2165
|
-
if ((_b = data.cell) == null ? void 0 : _b.
|
|
2224
|
+
if (((_b = data.cell) == null ? void 0 : _b.refinedFrom) && (data.cell.status === "pending" || data.cell.status === "generating")) {
|
|
2225
|
+
lines.push(
|
|
2226
|
+
`An improved take on ${untrusted(data.cell.refinedFrom, 64)} is being written \u2014 the current version keeps serving meanwhile.`
|
|
2227
|
+
);
|
|
2228
|
+
}
|
|
2229
|
+
if ((_c = data.cell) == null ? void 0 : _c.error) lines.push(`Last attempt failed: ${untrusted(data.cell.error, 300)}`);
|
|
2166
2230
|
lines.push(data.verdict);
|
|
2167
2231
|
if (data.performance) {
|
|
2168
2232
|
lines.push(`Shown ${data.performance.shown} times, ${data.performance.conversions} conversions.`);
|
|
@@ -2207,7 +2271,7 @@ function registerCellTools(server, client) {
|
|
|
2207
2271
|
}
|
|
2208
2272
|
|
|
2209
2273
|
// src/server.ts
|
|
2210
|
-
var PKG_VERSION = true ? "0.18.
|
|
2274
|
+
var PKG_VERSION = true ? "0.18.2" : "0.0.0-dev";
|
|
2211
2275
|
function createMcpServer(client) {
|
|
2212
2276
|
const server = new import_mcp.McpServer(
|
|
2213
2277
|
{
|
package/dist/lib.js
CHANGED