feat: wire Launcher AI Workspace entitlements
This commit is contained in:
parent
e11f9bd7e7
commit
f35cd34167
|
|
@ -86,6 +86,16 @@ NDC_LAUNCHER_INTERNAL_ACCESS_TOKEN=...
|
||||||
|
|
||||||
If `NDC_LAUNCHER_INTERNAL_ACCESS_TOKEN` is not set, the service falls back to `NODEDC_INTERNAL_ACCESS_TOKEN`.
|
If `NDC_LAUNCHER_INTERNAL_ACCESS_TOKEN` is not set, the service falls back to `NODEDC_INTERNAL_ACCESS_TOKEN`.
|
||||||
|
|
||||||
|
Launcher entitlement adapter configuration:
|
||||||
|
|
||||||
|
```text
|
||||||
|
AI_WORKSPACE_LAUNCHER_ENTITLEMENT_URL=http://launcher:5173/api/ai-workspace/internal/v1/entitlements
|
||||||
|
AI_WORKSPACE_LAUNCHER_ENTITLEMENT_TOKEN=...
|
||||||
|
AI_WORKSPACE_LAUNCHER_ENTITLEMENT_REQUIRED=true
|
||||||
|
```
|
||||||
|
|
||||||
|
The Launcher adapter is evaluated after app-owned adapters and may return `launcher`, `ops`, and `engine` app grants. A Launcher denial is the top-level service-access decision and clears MCP servers for that app in the run profile; app-owned adapters still own app-local scopes and runtime tokens.
|
||||||
|
|
||||||
The first HUB action ids exposed to assistants are:
|
The first HUB action ids exposed to assistants are:
|
||||||
|
|
||||||
- `hub.user.read_admin_summary`
|
- `hub.user.read_admin_summary`
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,14 @@ import { createHash, randomUUID } from "node:crypto";
|
||||||
|
|
||||||
const SECRET_TOKEN = "secret-run-token-for-smoke";
|
const SECRET_TOKEN = "secret-run-token-for-smoke";
|
||||||
const ACCESS_DENIED_TEXT = "Доступ к модулю ограничен, обратитесь к администратору системы.";
|
const ACCESS_DENIED_TEXT = "Доступ к модулю ограничен, обратитесь к администратору системы.";
|
||||||
|
const RUN_APP_ID_ALIASES = new Map([
|
||||||
|
["hub", "launcher"],
|
||||||
|
["nodedc-hub", "launcher"],
|
||||||
|
["nodedc_launcher", "launcher"],
|
||||||
|
["nodedc-launcher", "launcher"],
|
||||||
|
]);
|
||||||
const APP_ROUTING_CATALOG = [
|
const APP_ROUTING_CATALOG = [
|
||||||
{ appId: "launcher", appTitle: "NODE.DC Launcher", surface: "launcher", skillId: "launcher-context", mcpServerNames: [], deniedText: ACCESS_DENIED_TEXT },
|
{ appId: "launcher", appTitle: "NODE.DC Launcher", surface: "launcher", skillId: "launcher-context", appAliases: ["hub"], actionIdPrefixes: ["hub.", "launcher.", "access."], mcpServerNames: [], deniedText: ACCESS_DENIED_TEXT },
|
||||||
{ appId: "engine", appTitle: "NODE.DC Engine / InJoin", surface: "engine", skillId: "engine-context", mcpServerNames: ["nodedc-engine", "nodedc-agent-core"], deniedText: ACCESS_DENIED_TEXT },
|
{ appId: "engine", appTitle: "NODE.DC Engine / InJoin", surface: "engine", skillId: "engine-context", mcpServerNames: ["nodedc-engine", "nodedc-agent-core"], deniedText: ACCESS_DENIED_TEXT },
|
||||||
{ appId: "ops", appTitle: "NODE.DC Ops / Tasker", surface: "ops", skillId: "ops-context", mcpServerNames: ["nodedc_ops_agent"], deniedText: ACCESS_DENIED_TEXT },
|
{ appId: "ops", appTitle: "NODE.DC Ops / Tasker", surface: "ops", skillId: "ops-context", mcpServerNames: ["nodedc_ops_agent"], deniedText: ACCESS_DENIED_TEXT },
|
||||||
];
|
];
|
||||||
|
|
@ -60,6 +66,37 @@ const assistantActions = {
|
||||||
};
|
};
|
||||||
const appCatalog = buildRunProfileAppCatalog({ appGrants, mcpServers, assistantActions });
|
const appCatalog = buildRunProfileAppCatalog({ appGrants, mcpServers, assistantActions });
|
||||||
const appAccess = summarizeRunAppAccess(appCatalog);
|
const appAccess = summarizeRunAppAccess(appCatalog);
|
||||||
|
const launcherAdapter = { id: "launcher", appId: "launcher", title: "NODE.DC Launcher" };
|
||||||
|
const launcherGrants = normalizeEntitlementAdapterAppGrants({
|
||||||
|
ok: true,
|
||||||
|
appGrants: {
|
||||||
|
hub: {
|
||||||
|
appTitle: "NODE.DC Launcher",
|
||||||
|
surface: "launcher",
|
||||||
|
status: "granted",
|
||||||
|
scopes: ["launcher:access:read", "launcher_admin_scope.any"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, launcherAdapter);
|
||||||
|
const catalogWithLauncherGrant = buildRunProfileAppCatalog({
|
||||||
|
appGrants: { ...appGrants, ...launcherGrants },
|
||||||
|
mcpServers,
|
||||||
|
assistantActions,
|
||||||
|
});
|
||||||
|
const deniedByLauncher = mergeRunAppGrants(appGrants, normalizeEntitlementAdapterAppGrants({
|
||||||
|
ok: true,
|
||||||
|
appGrants: {
|
||||||
|
ops: {
|
||||||
|
appTitle: "NODE.DC Ops",
|
||||||
|
surface: "ops",
|
||||||
|
status: "denied",
|
||||||
|
allowed: false,
|
||||||
|
reason: "launcher_service_access_denied",
|
||||||
|
mcpServers: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, launcherAdapter));
|
||||||
|
const deniedMcpServers = runProfileMcpServersFromAppGrants({}, deniedByLauncher);
|
||||||
const runProfile = {
|
const runProfile = {
|
||||||
schemaVersion: "ai-workspace.run-profile.v1",
|
schemaVersion: "ai-workspace.run-profile.v1",
|
||||||
runId: randomUUID(),
|
runId: randomUUID(),
|
||||||
|
|
@ -138,6 +175,12 @@ assert.equal(runProfile.appCatalog.find((app) => app.appId === "ops")?.status, "
|
||||||
assert.deepEqual(runProfile.appCatalog.find((app) => app.appId === "ops")?.mcpServerNames, ["nodedc_ops_agent"]);
|
assert.deepEqual(runProfile.appCatalog.find((app) => app.appId === "ops")?.mcpServerNames, ["nodedc_ops_agent"]);
|
||||||
assert.equal(runProfile.appCatalog.find((app) => app.appId === "launcher")?.status, "not-granted");
|
assert.equal(runProfile.appCatalog.find((app) => app.appId === "launcher")?.status, "not-granted");
|
||||||
assert.equal(runProfile.appCatalog.find((app) => app.appId === "launcher")?.deniedText, ACCESS_DENIED_TEXT);
|
assert.equal(runProfile.appCatalog.find((app) => app.appId === "launcher")?.deniedText, ACCESS_DENIED_TEXT);
|
||||||
|
assert.equal(launcherGrants.launcher.appId, "launcher");
|
||||||
|
assert.equal(launcherGrants.hub, undefined);
|
||||||
|
assert.deepEqual(catalogWithLauncherGrant.find((app) => app.appId === "launcher")?.actionIds, ["hub.access_request.list_pending", "hub.user.read_admin_summary"]);
|
||||||
|
assert.equal(deniedByLauncher.ops.status, "denied");
|
||||||
|
assert.deepEqual(deniedByLauncher.ops.mcpServers, []);
|
||||||
|
assert.deepEqual(deniedMcpServers, []);
|
||||||
assert.deepEqual(runProfile.appAccess.grantedAppIds, ["ops"]);
|
assert.deepEqual(runProfile.appAccess.grantedAppIds, ["ops"]);
|
||||||
assert.deepEqual(runProfile.appAccess.deniedAppIds, ["engine", "launcher"]);
|
assert.deepEqual(runProfile.appAccess.deniedAppIds, ["engine", "launcher"]);
|
||||||
assert.equal(JSON.stringify(publicProfile).includes(SECRET_TOKEN), false);
|
assert.equal(JSON.stringify(publicProfile).includes(SECRET_TOKEN), false);
|
||||||
|
|
@ -150,6 +193,8 @@ console.log(JSON.stringify({
|
||||||
"token_scoped_ops_mcp_in_run_profile",
|
"token_scoped_ops_mcp_in_run_profile",
|
||||||
"assistant_action_relay_in_run_profile",
|
"assistant_action_relay_in_run_profile",
|
||||||
"app_routing_catalog_in_run_profile",
|
"app_routing_catalog_in_run_profile",
|
||||||
|
"hub_actions_route_to_launcher",
|
||||||
|
"launcher_denial_removes_ops_mcp",
|
||||||
"denied_apps_have_standard_text",
|
"denied_apps_have_standard_text",
|
||||||
"public_run_profile_redacts_mcp_headers",
|
"public_run_profile_redacts_mcp_headers",
|
||||||
"public_run_profile_redacts_assistant_action_gateway_token",
|
"public_run_profile_redacts_assistant_action_gateway_token",
|
||||||
|
|
@ -188,7 +233,7 @@ function normalizeEntitlementAdapterAppGrants(payload, currentAdapter) {
|
||||||
|
|
||||||
function normalizeEntitlementAdapterGrant(value, currentAdapter) {
|
function normalizeEntitlementAdapterGrant(value, currentAdapter) {
|
||||||
if (!isPlainObject(value)) return null;
|
if (!isPlainObject(value)) return null;
|
||||||
const appId = normalizeKey(value.appId || value.app_id || currentAdapter.appId);
|
const appId = canonicalRunAppId(value.appId || value.app_id || currentAdapter.appId);
|
||||||
if (!appId) return null;
|
if (!appId) return null;
|
||||||
return {
|
return {
|
||||||
...value,
|
...value,
|
||||||
|
|
@ -200,13 +245,60 @@ function normalizeEntitlementAdapterGrant(value, currentAdapter) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function canonicalRunAppId(value) {
|
||||||
|
const appId = normalizeKey(value);
|
||||||
|
if (!appId) return "";
|
||||||
|
return RUN_APP_ID_ALIASES.get(appId) || appId;
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeRunAppGrantsObject(value) {
|
||||||
|
const out = {};
|
||||||
|
if (!isPlainObject(value)) return out;
|
||||||
|
for (const [key, rawGrant] of Object.entries(value)) {
|
||||||
|
if (!isPlainObject(rawGrant)) continue;
|
||||||
|
const appId = canonicalRunAppId(rawGrant.appId || rawGrant.app_id || key);
|
||||||
|
if (!appId) continue;
|
||||||
|
const existing = isPlainObject(out[appId]) ? out[appId] : {};
|
||||||
|
const grant = {
|
||||||
|
...existing,
|
||||||
|
...rawGrant,
|
||||||
|
appId,
|
||||||
|
};
|
||||||
|
grant.mcpServers = isRunAppGrantDenied(grant)
|
||||||
|
? []
|
||||||
|
: Object.hasOwn(rawGrant, "mcpServers") ? rawGrant.mcpServers : existing.mcpServers;
|
||||||
|
out[appId] = grant;
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
function mergeRunAppGrants(base, next) {
|
||||||
|
const out = normalizeRunAppGrantsObject(base);
|
||||||
|
for (const [rawAppId, grant] of Object.entries(normalizeRunAppGrantsObject(next))) {
|
||||||
|
const appId = canonicalRunAppId(grant.appId || rawAppId);
|
||||||
|
if (!appId) continue;
|
||||||
|
const existing = isPlainObject(out[appId]) ? out[appId] : {};
|
||||||
|
const merged = {
|
||||||
|
...existing,
|
||||||
|
...grant,
|
||||||
|
appId,
|
||||||
|
};
|
||||||
|
merged.mcpServers = isRunAppGrantDenied(merged)
|
||||||
|
? []
|
||||||
|
: Object.hasOwn(grant, "mcpServers") ? grant.mcpServers : existing.mcpServers;
|
||||||
|
out[appId] = merged;
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
function runProfileMcpServersFromAppGrants(settings, appGrantsInput = {}) {
|
function runProfileMcpServersFromAppGrants(settings, appGrantsInput = {}) {
|
||||||
const servers = [];
|
const servers = [];
|
||||||
const metadata = isPlainObject(settings?.metadata) ? settings.metadata : {};
|
const metadata = isPlainObject(settings?.metadata) ? settings.metadata : {};
|
||||||
collectInstallerMcpServers(servers, metadata.mcpServers, {});
|
collectInstallerMcpServers(servers, metadata.mcpServers, {});
|
||||||
const grants = isPlainObject(appGrantsInput) ? appGrantsInput : {};
|
const grants = normalizeRunAppGrantsObject(appGrantsInput);
|
||||||
for (const [appId, grant] of Object.entries(grants)) {
|
for (const [appId, grant] of Object.entries(grants)) {
|
||||||
if (!isPlainObject(grant)) continue;
|
if (!isPlainObject(grant)) continue;
|
||||||
|
if (isRunAppGrantDenied(grant)) continue;
|
||||||
collectInstallerMcpServers(servers, grant.mcpServers, {
|
collectInstallerMcpServers(servers, grant.mcpServers, {
|
||||||
appId: optionalString(grant.appId) || normalizeKey(appId),
|
appId: optionalString(grant.appId) || normalizeKey(appId),
|
||||||
appTitle: optionalString(grant.appTitle || grant.title),
|
appTitle: optionalString(grant.appTitle || grant.title),
|
||||||
|
|
@ -222,11 +314,11 @@ function runProfileMcpServersFromAppGrants(settings, appGrantsInput = {}) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function summarizeRunAppGrants(metadata) {
|
function summarizeRunAppGrants(metadata) {
|
||||||
const grants = isPlainObject(metadata?.appGrants) ? metadata.appGrants : {};
|
const grants = normalizeRunAppGrantsObject(metadata?.appGrants);
|
||||||
const out = {};
|
const out = {};
|
||||||
for (const [key, value] of Object.entries(grants)) {
|
for (const [key, value] of Object.entries(grants)) {
|
||||||
if (!isPlainObject(value)) continue;
|
if (!isPlainObject(value)) continue;
|
||||||
const appId = optionalString(value.appId) || normalizeKey(key);
|
const appId = canonicalRunAppId(value.appId || key);
|
||||||
if (!appId) continue;
|
if (!appId) continue;
|
||||||
const denied = isRunAppGrantDenied(value);
|
const denied = isRunAppGrantDenied(value);
|
||||||
const mcpServers = Array.isArray(value.mcpServers)
|
const mcpServers = Array.isArray(value.mcpServers)
|
||||||
|
|
@ -256,7 +348,7 @@ function summarizeRunAppGrants(metadata) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildRunProfileAppCatalog({ appGrants, mcpServers, assistantActions }) {
|
function buildRunProfileAppCatalog({ appGrants, mcpServers, assistantActions }) {
|
||||||
const grants = isPlainObject(appGrants) ? appGrants : {};
|
const grants = normalizeRunAppGrantsObject(appGrants);
|
||||||
const allMcpServerNames = uniqueStrings((Array.isArray(mcpServers) ? mcpServers : [])
|
const allMcpServerNames = uniqueStrings((Array.isArray(mcpServers) ? mcpServers : [])
|
||||||
.map((server) => server?.serverName)
|
.map((server) => server?.serverName)
|
||||||
.filter(Boolean));
|
.filter(Boolean));
|
||||||
|
|
@ -276,7 +368,7 @@ function buildRunProfileAppCatalog({ appGrants, mcpServers, assistantActions })
|
||||||
granted: status === "granted",
|
granted: status === "granted",
|
||||||
denied: status !== "granted",
|
denied: status !== "granted",
|
||||||
deniedText: status !== "granted" ? entry.deniedText : null,
|
deniedText: status !== "granted" ? entry.deniedText : null,
|
||||||
actionIds: actionIds.filter((actionId) => String(actionId || "").startsWith(`${entry.appId}.`)),
|
actionIds: actionIds.filter((actionId) => actionIdPrefixesForEntry(entry).some((prefix) => String(actionId || "").startsWith(prefix))),
|
||||||
mcpServerNames: status === "granted"
|
mcpServerNames: status === "granted"
|
||||||
? uniqueStrings([
|
? uniqueStrings([
|
||||||
...entry.mcpServerNames,
|
...entry.mcpServerNames,
|
||||||
|
|
@ -288,6 +380,14 @@ function buildRunProfileAppCatalog({ appGrants, mcpServers, assistantActions })
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function actionIdPrefixesForEntry(entry) {
|
||||||
|
return uniqueStrings([
|
||||||
|
...(Array.isArray(entry.actionIdPrefixes) ? entry.actionIdPrefixes : []),
|
||||||
|
`${entry.appId}.`,
|
||||||
|
...(Array.isArray(entry.appAliases) ? entry.appAliases.map((alias) => `${normalizeKey(alias)}.`) : []),
|
||||||
|
].filter(Boolean));
|
||||||
|
}
|
||||||
|
|
||||||
function summarizeRunAppAccess(appCatalog) {
|
function summarizeRunAppAccess(appCatalog) {
|
||||||
const apps = Array.isArray(appCatalog) ? appCatalog : [];
|
const apps = Array.isArray(appCatalog) ? appCatalog : [];
|
||||||
return {
|
return {
|
||||||
|
|
@ -306,7 +406,7 @@ function isRunAppGrantDenied(grant) {
|
||||||
grant.allowed === false ||
|
grant.allowed === false ||
|
||||||
grant.granted === false ||
|
grant.granted === false ||
|
||||||
grant.denied === true ||
|
grant.denied === true ||
|
||||||
["denied", "disabled", "blocked", "revoked", "not_granted", "forbidden"].includes(status);
|
["denied", "disabled", "blocked", "revoked", "not-granted", "not_granted", "forbidden"].includes(status);
|
||||||
}
|
}
|
||||||
|
|
||||||
function collectInstallerMcpServers(target, value, defaults = {}) {
|
function collectInstallerMcpServers(target, value, defaults = {}) {
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,12 @@ const ASSISTANT_ACTION_TOOL_PROFILE_BASE = {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
const ACCESS_DENIED_TEXT = "Доступ к модулю ограничен, обратитесь к администратору системы.";
|
const ACCESS_DENIED_TEXT = "Доступ к модулю ограничен, обратитесь к администратору системы.";
|
||||||
|
const RUN_APP_ID_ALIASES = new Map([
|
||||||
|
["hub", "launcher"],
|
||||||
|
["nodedc-hub", "launcher"],
|
||||||
|
["nodedc_launcher", "launcher"],
|
||||||
|
["nodedc-launcher", "launcher"],
|
||||||
|
]);
|
||||||
const APP_ROUTING_CATALOG = [
|
const APP_ROUTING_CATALOG = [
|
||||||
{
|
{
|
||||||
appId: "launcher",
|
appId: "launcher",
|
||||||
|
|
@ -43,7 +49,9 @@ const APP_ROUTING_CATALOG = [
|
||||||
"invites",
|
"invites",
|
||||||
"entitlements",
|
"entitlements",
|
||||||
],
|
],
|
||||||
actionNamespaces: ["launcher.*", "access.*"],
|
appAliases: ["hub"],
|
||||||
|
actionNamespaces: ["hub.*", "launcher.*", "access.*"],
|
||||||
|
actionIdPrefixes: ["hub.", "launcher.", "access."],
|
||||||
mcpServerNames: [],
|
mcpServerNames: [],
|
||||||
requiredScopes: ["launcher:access:read"],
|
requiredScopes: ["launcher:access:read"],
|
||||||
deniedText: ACCESS_DENIED_TEXT,
|
deniedText: ACCESS_DENIED_TEXT,
|
||||||
|
|
@ -2144,7 +2152,7 @@ function delay(ms) {
|
||||||
async function resolveRunAppGrants({ owner, context, ownerSettings }) {
|
async function resolveRunAppGrants({ owner, context, ownerSettings }) {
|
||||||
const metadata = isPlainObject(ownerSettings?.metadata) ? ownerSettings.metadata : {};
|
const metadata = isPlainObject(ownerSettings?.metadata) ? ownerSettings.metadata : {};
|
||||||
const staticAppGrants = isPlainObject(metadata.appGrants) ? metadata.appGrants : {};
|
const staticAppGrants = isPlainObject(metadata.appGrants) ? metadata.appGrants : {};
|
||||||
const appGrants = { ...staticAppGrants };
|
const appGrants = normalizeRunAppGrantsObject(staticAppGrants);
|
||||||
const adapterDiagnostics = [];
|
const adapterDiagnostics = [];
|
||||||
const adapters = Array.isArray(config.entitlementAdapters) ? config.entitlementAdapters : [];
|
const adapters = Array.isArray(config.entitlementAdapters) ? config.entitlementAdapters : [];
|
||||||
if (!adapters.length) {
|
if (!adapters.length) {
|
||||||
|
|
@ -2177,8 +2185,10 @@ async function resolveRunAppGrants({ owner, context, ownerSettings }) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const adapterAppGrants = normalizeEntitlementAdapterAppGrants(result.payload, adapter);
|
const adapterAppGrants = normalizeEntitlementAdapterAppGrants(result.payload, adapter);
|
||||||
for (const [appId, grant] of Object.entries(adapterAppGrants)) {
|
for (const [rawAppId, grant] of Object.entries(adapterAppGrants)) {
|
||||||
if (!isPlainObject(grant)) continue;
|
if (!isPlainObject(grant)) continue;
|
||||||
|
const appId = canonicalRunAppId(grant.appId || rawAppId);
|
||||||
|
if (!appId) continue;
|
||||||
const existing = isPlainObject(appGrants[appId]) ? appGrants[appId] : {};
|
const existing = isPlainObject(appGrants[appId]) ? appGrants[appId] : {};
|
||||||
const merged = {
|
const merged = {
|
||||||
...existing,
|
...existing,
|
||||||
|
|
@ -2217,6 +2227,27 @@ async function resolveRunAppGrants({ owner, context, ownerSettings }) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function normalizeRunAppGrantsObject(value) {
|
||||||
|
const out = {};
|
||||||
|
if (!isPlainObject(value)) return out;
|
||||||
|
for (const [key, rawGrant] of Object.entries(value)) {
|
||||||
|
if (!isPlainObject(rawGrant)) continue;
|
||||||
|
const appId = canonicalRunAppId(rawGrant.appId || rawGrant.app_id || key);
|
||||||
|
if (!appId) continue;
|
||||||
|
const existing = isPlainObject(out[appId]) ? out[appId] : {};
|
||||||
|
const grant = {
|
||||||
|
...existing,
|
||||||
|
...rawGrant,
|
||||||
|
appId,
|
||||||
|
};
|
||||||
|
grant.mcpServers = isRunAppGrantDenied(grant)
|
||||||
|
? []
|
||||||
|
: Object.hasOwn(rawGrant, "mcpServers") ? rawGrant.mcpServers : existing.mcpServers;
|
||||||
|
out[appId] = grant;
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
async function fetchRunEntitlementAdapter({ adapter, owner, context, ownerSettings }) {
|
async function fetchRunEntitlementAdapter({ adapter, owner, context, ownerSettings }) {
|
||||||
const controller = new AbortController();
|
const controller = new AbortController();
|
||||||
const timeout = setTimeout(() => controller.abort(), adapter.timeoutMs);
|
const timeout = setTimeout(() => controller.abort(), adapter.timeoutMs);
|
||||||
|
|
@ -2288,7 +2319,7 @@ function normalizeEntitlementAdapterAppGrants(payload, adapter) {
|
||||||
|
|
||||||
function normalizeEntitlementAdapterGrant(value, adapter) {
|
function normalizeEntitlementAdapterGrant(value, adapter) {
|
||||||
if (!isPlainObject(value)) return null;
|
if (!isPlainObject(value)) return null;
|
||||||
const appId = normalizeKey(value.appId || value.app_id || adapter.appId);
|
const appId = canonicalRunAppId(value.appId || value.app_id || adapter.appId);
|
||||||
if (!appId) return null;
|
if (!appId) return null;
|
||||||
return {
|
return {
|
||||||
...value,
|
...value,
|
||||||
|
|
@ -2300,9 +2331,15 @@ function normalizeEntitlementAdapterGrant(value, adapter) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function canonicalRunAppId(value) {
|
||||||
|
const appId = normalizeKey(value);
|
||||||
|
if (!appId) return "";
|
||||||
|
return RUN_APP_ID_ALIASES.get(appId) || appId;
|
||||||
|
}
|
||||||
|
|
||||||
function runProfileMcpServersFromSettings(settings) {
|
function runProfileMcpServersFromSettings(settings) {
|
||||||
const metadata = isPlainObject(settings?.metadata) ? settings.metadata : {};
|
const metadata = isPlainObject(settings?.metadata) ? settings.metadata : {};
|
||||||
const appGrants = isPlainObject(metadata.appGrants) ? metadata.appGrants : {};
|
const appGrants = normalizeRunAppGrantsObject(metadata.appGrants);
|
||||||
return runProfileMcpServersFromAppGrants(settings, appGrants);
|
return runProfileMcpServersFromAppGrants(settings, appGrants);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2313,7 +2350,7 @@ function runProfileMcpServersFromAppGrants(settings, appGrantsInput = {}) {
|
||||||
if (!hasEntitlementAdapters) {
|
if (!hasEntitlementAdapters) {
|
||||||
collectInstallerMcpServers(servers, metadata.mcpServers, {});
|
collectInstallerMcpServers(servers, metadata.mcpServers, {});
|
||||||
}
|
}
|
||||||
const appGrants = isPlainObject(appGrantsInput) ? appGrantsInput : {};
|
const appGrants = normalizeRunAppGrantsObject(appGrantsInput);
|
||||||
for (const [appId, grant] of Object.entries(appGrants)) {
|
for (const [appId, grant] of Object.entries(appGrants)) {
|
||||||
if (!isPlainObject(grant)) continue;
|
if (!isPlainObject(grant)) continue;
|
||||||
if (isRunAppGrantDenied(grant)) continue;
|
if (isRunAppGrantDenied(grant)) continue;
|
||||||
|
|
@ -2332,11 +2369,11 @@ function runProfileMcpServersFromAppGrants(settings, appGrantsInput = {}) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function summarizeRunAppGrants(metadata) {
|
function summarizeRunAppGrants(metadata) {
|
||||||
const appGrants = isPlainObject(metadata?.appGrants) ? metadata.appGrants : {};
|
const appGrants = normalizeRunAppGrantsObject(metadata?.appGrants);
|
||||||
const out = {};
|
const out = {};
|
||||||
for (const [key, value] of Object.entries(appGrants)) {
|
for (const [key, value] of Object.entries(appGrants)) {
|
||||||
if (!isPlainObject(value)) continue;
|
if (!isPlainObject(value)) continue;
|
||||||
const appId = optionalString(value.appId) || normalizeKey(key);
|
const appId = canonicalRunAppId(value.appId || key);
|
||||||
if (!appId) continue;
|
if (!appId) continue;
|
||||||
const denied = isRunAppGrantDenied(value);
|
const denied = isRunAppGrantDenied(value);
|
||||||
const mcpServers = Array.isArray(value.mcpServers)
|
const mcpServers = Array.isArray(value.mcpServers)
|
||||||
|
|
@ -2366,9 +2403,9 @@ function summarizeRunAppGrants(metadata) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildRunProfileAppCatalog({ appGrants, mcpServers, assistantActions }) {
|
function buildRunProfileAppCatalog({ appGrants, mcpServers, assistantActions }) {
|
||||||
const grants = isPlainObject(appGrants) ? appGrants : {};
|
const grants = normalizeRunAppGrantsObject(appGrants);
|
||||||
const dynamicAppIds = Object.keys(grants)
|
const dynamicAppIds = Object.keys(grants)
|
||||||
.map(normalizeKey)
|
.map(canonicalRunAppId)
|
||||||
.filter(Boolean)
|
.filter(Boolean)
|
||||||
.filter((appId) => !APP_ROUTING_CATALOG.some((entry) => entry.appId === appId));
|
.filter((appId) => !APP_ROUTING_CATALOG.some((entry) => entry.appId === appId));
|
||||||
const catalog = [
|
const catalog = [
|
||||||
|
|
@ -2380,6 +2417,7 @@ function buildRunProfileAppCatalog({ appGrants, mcpServers, assistantActions })
|
||||||
skillId: `${appId}-context`,
|
skillId: `${appId}-context`,
|
||||||
whenToUse: [],
|
whenToUse: [],
|
||||||
actionNamespaces: [`${appId}.*`],
|
actionNamespaces: [`${appId}.*`],
|
||||||
|
actionIdPrefixes: [`${appId}.`],
|
||||||
mcpServerNames: [],
|
mcpServerNames: [],
|
||||||
requiredScopes: [],
|
requiredScopes: [],
|
||||||
deniedText: ACCESS_DENIED_TEXT,
|
deniedText: ACCESS_DENIED_TEXT,
|
||||||
|
|
@ -2417,6 +2455,11 @@ function buildRunProfileAppCatalog({ appGrants, mcpServers, assistantActions })
|
||||||
...grantMcpServerNames,
|
...grantMcpServerNames,
|
||||||
...(mcpByAppId.get(entry.appId) || []),
|
...(mcpByAppId.get(entry.appId) || []),
|
||||||
]);
|
]);
|
||||||
|
const actionIdPrefixes = uniqueStrings([
|
||||||
|
...(Array.isArray(entry.actionIdPrefixes) ? entry.actionIdPrefixes : []),
|
||||||
|
`${entry.appId}.`,
|
||||||
|
...(Array.isArray(entry.appAliases) ? entry.appAliases.map((alias) => `${normalizeKey(alias)}.`) : []),
|
||||||
|
].filter(Boolean));
|
||||||
return {
|
return {
|
||||||
schemaVersion: "ai-workspace.app-route.v1",
|
schemaVersion: "ai-workspace.app-route.v1",
|
||||||
appId: entry.appId,
|
appId: entry.appId,
|
||||||
|
|
@ -2434,7 +2477,7 @@ function buildRunProfileAppCatalog({ appGrants, mcpServers, assistantActions })
|
||||||
: null,
|
: null,
|
||||||
whenToUse: uniqueStrings(entry.whenToUse),
|
whenToUse: uniqueStrings(entry.whenToUse),
|
||||||
actionNamespaces: uniqueStrings(entry.actionNamespaces),
|
actionNamespaces: uniqueStrings(entry.actionNamespaces),
|
||||||
actionIds: actionIds.filter((actionId) => String(actionId || "").startsWith(`${entry.appId}.`)),
|
actionIds: actionIds.filter((actionId) => actionIdPrefixes.some((prefix) => String(actionId || "").startsWith(prefix))),
|
||||||
mcpServerNames: status === "granted" ? advertisedMcpServerNames : [],
|
mcpServerNames: status === "granted" ? advertisedMcpServerNames : [],
|
||||||
requiredScopes: uniqueStrings(grant?.requiredScopes || grant?.required_scopes || entry.requiredScopes),
|
requiredScopes: uniqueStrings(grant?.requiredScopes || grant?.required_scopes || entry.requiredScopes),
|
||||||
scopes: uniqueStrings(Array.isArray(grant?.scopes) ? grant.scopes : []),
|
scopes: uniqueStrings(Array.isArray(grant?.scopes) ? grant.scopes : []),
|
||||||
|
|
@ -4009,12 +4052,35 @@ function parseEntitlementAdapters() {
|
||||||
token: process.env.AI_WORKSPACE_ENGINE_ENTITLEMENT_TOKEN || process.env.NDC_AI_WORKSPACE_ENGINE_ENTITLEMENT_TOKEN,
|
token: process.env.AI_WORKSPACE_ENGINE_ENTITLEMENT_TOKEN || process.env.NDC_AI_WORKSPACE_ENGINE_ENTITLEMENT_TOKEN,
|
||||||
required: process.env.AI_WORKSPACE_ENGINE_ENTITLEMENT_REQUIRED || process.env.NDC_AI_WORKSPACE_ENGINE_ENTITLEMENT_REQUIRED,
|
required: process.env.AI_WORKSPACE_ENGINE_ENTITLEMENT_REQUIRED || process.env.NDC_AI_WORKSPACE_ENGINE_ENTITLEMENT_REQUIRED,
|
||||||
});
|
});
|
||||||
|
collectEntitlementAdapter(adapters, "launcher", {
|
||||||
|
url:
|
||||||
|
process.env.AI_WORKSPACE_LAUNCHER_ENTITLEMENT_URL ||
|
||||||
|
process.env.NDC_AI_WORKSPACE_LAUNCHER_ENTITLEMENT_URL ||
|
||||||
|
process.env.AI_WORKSPACE_HUB_ENTITLEMENT_URL ||
|
||||||
|
process.env.NDC_AI_WORKSPACE_HUB_ENTITLEMENT_URL,
|
||||||
|
token:
|
||||||
|
process.env.AI_WORKSPACE_LAUNCHER_ENTITLEMENT_TOKEN ||
|
||||||
|
process.env.NDC_AI_WORKSPACE_LAUNCHER_ENTITLEMENT_TOKEN ||
|
||||||
|
process.env.AI_WORKSPACE_HUB_ENTITLEMENT_TOKEN ||
|
||||||
|
process.env.NDC_AI_WORKSPACE_HUB_ENTITLEMENT_TOKEN,
|
||||||
|
required:
|
||||||
|
process.env.AI_WORKSPACE_LAUNCHER_ENTITLEMENT_REQUIRED ||
|
||||||
|
process.env.NDC_AI_WORKSPACE_LAUNCHER_ENTITLEMENT_REQUIRED ||
|
||||||
|
process.env.AI_WORKSPACE_HUB_ENTITLEMENT_REQUIRED ||
|
||||||
|
process.env.NDC_AI_WORKSPACE_HUB_ENTITLEMENT_REQUIRED,
|
||||||
|
});
|
||||||
|
|
||||||
const byAppId = new Map();
|
const byAppId = new Map();
|
||||||
for (const adapter of adapters) {
|
for (const adapter of adapters) {
|
||||||
byAppId.set(adapter.appId, adapter);
|
byAppId.set(adapter.appId, adapter);
|
||||||
}
|
}
|
||||||
return Array.from(byAppId.values());
|
const order = new Map([
|
||||||
|
["ops", 10],
|
||||||
|
["engine", 20],
|
||||||
|
["launcher", 90],
|
||||||
|
]);
|
||||||
|
return Array.from(byAppId.values())
|
||||||
|
.sort((left, right) => (order.get(left.appId) || 50) - (order.get(right.appId) || 50));
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseJsonEnv(value) {
|
function parseJsonEnv(value) {
|
||||||
|
|
@ -4045,7 +4111,7 @@ function collectEntitlementAdapter(target, defaultAppId, value) {
|
||||||
|
|
||||||
function sanitizeEntitlementAdapter(value, defaultAppId = "") {
|
function sanitizeEntitlementAdapter(value, defaultAppId = "") {
|
||||||
if (!isPlainObject(value)) return null;
|
if (!isPlainObject(value)) return null;
|
||||||
const appId = normalizeKey(value.appId || value.app_id || defaultAppId);
|
const appId = canonicalRunAppId(value.appId || value.app_id || defaultAppId);
|
||||||
const url = cleanHttpEndpoint(value.url || value.endpoint);
|
const url = cleanHttpEndpoint(value.url || value.endpoint);
|
||||||
if (!appId || !url) return null;
|
if (!appId || !url) return null;
|
||||||
const tokenEnv = optionalString(value.tokenEnv || value.token_env);
|
const tokenEnv = optionalString(value.tokenEnv || value.token_env);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue