@seldonframe/mcp 1.13.0 → 1.15.0
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/package.json +3 -1
- package/src/tools.js +313 -0
- package/src/welcome.js +19 -2
package/package.json
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@seldonframe/mcp",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.15.0",
|
|
4
|
+
"description": "MCP server for SeldonFrame — AI-native Business OS platform. v1.15.0: PORTAL COMPOSITE TREES + CUSTOMER.* EMBEDS. The composite primitive vocabulary (12 kinds, v1.12) now extends to the customer-portal surface via 5 new embed.ref values that pull per-customer data: customer.contact_info, customer.next_appointment, customer.recent_appointments, customer.documents, customer.deals. WHY (decisive (α) over (β) on every architectural principle): a portal block IS a landing block with a different render context — same structure, different data binding, different audience. Building a separate portal pipeline (β) would have meant 2× the harness, 2× the agent vocabulary, no compounding of LLM improvements across surfaces. (α) reuses the composite engine; the only new code is per-customer embed render branches + auth-scoped DB resolvers + a workspace-level template store. WHAT SHIPS: (1) Schema extension — 5 new customer.* values added to embed.ref enum; existing 5 workspace embeds still work; unknown refs (customer.password etc) still rejected. (2) CustomerRenderContext extends CompositeRenderContext with customer + 4 per-customer data fields. assembleCustomerContext THROWS on missing customer.id (auth-scope identity, never default to empty). (3) Per-customer DB resolvers (5 functions, each takes orgId+customerId as REQUIRED args, returns auth-scoped data). All five run in parallel via buildCustomerContext. (4) Portal-template storage on organizations.settings.portal_template (CompositeNode[]). One template per workspace; every customer renders against their own data. (5) Five portal MCP tools: get_portal_structure, add_portal_section, update_portal_section, move_portal_section, delete_portal_section. Same atomic-primitive pattern as v1.11/v1.13/v1.14. Empty templates are valid (different from landing's minimum-1 rule — a portal with no template just shows built-in tabs). (6) preview_portal({ workspace_id, contact_id }) renders the template against a real contact for visual verification before customers see it. (7) Renderer extensions: 5 new render branches in renderEmbed (one per customer.* ref). HTML-escaped customer-supplied data (no XSS via document filename or appointment title). (8) Composite SKILL.md addendum: portal patterns (WELCOME / NEXT-VISIT / DOCS+DEALS-SIDE-BY-SIDE), portal voice rules (second-person, customer-addressed), anti-patterns (no cross-customer data, no marketing content). Plus the 'check for near-duplicates before add_composite_section' rule from the v1.14 followup. Test count: 2525 (was 2495; +30: schema/render/assemble + portal-structure pure helpers + XSS + auth-scope contract). NO migrations (template stored on existing settings jsonb), NO env vars, NO new deps. Backward compatible. v1.16 candidate: wire the portal template into a customer-facing rendered route at /portal/<contactId> (currently the rendered HTML is exposed only via the operator-facing preview endpoint).",
|
|
5
|
+
"description": "MCP server for SeldonFrame — AI-native Business OS platform. v1.14.0: BOOKING FORM STRUCTURAL PRIMITIVES + COMPOSITE CINEMATIC CSS FIX. Five new tools mirror the v1.13 intake-structure pattern but for the booking form, with one critical addition: STANDARD-FIELD CONTRACT — fullName + email at indices 0/1 are server-owned (the renderer + public POST handler require them per the v1.4.2 fix). Destructive ops on indices 0/1 are rejected by design so an operator cannot accidentally break the booking flow. WHAT SHIPS: (1) get_booking_structure — title, description, duration, fields with is_standard flag + 1-line preview. (2) add_booking_field({ field, position? }) — minimum position 2 (slots 0/1 reserved). Allowed types: text, textarea, email, phone, select. ID uniqueness enforced; cannot be 'fullName' or 'email'. (3) move_booking_field({ from_index, to_index }) — both indices must be >= 2. (4) delete_booking_field({ index }) — index must be >= 2; floor is 'just the 2 standards'. (5) update_booking_field({ index, patch }) — index must be >= 2; ID changes blocked from colliding with another field OR with reserved standard ids. Persistence runs through mergeBookingFormFields as defense-in-depth — even if a primitive somehow let a mutation through, the merge re-prepends the standards before persistence. (6) BUNDLED CSS FIX: composite-block renderer adds .sf-frame.sf-cinematic .sf-cmp-* and .sf-frame.sf-light .sf-cmp-* overrides for headline / heading / subhead / text / card / divider / list-x / stat-label / embed-* — fixes the dark-on-dark headline + card-heading bug observed in the v1.12 smoke (composite section text was using var(--sf-text) which resolved to a dark color in workspaces tuned for light backgrounds, then rendered against the cinematic dark backdrop). Mirrors the override pattern in cinematic-overlay.ts for typed sections. Test count: 2495 (was 2475; +20 booking tests). NO migrations, NO env vars, NO new deps. Backward compatible. v1.15: portal blocks (composite tree on customer portal surface, with customer-data embed refs).",
|
|
4
6
|
"description": "MCP server for SeldonFrame — AI-native Business OS platform. v1.13.0: INTAKE FORM STRUCTURAL PRIMITIVES — five new tools mirror the v1.11 landing-structure pattern but for the intake form surface (linear, no nesting). Operators can now ask 'add a phone field', 'put email at the top', 'rename mobile to cellphone', 'remove the rating question' and the agent calls one atomic tool. WHY: pre-v1.13 the agent had to use persist_block(intake) which REPLACES the whole questions array — a bad fit for incremental edits. v1.13 lets the agent do single-field operations without touching the rest of the form. WHAT SHIPS: (1) get_intake_structure({workspace_id}) — returns title, description, indexed list of fields with type + label + required + 1-line preview. (2) add_intake_field({workspace_id, field, position?}) — append (default) or insert. ID uniqueness enforced. (3) move_intake_field({workspace_id, from_index, to_index}) — splice semantics (atomic). (4) delete_intake_field({workspace_id, index}) — refuses to leave 0 fields (public submit needs ≥1). (5) update_intake_field({workspace_id, index, patch}) — patch any subset of {id, type, label, helper, required, options, ratingScale, validation, showIf}; ID changes blocked from colliding. Persistence touches BOTH Blueprint.intake.questions AND intake_forms.fields (the public POST handler reads the latter); both updated atomically per mutation, formbricks-stack-v1 re-renders. (6) 23 new unit tests covering applyAddField (append / insert / id collision / out-of-range), applyMoveField, applyDeleteField (boundary indices / minimum-1 guard), applyUpdateField (patch / id-collision / type+options together / empty-patch reject), and deriveFieldPreview (label + type + required marker + option count + truncation). Test count: 2475 (was 2452). DESIGN: same thin-harness/fat-skill pattern. Server only validates + persists. Agent's LLM picks WHICH field by reading get_intake_structure previews. Antifragile: as LLMs improve at intent-mapping, output quality rises with zero harness changes. NO migrations, NO env vars, NO new deps. Backward compatible — existing persist_block(intake) still works for full-form replaces.",
|
|
5
7
|
"description": "MCP server for SeldonFrame — AI-native Business OS platform. v1.12.0: COMPOSITE-TREE PRIMITIVES — operators can now manifest ANY landing-page section by description (comparison columns, pricing tiers, 'how it works' steps, stats rows, side-by-side image+text, custom CTAs) without server-side type-per-block work. The agent's LLM composes a tree from 12 low-level primitives; the server validates + renders. WHY: typed blocks (hero, services, about, faq, cta) require dedicated SKILL.md + Zod schema + renderer mapping per type — fine for high-stakes surfaces, too rigid for the long tail of operator requests. WHAT SHIPS: (1) New SectionComposite section type — holds a recursive CompositeNode tree. (2) 12 primitive node kinds: containers (section / row / col / card), content leaves (heading / text / image / list / button / stat), special (embed / divider / spacer). embed.ref ∈ {services, faq, testimonials, hours, phone} pulls workspace-data into the section without re-typing. (3) Zod schema + structural validators: tree depth ≤ 4, children-per-container caps (section ≤ 8, row ≤ 4, card ≤ 8, list ≤ 12), heading levels descend without skipping, all text fields trim + length-cap. (4) Theme-aware renderer (~3 KB shared CSS, emitted once per page). Mobile-responsive by default (rows stack on narrow viewports). All operator/LLM-supplied text HTML-escaped on emit (no XSS). External nav buttons get target=_blank rel=noopener. (5) Voice scanner — scans all text-bearing fields against soul.voice.avoidWords; returns warnings (not errors) for the agent to self-correct on retry. (6) 2 new MCP tools: add_composite_section({ workspace_id, tree, position? }) and update_composite_section({ workspace_id, index, tree }). (7) get_block_skill('composite') serves a hand-written SKILL.md with the primitive vocabulary, validation rules, voice guidance, and 7 worked patterns (COMPARISON, STATS, HOW-IT-WORKS, SIDE-BY-SIDE, CALL-CTA, FEATURES-GRID, EMBED-DRIVEN-SERVICES). (8) 45 new unit tests (27 validation + 18 render). DESIGN PRINCIPLES: thin harness (server only validates + renders; no LLM judgment), fat skill (ONE skill file with vocabulary + patterns; agent's LLM does composition), antifragile (as LLMs improve at composing trees, validation_warnings rate drops without harness changes), permissive-on-read/strict-on-write (old persisted trees keep rendering even if schema evolves; new writes must validate). Test count: 2452 (was 2407). Backward compatible. NO migrations, NO env vars, NO new deps. Existing typed blocks (hero/services/etc.) untouched. v1.13 candidate: form primitives (intake field add/move/delete/update). v1.14: composite trees on the customer portal surface with customer-data embed refs.",
|
|
6
8
|
"description": "MCP server for SeldonFrame — AI-native Business OS platform. v1.11.0: STRUCTURAL PRIMITIVES — three new tools that cut landing-page restructuring time from minutes to seconds, with index-based addressing that handles duplicate section types. WHY: v1.10's reorder_landing_sections used type-based addressing ('hero', 'services-grid', 'faq', ...) which breaks when types repeat (smoke test 2026-05-05: a workspace had two 'services-grid' sections — one grid-3 services, one stats — and the reorder API correctly refused as ambiguous, but the agent had no way to disambiguate without fetching + PowerShell-parsing landing_pages.blueprintJson, which took 8+ minutes). WHAT SHIPS: (1) get_landing_structure({workspace_id}) — returns ordered sections with index (0..N-1) + type + 1-line preview ('Vancouver's Trusted HVAC Family — Same-Day Service' for hero, '3 services (grid-3)' vs 'stats — 4 numbers' for services-grid duplicates). Cheap one-DB-read. Replaces the v1.10 fetch-and-parse workflow. (2) move_section({workspace_id, from_index, to_index}) — atomic single-section move with splice semantics (section at from_index is removed, then inserted at to_index in the result). Handles duplicate types because index is unambiguous. Existing reorder_landing_sections stays as the bulk path for clean cases. (3) delete_section({workspace_id, index}) — atomic single-section remove. Refuses to leave 0 sections (minimum is 1). Use to clean up unintended duplicates from v2 generation artifacts. (4) 22 new unit tests covering applyMove (forward / backward / no-op / immutability / out-of-range), applyDelete (boundary indices / minimum-1-section guard / immutability), and derivePreview (each section type emits a useful 1-liner; services-grid grid-3 vs stats are distinguishable; long text truncates). DESIGN PRINCIPLES: thin harness (server only does mechanical reorder/delete + re-render; no LLM judgment), fat skill (agent's LLM picks WHICH section based on operator intent + preview text), antifragile (as LLMs improve at intent-mapping, output quality rises with zero harness changes). Test count: 2407 (was 2385). Time-to-restructure: 8 min → ~3 sec. NO migrations, NO env vars, NO new deps. Backward compatible. v1.12 candidate: add_section + new section primitives (comparison block, pricing-table, gallery) — separate larger work.",
|
package/src/tools.js
CHANGED
|
@@ -3036,6 +3036,319 @@ export const TOOLS = [
|
|
|
3036
3036
|
},
|
|
3037
3037
|
},
|
|
3038
3038
|
|
|
3039
|
+
// ─── v1.14.0 — booking-form structural primitives ───────────────────────
|
|
3040
|
+
//
|
|
3041
|
+
// Mirrors v1.13's intake-structure pattern but for the booking form,
|
|
3042
|
+
// PLUS a standard-field contract: fullName + email at indices 0/1
|
|
3043
|
+
// are server-owned (the renderer + public POST handler require
|
|
3044
|
+
// them). Destructive ops on indices 0/1 are rejected.
|
|
3045
|
+
|
|
3046
|
+
{
|
|
3047
|
+
name: "get_booking_structure",
|
|
3048
|
+
description:
|
|
3049
|
+
"Read the workspace's booking event-type + fields (indexed list with type + label + required + 1-line preview). Standard fields (fullName at index 0, email at index 1) are flagged is_standard:true — they're server-owned and cannot be moved/deleted/renamed. Use BEFORE add_booking_field / move_booking_field / delete_booking_field / update_booking_field to find the right index.",
|
|
3050
|
+
inputSchema: obj(
|
|
3051
|
+
{ workspace_id: str("Workspace id.") },
|
|
3052
|
+
["workspace_id"],
|
|
3053
|
+
),
|
|
3054
|
+
handler: async (args) => {
|
|
3055
|
+
const ws = args.workspace_id;
|
|
3056
|
+
const result = await api("GET", "/workspace/v2/booking/structure", {
|
|
3057
|
+
workspace_id: ws,
|
|
3058
|
+
});
|
|
3059
|
+
return result;
|
|
3060
|
+
},
|
|
3061
|
+
},
|
|
3062
|
+
|
|
3063
|
+
{
|
|
3064
|
+
name: "add_booking_field",
|
|
3065
|
+
description:
|
|
3066
|
+
"Add ONE field to the booking form (after the standard fullName + email). Field shape: { id, type, label, required?, placeholder?, options? }. Types: text, textarea, email, phone, select. " +
|
|
3067
|
+
"Use when the operator wants to capture extra info from bookers — service address, equipment type, preferred technician, party size, etc. ID must be unique within the form (cannot be 'fullName' or 'email' — those are reserved). " +
|
|
3068
|
+
"Position defaults to appending at the end. Insert positions must be >= 2 (slots 0/1 are reserved for the standards).",
|
|
3069
|
+
inputSchema: obj(
|
|
3070
|
+
{
|
|
3071
|
+
workspace_id: str("Workspace id."),
|
|
3072
|
+
field: {
|
|
3073
|
+
type: "object",
|
|
3074
|
+
description:
|
|
3075
|
+
"BookingFormField object: { id (kebab-case, unique, NOT 'fullName'/'email'), type, label, required?, placeholder?, options? for select fields }.",
|
|
3076
|
+
additionalProperties: true,
|
|
3077
|
+
},
|
|
3078
|
+
position: {
|
|
3079
|
+
type: "integer",
|
|
3080
|
+
description:
|
|
3081
|
+
"Optional 0-based insert position. Default: append. Must be >= 2 (0/1 are reserved for standards).",
|
|
3082
|
+
minimum: 2,
|
|
3083
|
+
},
|
|
3084
|
+
},
|
|
3085
|
+
["workspace_id", "field"],
|
|
3086
|
+
),
|
|
3087
|
+
handler: async (args) => {
|
|
3088
|
+
const ws = args.workspace_id;
|
|
3089
|
+
const result = await api("POST", "/workspace/v2/booking/fields", {
|
|
3090
|
+
body: {
|
|
3091
|
+
workspace_id: ws,
|
|
3092
|
+
op: "add",
|
|
3093
|
+
field: args.field,
|
|
3094
|
+
position: args.position,
|
|
3095
|
+
},
|
|
3096
|
+
workspace_id: ws,
|
|
3097
|
+
});
|
|
3098
|
+
return result;
|
|
3099
|
+
},
|
|
3100
|
+
},
|
|
3101
|
+
|
|
3102
|
+
{
|
|
3103
|
+
name: "move_booking_field",
|
|
3104
|
+
description:
|
|
3105
|
+
"Move ONE booking field (extra) to a new position. Splice semantics. Standards (fullName, email at indices 0/1) cannot be moved AND cannot be displaced — both from_index and to_index must be >= 2.",
|
|
3106
|
+
inputSchema: obj(
|
|
3107
|
+
{
|
|
3108
|
+
workspace_id: str("Workspace id."),
|
|
3109
|
+
from_index: { type: "integer", description: "0-based source index. Must be >= 2 (standards locked).", minimum: 2 },
|
|
3110
|
+
to_index: { type: "integer", description: "0-based target index in the result. Must be >= 2.", minimum: 2 },
|
|
3111
|
+
},
|
|
3112
|
+
["workspace_id", "from_index", "to_index"],
|
|
3113
|
+
),
|
|
3114
|
+
handler: async (args) => {
|
|
3115
|
+
const ws = args.workspace_id;
|
|
3116
|
+
const result = await api("POST", "/workspace/v2/booking/fields", {
|
|
3117
|
+
body: {
|
|
3118
|
+
workspace_id: ws,
|
|
3119
|
+
op: "move",
|
|
3120
|
+
from_index: args.from_index,
|
|
3121
|
+
to_index: args.to_index,
|
|
3122
|
+
},
|
|
3123
|
+
workspace_id: ws,
|
|
3124
|
+
});
|
|
3125
|
+
return result;
|
|
3126
|
+
},
|
|
3127
|
+
},
|
|
3128
|
+
|
|
3129
|
+
{
|
|
3130
|
+
name: "delete_booking_field",
|
|
3131
|
+
description:
|
|
3132
|
+
"Remove ONE booking field (extra). Standards (fullName, email at indices 0/1) cannot be deleted — index must be >= 2. Floor is 'just the 2 standards' (different from intake's 'minimum 1' rule because booking forms always have 2 standards).",
|
|
3133
|
+
inputSchema: obj(
|
|
3134
|
+
{
|
|
3135
|
+
workspace_id: str("Workspace id."),
|
|
3136
|
+
index: { type: "integer", description: "0-based index. Must be >= 2 (standards locked).", minimum: 2 },
|
|
3137
|
+
},
|
|
3138
|
+
["workspace_id", "index"],
|
|
3139
|
+
),
|
|
3140
|
+
handler: async (args) => {
|
|
3141
|
+
const ws = args.workspace_id;
|
|
3142
|
+
const result = await api("POST", "/workspace/v2/booking/fields", {
|
|
3143
|
+
body: { workspace_id: ws, op: "delete", index: args.index },
|
|
3144
|
+
workspace_id: ws,
|
|
3145
|
+
});
|
|
3146
|
+
return result;
|
|
3147
|
+
},
|
|
3148
|
+
},
|
|
3149
|
+
|
|
3150
|
+
{
|
|
3151
|
+
name: "update_booking_field",
|
|
3152
|
+
description:
|
|
3153
|
+
"Patch ONE booking field (extra) by index. Patch can include any subset of: id, type, label, required, placeholder, options. Standards (fullName, email) cannot be patched — index must be >= 2. ID changes blocked from colliding with another field OR with reserved standard ids.",
|
|
3154
|
+
inputSchema: obj(
|
|
3155
|
+
{
|
|
3156
|
+
workspace_id: str("Workspace id."),
|
|
3157
|
+
index: { type: "integer", description: "0-based index of the field to patch. Must be >= 2.", minimum: 2 },
|
|
3158
|
+
patch: {
|
|
3159
|
+
type: "object",
|
|
3160
|
+
description:
|
|
3161
|
+
"Subset of BookingFormField fields to overwrite. Empty patch is rejected. ID changes blocked from colliding (with another field or with reserved 'fullName'/'email').",
|
|
3162
|
+
additionalProperties: true,
|
|
3163
|
+
},
|
|
3164
|
+
},
|
|
3165
|
+
["workspace_id", "index", "patch"],
|
|
3166
|
+
),
|
|
3167
|
+
handler: async (args) => {
|
|
3168
|
+
const ws = args.workspace_id;
|
|
3169
|
+
const result = await api("POST", "/workspace/v2/booking/fields", {
|
|
3170
|
+
body: {
|
|
3171
|
+
workspace_id: ws,
|
|
3172
|
+
op: "update",
|
|
3173
|
+
index: args.index,
|
|
3174
|
+
patch: args.patch,
|
|
3175
|
+
},
|
|
3176
|
+
workspace_id: ws,
|
|
3177
|
+
});
|
|
3178
|
+
return result;
|
|
3179
|
+
},
|
|
3180
|
+
},
|
|
3181
|
+
|
|
3182
|
+
// ─── v1.15.0 — portal-template structural primitives ────────────────────
|
|
3183
|
+
//
|
|
3184
|
+
// Composite trees on the customer portal surface, with per-customer
|
|
3185
|
+
// embed refs (customer.next_appointment, customer.documents,
|
|
3186
|
+
// customer.deals, customer.contact_info, customer.recent_appointments).
|
|
3187
|
+
// Same primitive vocabulary as landing — just rendered against a per-
|
|
3188
|
+
// customer CustomerRenderContext at request time. Template is stored
|
|
3189
|
+
// once on the workspace; every customer sees their own data through it.
|
|
3190
|
+
//
|
|
3191
|
+
// Operators define the template once via add/update/move/delete.
|
|
3192
|
+
// The customer-facing portal route renders the template against
|
|
3193
|
+
// each customer's context. preview_portal renders it server-side
|
|
3194
|
+
// for visual inspection against any contact.
|
|
3195
|
+
|
|
3196
|
+
{
|
|
3197
|
+
name: "get_portal_structure",
|
|
3198
|
+
description:
|
|
3199
|
+
"Read the workspace's portal template — indexed list of composite-tree sections with previews. Use BEFORE add_portal_section / update_portal_section / move_portal_section / delete_portal_section to find the right index. Empty templates are valid (the portal just shows built-in tabs without a Custom tab).",
|
|
3200
|
+
inputSchema: obj(
|
|
3201
|
+
{ workspace_id: str("Workspace id.") },
|
|
3202
|
+
["workspace_id"],
|
|
3203
|
+
),
|
|
3204
|
+
handler: async (args) => {
|
|
3205
|
+
const ws = args.workspace_id;
|
|
3206
|
+
const result = await api("GET", "/workspace/v2/portal/structure", {
|
|
3207
|
+
workspace_id: ws,
|
|
3208
|
+
});
|
|
3209
|
+
return result;
|
|
3210
|
+
},
|
|
3211
|
+
},
|
|
3212
|
+
|
|
3213
|
+
{
|
|
3214
|
+
name: "add_portal_section",
|
|
3215
|
+
description:
|
|
3216
|
+
"Add a composite-tree section to the workspace's portal template. The template renders on every customer's portal — same composite primitive vocabulary as landing (12 node kinds), PLUS 5 customer.* embed refs that pull per-customer data: " +
|
|
3217
|
+
"customer.contact_info (name + email + phone), customer.next_appointment (upcoming booking card), customer.recent_appointments (history list), customer.documents (download links), customer.deals (active jobs/deals). " +
|
|
3218
|
+
"Read get_block_skill('composite') for the primitive vocabulary + voice rules. Tree root MUST be kind=section. Validation runs (Zod + structural rules + voice scan) same as add_composite_section. " +
|
|
3219
|
+
"Typical patterns: WELCOME = section { headline: 'Welcome back', children: [text + customer.contact_info] }. NEXT-APPOINTMENT = section { headline: 'Your next visit', children: [embed: customer.next_appointment, button: book] }. DOCS = section { headline: 'Your documents', children: [embed: customer.documents] }. " +
|
|
3220
|
+
"Position is optional — defaults to appending. Use get_portal_structure first if you want to insert between specific sections.",
|
|
3221
|
+
inputSchema: obj(
|
|
3222
|
+
{
|
|
3223
|
+
workspace_id: str("Workspace id."),
|
|
3224
|
+
tree: {
|
|
3225
|
+
type: "object",
|
|
3226
|
+
description:
|
|
3227
|
+
"Composite tree root (kind=section with optional eyebrow/headline/subhead and children). See get_block_skill('composite') for the primitive vocabulary. Customer.* embed refs are valid here.",
|
|
3228
|
+
additionalProperties: true,
|
|
3229
|
+
},
|
|
3230
|
+
position: {
|
|
3231
|
+
type: "integer",
|
|
3232
|
+
description:
|
|
3233
|
+
"Optional 0-based insert position. Default: append at the end.",
|
|
3234
|
+
minimum: 0,
|
|
3235
|
+
},
|
|
3236
|
+
},
|
|
3237
|
+
["workspace_id", "tree"],
|
|
3238
|
+
),
|
|
3239
|
+
handler: async (args) => {
|
|
3240
|
+
const ws = args.workspace_id;
|
|
3241
|
+
const result = await api("POST", "/workspace/v2/portal/section", {
|
|
3242
|
+
body: {
|
|
3243
|
+
workspace_id: ws,
|
|
3244
|
+
op: "add",
|
|
3245
|
+
tree: args.tree,
|
|
3246
|
+
position: args.position,
|
|
3247
|
+
},
|
|
3248
|
+
workspace_id: ws,
|
|
3249
|
+
});
|
|
3250
|
+
return result;
|
|
3251
|
+
},
|
|
3252
|
+
},
|
|
3253
|
+
|
|
3254
|
+
{
|
|
3255
|
+
name: "update_portal_section",
|
|
3256
|
+
description:
|
|
3257
|
+
"Replace the tree of an existing portal-template section. Use to refine ('shorten the welcome', 'add a CTA to the documents section'). Index must exist. Validation runs same as add_portal_section.",
|
|
3258
|
+
inputSchema: obj(
|
|
3259
|
+
{
|
|
3260
|
+
workspace_id: str("Workspace id."),
|
|
3261
|
+
index: { type: "integer", description: "0-based index of the portal section to replace.", minimum: 0 },
|
|
3262
|
+
tree: {
|
|
3263
|
+
type: "object",
|
|
3264
|
+
description: "Replacement tree. Same shape as add_portal_section.",
|
|
3265
|
+
additionalProperties: true,
|
|
3266
|
+
},
|
|
3267
|
+
},
|
|
3268
|
+
["workspace_id", "index", "tree"],
|
|
3269
|
+
),
|
|
3270
|
+
handler: async (args) => {
|
|
3271
|
+
const ws = args.workspace_id;
|
|
3272
|
+
const result = await api("POST", "/workspace/v2/portal/section", {
|
|
3273
|
+
body: {
|
|
3274
|
+
workspace_id: ws,
|
|
3275
|
+
op: "update",
|
|
3276
|
+
tree: args.tree,
|
|
3277
|
+
index: args.index,
|
|
3278
|
+
},
|
|
3279
|
+
workspace_id: ws,
|
|
3280
|
+
});
|
|
3281
|
+
return result;
|
|
3282
|
+
},
|
|
3283
|
+
},
|
|
3284
|
+
|
|
3285
|
+
{
|
|
3286
|
+
name: "move_portal_section",
|
|
3287
|
+
description:
|
|
3288
|
+
"Move ONE portal-template section atomically. Splice semantics: section at from_index removed, then inserted at to_index in the result.",
|
|
3289
|
+
inputSchema: obj(
|
|
3290
|
+
{
|
|
3291
|
+
workspace_id: str("Workspace id."),
|
|
3292
|
+
from_index: { type: "integer", description: "0-based source index.", minimum: 0 },
|
|
3293
|
+
to_index: { type: "integer", description: "0-based target index in the result.", minimum: 0 },
|
|
3294
|
+
},
|
|
3295
|
+
["workspace_id", "from_index", "to_index"],
|
|
3296
|
+
),
|
|
3297
|
+
handler: async (args) => {
|
|
3298
|
+
const ws = args.workspace_id;
|
|
3299
|
+
const result = await api("POST", "/workspace/v2/portal/section", {
|
|
3300
|
+
body: {
|
|
3301
|
+
workspace_id: ws,
|
|
3302
|
+
op: "move",
|
|
3303
|
+
from_index: args.from_index,
|
|
3304
|
+
to_index: args.to_index,
|
|
3305
|
+
},
|
|
3306
|
+
workspace_id: ws,
|
|
3307
|
+
});
|
|
3308
|
+
return result;
|
|
3309
|
+
},
|
|
3310
|
+
},
|
|
3311
|
+
|
|
3312
|
+
{
|
|
3313
|
+
name: "delete_portal_section",
|
|
3314
|
+
description:
|
|
3315
|
+
"Remove ONE portal-template section. UNLIKE landing's delete_section, leaving 0 portal sections is valid — the portal just shows built-in tabs (Documents, Bookings) without a Custom tab.",
|
|
3316
|
+
inputSchema: obj(
|
|
3317
|
+
{
|
|
3318
|
+
workspace_id: str("Workspace id."),
|
|
3319
|
+
index: { type: "integer", description: "0-based index to delete.", minimum: 0 },
|
|
3320
|
+
},
|
|
3321
|
+
["workspace_id", "index"],
|
|
3322
|
+
),
|
|
3323
|
+
handler: async (args) => {
|
|
3324
|
+
const ws = args.workspace_id;
|
|
3325
|
+
const result = await api("POST", "/workspace/v2/portal/section", {
|
|
3326
|
+
body: { workspace_id: ws, op: "delete", index: args.index },
|
|
3327
|
+
workspace_id: ws,
|
|
3328
|
+
});
|
|
3329
|
+
return result;
|
|
3330
|
+
},
|
|
3331
|
+
},
|
|
3332
|
+
|
|
3333
|
+
{
|
|
3334
|
+
name: "preview_portal",
|
|
3335
|
+
description:
|
|
3336
|
+
"Render the workspace's portal template against a SPECIFIC contact's data. Returns HTML + CSS so you can visually verify the template before customers see it. Pass contact_id of any contact in the workspace; if the id doesn't belong to this workspace, you get a 404. Use after add_portal_section / update_portal_section to confirm the per-customer embeds resolve correctly with real data.",
|
|
3337
|
+
inputSchema: obj(
|
|
3338
|
+
{
|
|
3339
|
+
workspace_id: str("Workspace id."),
|
|
3340
|
+
contact_id: str("Contact id of an existing customer in this workspace. Use list_contacts to discover ids."),
|
|
3341
|
+
},
|
|
3342
|
+
["workspace_id", "contact_id"],
|
|
3343
|
+
),
|
|
3344
|
+
handler: async (args) => {
|
|
3345
|
+
const ws = args.workspace_id;
|
|
3346
|
+
const path = `/workspace/v2/portal/preview?contact_id=${encodeURIComponent(args.contact_id)}`;
|
|
3347
|
+
const result = await api("GET", path, { workspace_id: ws });
|
|
3348
|
+
return result;
|
|
3349
|
+
},
|
|
3350
|
+
},
|
|
3351
|
+
|
|
3039
3352
|
{
|
|
3040
3353
|
name: "reorder_landing_sections",
|
|
3041
3354
|
description:
|
package/src/welcome.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
// stripped. `create_full_workspace` is the only workspace-creation
|
|
9
9
|
// path mentioned anywhere in this briefing.
|
|
10
10
|
|
|
11
|
-
export const VERSION = "1.
|
|
11
|
+
export const VERSION = "1.15.0";
|
|
12
12
|
|
|
13
13
|
export const WELCOME_MARKDOWN = `# SeldonFrame — create a real Business OS in one conversation
|
|
14
14
|
|
|
@@ -254,6 +254,23 @@ further natural-language requests ("change the headline to …",
|
|
|
254
254
|
phone field", "rename email to primary email"). For full-form
|
|
255
255
|
replaces use persist_block(intake).
|
|
256
256
|
- **\`update_form\`** — edit the intake form's questions (legacy).
|
|
257
|
+
- **\`get_booking_structure\`** / **\`add_booking_field\`** /
|
|
258
|
+
**\`move_booking_field\`** / **\`delete_booking_field\`** /
|
|
259
|
+
**\`update_booking_field\`** (v1.14+) — atomic primitives for the
|
|
260
|
+
booking form. Indices 0/1 (fullName, email) are server-owned and
|
|
261
|
+
rejected for destructive ops by design — booking flow stays intact.
|
|
262
|
+
Use these for incremental edits ("ask for service address",
|
|
263
|
+
"make equipment field optional", "drop the preferred technician
|
|
264
|
+
field"). For full-form replaces use persist_block(booking).
|
|
265
|
+
- **\`get_portal_structure\`** / **\`add_portal_section\`** /
|
|
266
|
+
**\`update_portal_section\`** / **\`move_portal_section\`** /
|
|
267
|
+
**\`delete_portal_section\`** / **\`preview_portal\`** (v1.15+) —
|
|
268
|
+
composite-tree primitives for the customer portal. Same vocabulary
|
|
269
|
+
as add_composite_section PLUS 5 new customer.* embed refs that
|
|
270
|
+
pull per-customer data (next appointment, documents, deals,
|
|
271
|
+
recent appointments, contact info). Template stored once on the
|
|
272
|
+
workspace; every customer sees their own data. preview_portal
|
|
273
|
+
renders against a real contact for visual verification.
|
|
257
274
|
- **\`update_appointment_type\`** — edit the booking page's slot length,
|
|
258
275
|
title, description.
|
|
259
276
|
- **\`install_vertical_pack\`** — set up an industry template
|
|
@@ -287,4 +304,4 @@ admin dashboard. Pre-fills their email automatically.
|
|
|
287
304
|
<https://seldonframe.com> · **Discord:** <https://discord.gg/sbVUu976NW>
|
|
288
305
|
`;
|
|
289
306
|
|
|
290
|
-
export const FIRST_CALL_BANNER = `🚀 SeldonFrame v1.
|
|
307
|
+
export const FIRST_CALL_BANNER = `🚀 SeldonFrame v1.15.0 is connected. PREFERRED workspace creation: create_workspace_v2 → IN PARALLEL for all 7 recommended_blocks (hero, services, about, faq, cta, booking, intake): get_block_skill + persist_block → complete_workspace_v2 → finalize_workspace({ workspace_id, email }). The v2 flow puts YOUR LLM in charge of every operator-facing surface using one SKILL.md per block. Each block's prop schema is server-validated. Run blocks in PARALLEL (Promise.all) — sequential takes 60+ seconds. v1.10+ TIER 2 CUSTOMIZE TOOLS: regenerate_block (re-do one block with operator instructions; thin-harness — server bundles context, your LLM generates), upload_workspace_image (set logo/hero_background; v1.10.1+ accepts image_url or local_file_path — DON'T base64 unless you have to, the encoded string eats your tool-call token budget). v1.11+ STRUCTURAL PRIMITIVES: get_landing_structure → move_section → delete_section. INDEX-based, handle duplicate section types, atomic. v1.12+ COMPOSITE TREES: add_composite_section / update_composite_section — manifest ANY block (comparison, pricing, "how it works," stats, custom CTAs) from 12 low-level primitives. Server validates + renders; YOUR LLM composes. Read the SKILL.md via get_block_skill('composite') before composing. Every URL is real. NEVER create local files. Skipping finalize_workspace leaves the operator with no admin login.`;
|