@frankieai/server 0.2.3 → 0.2.4

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.
@@ -441,7 +441,6 @@ var OpenApiError = class extends Error {
441
441
  };
442
442
  async function inspectApiCapabilities(source, options = {}) {
443
443
  const doc = await loadSpec(source.spec, options.fetchImpl);
444
- const allowedMethods = new Set(source.methods);
445
444
  const hasPerUserAuth = Boolean(source.forwardUserTokenAs) || source.forwardUserClaims.length > 0;
446
445
  const capabilities = [];
447
446
  for (const [path, item] of Object.entries(doc.paths ?? {})) {
@@ -451,7 +450,6 @@ async function inspectApiCapabilities(source, options = {}) {
451
450
  const operation = item[method];
452
451
  if (!isRecord(operation)) continue;
453
452
  const httpMethod = method.toUpperCase();
454
- if (!allowedMethods.has(httpMethod)) continue;
455
453
  const operationId = typeof operation["operationId"] === "string" ? operation["operationId"] : `${method}_${slug(path)}`;
456
454
  if (source.operations.length > 0 && !source.operations.includes(operationId)) continue;
457
455
  const toolName = `${API_TOOL_PREFIX}${source.name}__${sanitize(operationId)}`;
@@ -499,14 +497,14 @@ async function buildOne(source, options) {
499
497
  for (const method of ["get", "post", "put", "patch", "delete"]) {
500
498
  const operation = item[method];
501
499
  if (!isRecord(operation)) continue;
502
- if (!allowedMethods.has(method.toUpperCase())) continue;
503
500
  const operationId = typeof operation["operationId"] === "string" ? operation["operationId"] : `${method}_${slug(path)}`;
504
501
  if (source.operations.length > 0 && !source.operations.includes(operationId)) continue;
505
- if (source.selectedTools && !source.selectedTools.includes(operationId)) continue;
502
+ if (source.selectedTools ? !source.selectedTools.includes(operationId) : !allowedMethods.has(method.toUpperCase())) continue;
506
503
  const name = `${API_TOOL_PREFIX}${source.name}__${sanitize(operationId)}`;
507
504
  if (!evaluateToolPolicy(name, options.policy).allowed) continue;
508
505
  const params = collectParameters(doc, item, operation);
509
506
  const body = collectRequestBody(doc, operation);
507
+ const scopeRules = parseScopeRules(operation);
510
508
  const { headers: securedHeaders, query: securityQuery } = applySecurity(doc, source, operation, schemes);
511
509
  const headers = buildForwardedHeaders({ ...source, headers: securedHeaders }, options.session);
512
510
  const opBaseUrl = resolveOperationBaseUrl(source, doc, item, operation, baseUrl ?? "");
@@ -540,6 +538,7 @@ ${retrySafetyNote(method, doc, item, operation)}`,
540
538
  );
541
539
  }
542
540
  const input = enforceIdentityParams(params, isRecord(args) ? args : {}, options.session);
541
+ enforceResourceScope(scopeRules, input, options.session);
543
542
  const url = buildUrl(opBaseUrl, path, params, input, securityQuery);
544
543
  const rawBody = body ? input["body"] : void 0;
545
544
  const requestBody = isRecord(rawBody) ? enforceServerDerivedIdentity(rawBody, options.session) : rawBody;
@@ -859,6 +858,39 @@ function enforceIdentityParams(params, input, session) {
859
858
  }
860
859
  return next;
861
860
  }
861
+ function parseScopeRules(operation) {
862
+ const raw = operation["x-frankie-scope"];
863
+ const entries = Array.isArray(raw) ? raw : raw !== void 0 ? [raw] : [];
864
+ const rules = [];
865
+ for (const entry of entries) {
866
+ if (isRecord(entry) && typeof entry["param"] === "string" && typeof entry["claim"] === "string") {
867
+ rules.push({ param: entry["param"], claim: entry["claim"] });
868
+ }
869
+ }
870
+ return rules;
871
+ }
872
+ function enforceResourceScope(rules, input, session) {
873
+ for (const rule of rules) {
874
+ if (session.user.anonymous) {
875
+ throw new Error(
876
+ `"${rule.param}" is access-scoped, but the chat session is anonymous \u2014 Frankie will not call it for an unauthenticated visitor. Report this to the user instead of retrying.`
877
+ );
878
+ }
879
+ const requested = input[rule.param];
880
+ if (requested === void 0 || requested === null || requested === "") {
881
+ throw new Error(
882
+ `This operation requires an in-scope "${rule.param}" and none was given. Report this to the user instead of retrying.`
883
+ );
884
+ }
885
+ const allowed = session.user.claims[rule.claim];
886
+ const allowedList = Array.isArray(allowed) ? allowed.map((entry) => String(entry)) : typeof allowed === "string" ? [allowed] : [];
887
+ if (!allowedList.includes(String(requested))) {
888
+ throw new Error(
889
+ `Access denied: "${String(requested)}" is not in this user's "${rule.claim}" scope. Do not retry with a different id \u2014 report this to the user.`
890
+ );
891
+ }
892
+ }
893
+ }
862
894
 
863
895
  // src/agent/mcp.ts
864
896
  import { Client } from "@modelcontextprotocol/sdk/client/index.js";
@@ -1922,6 +1954,7 @@ var EventQueue = class {
1922
1954
  // src/app.ts
1923
1955
  import { randomUUID as randomUUID4 } from "crypto";
1924
1956
  import { Hono as Hono3 } from "hono";
1957
+ import { suggestedPromptsFromSkills } from "@frankieai/core";
1925
1958
 
1926
1959
  // src/guide.ts
1927
1960
  import { Hono as Hono2 } from "hono";
@@ -2154,7 +2187,13 @@ function createFrankieApp(options) {
2154
2187
  launcherLabel: config.branding.launcherLabel
2155
2188
  },
2156
2189
  greeting: config.agent.greeting,
2157
- suggestedPrompts: config.agent.suggestedPrompts,
2190
+ // Fall back to prompts derived from the workspace's own published skills,
2191
+ // so an empty chat suggests what this assistant can actually do instead of
2192
+ // nothing. An explicitly authored list always wins.
2193
+ suggestedPrompts: config.agent.suggestedPrompts.length ? config.agent.suggestedPrompts : suggestedPromptsFromSkills(config),
2194
+ // Explicit starter cards configured in the dashboard's Appearance tab.
2195
+ // When set, the widget renders these instead of the suggestedPrompts.
2196
+ starterPrompts: config.agent.starterPrompts,
2158
2197
  theme: config.theme,
2159
2198
  allowAnonymous: config.auth.allowAnonymous,
2160
2199
  showToolActivity: config.agent.showToolActivity,
@@ -2652,6 +2691,11 @@ function toPublicTenant(record) {
2652
2691
  ...record.vercel ? { vercel: publicVercelLink(record.vercel) } : {},
2653
2692
  theme: config.theme,
2654
2693
  features: config.features,
2694
+ agent: {
2695
+ greeting: config.agent.greeting,
2696
+ suggestedPrompts: config.agent.suggestedPrompts,
2697
+ starterPrompts: config.agent.starterPrompts
2698
+ },
2655
2699
  createdAt: record.createdAt,
2656
2700
  updatedAt: record.updatedAt
2657
2701
  };
@@ -3163,4 +3207,4 @@ export {
3163
3207
  resolveStores,
3164
3208
  resolveTenantStore
3165
3209
  };
3166
- //# sourceMappingURL=chunk-MKN5663J.js.map
3210
+ //# sourceMappingURL=chunk-7J5Z5MIK.js.map