Files
openwork/docs/desktop-app-policies.md
T

137 lines
4.9 KiB
Markdown

# Desktop App Policies
Desktop app policy config is loaded from OpenWork Cloud through `GET /v1/me/desktop-config` and exposed inside the desktop app through `DesktopConfigProvider`.
App code should read policy state through the hooks in `apps/app/src/react-app/domains/cloud/desktop-config-provider.tsx`. Do not read the provider's internal refs directly; those are only used to compare and apply newly loaded config safely.
## Policy Keys
The canonical policy catalog lives in `packages/types/src/den/desktop-policies.ts` as `desktopPolicyDefinitions`. Add or change policy items there first so the API, Den web, and desktop app share the same IDs and copy.
Do not duplicate the list of policy IDs in app docs or feature code unless a feature is intentionally checking a specific policy. Import from the shared package when you need the full catalog.
`allowedDesktopVersions` is part of the desktop config response but is not a boolean policy item in `desktopPolicyDefinitions`.
For boolean policy keys, `false` means the feature is restricted or disabled. `true` or `undefined` means the app should not block the feature locally.
## Organization Prompt Suggestions
Desktop policy documents can also include `onboardingPrompts` and `onboardingPromptDescriptions`. The shared schema requires 2 or 3 prompts, caps each prompt at 500 characters, and caps each description at 120 characters.
In the desktop app, `onboardingPromptDescriptions` become prompt-card titles. `onboardingPrompts` become the visible card descriptions and the text inserted into the composer when a user clicks a card; the click does not auto-send.
Boolean policy keys are OR-unioned across matching policies by `calculateEffectiveDesktopPolicy()`. Prompt suggestions are different: `selectEffectiveOnboardingPromptConfig()` picks one matching prompt config by highest `priority`, then earliest `createdAt`, then policy `id`, falling back to the default policy only when no targeted prompt config applies.
## Preferred Check
Use `useCheckDesktopRestriction()` when gating app behavior.
```tsx
import { useCheckDesktopRestriction } from "../domains/cloud/desktop-config-provider";
function Example() {
const checkDesktopRestriction = useCheckDesktopRestriction();
const zenModelsRestricted = checkDesktopRestriction({
restriction: "allowZenModel",
});
return zenModelsRestricted ? null : <ZenModelPicker />;
}
```
`checkDesktopRestriction()` returns `true` when the feature is restricted.
## Single Policy Hook
Use `useDesktopRestriction()` when a component only needs one policy value.
```tsx
import { useDesktopRestriction } from "../domains/cloud/desktop-config-provider";
function AddWorkspaceButton() {
const multipleWorkspacesRestricted = useDesktopRestriction(
"allowMultipleWorkspaces",
);
return (
<button disabled={multipleWorkspacesRestricted}>
Add workspace
</button>
);
}
```
## Raw Config
Use `useDesktopConfig()` when you need the raw config, loading state, or manual refresh function.
```tsx
import { useDesktopConfig } from "../domains/cloud/desktop-config-provider";
function DesktopPolicyDebug() {
const desktopConfig = useDesktopConfig();
return (
<pre>
{JSON.stringify({
loading: desktopConfig.loading,
config: desktopConfig.config,
}, null, 2)}
</pre>
);
}
```
Use `useOrgRestrictions()` only when you need the raw config object without `loading`, `refresh`, or `checkRestriction`.
```tsx
import { useOrgRestrictions } from "../domains/cloud/desktop-config-provider";
function Example() {
const config = useOrgRestrictions();
const customProvidersRestricted = config.allowCustomProviders === false;
return customProvidersRestricted ? <RestrictedNotice /> : <ProviderForm />;
}
```
## Helper Functions
For model/provider-specific gates, use the helpers in `apps/app/src/app/cloud/desktop-app-restrictions.ts`.
```tsx
import { isDesktopModelBlocked } from "../../app/cloud/desktop-app-restrictions";
import { useCheckDesktopRestriction } from "../domains/cloud/desktop-config-provider";
function ModelOption({ model }: { model: ModelRef }) {
const checkDesktopRestriction = useCheckDesktopRestriction();
const blocked = isDesktopModelBlocked({
model,
checkRestriction: checkDesktopRestriction,
});
return <ModelRow model={model} disabled={blocked} />;
}
```
## Loading And Refresh
The provider loads cached config first, then fetches the latest config from Cloud. It refreshes on sign-in/session changes, Den settings changes, and a one-hour interval.
Manual refresh is available through `useDesktopConfig().refresh()`:
```tsx
const desktopConfig = useDesktopConfig();
await desktopConfig.refresh();
```
## Rule Of Thumb
Use these APIs in this order:
1. `useCheckDesktopRestriction()` for most feature gates.
2. `useDesktopRestriction(key)` for one-off component checks.
3. `useDesktopConfig()` when you need loading/refresh/raw config.
4. `useOrgRestrictions()` only for raw config reads.