/** * device-capability-contract.ts, the paired-device capability contract. * * This is the peer-facing contract for using a PAIRED device (today a phone) * as an agent tool: its cameras, its screen, its location, its clipboard, and * a small set of device commands. It is a NATIVE contract carried over the * existing distributed-runtime peer transport, deliberately not an MCP * server, per the owner's standing design constraint. * * Node-kind neutrality is the point of this file. A node announces which * capability ids it implements; nothing here enumerates, branches on, or * privileges a particular node kind. The first shipping node is the web PWA * ('web-pwa'); a native node ('android-native') is a drop-in peer that * announces the same ids over the same endpoints and needs no code here. * `KNOWN_DEVICE_NODE_KINDS` is a documentation aid for surfaces that want a * friendly label, `isDeviceNodeKind()` accepts any well-formed slug, so an * unlisted kind pairs and works without a contract change. * * Confirmation posture (owner ruling 2026-07-25): "default is ask-every-time * for every capture/effect, but 'always allow' is OFFERED on every capability *, including front camera, screen capture, precise location, and clipboard, * as a durable per-capability, per-node grant, visible and revocable in the * grants surface." Every descriptor below therefore carries * `defaultDecision: 'ask-every-time'` and `allowAlwaysOffered: true`. There is * no session-scoped-only or never-offered capability. * * This module is runtime-neutral (no node: imports) so browser nodes and * surfaces can import the catalog directly. */ /** Contract revision a node and host negotiate on. Bumped on breaking shape changes. */ export declare const DEVICE_CAPABILITY_CONTRACT_VERSION = 1; /** Capability families a paired device exposes. */ export type DeviceCapabilityFamily = 'camera' | 'screen' | 'location' | 'clipboard' | 'command'; /** Every capability id in the v1 catalog. */ export type DeviceCapabilityId = 'device.camera.rear.capture' | 'device.camera.front.capture' | 'device.screen.capture' | 'device.location.coarse' | 'device.location.precise' | 'device.clipboard.read' | 'device.clipboard.write' | 'device.command.notify' | 'device.command.open_url' | 'device.command.vibrate'; /** * What the capability does to the world: * - 'capture' produces a retained artifact (image/video frames), * - 'read' returns data without retaining a media artifact, * - 'actuate' changes the device's state or shows something to its holder. */ export type DeviceCapabilityEffect = 'capture' | 'read' | 'actuate'; /** Artifact class a capability yields, or 'none' when it retains nothing. */ export type DeviceArtifactKind = 'image' | 'video' | 'text' | 'geo' | 'none'; /** * How intrusive the capability is for the person holding the device. Purely * descriptive, it drives copy and ordering in the grants surface, never * whether "always allow" is offered (the ruling offers it on everything). */ export type DeviceCapabilitySensitivity = 'standard' | 'elevated'; /** One typed input field a capability accepts. */ export interface DeviceCapabilityField { readonly name: string; readonly type: 'string' | 'number' | 'boolean'; readonly required: boolean; readonly description: string; } /** A single capability the contract defines. */ export interface DeviceCapabilityDescriptor { readonly id: DeviceCapabilityId; readonly family: DeviceCapabilityFamily; /** Short human label for surfaces. */ readonly title: string; /** Written purpose: what it does and why a person would allow it. */ readonly purpose: string; readonly effect: DeviceCapabilityEffect; readonly artifactKind: DeviceArtifactKind; readonly producesArtifact: boolean; /** Always 'ask-every-time', the owner-ruled default for every capture/effect. */ readonly defaultDecision: 'ask-every-time'; /** Always true, "always allow" is offered on every capability. */ readonly allowAlwaysOffered: true; readonly sensitivity: DeviceCapabilitySensitivity; /** * Whether a browser requires a secure context (https, or loopback) to serve * this capability. Native nodes ignore it; a web node uses it to report an * honest "unavailable, and why" instead of a dead button. */ readonly secureContextRequired: boolean; readonly inputFields: readonly DeviceCapabilityField[]; } /** The v1 capability catalog. Node-kind neutral by construction. */ export declare const DEVICE_CAPABILITY_CATALOG: readonly DeviceCapabilityDescriptor[]; /** Every capability id the contract defines, in catalog order. */ export declare const DEVICE_CAPABILITY_IDS: readonly DeviceCapabilityId[]; /** Type guard: is this string a capability the catalog defines? */ export declare function isDeviceCapabilityId(value: unknown): value is DeviceCapabilityId; /** Look up a capability descriptor, or null when the id is not in the catalog. */ export declare function getDeviceCapability(id: string): DeviceCapabilityDescriptor | null; /** Capabilities in one family, in catalog order. */ export declare function listDeviceCapabilitiesByFamily(family: DeviceCapabilityFamily): readonly DeviceCapabilityDescriptor[]; /** * Node kinds that ship or are planned. This list exists ONLY so surfaces can * render a friendly label; it is never used to accept or reject a node. */ export declare const KNOWN_DEVICE_NODE_KINDS: readonly string[]; /** A node kind slug. Any lowercase slug is valid, the list above is advisory. */ export type DeviceNodeKind = string; /** Accepts any well-formed lowercase slug, listed or not. */ export declare function isDeviceNodeKind(value: unknown): value is DeviceNodeKind; /** What a node tells the host about itself when it pairs or heartbeats. */ export interface DeviceNodeAnnouncement { readonly nodeId: string; readonly nodeKind: DeviceNodeKind; readonly label: string; readonly platform?: string | undefined; readonly appVersion?: string | undefined; readonly contractVersion: number; /** Capability ids the node implements. Unknown ids are reported, never fatal. */ readonly capabilities: readonly string[]; /** * Whether the node runs in a context that can serve secure-context-gated * capabilities. Native nodes report true; a web node reports its origin's * real posture so the host can explain an unavailable capability honestly. */ readonly secureContext?: boolean | undefined; } /** The host's resolved view of a node's capability surface. */ export interface DeviceNodeProfile { readonly nodeId: string; readonly nodeKind: DeviceNodeKind; readonly label: string; readonly platform: string; readonly appVersion: string; readonly contractVersion: number; readonly contractCompatible: boolean; /** Catalog capabilities this node declared AND can currently serve. */ readonly supported: readonly DeviceCapabilityId[]; /** Catalog capabilities this node did not declare at all. */ readonly undeclared: readonly DeviceCapabilityId[]; /** * Declared capabilities the node cannot currently serve because its context * is not secure. Reported so a surface labels WHY, never a dead button. */ readonly gatedBySecureContext: readonly DeviceCapabilityId[]; /** Ids the node declared that this catalog does not define (newer node, older host). */ readonly unknownDeclared: readonly string[]; } /** Why a node announcement was rejected. */ export type DeviceNodeRejectionReason = 'missing-node-id' | 'invalid-node-kind' | 'missing-label' | 'unsupported-contract-version'; export type DeviceNodeResolution = { readonly ok: true; readonly profile: DeviceNodeProfile; } | { readonly ok: false; readonly reason: DeviceNodeRejectionReason; readonly detail: string; }; /** * Resolve a node announcement into a capability profile. * * Node-kind neutral: the only thing checked about `nodeKind` is that it is a * well-formed slug. A second node type (a native Android node, say) resolves * through this exact path with no branch of its own, which is what makes it a * peer rather than a special case. */ export declare function resolveDeviceNodeProfile(announcement: DeviceNodeAnnouncement): DeviceNodeResolution; /** Friendly label for a node kind; unlisted kinds get a title-cased slug. */ export declare function describeDeviceNodeKind(kind: DeviceNodeKind): string; //# sourceMappingURL=device-capability-contract.d.ts.map