blob: 54af699f906a2400ab57f850d23399193626e6aa [file]
import type { BotProvider, BotReadinessState } from './bot-chat-settings.js';
export const OS_PERMISSION_IDS = [
'accessibility',
'screen_recording',
'notifications',
'automation',
] as const;
export type OsPermissionId = (typeof OS_PERMISSION_IDS)[number];
/**
* The macOS permissions granted by dragging the app bundle onto the
* Privacy list, rather than through a consent dialog.
*
* These two are the ones whose normal setup requires adding the app to a
* System Settings list, so the stock path is: open System Settings, press `+`,
* navigate a file picker to /Applications, pick the app, tick the box.
* Dropping the bundle satisfies the same explicit-consent intent in one
* gesture, which is what the drag-to-grant onboarding automates.
*
* Lives in core because both the main-process flow and the Permission
* Center row need the same list; two copies would drift.
*/
export const DRAG_GRANT_PERMISSION_IDS = ['accessibility', 'screen_recording'] as const;
export type DragGrantPermissionId = (typeof DRAG_GRANT_PERMISSION_IDS)[number];
export function isDragGrantPermissionId(value: unknown): value is DragGrantPermissionId {
return (DRAG_GRANT_PERMISSION_IDS as readonly unknown[]).includes(value);
}
export const OS_PERMISSION_STATES = [
'unsupported',
'unknown',
'not_determined',
'denied',
'granted',
] as const;
export type OsPermissionState = (typeof OS_PERMISSION_STATES)[number];
export const FEATURE_ENABLEMENT_STATES = [
'not_available',
'partial',
'disabled',
'enabled',
] as const;
export type FeatureEnablementState = (typeof FEATURE_ENABLEMENT_STATES)[number];
export const ACTION_APPROVAL_STATES = [
'not_required',
'required_per_action',
'required_scoped_lease',
'pending',
'approved',
'denied',
] as const;
export type ActionApprovalState = (typeof ACTION_APPROVAL_STATES)[number];
export const CAPABILITY_CONFIGURATION_STATES = ['not_required', 'missing', 'present'] as const;
export type CapabilityConfigurationState = (typeof CAPABILITY_CONFIGURATION_STATES)[number];
export const MEMORY_ACCEPTANCE_STATES = [
'not_applicable',
'disabled',
'draft_required',
'accepted',
] as const;
export type MemoryAcceptanceState = (typeof MEMORY_ACCEPTANCE_STATES)[number];
export const RUNTIME_PROBE_STATES = ['not_available', 'not_run', 'healthy', 'degraded'] as const;
export type RuntimeProbeState = (typeof RUNTIME_PROBE_STATES)[number];
export const CAPABILITY_READINESS_STATES = [
'not_configured',
'denied',
'enabled',
'degraded',
'paused',
] as const;
export type CapabilityReadinessState = (typeof CAPABILITY_READINESS_STATES)[number];
export type CapabilityId =
| 'computer_use'
| 'activity_recorder'
| 'memory_write'
| `bot:${BotProvider}`;
export interface OsPermissionSnapshot {
id: OsPermissionId;
status: OsPermissionState;
source: 'electron' | 'platform' | 'static';
checkedAt: number;
reason?: string;
canOpenSettings: boolean;
canRequest: boolean;
}
export interface PermissionSnapshot {
checkedAt: number;
platform: NodeJS.Platform;
permissions: Record<OsPermissionId, OsPermissionSnapshot>;
}
export interface CapabilityPermissionRequirement {
id: OsPermissionId;
required: boolean;
status: OsPermissionState;
}
export interface CapabilityFeatureSignal {
state: FeatureEnablementState;
source: 'settings' | 'scaffold' | 'runtime';
reason?: string;
}
export interface CapabilityConfigurationSignal {
state: CapabilityConfigurationState;
source: 'settings' | 'runtime' | 'not_applicable';
reason?: string;
}
export interface CapabilityActionApprovalSignal {
state: ActionApprovalState;
source: 'permission_engine' | 'capability_policy' | 'not_applicable';
}
export interface CapabilityMemoryAcceptanceSignal {
state: MemoryAcceptanceState;
source: 'memory_contract' | 'not_applicable';
}
export interface CapabilityRuntimeProbeSignal {
state: RuntimeProbeState;
source: 'runtime_probe' | 'bot_registry' | 'not_applicable';
lastCheckedAt?: number;
reason?: string;
}
export interface CapabilitySnapshot {
id: CapabilityId;
label: string;
readiness: CapabilityReadinessState;
feature: CapabilityFeatureSignal;
configuration: CapabilityConfigurationSignal;
osPermissions: CapabilityPermissionRequirement[];
actionApproval: CapabilityActionApprovalSignal;
memoryAcceptance: CapabilityMemoryAcceptanceSignal;
runtimeProbe: CapabilityRuntimeProbeSignal;
canRevoke: boolean;
canPause: boolean;
guidance: string[];
auditEvents: string[];
updatedAt: number;
}
export interface CapabilitySnapshotCollection {
checkedAt: number;
capabilities: CapabilitySnapshot[];
}
export interface DeriveCapabilityReadinessInput {
feature: CapabilityFeatureSignal;
configuration: CapabilityConfigurationSignal;
osPermissions: CapabilityPermissionRequirement[];
runtimeProbe: CapabilityRuntimeProbeSignal;
}
export function isOsPermissionState(value: unknown): value is OsPermissionState {
return typeof value === 'string' && (OS_PERMISSION_STATES as readonly string[]).includes(value);
}
export function isCapabilityReadinessState(value: unknown): value is CapabilityReadinessState {
return (
typeof value === 'string' && (CAPABILITY_READINESS_STATES as readonly string[]).includes(value)
);
}
export function deriveCapabilityReadiness(
input: DeriveCapabilityReadinessInput,
): CapabilityReadinessState {
if (input.feature.state === 'disabled') return 'paused';
if (input.feature.state === 'not_available') return 'not_configured';
if (input.configuration.state === 'missing') return 'not_configured';
const required = input.osPermissions.filter((permission) => permission.required);
if (
required.some(
(permission) => permission.status === 'denied' || permission.status === 'unsupported',
)
) {
return 'denied';
}
if (
required.some(
(permission) => permission.status === 'not_determined' || permission.status === 'unknown',
)
) {
return 'not_configured';
}
if (input.runtimeProbe.state === 'degraded' || input.runtimeProbe.state === 'not_available')
return 'degraded';
if (input.feature.state === 'partial') return 'not_configured';
return 'enabled';
}
export function runtimeProbeFromBotReadiness(
readiness: BotReadinessState,
lastCheckedAt?: number,
reason?: string,
): CapabilityRuntimeProbeSignal {
if (readiness === 'operational') {
return { state: 'healthy', source: 'bot_registry', lastCheckedAt, reason };
}
if (readiness === 'degraded') {
return { state: 'degraded', source: 'bot_registry', lastCheckedAt, reason };
}
return { state: 'not_run', source: 'bot_registry', lastCheckedAt, reason };
}