|
1 | 1 | import { Schema } from "effect"; |
2 | | -import { IsoDateTime, TrimmedNonEmptyString } from "./baseSchemas"; |
| 2 | +import { DeviceId, IsoDateTime, PairingId, TrimmedNonEmptyString } from "./baseSchemas"; |
3 | 3 | import { BuildMetadata } from "./buildInfo"; |
4 | 4 | import { KeybindingRule, ResolvedKeybindingsConfig } from "./keybindings"; |
5 | 5 | import { EditorId } from "./editor"; |
@@ -135,6 +135,93 @@ export const ListTokensResult = Schema.Struct({ |
135 | 135 | }); |
136 | 136 | export type ListTokensResult = typeof ListTokensResult.Type; |
137 | 137 |
|
| 138 | +// ── Companion Pairing (new model) ────────────────────────────────── |
| 139 | +// The companion pairing model replaces the single-token deep-link flow |
| 140 | +// with endpoint-aware bundles and device-scoped sessions. The legacy |
| 141 | +// `GeneratePairingLinkInput`/`GeneratePairingLinkResult` contracts above |
| 142 | +// remain supported during rollout. |
| 143 | + |
| 144 | +export const CompanionEndpointKind = Schema.Literals(["tailscale", "lan", "manual"]); |
| 145 | +export type CompanionEndpointKind = typeof CompanionEndpointKind.Type; |
| 146 | + |
| 147 | +export const CompanionEndpoint = Schema.Struct({ |
| 148 | + kind: CompanionEndpointKind, |
| 149 | + url: TrimmedNonEmptyString, |
| 150 | + label: Schema.optional(TrimmedNonEmptyString), |
| 151 | + reachable: Schema.Boolean, |
| 152 | +}); |
| 153 | +export type CompanionEndpoint = typeof CompanionEndpoint.Type; |
| 154 | + |
| 155 | +export const CompanionPairingBundle = Schema.Struct({ |
| 156 | + pairingId: PairingId, |
| 157 | + expiresAt: IsoDateTime, |
| 158 | + endpoints: Schema.Array(CompanionEndpoint), |
| 159 | + bootstrapToken: TrimmedNonEmptyString, |
| 160 | + passwordRequired: Schema.Boolean, |
| 161 | + passwordHint: Schema.optional(TrimmedNonEmptyString), |
| 162 | +}); |
| 163 | +export type CompanionPairingBundle = typeof CompanionPairingBundle.Type; |
| 164 | + |
| 165 | +export const PairedDeviceSession = Schema.Struct({ |
| 166 | + deviceId: DeviceId, |
| 167 | + deviceName: TrimmedNonEmptyString, |
| 168 | + serverUrl: TrimmedNonEmptyString, |
| 169 | + sessionToken: TrimmedNonEmptyString, |
| 170 | + issuedAt: IsoDateTime, |
| 171 | + expiresAt: Schema.NullOr(IsoDateTime), |
| 172 | + lastSeenAt: Schema.NullOr(IsoDateTime), |
| 173 | +}); |
| 174 | +export type PairedDeviceSession = typeof PairedDeviceSession.Type; |
| 175 | + |
| 176 | +// ── Companion RPC Inputs/Outputs ─────────────────────────────────── |
| 177 | + |
| 178 | +export const GenerateCompanionPairingBundleInput = Schema.Struct({ |
| 179 | + /** Lifetime in seconds for the bootstrap token. Defaults to 300 (5 min). */ |
| 180 | + ttlSeconds: Schema.optional(Schema.Number), |
| 181 | + /** Desktop-advertised endpoints to include in the bundle. */ |
| 182 | + advertisedEndpoints: Schema.optional(Schema.Array(CompanionEndpoint)), |
| 183 | +}); |
| 184 | +export type GenerateCompanionPairingBundleInput = typeof GenerateCompanionPairingBundleInput.Type; |
| 185 | + |
| 186 | +export const GenerateCompanionPairingBundleResult = CompanionPairingBundle; |
| 187 | +export type GenerateCompanionPairingBundleResult = typeof GenerateCompanionPairingBundleResult.Type; |
| 188 | + |
| 189 | +export const ExchangeCompanionBootstrapInput = Schema.Struct({ |
| 190 | + bootstrapToken: TrimmedNonEmptyString, |
| 191 | + endpointUrl: TrimmedNonEmptyString, |
| 192 | + password: Schema.optional(Schema.String), |
| 193 | + deviceName: TrimmedNonEmptyString, |
| 194 | +}); |
| 195 | +export type ExchangeCompanionBootstrapInput = typeof ExchangeCompanionBootstrapInput.Type; |
| 196 | + |
| 197 | +export const ExchangeCompanionBootstrapResult = PairedDeviceSession; |
| 198 | +export type ExchangeCompanionBootstrapResult = typeof ExchangeCompanionBootstrapResult.Type; |
| 199 | + |
| 200 | +export const ListPairedDevicesResult = Schema.Struct({ |
| 201 | + devices: Schema.Array( |
| 202 | + Schema.Struct({ |
| 203 | + deviceId: DeviceId, |
| 204 | + deviceName: TrimmedNonEmptyString, |
| 205 | + issuedAt: IsoDateTime, |
| 206 | + lastSeenAt: Schema.NullOr(IsoDateTime), |
| 207 | + endpointKind: Schema.optional(CompanionEndpointKind), |
| 208 | + revoked: Schema.Boolean, |
| 209 | + }), |
| 210 | + ), |
| 211 | +}); |
| 212 | +export type ListPairedDevicesResult = typeof ListPairedDevicesResult.Type; |
| 213 | + |
| 214 | +export const RevokePairedDeviceInput = Schema.Struct({ |
| 215 | + deviceId: DeviceId, |
| 216 | +}); |
| 217 | +export type RevokePairedDeviceInput = typeof RevokePairedDeviceInput.Type; |
| 218 | + |
| 219 | +export const RevokePairedDeviceResult = Schema.Struct({ |
| 220 | + deviceId: DeviceId, |
| 221 | + revoked: Schema.Boolean, |
| 222 | +}); |
| 223 | +export type RevokePairedDeviceResult = typeof RevokePairedDeviceResult.Type; |
| 224 | + |
138 | 225 | // ── OpenClaw Gateway Test ─────────────────────────────────────────── |
139 | 226 |
|
140 | 227 | export const TestOpenclawGatewayInput = Schema.Struct({ |
|
0 commit comments