@parall/sdk 1.35.0 → 1.36.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/src/client.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { ENDPOINTS } from './constants.js';
1
+ import { ENDPOINTS, WIKI_BASE } from './constants.js';
2
2
  import type {
3
3
  AuthTokens,
4
4
  AvatarUploadResponse,
@@ -70,6 +70,19 @@ import type {
70
70
  UpdateScheduleInput,
71
71
  ScheduleFilters,
72
72
  ScheduleRunFilters,
73
+ ExternalConnection,
74
+ CreateExternalConnectionInput,
75
+ UpdateExternalConnectionInput,
76
+ ExternalConnectionFilters,
77
+ ExternalIngressEvent,
78
+ ExternalIngressEventFilters,
79
+ ExternalTrigger,
80
+ CreateExternalTriggerInput,
81
+ UpdateExternalTriggerInput,
82
+ ExternalTriggerFilters,
83
+ ExternalTriggerRun,
84
+ ExternalTriggerRunFilters,
85
+ ExternalTriggerSchema,
73
86
  AgentSessionDB,
74
87
  AgentStep,
75
88
  CreateAgentSessionRequest,
@@ -160,6 +173,7 @@ import type {
160
173
  RegistryClipInfo,
161
174
  MachineClip,
162
175
  BrowserProfile,
176
+ BrowserProfileListItem,
163
177
  BrowserProfileStatus,
164
178
  BrowserProfileConsent,
165
179
  CreateBrowserProfileRequest,
@@ -172,6 +186,15 @@ import type {
172
186
 
173
187
  export interface ParallClientOptions {
174
188
  baseUrl?: string;
189
+ /**
190
+ * Base URL for wiki-service endpoints (paths under `/wiki/v1`). Defaults to
191
+ * `baseUrl`. Wiki endpoints are served by a separate service from the rest of
192
+ * the API; in staging/prod one gateway fronts both (so `baseUrl` alone is
193
+ * correct), but local dev runs api-server (:8080) and wiki-service (:8090)
194
+ * separately. Set this so wiki calls reach wiki-service while everything else
195
+ * (chat, tasks, comments, refs) keeps using `baseUrl`.
196
+ */
197
+ wikiBaseUrl?: string;
175
198
  token?: string;
176
199
  onTokenExpired?: () => void;
177
200
  getRefreshToken?: () => string | null;
@@ -181,6 +204,7 @@ export interface ParallClientOptions {
181
204
 
182
205
  export class ParallClient {
183
206
  private baseUrl: string;
207
+ private wikiBaseUrl: string;
184
208
  private token: string | null;
185
209
  private onTokenExpired?: () => void;
186
210
  private getRefreshToken?: () => string | null;
@@ -242,6 +266,10 @@ export class ParallClient {
242
266
 
243
267
  constructor(options: ParallClientOptions = {}) {
244
268
  this.baseUrl = options.baseUrl ?? '';
269
+ // Defaults to baseUrl so existing single-origin consumers (web, desktop,
270
+ // hosted agents behind one gateway) are unchanged; only split api/wiki
271
+ // deployments (local dev) need to set it.
272
+ this.wikiBaseUrl = options.wikiBaseUrl ?? this.baseUrl;
245
273
  this.token = options.token ?? null;
246
274
  this.onTokenExpired = options.onTokenExpired;
247
275
  this.getRefreshToken = options.getRefreshToken;
@@ -249,6 +277,16 @@ export class ParallClient {
249
277
  this.swimlaneName = options.swimlaneName;
250
278
  }
251
279
 
280
+ /**
281
+ * Pick the origin for a request path: wiki-service base for `/wiki/v1`
282
+ * endpoints, api base for everything else. The path itself (from ENDPOINTS)
283
+ * is authoritative, so wiki vs api routing can't drift from how a caller
284
+ * happens to invoke the client.
285
+ */
286
+ private baseUrlFor(path: string): string {
287
+ return path.startsWith(WIKI_BASE) ? this.wikiBaseUrl : this.baseUrl;
288
+ }
289
+
252
290
  setToken(token: string | null) {
253
291
  this.token = token;
254
292
  }
@@ -326,7 +364,7 @@ export class ParallClient {
326
364
  await this.ensureFreshToken(path);
327
365
  }
328
366
 
329
- let url = `${this.baseUrl}${path}`;
367
+ let url = `${this.baseUrlFor(path)}${path}`;
330
368
 
331
369
  if (query) {
332
370
  const params = new URLSearchParams();
@@ -419,7 +457,7 @@ export class ParallClient {
419
457
 
420
458
  let res: Response;
421
459
  try {
422
- res = await fetch(`${this.baseUrl}${path}`, {
460
+ res = await fetch(`${this.baseUrlFor(path)}${path}`, {
423
461
  method,
424
462
  headers,
425
463
  body,
@@ -654,6 +692,22 @@ export class ParallClient {
654
692
  );
655
693
  }
656
694
 
695
+ // Auto-paginated variant of getMemberTasks: fetches ALL pending tasks
696
+ // (todo + in_progress) assigned to a member, including subtasks (the
697
+ // endpoint does not filter parent_id). Powers the CLI `tasks assigned`
698
+ // command so an agent answering "what's on X's plate" sees the full
699
+ // backlog, not just the first page.
700
+ async getMemberTasksAll(orgId: string, memberId: string): Promise<Task[]> {
701
+ const all: Task[] = [];
702
+ let cursor: string | undefined;
703
+ do {
704
+ const res = await this.getMemberTasks(orgId, memberId, { cursor, limit: 100 });
705
+ all.push(...res.data);
706
+ cursor = res.has_more ? res.next_cursor : undefined;
707
+ } while (cursor);
708
+ return all;
709
+ }
710
+
657
711
  // ---- Invitations ----
658
712
 
659
713
  async createInvitation(
@@ -1747,7 +1801,7 @@ export class ParallClient {
1747
1801
  * Returns null when the server responds with 304 (config unchanged).
1748
1802
  */
1749
1803
  async getPlatformConfig(currentVersion?: string): Promise<PlatformConfigResponse | null> {
1750
- const url = `${this.baseUrl}${ENDPOINTS.PLATFORM_CONFIG}`;
1804
+ const url = `${this.baseUrlFor(ENDPOINTS.PLATFORM_CONFIG)}${ENDPOINTS.PLATFORM_CONFIG}`;
1751
1805
  const extra: Record<string, string> = {};
1752
1806
  if (currentVersion !== undefined) {
1753
1807
  extra['If-None-Match'] = currentVersion;
@@ -2058,6 +2112,127 @@ export class ParallClient {
2058
2112
  return this.request('GET', ENDPOINTS.SCHEDULE_RUN(orgId, runId));
2059
2113
  }
2060
2114
 
2115
+ // ---- External triggers (org-scoped) ----
2116
+
2117
+ async createExternalConnection(
2118
+ orgId: string,
2119
+ input: CreateExternalConnectionInput,
2120
+ ): Promise<ExternalConnection> {
2121
+ return this.request('POST', ENDPOINTS.EXTERNAL_CONNECTIONS(orgId), input);
2122
+ }
2123
+
2124
+ async listExternalConnections(
2125
+ orgId: string,
2126
+ filters?: ExternalConnectionFilters,
2127
+ ): Promise<PaginatedResponse<ExternalConnection>> {
2128
+ return this.request(
2129
+ 'GET',
2130
+ ENDPOINTS.EXTERNAL_CONNECTIONS(orgId),
2131
+ undefined,
2132
+ filters as Record<string, string | number | undefined>,
2133
+ );
2134
+ }
2135
+
2136
+ async getExternalConnection(orgId: string, connectionId: string): Promise<ExternalConnection> {
2137
+ return this.request('GET', ENDPOINTS.EXTERNAL_CONNECTION(orgId, connectionId));
2138
+ }
2139
+
2140
+ async updateExternalConnection(
2141
+ orgId: string,
2142
+ connectionId: string,
2143
+ patch: UpdateExternalConnectionInput,
2144
+ ): Promise<ExternalConnection> {
2145
+ return this.request('PATCH', ENDPOINTS.EXTERNAL_CONNECTION(orgId, connectionId), patch);
2146
+ }
2147
+
2148
+ async regenerateExternalConnectionIngressToken(
2149
+ orgId: string,
2150
+ connectionId: string,
2151
+ ): Promise<ExternalConnection> {
2152
+ return this.request(
2153
+ 'POST',
2154
+ ENDPOINTS.EXTERNAL_CONNECTION_INGRESS_TOKEN_REGENERATE(orgId, connectionId),
2155
+ );
2156
+ }
2157
+
2158
+ async deleteExternalConnection(orgId: string, connectionId: string): Promise<void> {
2159
+ return this.request('DELETE', ENDPOINTS.EXTERNAL_CONNECTION(orgId, connectionId));
2160
+ }
2161
+
2162
+ async getExternalTriggerSchema(
2163
+ orgId: string,
2164
+ connectionId: string,
2165
+ ): Promise<ExternalTriggerSchema> {
2166
+ return this.request('GET', ENDPOINTS.EXTERNAL_TRIGGER_SCHEMA(orgId, connectionId));
2167
+ }
2168
+
2169
+ async listExternalIngressEvents(
2170
+ orgId: string,
2171
+ filters?: ExternalIngressEventFilters,
2172
+ ): Promise<PaginatedResponse<ExternalIngressEvent>> {
2173
+ return this.request(
2174
+ 'GET',
2175
+ ENDPOINTS.EXTERNAL_INGRESS_EVENTS(orgId),
2176
+ undefined,
2177
+ filters as Record<string, string | number | undefined>,
2178
+ );
2179
+ }
2180
+
2181
+ async getExternalIngressEvent(orgId: string, eventId: string): Promise<ExternalIngressEvent> {
2182
+ return this.request('GET', ENDPOINTS.EXTERNAL_INGRESS_EVENT(orgId, eventId));
2183
+ }
2184
+
2185
+ async createExternalTrigger(
2186
+ orgId: string,
2187
+ input: CreateExternalTriggerInput,
2188
+ ): Promise<ExternalTrigger> {
2189
+ return this.request('POST', ENDPOINTS.EXTERNAL_TRIGGERS(orgId), input);
2190
+ }
2191
+
2192
+ async listExternalTriggers(
2193
+ orgId: string,
2194
+ filters?: ExternalTriggerFilters,
2195
+ ): Promise<PaginatedResponse<ExternalTrigger>> {
2196
+ return this.request(
2197
+ 'GET',
2198
+ ENDPOINTS.EXTERNAL_TRIGGERS(orgId),
2199
+ undefined,
2200
+ filters as Record<string, string | number | undefined>,
2201
+ );
2202
+ }
2203
+
2204
+ async getExternalTrigger(orgId: string, triggerId: string): Promise<ExternalTrigger> {
2205
+ return this.request('GET', ENDPOINTS.EXTERNAL_TRIGGER(orgId, triggerId));
2206
+ }
2207
+
2208
+ async updateExternalTrigger(
2209
+ orgId: string,
2210
+ triggerId: string,
2211
+ patch: UpdateExternalTriggerInput,
2212
+ ): Promise<ExternalTrigger> {
2213
+ return this.request('PATCH', ENDPOINTS.EXTERNAL_TRIGGER(orgId, triggerId), patch);
2214
+ }
2215
+
2216
+ async deleteExternalTrigger(orgId: string, triggerId: string): Promise<void> {
2217
+ return this.request('DELETE', ENDPOINTS.EXTERNAL_TRIGGER(orgId, triggerId));
2218
+ }
2219
+
2220
+ async listExternalTriggerRuns(
2221
+ orgId: string,
2222
+ filters?: ExternalTriggerRunFilters,
2223
+ ): Promise<PaginatedResponse<ExternalTriggerRun>> {
2224
+ return this.request(
2225
+ 'GET',
2226
+ ENDPOINTS.EXTERNAL_TRIGGER_RUNS(orgId),
2227
+ undefined,
2228
+ filters as Record<string, string | number | undefined>,
2229
+ );
2230
+ }
2231
+
2232
+ async getExternalTriggerRun(orgId: string, runId: string): Promise<ExternalTriggerRun> {
2233
+ return this.request('GET', ENDPOINTS.EXTERNAL_TRIGGER_RUN(orgId, runId));
2234
+ }
2235
+
2061
2236
  // ---- Wikis (org-scoped) ----
2062
2237
 
2063
2238
  async createWiki(orgId: string, data: CreateWikiRequest): Promise<Wiki> {
@@ -2081,12 +2256,43 @@ export class ParallClient {
2081
2256
  return this.request('GET', ENDPOINTS.WIKI_TREE(orgId, wikiId), undefined, params);
2082
2257
  }
2083
2258
 
2259
+ /**
2260
+ * Resolve a server-returned, host-relative media URL (a wiki `signed_url`
2261
+ * like `/wiki/v1/signed/files?token=...`) against this client's base origin,
2262
+ * so it can be dropped straight into a browser `<img>`/`<video>`/`<iframe>`
2263
+ * `src`.
2264
+ *
2265
+ * wiki-service returns these relative on purpose — it doesn't know its own
2266
+ * public origin. A relative `src` resolves against the *page* origin, which
2267
+ * only works when the page and wiki-service share an origin (local dev:
2268
+ * same-origin + Next.js `/wiki/*` proxy). In deployed envs the app
2269
+ * (app.parall.com) and wiki-service (api.parall.com) are different origins,
2270
+ * so `app.parall.com/wiki/v1/signed/files` hits the SPA's own `/wiki/[...]`
2271
+ * catch-all route — an `<iframe>` then recursively renders the whole app
2272
+ * instead of the file. Prefixing with the wiki base (the exact origin every
2273
+ * wiki API request already uses — `baseUrlFor` resolves `/wiki/v1` paths to
2274
+ * `wikiBaseUrl`) makes the URL absolute against the origin that actually
2275
+ * serves the bytes. An empty base (local dev, same-origin proxy) leaves it
2276
+ * relative, preserving the proxy path.
2277
+ */
2278
+ private absoluteMediaUrl(url: string): string {
2279
+ if (/^https?:\/\//i.test(url)) return url; // already absolute — leave as-is
2280
+ return `${this.baseUrlFor(url)}${url}`;
2281
+ }
2282
+
2084
2283
  async getWikiBlob(
2085
2284
  orgId: string,
2086
2285
  wikiId: string,
2087
2286
  params: { path: string; ref?: string },
2088
2287
  ): Promise<WikiBlob> {
2089
- return this.request('GET', ENDPOINTS.WIKI_BLOB(orgId, wikiId), undefined, params);
2288
+ const blob = await this.request<WikiBlob>(
2289
+ 'GET',
2290
+ ENDPOINTS.WIKI_BLOB(orgId, wikiId),
2291
+ undefined,
2292
+ params,
2293
+ );
2294
+ if (blob.signed_url) blob.signed_url = this.absoluteMediaUrl(blob.signed_url);
2295
+ return blob;
2090
2296
  }
2091
2297
 
2092
2298
  async getWikiNodeSections(
@@ -2287,7 +2493,13 @@ export class ParallClient {
2287
2493
  wikiId: string,
2288
2494
  params: { path: string; ref?: string },
2289
2495
  ): Promise<WikiFilePreviewUrlResponse> {
2290
- return this.request('POST', ENDPOINTS.WIKI_FILE_PREVIEW_URL(orgId, wikiId), params);
2496
+ const res = await this.request<WikiFilePreviewUrlResponse>(
2497
+ 'POST',
2498
+ ENDPOINTS.WIKI_FILE_PREVIEW_URL(orgId, wikiId),
2499
+ params,
2500
+ );
2501
+ if (res.url) res.url = this.absoluteMediaUrl(res.url);
2502
+ return res;
2291
2503
  }
2292
2504
 
2293
2505
  // ---- Wiki Path Scopes (AFCS ACL) ----
@@ -2665,8 +2877,11 @@ export class ParallClient {
2665
2877
  return resp.data;
2666
2878
  }
2667
2879
 
2668
- async listBrowserProfiles(orgId: string): Promise<BrowserProfile[]> {
2669
- const resp = await this.request<{ data: BrowserProfile[] }>(
2880
+ /** Org-wide browser-profile discovery list. Returns the sanitized
2881
+ * {@link BrowserProfileListItem} shape (not the full domain model), each row
2882
+ * carrying a per-viewer `can_open` control hint. */
2883
+ async listBrowserProfiles(orgId: string): Promise<BrowserProfileListItem[]> {
2884
+ const resp = await this.request<{ data: BrowserProfileListItem[] }>(
2670
2885
  'GET',
2671
2886
  ENDPOINTS.BROWSER_PROFILES(orgId),
2672
2887
  );
package/src/constants.ts CHANGED
@@ -566,6 +566,24 @@ export const ENDPOINTS = {
566
566
  SCHEDULE_RUN: (orgId: string, runId: string) =>
567
567
  `${API_BASE}/orgs/${orgId}/schedule_runs/${runId}`,
568
568
 
569
+ // External triggers (org-scoped incoming integration primitive)
570
+ EXTERNAL_CONNECTIONS: (orgId: string) => `${API_BASE}/orgs/${orgId}/external-connections`,
571
+ EXTERNAL_CONNECTION: (orgId: string, connectionId: string) =>
572
+ `${API_BASE}/orgs/${orgId}/external-connections/${connectionId}`,
573
+ EXTERNAL_CONNECTION_INGRESS_TOKEN_REGENERATE: (orgId: string, connectionId: string) =>
574
+ `${API_BASE}/orgs/${orgId}/external-connections/${connectionId}/ingress-token/regenerate`,
575
+ EXTERNAL_TRIGGER_SCHEMA: (orgId: string, connectionId: string) =>
576
+ `${API_BASE}/orgs/${orgId}/external-connections/${connectionId}/trigger-schema`,
577
+ EXTERNAL_INGRESS_EVENTS: (orgId: string) => `${API_BASE}/orgs/${orgId}/external-ingress-events`,
578
+ EXTERNAL_INGRESS_EVENT: (orgId: string, eventId: string) =>
579
+ `${API_BASE}/orgs/${orgId}/external-ingress-events/${eventId}`,
580
+ EXTERNAL_TRIGGERS: (orgId: string) => `${API_BASE}/orgs/${orgId}/external-triggers`,
581
+ EXTERNAL_TRIGGER: (orgId: string, triggerId: string) =>
582
+ `${API_BASE}/orgs/${orgId}/external-triggers/${triggerId}`,
583
+ EXTERNAL_TRIGGER_RUNS: (orgId: string) => `${API_BASE}/orgs/${orgId}/external-trigger-runs`,
584
+ EXTERNAL_TRIGGER_RUN: (orgId: string, runId: string) =>
585
+ `${API_BASE}/orgs/${orgId}/external-trigger-runs/${runId}`,
586
+
569
587
  // Invitations (org-scoped, admin)
570
588
  ORG_INVITATIONS: (orgId: string) => `${API_BASE}/orgs/${orgId}/invitations`,
571
589
  ORG_INVITATION: (orgId: string, invId: string) =>
package/src/types.ts CHANGED
@@ -264,6 +264,15 @@ export interface Message {
264
264
  content: MessageContent;
265
265
  version: number;
266
266
  reply_count: number;
267
+ /** Timestamp of the most recent reply in this message's thread (root only). */
268
+ last_reply_at?: string | null;
269
+ /**
270
+ * Most-recent distinct reply authors, newest-first (root only, capped at 3).
271
+ * Approximate: derived from the newest ~30 replies per root, so on very long
272
+ * threads dominated by a few recent senders, older distinct authors may be
273
+ * absent from the stack. A reload re-derives it from the server's view.
274
+ */
275
+ recent_repliers?: User[];
267
276
  edited_at: string | null;
268
277
  deleted_at: string | null;
269
278
  created_at: string;
@@ -1308,6 +1317,186 @@ export interface ScheduleRunFilters {
1308
1317
  limit?: number;
1309
1318
  }
1310
1319
 
1320
+ // ============================================================
1321
+ // External Trigger Types
1322
+ // ============================================================
1323
+
1324
+ export type ExternalConnectionStatus = 'active' | 'disabled';
1325
+ export type ExternalIngressEventStatus = 'received' | 'processed' | 'failed';
1326
+ export type ExternalTriggerStatus = 'active' | 'paused';
1327
+ export type ExternalTriggerEffectiveStatus = 'active' | 'inactive';
1328
+ export type ExternalTriggerInactiveReason =
1329
+ | 'connection_disabled'
1330
+ | 'connection_archived'
1331
+ | 'trigger_paused'
1332
+ | 'trigger_archived'
1333
+ | 'expired'
1334
+ | 'max_runs_reached';
1335
+ export type ExternalTriggerRunStatus = 'delivered' | 'failed';
1336
+
1337
+ export interface ExternalConnection {
1338
+ id: string;
1339
+ org_id: string;
1340
+ source_type: string;
1341
+ display_name: string;
1342
+ owner_id: string;
1343
+ status: ExternalConnectionStatus;
1344
+ auth_config: Record<string, unknown>;
1345
+ archived_at: string | null;
1346
+ archive_reason: string | null;
1347
+ ingress_url?: string;
1348
+ ingress_token?: string;
1349
+ created_at: string;
1350
+ updated_at: string;
1351
+ }
1352
+
1353
+ export interface CreateExternalConnectionInput {
1354
+ source_type?: string;
1355
+ display_name: string;
1356
+ }
1357
+
1358
+ export interface UpdateExternalConnectionInput {
1359
+ display_name?: string;
1360
+ status?: ExternalConnectionStatus;
1361
+ }
1362
+
1363
+ export interface ExternalIngressEvent {
1364
+ id: string;
1365
+ org_id: string;
1366
+ connection_id: string;
1367
+ status: ExternalIngressEventStatus;
1368
+ dedupe_key: string | null;
1369
+ event_type: string;
1370
+ request_snapshot: Record<string, unknown>;
1371
+ body_snapshot: Record<string, unknown>;
1372
+ envelope_snapshot: Record<string, unknown>;
1373
+ raw_body_sha256: string;
1374
+ body_size: number;
1375
+ error_code: string | null;
1376
+ error_message: string | null;
1377
+ received_at: string;
1378
+ processed_at: string | null;
1379
+ }
1380
+
1381
+ export interface ExternalTrigger {
1382
+ id: string;
1383
+ org_id: string;
1384
+ connection_id: string;
1385
+ creator_id: string;
1386
+ name: string;
1387
+ description: string;
1388
+ status: ExternalTriggerStatus;
1389
+ effective_status: ExternalTriggerEffectiveStatus;
1390
+ inactive_reason?: ExternalTriggerInactiveReason;
1391
+ archived_at: string | null;
1392
+ archive_reason: string | null;
1393
+ attached_to_uri: string | null;
1394
+ filter_language: 'cel';
1395
+ filter_profile: 'parall_cel_v1';
1396
+ filter_expr: string;
1397
+ filter_display_metadata: Record<string, unknown>;
1398
+ template_language: 'liquid';
1399
+ template_profile: 'parall_safe_v1';
1400
+ agent_input_template: string;
1401
+ max_runs: number | null;
1402
+ run_count: number;
1403
+ expires_at: string | null;
1404
+ client_context: Record<string, unknown>;
1405
+ target_agent_ids: string[];
1406
+ created_at: string;
1407
+ updated_at: string;
1408
+ }
1409
+
1410
+ export interface CreateExternalTriggerInput {
1411
+ connection_id: string;
1412
+ name: string;
1413
+ description?: string;
1414
+ target_agent_ids: string[];
1415
+ attached_to_uri?: string;
1416
+ filter_expr: string;
1417
+ filter_display_metadata?: Record<string, unknown>;
1418
+ agent_input_template: string;
1419
+ max_runs?: number;
1420
+ expires_at?: string;
1421
+ client_context?: Record<string, unknown>;
1422
+ }
1423
+
1424
+ export interface UpdateExternalTriggerInput {
1425
+ name?: string;
1426
+ description?: string;
1427
+ status?: ExternalTriggerStatus;
1428
+ target_agent_ids?: string[];
1429
+ attached_to_uri?: string;
1430
+ attached_to_uri_clear?: boolean;
1431
+ filter_expr?: string;
1432
+ filter_display_metadata?: Record<string, unknown>;
1433
+ agent_input_template?: string;
1434
+ max_runs?: number;
1435
+ max_runs_clear?: boolean;
1436
+ expires_at?: string;
1437
+ expires_at_clear?: boolean;
1438
+ client_context?: Record<string, unknown>;
1439
+ }
1440
+
1441
+ export interface ExternalTriggerRun {
1442
+ id: string;
1443
+ org_id: string;
1444
+ connection_id: string;
1445
+ ingress_event_id: string;
1446
+ trigger_id: string;
1447
+ status: ExternalTriggerRunStatus;
1448
+ failure_stage: string | null;
1449
+ error_code: string | null;
1450
+ error_message: string | null;
1451
+ agent_input_body: string | null;
1452
+ trigger_snapshot: Record<string, unknown>;
1453
+ configured_target_agent_ids: string[];
1454
+ eligible_target_agent_ids: string[];
1455
+ skipped_target_agent_ids: string[];
1456
+ dispatch_event_ids: string[];
1457
+ trigger_name?: string;
1458
+ connection_source_type?: string;
1459
+ connection_display_name?: string;
1460
+ ingress_event_type?: string;
1461
+ created_at: string;
1462
+ delivered_at: string | null;
1463
+ }
1464
+
1465
+ export interface ExternalConnectionFilters {
1466
+ cursor?: string;
1467
+ limit?: number;
1468
+ }
1469
+
1470
+ export interface ExternalTriggerFilters {
1471
+ connection_id?: string;
1472
+ creator_id?: string;
1473
+ cursor?: string;
1474
+ limit?: number;
1475
+ }
1476
+
1477
+ export interface ExternalIngressEventFilters {
1478
+ connection_id?: string;
1479
+ cursor?: string;
1480
+ limit?: number;
1481
+ }
1482
+
1483
+ export interface ExternalTriggerRunFilters {
1484
+ trigger_id?: string;
1485
+ cursor?: string;
1486
+ limit?: number;
1487
+ }
1488
+
1489
+ export interface ExternalTriggerSchema {
1490
+ filter_language: 'cel';
1491
+ filter_profile: 'parall_cel_v1';
1492
+ template_language: 'liquid';
1493
+ template_profile: 'parall_safe_v1';
1494
+ filter_variables: string[];
1495
+ template_variables: string[];
1496
+ template_filters: string[];
1497
+ examples: Record<string, string>;
1498
+ }
1499
+
1311
1500
  // Schedule WS event data
1312
1501
  export type ScheduleCreatedData = Schedule;
1313
1502
  export type ScheduleUpdatedData = Schedule;
@@ -2085,6 +2274,7 @@ export type DispatchEventType =
2085
2274
  | 'task_comment'
2086
2275
  | 'wiki_comment'
2087
2276
  | 'schedule.fire'
2277
+ | 'external_trigger'
2088
2278
  | 'approval_decided';
2089
2279
  export type DispatchStatus = 'pending' | 'received' | 'acked';
2090
2280
  export type DispatchDeliveryReason = 'mention' | 'watcher' | 'assignee' | 'creator';
@@ -2373,6 +2563,8 @@ export interface ResolvedRef {
2373
2563
  // Wiki fields
2374
2564
  file_name?: string;
2375
2565
  wiki_name?: string;
2566
+ /** Frontmatter description of the referenced wiki page (OKF), when present. */
2567
+ wiki_description?: string;
2376
2568
  fragment_exists?: boolean;
2377
2569
  anchor_kind?: string;
2378
2570
  anchor_exists?: boolean;
@@ -2778,13 +2970,28 @@ export interface InvokeClipResponse {
2778
2970
  error: string | null;
2779
2971
  }
2780
2972
 
2781
- export type BrowserProfileStatus = 'pending' | 'running' | 'stopped' | 'error';
2973
+ /**
2974
+ * profile.status is owner INTENT only, never runtime readiness:
2975
+ * - BYOC: pending | running | stopped | error
2976
+ * - hosted: idle | stopped | error (idle = enabled, activates lazily on invoke/viewer)
2977
+ * Whether a hosted pod is registered + routable is the lease/controller/provider's
2978
+ * runtime state, NOT profile status — hosted profiles are never `active`.
2979
+ */
2980
+ export type BrowserProfileStatus = 'pending' | 'running' | 'stopped' | 'error' | 'idle';
2981
+
2982
+ /**
2983
+ * Routing/identity discriminant: `byoc` runs on a user machine (machine_id
2984
+ * required), `hosted` runs on the platform pool (machine_id null).
2985
+ */
2986
+ export type BrowserPlacement = 'byoc' | 'hosted';
2782
2987
 
2783
2988
  export interface BrowserProfile {
2784
2989
  id: string;
2785
2990
  org_id: string;
2786
- /** Host machine where the profile's cookies / bb-browser account live. */
2787
- machine_id: string;
2991
+ /** byoc = user machine, hosted = platform pool. */
2992
+ placement: BrowserPlacement;
2993
+ /** Host machine for byoc profiles; null for hosted (the platform pool owns the pod). */
2994
+ machine_id: string | null;
2788
2995
  owner_user_id: string;
2789
2996
  display_name: string;
2790
2997
  status: BrowserProfileStatus;
@@ -2793,6 +3000,34 @@ export interface BrowserProfile {
2793
3000
  created_by?: string | null;
2794
3001
  created_at: string;
2795
3002
  updated_at: string;
3003
+ /** Control hint on the single-profile responses (create / get / lifecycle) —
3004
+ * always true, since reaching those endpoints means the caller passed the read
3005
+ * or control gate and owner/admin can always control. The org-wide list uses
3006
+ * {@link BrowserProfileListItem.can_open} (per-viewer) instead. */
3007
+ can_open?: boolean;
3008
+ }
3009
+
3010
+ /** Sanitized row from `GET /orgs/{orgId}/browser-profiles` (org-wide discovery).
3011
+ * Carries only the fields the Browser Profiles tab needs to render + gate
3012
+ * actions — the full domain model (provenance, timestamps) is never returned
3013
+ * org-wide. `error_msg` / `last_seen` are present only for readers (the profile
3014
+ * owner or an org admin), not merely for own-local host owners who can `can_open`. */
3015
+ export interface BrowserProfileListItem {
3016
+ id: string;
3017
+ org_id: string;
3018
+ /** byoc = user machine, hosted = platform pool. */
3019
+ placement: BrowserPlacement;
3020
+ /** Host machine for byoc; null for hosted (the platform pool owns the pod). */
3021
+ machine_id: string | null;
3022
+ owner_user_id: string;
3023
+ display_name: string;
3024
+ status: BrowserProfileStatus;
3025
+ /** Per-viewer control hint computed by the server (owner / org admin / own
3026
+ * local host machine). Optional only for forward-compat with an older
3027
+ * clip-service mid web-first rollout; the current server always sets it. */
3028
+ can_open?: boolean;
3029
+ error_msg?: string | null;
3030
+ last_seen?: string | null;
2796
3031
  }
2797
3032
 
2798
3033
  export interface BrowserProfileConsent {
@@ -2807,8 +3042,10 @@ export interface BrowserProfileConsent {
2807
3042
  }
2808
3043
 
2809
3044
  export interface CreateBrowserProfileRequest {
2810
- /** Host machine that runs the BrowserProfile (cookies / bb-browser account). */
2811
- machine_id: string;
3045
+ /** Defaults to `byoc` when omitted (backward compatible). */
3046
+ placement?: BrowserPlacement;
3047
+ /** Required for byoc; omitted for hosted (the platform pool assigns a pod). */
3048
+ machine_id?: string | null;
2812
3049
  owner_user_id: string;
2813
3050
  display_name: string;
2814
3051
  }