@happyvertical/smrt-template-sveltekit 0.53.3 → 0.53.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/AGENTS.md +53 -26
- package/README.md +23 -16
- package/mcp-apps-template/package.dependencies.json +2 -2
- package/mcp-apps-template/src/routes/.well-known/oauth-protected-resource/api/mcp/+server.ts +14 -6
- package/mcp-apps-template/src/routes/api/mcp/+server.ts +69 -12
- package/package.json +17 -15
- package/template/AGENTS.md +16 -5
- package/template/Dockerfile +0 -1
- package/template/INSTALL_PROMPT.md +2 -2
- package/template/README.md +52 -27
- package/template/compose.yaml +2 -2
- package/template/package.json +28 -27
- package/template/smrt.config.ts +17 -4
- package/template/src/app.d.ts +4 -11
- package/template/src/hooks.server.ts +10 -51
- package/template/src/lib/server/smrt.ts +70 -62
- package/template/src/routes/+layout.server.ts +4 -12
- package/template/src/routes/+layout.svelte +27 -99
- package/template/src/routes/api/_runtime/diagnostics/+server.ts +17 -3
- package/template/src/routes/api/_runtime/health/+server.ts +4 -28
- package/template/src/routes/settings/+page.svelte +3 -34
- package/template/src/routes/setup/+page.server.ts +15 -88
- package/template/src/routes/setup/+page.svelte +3 -23
- package/template/vite.config.ts +5 -43
- package/template.config.js +11 -11
- package/mcp-apps-template/src/lib/McpAppsBridge.svelte +0 -37
- package/mcp-apps-template/src/lib/server/mcp-hosted.ts +0 -28
- package/mcp-apps-template/src/lib/server/mcp.ts +0 -85
- package/template/scripts/smrt-app.mjs +0 -885
- package/template/scripts/smrt-mcp-apps.mjs +0 -18
- package/template/scripts/smrt-operation-lock.mjs +0 -102
- package/template/scripts/smrt-portability-assets.mjs +0 -707
- package/template/scripts/smrt-portability.mjs +0 -470
- package/template/scripts/smrt-prepare-migration.mjs +0 -93
- package/template/scripts/smrt-process.mjs +0 -82
- package/template/scripts/smrt-provider-readiness.mjs +0 -33
- package/template/scripts/smrt-runtime-identity.mjs +0 -245
- package/template/scripts/smrt-vite.mjs +0 -45
- package/template/scripts/smrt-web.mjs +0 -10
- package/template/scripts/smrt-worker.mjs +0 -100
- package/template/scripts/smrt-writer-lease.mjs +0 -199
- package/template/src/lib/RuntimeDiagnosticsWebMcp.svelte +0 -11
- package/template/src/lib/runtime-diagnostics-webmcp.ts +0 -108
- package/template/src/lib/server/application-runtime.ts +0 -262
- package/template/src/lib/server/runtime-diagnostics.ts +0 -114
- package/template/src/lib/server/tenancy.ts +0 -114
- package/template/src/smrt-webmcp.d.ts +0 -6
|
@@ -1,96 +1,23 @@
|
|
|
1
1
|
import { rmSync } from 'node:fs';
|
|
2
2
|
import { join } from 'node:path';
|
|
3
|
-
import { fail, redirect } from '@sveltejs/kit';
|
|
4
|
-
import type { Actions, PageServerLoad } from './$types';
|
|
5
3
|
|
|
6
|
-
import {
|
|
7
|
-
|
|
8
|
-
getLocalApplicationRuntime,
|
|
9
|
-
} from '$lib/server/application-runtime';
|
|
10
|
-
import {
|
|
11
|
-
resolveApplicationId,
|
|
12
|
-
resolveApplicationStateRoot,
|
|
13
|
-
} from '../../../scripts/smrt-runtime-identity.mjs';
|
|
4
|
+
import { resolveApplicationStateRoot } from '@happyvertical/smrt-app-runtime';
|
|
5
|
+
import { createOwnerSetupPage } from '@happyvertical/smrt-app-runtime/sveltekit';
|
|
14
6
|
|
|
15
|
-
|
|
16
|
-
if (locals.user) throw redirect(303, '/');
|
|
17
|
-
if (applicationRuntime.profile !== 'local') {
|
|
18
|
-
return { available: false, token: '' };
|
|
19
|
-
}
|
|
20
|
-
const runtime = await getLocalApplicationRuntime();
|
|
21
|
-
const diagnostics = await runtime.diagnostics();
|
|
22
|
-
return {
|
|
23
|
-
available: diagnostics.bootstrap.status !== 'claimed',
|
|
24
|
-
token: url.searchParams.get('token') || '',
|
|
25
|
-
};
|
|
26
|
-
};
|
|
7
|
+
import { runtime } from '$lib/server/smrt';
|
|
27
8
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
if (!token || !name.trim() || !email.includes('@')) {
|
|
38
|
-
return fail(400, { message: 'Name, email, and a valid setup token are required.' });
|
|
39
|
-
}
|
|
40
|
-
const runtime = await getLocalApplicationRuntime();
|
|
41
|
-
let result;
|
|
42
|
-
try {
|
|
43
|
-
result = await runtime.claimOwner({
|
|
44
|
-
token,
|
|
45
|
-
name,
|
|
46
|
-
email,
|
|
47
|
-
userAgent: event.request.headers.get('user-agent') || undefined,
|
|
48
|
-
ipAddress: event.getClientAddress(),
|
|
49
|
-
});
|
|
50
|
-
} catch {
|
|
51
|
-
return fail(400, {
|
|
52
|
-
message:
|
|
53
|
-
'The setup invitation is invalid, expired, or already used. Run pnpm app:stop, pnpm app:recover, pnpm app:start, then pnpm app:open.',
|
|
54
|
-
});
|
|
55
|
-
}
|
|
56
|
-
event.cookies.set('sid', result.sessionId, {
|
|
57
|
-
path: '/',
|
|
58
|
-
httpOnly: true,
|
|
59
|
-
secure: event.url.protocol === 'https:',
|
|
60
|
-
sameSite: 'lax',
|
|
61
|
-
maxAge: 7 * 24 * 60 * 60,
|
|
9
|
+
/** Local-profile, loopback-only, single-use owner bootstrap. */
|
|
10
|
+
export const { load, actions } = createOwnerSetupPage(runtime, {
|
|
11
|
+
// `pnpm app:setup` leaves the invitation in mode-0600 handoff files; once
|
|
12
|
+
// the owner exists they only hold a token that now fails closed.
|
|
13
|
+
onOwnerClaimed() {
|
|
14
|
+
const stateRoot = resolveApplicationStateRoot({
|
|
15
|
+
appId: runtime.applicationId(),
|
|
16
|
+
dataDirectory: process.env.SMRT_DATA_DIR,
|
|
17
|
+
sourceRoot: process.cwd(),
|
|
62
18
|
});
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
sourceRoot: process.cwd(),
|
|
66
|
-
explicitId: process.env.SMRT_APP_ID,
|
|
67
|
-
});
|
|
68
|
-
rmSync(
|
|
69
|
-
join(
|
|
70
|
-
resolveApplicationStateRoot({
|
|
71
|
-
appId,
|
|
72
|
-
dataDirectory: process.env.SMRT_DATA_DIR,
|
|
73
|
-
sourceRoot: process.cwd(),
|
|
74
|
-
}),
|
|
75
|
-
'onboarding.json',
|
|
76
|
-
),
|
|
77
|
-
{ force: true },
|
|
78
|
-
);
|
|
79
|
-
rmSync(
|
|
80
|
-
join(
|
|
81
|
-
resolveApplicationStateRoot({
|
|
82
|
-
appId,
|
|
83
|
-
dataDirectory: process.env.SMRT_DATA_DIR,
|
|
84
|
-
sourceRoot: process.cwd(),
|
|
85
|
-
}),
|
|
86
|
-
'onboarding-launch.html',
|
|
87
|
-
),
|
|
88
|
-
{ force: true },
|
|
89
|
-
);
|
|
90
|
-
} catch {
|
|
91
|
-
// The owner and session are authoritative; stale mode-0600 handoff files
|
|
92
|
-
// only contain a token that now fails closed as already claimed.
|
|
19
|
+
for (const file of ['onboarding.json', 'onboarding-launch.html']) {
|
|
20
|
+
rmSync(join(stateRoot, file), { force: true });
|
|
93
21
|
}
|
|
94
|
-
throw redirect(303, '/');
|
|
95
22
|
},
|
|
96
|
-
};
|
|
23
|
+
});
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import { enhance } from '$app/forms';
|
|
3
|
+
import { OwnerSetupForm } from '@happyvertical/smrt-svelte/app';
|
|
2
4
|
import type { PageProps } from './$types';
|
|
3
5
|
|
|
4
6
|
let { data, form }: PageProps = $props();
|
|
@@ -6,26 +8,4 @@
|
|
|
6
8
|
|
|
7
9
|
<svelte:head><title>Set up your local application</title></svelte:head>
|
|
8
10
|
|
|
9
|
-
<
|
|
10
|
-
<h1>Set up your local application</h1>
|
|
11
|
-
{#if !data.available}
|
|
12
|
-
<p>Local owner setup is unavailable or has already been completed.</p>
|
|
13
|
-
{:else}
|
|
14
|
-
<p>Create the real local owner account. This invitation works once and stays on this device.</p>
|
|
15
|
-
{#if form?.message}<p role="alert">{form.message}</p>{/if}
|
|
16
|
-
<form method="POST">
|
|
17
|
-
<input type="hidden" name="token" value={data.token} />
|
|
18
|
-
<label>Name <input name="name" autocomplete="name" required /></label>
|
|
19
|
-
<label>Email <input name="email" type="email" autocomplete="email" required /></label>
|
|
20
|
-
<button type="submit">Create owner</button>
|
|
21
|
-
</form>
|
|
22
|
-
{/if}
|
|
23
|
-
</main>
|
|
24
|
-
|
|
25
|
-
<style>
|
|
26
|
-
main { max-width: 36rem; margin: 4rem auto; padding: 1.5rem; }
|
|
27
|
-
form { display: grid; gap: 1rem; }
|
|
28
|
-
label { display: grid; gap: 0.35rem; }
|
|
29
|
-
input { padding: 0.65rem; }
|
|
30
|
-
button { width: fit-content; padding: 0.65rem 1rem; }
|
|
31
|
-
</style>
|
|
11
|
+
<OwnerSetupForm {data} {form} {enhance} />
|
package/template/vite.config.ts
CHANGED
|
@@ -1,48 +1,10 @@
|
|
|
1
1
|
import { sveltekit } from '@sveltejs/kit/vite';
|
|
2
|
-
import {
|
|
3
|
-
import { smrtPlugin } from '@happyvertical/smrt-core/vite-plugin';
|
|
2
|
+
import { smrt } from '@happyvertical/smrt-core/vite';
|
|
4
3
|
import { defineConfig } from 'vite';
|
|
5
4
|
|
|
5
|
+
// `smrt()` applies the legacy-decorator transform, consumes the manifests of
|
|
6
|
+
// the packages listed in `smrt.config.ts` (`consumer.packages`), and generates
|
|
7
|
+
// this app's object registration, types, and API routes.
|
|
6
8
|
export default defineConfig({
|
|
7
|
-
|
|
8
|
-
// tsconfig.json sets experimentalDecorators, but Vite's oxc transform does
|
|
9
|
-
// not reliably honor it through the SvelteKit `extends
|
|
10
|
-
// "./.svelte-kit/tsconfig.json"` chain — raw `@decorator class` syntax then
|
|
11
|
-
// reaches the SSR runtime and throws `SyntaxError: Invalid or unexpected
|
|
12
|
-
// token` on the first request.
|
|
13
|
-
oxc: {
|
|
14
|
-
decorator: {
|
|
15
|
-
legacy: true,
|
|
16
|
-
emitDecoratorMetadata: true,
|
|
17
|
-
},
|
|
18
|
-
},
|
|
19
|
-
plugins: [
|
|
20
|
-
sveltekit(),
|
|
21
|
-
// Only packages whose object manifests this app consumes belong here.
|
|
22
|
-
// Keeping the list explicit avoids treating tooling/UI packages as domain
|
|
23
|
-
// model providers and makes generated runtime registration deterministic.
|
|
24
|
-
smrtConsumer({
|
|
25
|
-
packages: [
|
|
26
|
-
'@happyvertical/smrt-profiles',
|
|
27
|
-
'@happyvertical/smrt-tenancy',
|
|
28
|
-
'@happyvertical/smrt-users',
|
|
29
|
-
],
|
|
30
|
-
generateTypes: true,
|
|
31
|
-
typesDir: 'src/lib/types/smrt-generated',
|
|
32
|
-
svelteKit: true,
|
|
33
|
-
}),
|
|
34
|
-
smrtPlugin({
|
|
35
|
-
include: ['src/lib/objects/**/*.ts'],
|
|
36
|
-
exclude: ['**/*.test.ts', '**/*.spec.ts'],
|
|
37
|
-
generateTypes: true,
|
|
38
|
-
typeDeclarationsPath: 'src/lib/types/smrt-generated',
|
|
39
|
-
svelteKit: {
|
|
40
|
-
enabled: true,
|
|
41
|
-
routesDir: 'src/routes/api',
|
|
42
|
-
objectsDir: 'src/lib/objects',
|
|
43
|
-
configPath: 'src/lib/server',
|
|
44
|
-
configFileName: 'smrt.ts',
|
|
45
|
-
},
|
|
46
|
-
}),
|
|
47
|
-
],
|
|
9
|
+
plugins: [sveltekit(), smrt()],
|
|
48
10
|
});
|
package/template.config.js
CHANGED
|
@@ -19,22 +19,22 @@ export default {
|
|
|
19
19
|
|
|
20
20
|
// Dependencies to add to generated project
|
|
21
21
|
dependencies: {
|
|
22
|
-
'@happyvertical/smrt-app-runtime': '^0.53.
|
|
23
|
-
'@happyvertical/smrt-config': '^0.53.
|
|
24
|
-
'@happyvertical/smrt-core': '^0.53.
|
|
25
|
-
'@happyvertical/smrt-jobs': '^0.53.
|
|
26
|
-
'@happyvertical/smrt-profiles': '^0.53.
|
|
27
|
-
'@happyvertical/smrt-svelte': '^0.53.
|
|
28
|
-
'@happyvertical/smrt-tenancy': '^0.53.
|
|
29
|
-
'@happyvertical/smrt-ui': '^0.53.
|
|
30
|
-
'@happyvertical/smrt-users': '^0.53.
|
|
31
|
-
'@happyvertical/smrt-web': '^0.53.
|
|
22
|
+
'@happyvertical/smrt-app-runtime': '^0.53.4',
|
|
23
|
+
'@happyvertical/smrt-config': '^0.53.4',
|
|
24
|
+
'@happyvertical/smrt-core': '^0.53.4',
|
|
25
|
+
'@happyvertical/smrt-jobs': '^0.53.4',
|
|
26
|
+
'@happyvertical/smrt-profiles': '^0.53.4',
|
|
27
|
+
'@happyvertical/smrt-svelte': '^0.53.4',
|
|
28
|
+
'@happyvertical/smrt-tenancy': '^0.53.4',
|
|
29
|
+
'@happyvertical/smrt-ui': '^0.53.4',
|
|
30
|
+
'@happyvertical/smrt-users': '^0.53.4',
|
|
31
|
+
'@happyvertical/smrt-web': '^0.53.4',
|
|
32
32
|
'@happyvertical/sql': '^0.89.12',
|
|
33
33
|
'@modelcontextprotocol/server': '^2.0.0',
|
|
34
34
|
},
|
|
35
35
|
|
|
36
36
|
devDependencies: {
|
|
37
|
-
'@happyvertical/smrt-cli': '^0.53.
|
|
37
|
+
'@happyvertical/smrt-cli': '^0.53.4',
|
|
38
38
|
'@sveltejs/adapter-node': '^5.5.7',
|
|
39
39
|
'@sveltejs/kit': '2.70.3',
|
|
40
40
|
'@sveltejs/vite-plugin-svelte': '^7.3.0',
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
import { requestOpenAiDisplayMode } from '@happyvertical/smrt-mcp-openai/client';
|
|
3
|
-
import { useMcpApp } from '@happyvertical/smrt-svelte/mcp-apps';
|
|
4
|
-
|
|
5
|
-
interface Props {
|
|
6
|
-
/** Exact, application-configured host origin. Never derive this from URL input. */
|
|
7
|
-
hostOrigin: string;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
let { hostOrigin }: Props = $props();
|
|
11
|
-
let expandUnavailable = $state(false);
|
|
12
|
-
const app = useMcpApp(() => ({
|
|
13
|
-
appInfo: { name: 'smrt-app', version: '0.1.0' },
|
|
14
|
-
availableDisplayModes: ['inline', 'fullscreen'],
|
|
15
|
-
hostOrigin,
|
|
16
|
-
hostWindow: parent,
|
|
17
|
-
}));
|
|
18
|
-
|
|
19
|
-
async function expand() {
|
|
20
|
-
try {
|
|
21
|
-
if (!app.bridge) throw new Error('Host bridge unavailable');
|
|
22
|
-
await requestOpenAiDisplayMode(app.bridge, 'fullscreen');
|
|
23
|
-
} catch {
|
|
24
|
-
expandUnavailable = true;
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
</script>
|
|
28
|
-
|
|
29
|
-
<section aria-labelledby="mcp-apps-title" class="mcp-apps-bridge">
|
|
30
|
-
<h1 id="mcp-apps-title">Items</h1>
|
|
31
|
-
<p>Use this view to review the items your signed-in tenant authorizes.</p>
|
|
32
|
-
{#if app.error || expandUnavailable}
|
|
33
|
-
<p role="status">Host controls are unavailable; this view remains inline.</p>
|
|
34
|
-
{:else}
|
|
35
|
-
<button type="button" onclick={expand}>Expand view</button>
|
|
36
|
-
{/if}
|
|
37
|
-
</section>
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import { createMcpResourceAuth } from '@happyvertical/smrt-app-mcp/auth';
|
|
2
|
-
import { applicationRuntime } from './application-runtime.js';
|
|
3
|
-
import { resolveHostedMcpPrincipal } from './mcp-hosted-principal.js';
|
|
4
|
-
|
|
5
|
-
function required(name: string): string {
|
|
6
|
-
const value = process.env[name]?.trim();
|
|
7
|
-
if (!value) throw new Error(`Hosted MCP requires ${name}.`);
|
|
8
|
-
return value;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
let cachedAuth: ReturnType<typeof createMcpResourceAuth> | undefined;
|
|
12
|
-
|
|
13
|
-
export function hostedMcpAuth() {
|
|
14
|
-
if (applicationRuntime.profile === 'local') return null;
|
|
15
|
-
if (cachedAuth) return cachedAuth;
|
|
16
|
-
if (!resolveHostedMcpPrincipal)
|
|
17
|
-
throw new Error('Hosted MCP requires an application-owned resolveHostedMcpPrincipal binding.');
|
|
18
|
-
cachedAuth = createMcpResourceAuth({
|
|
19
|
-
profile: applicationRuntime.profile,
|
|
20
|
-
resource: required('SMRT_MCP_RESOURCE'),
|
|
21
|
-
issuer: required('SMRT_MCP_ISSUER'),
|
|
22
|
-
jwksUri: required('SMRT_MCP_JWKS_URI'),
|
|
23
|
-
scopes: required('SMRT_MCP_SCOPES').split(/\s+/u),
|
|
24
|
-
algorithms: ['RS256'],
|
|
25
|
-
resolvePrincipal: resolveHostedMcpPrincipal,
|
|
26
|
-
});
|
|
27
|
-
return cachedAuth;
|
|
28
|
-
}
|
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
createMcpAppServer,
|
|
3
|
-
type McpAppPrincipal,
|
|
4
|
-
type McpWorkflowToolDefinition,
|
|
5
|
-
} from '@happyvertical/smrt-app-mcp';
|
|
6
|
-
import { openAiDisplayMetadata, withOpenAiEntrypoints } from '@happyvertical/smrt-mcp-openai';
|
|
7
|
-
import { getApplicationDatabaseConfig } from './application-runtime.js';
|
|
8
|
-
|
|
9
|
-
const ITEM_RESOURCE_URI = 'ui://smrt-app/v1/items.html';
|
|
10
|
-
const itemResourceHtml = `<!doctype html><html lang="en"><head><title>Items</title></head><body><main tabindex="-1"><h1>Items</h1><p>Use the host controls to review your authorized items.</p></main></body></html>`;
|
|
11
|
-
|
|
12
|
-
const itemsOverview: McpWorkflowToolDefinition = withOpenAiEntrypoints(
|
|
13
|
-
{
|
|
14
|
-
name: 'items_overview',
|
|
15
|
-
description: 'Open the authorized item overview.',
|
|
16
|
-
title: 'Items',
|
|
17
|
-
inputSchema: { type: 'object', properties: {}, additionalProperties: false },
|
|
18
|
-
outputSchema: {
|
|
19
|
-
type: 'object',
|
|
20
|
-
properties: { state: { type: 'string' } },
|
|
21
|
-
required: ['state'],
|
|
22
|
-
additionalProperties: false,
|
|
23
|
-
},
|
|
24
|
-
effect: 'read',
|
|
25
|
-
idempotent: true,
|
|
26
|
-
openWorld: false,
|
|
27
|
-
ui: { resourceUri: ITEM_RESOURCE_URI },
|
|
28
|
-
async execute() {
|
|
29
|
-
return {
|
|
30
|
-
content: [{ type: 'text', text: 'Item overview is ready.' }],
|
|
31
|
-
structuredContent: { state: 'ready' },
|
|
32
|
-
};
|
|
33
|
-
},
|
|
34
|
-
},
|
|
35
|
-
['global'],
|
|
36
|
-
);
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* The session hook resolves membership and permissions for every request. This
|
|
40
|
-
* adapter only projects that trusted, request-local result; headers and route
|
|
41
|
-
* arguments are never identity inputs.
|
|
42
|
-
*/
|
|
43
|
-
export function resolveMcpPrincipal(event: {
|
|
44
|
-
locals?: Record<string, unknown>;
|
|
45
|
-
}): McpAppPrincipal | null {
|
|
46
|
-
const user = event.locals?.user as { id?: unknown } | undefined;
|
|
47
|
-
const tenantId = event.locals?.tenantId;
|
|
48
|
-
const permissions = event.locals?.permissions;
|
|
49
|
-
if (typeof user?.id !== 'string' || typeof tenantId !== 'string') return null;
|
|
50
|
-
if (!Array.isArray(permissions) || !permissions.every((value) => typeof value === 'string'))
|
|
51
|
-
return null;
|
|
52
|
-
return {
|
|
53
|
-
id: user.id,
|
|
54
|
-
tenantId,
|
|
55
|
-
kind: 'human',
|
|
56
|
-
scopes: [...permissions].sort(),
|
|
57
|
-
};
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
export const mcpServer = createMcpAppServer({
|
|
61
|
-
smrtOptions: () => ({ db: getApplicationDatabaseConfig() }),
|
|
62
|
-
serverInfo: { name: 'smrt-app', version: '0.1.0' },
|
|
63
|
-
allowedClassNames: ['Item'],
|
|
64
|
-
workflowTools: [itemsOverview],
|
|
65
|
-
resources: [
|
|
66
|
-
{
|
|
67
|
-
uri: ITEM_RESOURCE_URI,
|
|
68
|
-
version: 'v1',
|
|
69
|
-
name: 'Items',
|
|
70
|
-
html: itemResourceHtml,
|
|
71
|
-
csp: {},
|
|
72
|
-
metadata: openAiDisplayMetadata({ availableDisplayModes: ['inline'] }),
|
|
73
|
-
},
|
|
74
|
-
],
|
|
75
|
-
toolPolicy: ({ principal }) =>
|
|
76
|
-
principal?.kind === 'human' &&
|
|
77
|
-
typeof principal.id === 'string' &&
|
|
78
|
-
typeof principal.tenantId === 'string' &&
|
|
79
|
-
principal.scopes?.includes('items.read') === true,
|
|
80
|
-
resourcePolicy: ({ principal }) =>
|
|
81
|
-
principal?.kind === 'human' &&
|
|
82
|
-
typeof principal.id === 'string' &&
|
|
83
|
-
typeof principal.tenantId === 'string' &&
|
|
84
|
-
principal.scopes?.includes('items.read') === true,
|
|
85
|
-
});
|