@opengeni/contracts 0.6.0 → 0.9.0

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opengeni/contracts",
3
- "version": "0.6.0",
3
+ "version": "0.9.0",
4
4
  "description": "Shared zod schemas and wire-contract types for the OpenGeni API.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -32,13 +32,10 @@
32
32
  },
33
33
  "scripts": {
34
34
  "typecheck": "tsc --noEmit",
35
- "build": "tsup"
35
+ "build": "tsup",
36
+ "prepublishOnly": "bash ../../scripts/prepublish-guard"
36
37
  },
37
38
  "dependencies": {
38
39
  "zod": "^4.2.1"
39
- },
40
- "devDependencies": {
41
- "tsup": "^8.5.0",
42
- "typescript": "^6.0.3"
43
40
  }
44
41
  }
package/src/index.ts CHANGED
@@ -429,20 +429,6 @@ export const ErrorEnvelope = z.object({
429
429
  });
430
430
  export type ErrorEnvelope = z.infer<typeof ErrorEnvelope>;
431
431
 
432
- export const PageInfo = z.object({
433
- limit: z.number().int().positive(),
434
- nextCursor: z.string().nullable(),
435
- hasMore: z.boolean(),
436
- });
437
- export type PageInfo = z.infer<typeof PageInfo>;
438
-
439
- export function paginated<T extends z.ZodTypeAny>(item: T) {
440
- return z.object({
441
- data: z.array(item),
442
- page: PageInfo,
443
- });
444
- }
445
-
446
432
  export const Permission = z.enum([
447
433
  "account:read",
448
434
  "account:admin",
@@ -484,12 +470,18 @@ export const Permission = z.enum([
484
470
  "github:manage",
485
471
  "github:use",
486
472
  "api_keys:manage",
473
+ "connections:read",
474
+ "connections:write",
487
475
  "environments:manage",
488
476
  "environments:use",
489
477
  // Attach or rotate per-session third-party MCP server credentials. Deliberately
490
478
  // not part of the worker's default first-party MCP permission set: a sandboxed
491
479
  // agent must not be able to hand itself new bearer credentials.
492
480
  "mcp_servers:attach",
481
+ // Programmatic sandbox -> tool access through the first-party MCP gate. This is
482
+ // intentionally narrow and is never part of first-party MCP defaults; callers
483
+ // must receive it through an explicit delegated `ogd_` mint carrying sessionId.
484
+ "toolspace:call",
493
485
  "goals:manage",
494
486
  // Bring-your-own-compute (M5). enrollments:read lists a workspace's machines;
495
487
  // enrollments:manage approves a device-flow enrollment (the LOUD whole-machine
@@ -501,6 +493,10 @@ export const Permission = z.enum([
501
493
  ]);
502
494
  export type Permission = z.infer<typeof Permission>;
503
495
 
496
+ export function prefixedMcpToolName(registryId: string, toolName: string): string {
497
+ return `${registryId}__${toolName}`;
498
+ }
499
+
504
500
  export const ProductAccessMode = z.enum(["local", "configured", "managed"]);
505
501
  export type ProductAccessMode = z.infer<typeof ProductAccessMode>;
506
502
 
@@ -1296,6 +1292,12 @@ export type FileDownloadUrlResponse = z.infer<typeof FileDownloadUrlResponse>;
1296
1292
  export const DocumentStatus = z.enum(["queued", "indexing", "ready", "failed"]);
1297
1293
  export type DocumentStatus = z.infer<typeof DocumentStatus>;
1298
1294
 
1295
+ export const KnowledgeSourceKind = z.enum(["manual_upload", "meeting_transcript", "repository", "email", "chat", "document", "web", "other"]);
1296
+ export type KnowledgeSourceKind = z.infer<typeof KnowledgeSourceKind>;
1297
+
1298
+ export const DocumentSearchMode = z.enum(["hybrid", "vector", "keyword"]);
1299
+ export type DocumentSearchMode = z.infer<typeof DocumentSearchMode>;
1300
+
1299
1301
  export const DocumentBase = z.object({
1300
1302
  id: z.string().uuid(),
1301
1303
  workspaceId: z.string().uuid(),
@@ -1316,6 +1318,15 @@ export const Document = z.object({
1316
1318
  parser: z.string(),
1317
1319
  chunkCount: z.number().int().nonnegative(),
1318
1320
  error: z.string().nullable(),
1321
+ sourceKind: KnowledgeSourceKind,
1322
+ sourceUri: z.string().nullable(),
1323
+ sourceExternalId: z.string().nullable(),
1324
+ sourceTitle: z.string().nullable(),
1325
+ sourceAuthor: z.string().nullable(),
1326
+ sourceCreatedAt: z.string().nullable(),
1327
+ sourceUpdatedAt: z.string().nullable(),
1328
+ sourceVersion: z.string().nullable(),
1329
+ aclTags: z.array(z.string()),
1319
1330
  createdAt: z.string(),
1320
1331
  updatedAt: z.string(),
1321
1332
  });
@@ -1330,8 +1341,20 @@ export const DocumentSearchResult = z.object({
1330
1341
  title: z.string(),
1331
1342
  text: z.string(),
1332
1343
  score: z.number(),
1344
+ matchType: DocumentSearchMode,
1345
+ vectorScore: z.number().nullable(),
1346
+ keywordScore: z.number().nullable(),
1333
1347
  chunkIndex: z.number().int().nonnegative(),
1334
1348
  metadata: z.record(z.string(), z.unknown()),
1349
+ sourceKind: KnowledgeSourceKind,
1350
+ sourceUri: z.string().nullable(),
1351
+ sourceExternalId: z.string().nullable(),
1352
+ sourceTitle: z.string().nullable(),
1353
+ sourceAuthor: z.string().nullable(),
1354
+ sourceCreatedAt: z.string().nullable(),
1355
+ sourceUpdatedAt: z.string().nullable(),
1356
+ sourceVersion: z.string().nullable(),
1357
+ aclTags: z.array(z.string()),
1335
1358
  });
1336
1359
  export type DocumentSearchResult = z.infer<typeof DocumentSearchResult>;
1337
1360
 
@@ -1343,15 +1366,95 @@ export type CreateDocumentBaseRequest = z.infer<typeof CreateDocumentBaseRequest
1343
1366
 
1344
1367
  export const AddDocumentRequest = z.object({
1345
1368
  fileId: z.string().uuid(),
1369
+ title: z.string().min(1).optional(),
1370
+ sourceKind: KnowledgeSourceKind.optional(),
1371
+ sourceUri: z.string().min(1).optional(),
1372
+ sourceExternalId: z.string().min(1).optional(),
1373
+ sourceTitle: z.string().min(1).optional(),
1374
+ sourceAuthor: z.string().min(1).optional(),
1375
+ sourceCreatedAt: z.string().datetime({ offset: true }).optional(),
1376
+ sourceUpdatedAt: z.string().datetime({ offset: true }).optional(),
1377
+ sourceVersion: z.string().min(1).optional(),
1378
+ aclTags: z.array(z.string().min(1)).optional(),
1346
1379
  });
1347
1380
  export type AddDocumentRequest = z.infer<typeof AddDocumentRequest>;
1348
1381
 
1349
1382
  export const DocumentSearchRequest = z.object({
1350
1383
  query: z.string().min(1),
1351
- limit: z.number().int().positive().max(20).default(5),
1384
+ baseIds: z.array(z.string().uuid()).optional(),
1385
+ mode: DocumentSearchMode.optional(),
1386
+ sourceKinds: z.array(KnowledgeSourceKind).optional(),
1387
+ aclTags: z.array(z.string().min(1)).optional(),
1388
+ limit: z.number().int().positive().max(50).default(5),
1352
1389
  });
1353
1390
  export type DocumentSearchRequest = z.infer<typeof DocumentSearchRequest>;
1354
1391
 
1392
+ export const KnowledgeMemoryStatus = z.enum(["proposed", "approved", "rejected"]);
1393
+ export type KnowledgeMemoryStatus = z.infer<typeof KnowledgeMemoryStatus>;
1394
+
1395
+ export const KnowledgeMemoryKind = z.enum(["semantic", "episodic", "procedural", "decision", "preference"]);
1396
+ export type KnowledgeMemoryKind = z.infer<typeof KnowledgeMemoryKind>;
1397
+
1398
+ export const KnowledgeSourceRef = z.object({
1399
+ kind: z.enum(["document_chunk", "document", "session_event", "memory", "external"]),
1400
+ id: z.string().min(1),
1401
+ uri: z.string().min(1).optional(),
1402
+ title: z.string().min(1).optional(),
1403
+ metadata: z.record(z.string(), z.unknown()).default({}),
1404
+ });
1405
+ export type KnowledgeSourceRef = z.infer<typeof KnowledgeSourceRef>;
1406
+
1407
+ export const KnowledgeMemory = z.object({
1408
+ id: z.string().uuid(),
1409
+ workspaceId: z.string().uuid(),
1410
+ status: KnowledgeMemoryStatus,
1411
+ kind: KnowledgeMemoryKind,
1412
+ scope: z.string(),
1413
+ text: z.string(),
1414
+ sourceRefs: z.array(KnowledgeSourceRef),
1415
+ confidence: z.number().min(0).max(1),
1416
+ metadata: z.record(z.string(), z.unknown()),
1417
+ createdBySessionId: z.string().uuid().nullable(),
1418
+ reviewedBy: z.string().nullable(),
1419
+ reviewedAt: z.string().nullable(),
1420
+ createdAt: z.string(),
1421
+ updatedAt: z.string(),
1422
+ });
1423
+ export type KnowledgeMemory = z.infer<typeof KnowledgeMemory>;
1424
+
1425
+ export const CreateKnowledgeMemoryRequest = z.object({
1426
+ status: KnowledgeMemoryStatus.default("proposed"),
1427
+ kind: KnowledgeMemoryKind.default("semantic"),
1428
+ scope: z.string().min(1).default("workspace"),
1429
+ text: z.string().min(1),
1430
+ sourceRefs: z.array(KnowledgeSourceRef).default([]),
1431
+ confidence: z.number().min(0).max(1).default(0.5),
1432
+ metadata: z.record(z.string(), z.unknown()).default({}),
1433
+ createdBySessionId: z.string().uuid().optional(),
1434
+ });
1435
+ export type CreateKnowledgeMemoryRequest = z.infer<typeof CreateKnowledgeMemoryRequest>;
1436
+
1437
+ export const UpdateKnowledgeMemoryRequest = z.object({
1438
+ status: KnowledgeMemoryStatus.optional(),
1439
+ kind: KnowledgeMemoryKind.optional(),
1440
+ scope: z.string().min(1).optional(),
1441
+ text: z.string().min(1).optional(),
1442
+ sourceRefs: z.array(KnowledgeSourceRef).optional(),
1443
+ confidence: z.number().min(0).max(1).optional(),
1444
+ metadata: z.record(z.string(), z.unknown()).optional(),
1445
+ reviewedBy: z.string().min(1).optional(),
1446
+ });
1447
+ export type UpdateKnowledgeMemoryRequest = z.infer<typeof UpdateKnowledgeMemoryRequest>;
1448
+
1449
+ export const KnowledgeMemorySearchRequest = z.object({
1450
+ query: z.string().min(1).optional(),
1451
+ status: KnowledgeMemoryStatus.optional(),
1452
+ kind: KnowledgeMemoryKind.optional(),
1453
+ scope: z.string().min(1).optional(),
1454
+ limit: z.number().int().positive().max(100).default(20),
1455
+ });
1456
+ export type KnowledgeMemorySearchRequest = z.infer<typeof KnowledgeMemorySearchRequest>;
1457
+
1355
1458
  export const ToolRef = z.object({
1356
1459
  kind: z.literal("mcp"),
1357
1460
  id: z.string().min(1),
@@ -1381,6 +1484,12 @@ export const SessionMcpServerInput = z.object({
1381
1484
  allowedTools: z.array(z.string().min(1)).optional(),
1382
1485
  timeoutMs: z.number().int().positive().optional(),
1383
1486
  cacheToolsList: z.boolean().optional(),
1487
+ // Human-approval policy for this server's tools. `true` = every tool of this
1488
+ // server requires approval before it runs (a `session.requiresAction` pause
1489
+ // the caller resolves with `user.approvalDecision`); a string[] = ONLY the
1490
+ // listed UNPREFIXED tool names require approval (e.g. reads auto-run, writes
1491
+ // ask); absent / `false` = auto-run everything (the historical default).
1492
+ requireApproval: z.union([z.boolean(), z.array(z.string().min(1))]).optional(),
1384
1493
  // Write-only credential headers. Values are encrypted at rest and never
1385
1494
  // returned in session responses or events; response metadata exposes names.
1386
1495
  headers: z.record(z.string(), z.string()).optional(),
@@ -2057,6 +2166,110 @@ export const CreateSocialPostRequest = z.object({
2057
2166
  });
2058
2167
  export type CreateSocialPostRequest = z.infer<typeof CreateSocialPostRequest>;
2059
2168
 
2169
+ export const ConnectionKind = z.enum(["oauth2", "api_key", "app_install", "delegated"]);
2170
+ export type ConnectionKind = z.infer<typeof ConnectionKind>;
2171
+
2172
+ export const ConnectionStatus = z.enum(["active", "needs_reauth", "revoked", "error"]);
2173
+ export type ConnectionStatus = z.infer<typeof ConnectionStatus>;
2174
+
2175
+ export const McpServerConnectionRef = z.object({
2176
+ connectionId: z.string().uuid().optional(),
2177
+ providerDomain: z.string().min(1),
2178
+ kind: ConnectionKind.optional(),
2179
+ scopes: z.array(z.string().min(1)).optional(),
2180
+ resource: z.string().min(1).optional(),
2181
+ subjectScope: z.enum(["workspace", "subject"]).optional(),
2182
+ }).strict();
2183
+ export type McpServerConnectionRef = z.infer<typeof McpServerConnectionRef>;
2184
+
2185
+ export const ConnectionMetadata = z.object({
2186
+ id: z.string().uuid(),
2187
+ accountId: z.string().uuid(),
2188
+ workspaceId: z.string().uuid(),
2189
+ subjectId: z.string().nullable(),
2190
+ providerDomain: z.string(),
2191
+ kind: ConnectionKind,
2192
+ status: ConnectionStatus,
2193
+ grantedScopes: z.array(z.string()),
2194
+ expiresAt: z.string().nullable(),
2195
+ lastRefreshAt: z.string().nullable(),
2196
+ lastUsedAt: z.string().nullable(),
2197
+ lastError: z.string().nullable(),
2198
+ version: z.number().int().positive(),
2199
+ metadata: z.record(z.string(), z.unknown()),
2200
+ createdBySubjectId: z.string().nullable(),
2201
+ updatedBySubjectId: z.string().nullable(),
2202
+ createdAt: z.string(),
2203
+ updatedAt: z.string(),
2204
+ });
2205
+ export type ConnectionMetadata = z.infer<typeof ConnectionMetadata>;
2206
+
2207
+ export const ConnectionCredentialBundle = z.record(z.string(), z.unknown());
2208
+ export type ConnectionCredentialBundle = z.infer<typeof ConnectionCredentialBundle>;
2209
+
2210
+ export const CreateConnectionRequest = z.object({
2211
+ providerDomain: z.string().min(1),
2212
+ kind: ConnectionKind,
2213
+ subjectId: z.string().min(1).nullable().optional(),
2214
+ credential: ConnectionCredentialBundle,
2215
+ grantedScopes: z.array(z.string().min(1)).default([]),
2216
+ expiresAt: z.string().datetime({ offset: true }).nullable().optional(),
2217
+ metadata: z.record(z.string(), z.unknown()).default({}),
2218
+ });
2219
+ export type CreateConnectionRequest = z.infer<typeof CreateConnectionRequest>;
2220
+
2221
+ export const UpdateConnectionRequest = z.object({
2222
+ providerDomain: z.string().min(1).optional(),
2223
+ subjectId: z.string().min(1).nullable().optional(),
2224
+ kind: ConnectionKind.optional(),
2225
+ status: ConnectionStatus.optional(),
2226
+ credential: ConnectionCredentialBundle.optional(),
2227
+ grantedScopes: z.array(z.string().min(1)).optional(),
2228
+ expiresAt: z.string().datetime({ offset: true }).nullable().optional(),
2229
+ metadata: z.record(z.string(), z.unknown()).optional(),
2230
+ });
2231
+ export type UpdateConnectionRequest = z.infer<typeof UpdateConnectionRequest>;
2232
+
2233
+ export const ConnectionResponse = z.object({
2234
+ connection: ConnectionMetadata,
2235
+ });
2236
+ export type ConnectionResponse = z.infer<typeof ConnectionResponse>;
2237
+
2238
+ export const ListConnectionsResponse = z.object({
2239
+ connections: z.array(ConnectionMetadata),
2240
+ });
2241
+ export type ListConnectionsResponse = z.infer<typeof ListConnectionsResponse>;
2242
+
2243
+ export const OAuthStartRequest = z.object({
2244
+ providerDomain: z.string().min(1).optional(),
2245
+ mcpUrl: z.string().url().optional(),
2246
+ resource: z.string().url().optional(),
2247
+ requestedScopes: z.array(z.string().min(1)).default([]),
2248
+ returnPath: z.string().min(1).optional(),
2249
+ connectionId: z.string().uuid().optional(),
2250
+ }).refine((value) => Boolean(value.mcpUrl ?? value.resource), {
2251
+ message: "mcpUrl is required",
2252
+ path: ["mcpUrl"],
2253
+ });
2254
+ export type OAuthStartRequest = z.infer<typeof OAuthStartRequest>;
2255
+
2256
+ export const OAuthStartResponse = z.object({
2257
+ state: z.string().min(1),
2258
+ authorizationUrl: z.string().url().nullable(),
2259
+ expiresAt: z.string(),
2260
+ });
2261
+ export type OAuthStartResponse = z.infer<typeof OAuthStartResponse>;
2262
+
2263
+ export const IntegrationClientMetadata = z.object({
2264
+ client_id: z.string().url(),
2265
+ client_name: z.literal("OpenGeni"),
2266
+ redirect_uris: z.array(z.string().url()),
2267
+ token_endpoint_auth_method: z.literal("none"),
2268
+ grant_types: z.array(z.enum(["authorization_code", "refresh_token"])),
2269
+ response_types: z.array(z.literal("code")),
2270
+ });
2271
+ export type IntegrationClientMetadata = z.infer<typeof IntegrationClientMetadata>;
2272
+
2060
2273
  export const MarketingDailyAnalysisTaskRequest = z.object({
2061
2274
  name: z.string().min(1).optional(),
2062
2275
  connectionIds: z.array(z.string().uuid()).default([]),
@@ -2074,12 +2287,18 @@ export type MarketingDailyAnalysisTaskRequest = z.infer<typeof MarketingDailyAna
2074
2287
  export const CapabilityKind = z.enum(["pack", "mcp", "api", "skill", "plugin"]);
2075
2288
  export type CapabilityKind = z.infer<typeof CapabilityKind>;
2076
2289
 
2077
- export const CapabilitySource = z.enum(["built_in", "configured", "public_registry", "manual"]);
2290
+ export const CapabilitySource = z.enum(["built_in", "configured", "public_registry", "registry", "manual"]);
2078
2291
  export type CapabilitySource = z.infer<typeof CapabilitySource>;
2079
2292
 
2080
2293
  export const CapabilityInstallationStatus = z.enum(["active", "disabled"]);
2081
2294
  export type CapabilityInstallationStatus = z.infer<typeof CapabilityInstallationStatus>;
2082
2295
 
2296
+ export const CapabilityCatalogAuthKind = z.enum(["oauth2", "api_key", "none", "unknown"]);
2297
+ export type CapabilityCatalogAuthKind = z.infer<typeof CapabilityCatalogAuthKind>;
2298
+
2299
+ export const CapabilityCatalogTier = z.enum(["verified", "community"]);
2300
+ export type CapabilityCatalogTier = z.infer<typeof CapabilityCatalogTier>;
2301
+
2083
2302
  export const CapabilityRuntime = z.object({
2084
2303
  available: z.boolean().default(false),
2085
2304
  mcpServerId: z.string().min(1).optional(),
@@ -2102,6 +2321,18 @@ export const CapabilityCatalogItem = z.object({
2102
2321
  endpointUrl: z.string().url().nullable().default(null),
2103
2322
  installUrl: z.string().url().nullable().default(null),
2104
2323
  authModel: z.string().min(1).nullable().default(null),
2324
+ providerDomain: z.string().min(1).nullable().default(null),
2325
+ surfaceType: z.string().min(1).nullable().default(null),
2326
+ transport: z.string().min(1).nullable().default(null),
2327
+ mcpUrl: z.string().url().nullable().default(null),
2328
+ authKind: CapabilityCatalogAuthKind.nullable().default(null),
2329
+ credentialFacts: z.array(z.record(z.string(), z.unknown())).default([]),
2330
+ tier: CapabilityCatalogTier.nullable().default(null),
2331
+ provenance: z.string().min(1).nullable().default(null),
2332
+ logoAssetPath: z.string().min(1).nullable().default(null),
2333
+ importBatchId: z.string().uuid().nullable().default(null),
2334
+ stale: z.boolean().default(false),
2335
+ staleAt: z.string().nullable().default(null),
2105
2336
  tools: z.array(ToolRef).default([]),
2106
2337
  runtime: CapabilityRuntime.default({ available: false, notes: null }),
2107
2338
  enabled: z.boolean().default(false),
@@ -2145,6 +2376,7 @@ export type CreateCapabilityCatalogItemRequest = z.infer<typeof CreateCapability
2145
2376
  export const EnableCapabilityRequest = z.object({
2146
2377
  config: z.record(z.string(), z.unknown()).default({}),
2147
2378
  metadata: z.record(z.string(), z.unknown()).default({}),
2379
+ connectionRef: McpServerConnectionRef.optional(),
2148
2380
  /**
2149
2381
  * Credential headers for remote MCP capabilities (for example an
2150
2382
  * Authorization bearer token). Values are encrypted at rest with the
@@ -2260,6 +2492,7 @@ export const SessionEventType = z.enum([
2260
2492
  "agent.reasoning.delta",
2261
2493
  "agent.toolCall.created",
2262
2494
  "agent.toolCall.output",
2495
+ "tool.auth_needed",
2263
2496
  "agent.updated",
2264
2497
  "sandbox.operation.started",
2265
2498
  "sandbox.operation.completed",
@@ -2307,6 +2540,19 @@ export const SessionEventType = z.enum([
2307
2540
  ]);
2308
2541
  export type SessionEventType = z.infer<typeof SessionEventType>;
2309
2542
 
2543
+ export const ToolAuthNeededPayload = z.object({
2544
+ serverId: z.string().min(1),
2545
+ toolName: z.string().min(1).nullable().optional(),
2546
+ providerDomain: z.string().min(1),
2547
+ connectionId: z.string().uuid().nullable().optional(),
2548
+ reason: z.enum(["missing_connection", "expired", "insufficient_scope", "refresh_failed"]),
2549
+ scopes: z.array(z.string().min(1)).optional(),
2550
+ resource: z.string().min(1).optional(),
2551
+ authorizationUrl: z.string().url().optional(),
2552
+ subjectId: z.string().min(1).nullable().optional(),
2553
+ });
2554
+ export type ToolAuthNeededPayload = z.infer<typeof ToolAuthNeededPayload>;
2555
+
2310
2556
  // Channel-B stream-event payloads (07-channel-b §1.2). SessionEvent.payload is
2311
2557
  // z.unknown() (NOT a discriminated union) — these are standalone schemas parsed
2312
2558
  // explicitly at the producer (the API-direct handshake/rotation) and the SDK/
@@ -3241,6 +3487,11 @@ export const EnrollmentSummary = z.object({
3241
3487
  pubkey: z.string(),
3242
3488
  exposure: z.literal("whole-machine"),
3243
3489
  hasDisplay: z.boolean(),
3490
+ // Present (non-null) only when a display EXISTS but capture is blocked (macOS
3491
+ // Screen Recording / TCC not granted): a human, actionable reason so the UI can
3492
+ // show "display: capture not granted" instead of a bare "headless". null == capture
3493
+ // permitted OR genuinely headless.
3494
+ desktopUnavailableReason: z.string().nullish(),
3244
3495
  allowScreenControl: z.boolean(),
3245
3496
  status: z.enum(["active", "revoked"]),
3246
3497
  os: EnrollmentOs,
@@ -3421,6 +3672,10 @@ export const MachineView = z.object({
3421
3672
  os: z.string(),
3422
3673
  arch: z.string(),
3423
3674
  hasDisplay: z.boolean(),
3675
+ // Non-null only when a display exists but capture is blocked (macOS Screen
3676
+ // Recording / TCC not granted) — the UI can surface "display: capture not granted".
3677
+ // null == capture permitted OR headless.
3678
+ desktopUnavailableReason: z.string().nullish(),
3424
3679
  allowScreenControl: z.boolean(),
3425
3680
  sharedSessionCount: z.number().int(),
3426
3681
  lastSeenAt: z.string().nullable(),
@@ -3493,6 +3748,10 @@ export type ClientModel = z.infer<typeof ClientModel>;
3493
3748
 
3494
3749
  export const ClientConfig = z.object({
3495
3750
  deploymentRevision: z.string(),
3751
+ // Release-train version of the server (absent on dev/source builds). The
3752
+ // compatibility policy lives in docs/architecture.md — clients within the
3753
+ // same major are supported; evolution is additive within a major.
3754
+ serverVersion: z.string().optional(),
3496
3755
  defaultModel: z.string(),
3497
3756
  allowedModels: z.array(z.string()).min(1),
3498
3757
  // Richer model list (provider-grouped) for the picker. Defaults to [] for