@frankieai/core 0.2.6 → 0.2.7

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/index.cjs CHANGED
@@ -63,6 +63,7 @@ __export(index_exports, {
63
63
  operationId: () => operationId,
64
64
  parseFrankieConfig: () => parseFrankieConfig,
65
65
  personaEnvKey: () => personaEnvKey,
66
+ personaInstructions: () => personaInstructions,
66
67
  personaProfileSchema: () => personaProfileSchema,
67
68
  plainText: () => plainText,
68
69
  planHostedInstall: () => planHostedInstall,
@@ -612,6 +613,18 @@ var personaProfileSchema = import_zod2.z.object({
612
613
  * sign in.
613
614
  */
614
615
  allowAnonymous: import_zod2.z.boolean().optional(),
616
+ /**
617
+ * What this agent helps with, in the team's own words ("pricing, plans,
618
+ * getting started"). The one thing a team needs to write; the agent's role,
619
+ * greeting and instructions come from defaults. It is added to the agent's
620
+ * instructions at runtime (see `personaInstructions`).
621
+ */
622
+ scope: import_zod2.z.string().trim().max(2e3).optional(),
623
+ /**
624
+ * Where to send people when the agent can't answer and it doesn't hand the
625
+ * chat to the team itself: a contact page (https) or an email (mailto:).
626
+ */
627
+ contactUrl: import_zod2.z.string().trim().max(500).regex(/^(?:https:\/\/[^\s]+|mailto:[^\s@]+@[^\s@]+)$/, "Use an https:// link or a mailto: address.").optional(),
615
628
  /**
616
629
  * Shallow-merged over the workspace branding. Each field is optional with no
617
630
  * default, so only the keys the persona sets override the workspace's — an
@@ -678,11 +691,20 @@ function resolvePersonaConfig(config, publishableKey) {
678
691
  branding: { ...config.branding, ...persona.branding ?? {} },
679
692
  auth: { ...config.auth, publishableKey: persona.publishableKey, allowAnonymous: persona.allowAnonymous ?? config.auth.allowAnonymous },
680
693
  pagePatterns: persona.pagePatterns,
681
- agent: persona.agent,
694
+ agent: { ...persona.agent, systemPrompt: personaInstructions(persona) },
682
695
  features: persona.features,
683
696
  knowledgeBase: persona.knowledgeBase
684
697
  });
685
698
  }
699
+ function personaInstructions(persona) {
700
+ const base = persona.agent.systemPrompt.trim();
701
+ const scope = persona.scope?.trim();
702
+ const handsOff = persona.features.escalation.enabled;
703
+ const contact = !handsOff ? persona.contactUrl?.trim() : void 0;
704
+ const fallback = handsOff ? "" : contact ? `When you can't answer from what you know, or someone asks for a person, say so briefly and point them to ${contact} to reach the team. Never guess.` : "When you can't answer from what you know, say so plainly. Never guess, and never promise that someone will follow up.";
705
+ const boundary = "Stay within this. If someone asks about something outside it, say briefly that it isn't something you can help with here" + (handsOff ? ", and offer to connect them with the team." : contact ? ", and share the contact link." : ".");
706
+ return [base, scope ? "What you help with, in the team's own words:\n" + scope + "\n\n" + boundary : "", fallback].filter(Boolean).join("\n\n");
707
+ }
686
708
  function normalizeRoutePath(value) {
687
709
  let path = (value || "/").split(/[?#]/)[0].trim();
688
710
  if (!path.startsWith("/")) path = "/" + path;
@@ -1602,6 +1624,7 @@ function oklchToHex(l, c, hDeg) {
1602
1624
  var NUMBER = String.raw`-?\d*\.?\d+`;
1603
1625
  var parts = (body) => body.replace(/\/.*$/, "").split(/[\s,]+/).filter(Boolean);
1604
1626
  function normalizeColor(raw) {
1627
+ if (raw.length > 200) return void 0;
1605
1628
  const value = raw.trim().replace(/\s*!important$/, "").toLowerCase();
1606
1629
  let match = value.match(/^#([0-9a-f]{3,4}|[0-9a-f]{6}|[0-9a-f]{8})$/);
1607
1630
  if (match) {
@@ -1628,20 +1651,58 @@ function normalizeColor(raw) {
1628
1651
  }
1629
1652
  return void 0;
1630
1653
  }
1654
+ function cssBlocks(source) {
1655
+ const blocks = [];
1656
+ let segmentStart = 0;
1657
+ for (let at = 0; at < source.length; ) {
1658
+ const open = source.indexOf("{", at);
1659
+ if (open === -1) break;
1660
+ const nextOpen = source.indexOf("{", open + 1);
1661
+ const close = source.indexOf("}", open + 1);
1662
+ if (close === -1) break;
1663
+ if (nextOpen !== -1 && nextOpen < close) {
1664
+ segmentStart = open + 1;
1665
+ at = open + 1;
1666
+ continue;
1667
+ }
1668
+ blocks.push([source.slice(segmentStart, open), source.slice(open + 1, close)]);
1669
+ segmentStart = close + 1;
1670
+ at = close + 1;
1671
+ }
1672
+ return blocks;
1673
+ }
1674
+ function customProperty(declaration) {
1675
+ const colon = declaration.indexOf(":");
1676
+ if (colon === -1) return void 0;
1677
+ const name = declaration.slice(0, colon).trim();
1678
+ const value = declaration.slice(colon + 1).trim();
1679
+ return name.length <= 100 && value.length <= 200 && /^--[\w-]+$/.test(name) && value ? [name, value] : void 0;
1680
+ }
1631
1681
  function rootVariables(css) {
1632
1682
  const vars = /* @__PURE__ */ new Map();
1633
- const source = css.replace(/\/\*[\s\S]*?\*\//g, "");
1634
- const blocks = /([^{}]*)\{([^{}]*)\}/g;
1635
- for (let block = blocks.exec(source); block; block = blocks.exec(source)) {
1636
- const selector = block[1].split(";").pop().trim();
1683
+ const source = stripComments(css);
1684
+ for (const [rawSelector, body] of cssBlocks(source)) {
1685
+ const selector = rawSelector.split(";").pop().trim();
1686
+ if (selector.length > 1e3) continue;
1637
1687
  if (!/(?:^|[\s,]):root\b(?![^,]*\.dark)|^@theme\b/.test(selector) || /\.dark|\[data-theme/.test(selector)) continue;
1638
- for (const declaration of block[2].split(";")) {
1639
- const found = declaration.match(/^\s*(--[\w-]+)\s*:\s*([\s\S]+?)\s*$/);
1640
- if (found && !vars.has(found[1])) vars.set(found[1], found[2]);
1688
+ for (const declaration of body.split(";")) {
1689
+ const found = customProperty(declaration);
1690
+ if (found && !vars.has(found[0])) vars.set(found[0], found[1]);
1641
1691
  }
1642
1692
  }
1643
1693
  return vars;
1644
1694
  }
1695
+ function stripComments(css) {
1696
+ let out = "";
1697
+ for (let at = 0; ; ) {
1698
+ const start = css.indexOf("/*", at);
1699
+ if (start === -1) return out + css.slice(at);
1700
+ out += css.slice(at, start);
1701
+ const end = css.indexOf("*/", start + 2);
1702
+ if (end === -1) return out;
1703
+ at = end + 2;
1704
+ }
1705
+ }
1645
1706
  function resolve(value, vars, depth = 0) {
1646
1707
  if (depth > 4) return void 0;
1647
1708
  const reference = value.match(/^(?:(hsla?|oklch|rgba?)\(\s*)?var\(\s*(--[\w-]+)\s*(?:,[^)]*)?\)\s*\)?$/i);
@@ -1754,6 +1815,7 @@ function inferTheme(files, root = "") {
1754
1815
  operationId,
1755
1816
  parseFrankieConfig,
1756
1817
  personaEnvKey,
1818
+ personaInstructions,
1757
1819
  personaProfileSchema,
1758
1820
  plainText,
1759
1821
  planHostedInstall,