@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.
- package/dist/{chunk-MKN5663J.js → chunk-7J5Z5MIK.js} +50 -6
- package/dist/chunk-7J5Z5MIK.js.map +1 -0
- package/dist/{chunk-37VMCQCI.js → chunk-YDXEBJON.js} +44 -3
- package/dist/chunk-YDXEBJON.js.map +1 -0
- package/dist/control-server.js +2 -2
- package/dist/index.d.ts +6 -0
- package/dist/index.js +2 -2
- package/dist/server.js +1 -1
- package/package.json +2 -2
- package/dist/chunk-37VMCQCI.js.map +0 -1
- package/dist/chunk-MKN5663J.js.map +0 -1
|
@@ -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
|
|
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
|
-
|
|
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-
|
|
3210
|
+
//# sourceMappingURL=chunk-7J5Z5MIK.js.map
|