@parall/sdk 1.34.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,
@@ -11,6 +11,7 @@ import type {
11
11
  Organization,
12
12
  OrgMember,
13
13
  OrgMemberRole,
14
+ Team,
14
15
  Chat,
15
16
  ChatMember,
16
17
  ChatMemberRole,
@@ -34,6 +35,7 @@ import type {
34
35
  ApiKey,
35
36
  WsTicketResponse,
36
37
  Machine,
38
+ MachineCapability,
37
39
  ResizeMachineRequest,
38
40
  MachineKey,
39
41
  MachineRuntimeAuthState,
@@ -68,6 +70,19 @@ import type {
68
70
  UpdateScheduleInput,
69
71
  ScheduleFilters,
70
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,
71
86
  AgentSessionDB,
72
87
  AgentStep,
73
88
  CreateAgentSessionRequest,
@@ -103,6 +118,8 @@ import type {
103
118
  CreateWikiChangesetRequest,
104
119
  UpdateWikiChangesetRequest,
105
120
  CreateWikiPathScopeRequest,
121
+ WikiPathRestriction,
122
+ CreateWikiPathRestrictionRequest,
106
123
  CreateWikiAccessRequest,
107
124
  Comment,
108
125
  CreateCommentRequest,
@@ -148,6 +165,7 @@ import type {
148
165
  AgentClip,
149
166
  CreateClipRequest,
150
167
  UpdateClipRequest,
168
+ BulkUpdateClipMetadataRequest,
151
169
  BindAgentClipRequest,
152
170
  InvokeClipRequest,
153
171
  InvokeClipResponse,
@@ -155,16 +173,28 @@ import type {
155
173
  RegistryClipInfo,
156
174
  MachineClip,
157
175
  BrowserProfile,
176
+ BrowserProfileListItem,
158
177
  BrowserProfileStatus,
159
178
  BrowserProfileConsent,
160
179
  CreateBrowserProfileRequest,
161
180
  UpdateBrowserProfileRequest,
162
181
  BrowserProfileLifecycleRequest,
182
+ BrowserViewerCommandRequest,
183
+ BrowserViewerCommandResponse,
163
184
  GrantBrowserProfileConsentRequest,
164
185
  } from './types.js';
165
186
 
166
187
  export interface ParallClientOptions {
167
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;
168
198
  token?: string;
169
199
  onTokenExpired?: () => void;
170
200
  getRefreshToken?: () => string | null;
@@ -174,6 +204,7 @@ export interface ParallClientOptions {
174
204
 
175
205
  export class ParallClient {
176
206
  private baseUrl: string;
207
+ private wikiBaseUrl: string;
177
208
  private token: string | null;
178
209
  private onTokenExpired?: () => void;
179
210
  private getRefreshToken?: () => string | null;
@@ -235,6 +266,10 @@ export class ParallClient {
235
266
 
236
267
  constructor(options: ParallClientOptions = {}) {
237
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;
238
273
  this.token = options.token ?? null;
239
274
  this.onTokenExpired = options.onTokenExpired;
240
275
  this.getRefreshToken = options.getRefreshToken;
@@ -242,6 +277,16 @@ export class ParallClient {
242
277
  this.swimlaneName = options.swimlaneName;
243
278
  }
244
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
+
245
290
  setToken(token: string | null) {
246
291
  this.token = token;
247
292
  }
@@ -312,14 +357,14 @@ export class ParallClient {
312
357
  body?: unknown,
313
358
  query?: Record<string, string | number | boolean | undefined>,
314
359
  retried = false,
315
- opts?: { timeoutMs?: number; signal?: AbortSignal },
360
+ opts?: { timeoutMs?: number; signal?: AbortSignal; keepalive?: boolean },
316
361
  ): Promise<T> {
317
362
  // Proactive refresh: block until token is fresh (no-op if still valid)
318
363
  if (!retried) {
319
364
  await this.ensureFreshToken(path);
320
365
  }
321
366
 
322
- let url = `${this.baseUrl}${path}`;
367
+ let url = `${this.baseUrlFor(path)}${path}`;
323
368
 
324
369
  if (query) {
325
370
  const params = new URLSearchParams();
@@ -349,6 +394,9 @@ export class ParallClient {
349
394
  headers,
350
395
  body: body ? JSON.stringify(body) : undefined,
351
396
  signal,
397
+ // keepalive lets a request fired during page unload (e.g. the browser
398
+ // viewer's stream.close on pagehide) outlive the document.
399
+ keepalive: opts?.keepalive,
352
400
  });
353
401
  } catch (err) {
354
402
  throw ParallClient.normalizeFetchError(err);
@@ -409,7 +457,7 @@ export class ParallClient {
409
457
 
410
458
  let res: Response;
411
459
  try {
412
- res = await fetch(`${this.baseUrl}${path}`, {
460
+ res = await fetch(`${this.baseUrlFor(path)}${path}`, {
413
461
  method,
414
462
  headers,
415
463
  body,
@@ -580,6 +628,11 @@ export class ParallClient {
580
628
  return res.data;
581
629
  }
582
630
 
631
+ async getTeams(orgId: string): Promise<Team[]> {
632
+ const res = await this.request<{ data: Team[] }>('GET', ENDPOINTS.TEAMS(orgId));
633
+ return res.data;
634
+ }
635
+
583
636
  async getOnlineMembers(orgId: string): Promise<string[]> {
584
637
  const res = await this.request<{ user_ids: string[] }>(
585
638
  'GET',
@@ -639,6 +692,22 @@ export class ParallClient {
639
692
  );
640
693
  }
641
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
+
642
711
  // ---- Invitations ----
643
712
 
644
713
  async createInvitation(
@@ -1007,6 +1076,11 @@ export class ParallClient {
1007
1076
  return this.request('POST', ENDPOINTS.AGENT_API_KEYS(orgId, agentId));
1008
1077
  }
1009
1078
 
1079
+ /** Revokes all of the agent's active API keys and mints a replacement. */
1080
+ async regenerateAgentApiKey(orgId: string, agentId: string): Promise<ApiKey> {
1081
+ return this.request('POST', ENDPOINTS.AGENT_API_KEY_REGENERATE(orgId, agentId));
1082
+ }
1083
+
1010
1084
  async revokeAgentApiKey(orgId: string, agentId: string, key: string): Promise<void> {
1011
1085
  return this.request('DELETE', ENDPOINTS.AGENT_API_KEY(orgId, agentId, key));
1012
1086
  }
@@ -1254,7 +1328,15 @@ export class ParallClient {
1254
1328
  */
1255
1329
  async createMachine(
1256
1330
  orgId: string,
1257
- opts: { label?: string; compute_mode: 'local'; llm_source?: 'parall' | 'runtime_auth' },
1331
+ opts: {
1332
+ label?: string;
1333
+ compute_mode: 'local';
1334
+ llm_source?: 'parall' | 'runtime_auth';
1335
+ // Omit for the default BYOC daemon (agent_host + browser_provider). Pass an
1336
+ // explicit set for a single-purpose host, e.g. ['browser_provider'] for a
1337
+ // platform-hosted bb-browser + Chromium provider.
1338
+ capabilities?: MachineCapability[];
1339
+ },
1258
1340
  ): Promise<{
1259
1341
  machine: Machine;
1260
1342
  machine_key: string;
@@ -1338,6 +1420,22 @@ export class ParallClient {
1338
1420
  });
1339
1421
  }
1340
1422
 
1423
+ /**
1424
+ * Replace the machine's capability set (admin). Primary use: healing a
1425
+ * machine that registered without `browser_provider` during the capability
1426
+ * migration's rolling-deploy window. Removing `agent_host` is rejected by the
1427
+ * server while agents are attached (409 AGENTS_STILL_ATTACHED).
1428
+ */
1429
+ async patchMachineCapabilities(
1430
+ orgId: string,
1431
+ machineId: string,
1432
+ capabilities: MachineCapability[],
1433
+ ): Promise<Machine> {
1434
+ return this.request('PATCH', ENDPOINTS.MACHINE_CAPABILITIES(orgId, machineId), {
1435
+ capabilities,
1436
+ });
1437
+ }
1438
+
1341
1439
  /** Get machine-level runtime auth state. */
1342
1440
  async getMachineRuntimeAuth(orgId: string, machineId: string): Promise<MachineRuntimeAuthState> {
1343
1441
  return this.request('GET', ENDPOINTS.MACHINE_RUNTIME_AUTH(orgId, machineId));
@@ -1483,6 +1581,43 @@ export class ParallClient {
1483
1581
  return this.request('POST', ENDPOINTS.MACHINES_ME_BROWSE_RESPONSE(requestId), response);
1484
1582
  }
1485
1583
 
1584
+ /**
1585
+ * `POST /machines/me/browser-profiles/viewer-response/{requestId}` — daemon
1586
+ * reply to a `machine.browser_profile.viewer` control command. Wakes the
1587
+ * api-server request/reply bridge (mirrors {@link postBrowseResponse}).
1588
+ */
1589
+ async postBrowserProfileViewerResponse(
1590
+ requestId: string,
1591
+ response: { result?: Record<string, unknown>; error?: { message: string } },
1592
+ ): Promise<void> {
1593
+ return this.request(
1594
+ 'POST',
1595
+ ENDPOINTS.MACHINES_ME_BROWSER_PROFILE_VIEWER_RESPONSE(requestId),
1596
+ response,
1597
+ );
1598
+ }
1599
+
1600
+ /**
1601
+ * `POST /orgs/{orgId}/browser-profiles/{profileId}/viewer/command` — drive the
1602
+ * hosted browser live viewer (WebRTC signaling + tab nav). Authz: profile
1603
+ * owner or org admin. api-server brokers the command to the host daemon.
1604
+ */
1605
+ async browserViewerCommand(
1606
+ orgId: string,
1607
+ profileId: string,
1608
+ req: BrowserViewerCommandRequest,
1609
+ opts?: { timeoutMs?: number; keepalive?: boolean },
1610
+ ): Promise<BrowserViewerCommandResponse> {
1611
+ return this.request(
1612
+ 'POST',
1613
+ ENDPOINTS.BROWSER_PROFILE_VIEWER_COMMAND(orgId, profileId),
1614
+ req,
1615
+ undefined,
1616
+ false,
1617
+ opts,
1618
+ );
1619
+ }
1620
+
1486
1621
  async resizeMachine(
1487
1622
  orgId: string,
1488
1623
  machineId: string,
@@ -1666,7 +1801,7 @@ export class ParallClient {
1666
1801
  * Returns null when the server responds with 304 (config unchanged).
1667
1802
  */
1668
1803
  async getPlatformConfig(currentVersion?: string): Promise<PlatformConfigResponse | null> {
1669
- const url = `${this.baseUrl}${ENDPOINTS.PLATFORM_CONFIG}`;
1804
+ const url = `${this.baseUrlFor(ENDPOINTS.PLATFORM_CONFIG)}${ENDPOINTS.PLATFORM_CONFIG}`;
1670
1805
  const extra: Record<string, string> = {};
1671
1806
  if (currentVersion !== undefined) {
1672
1807
  extra['If-None-Match'] = currentVersion;
@@ -1977,6 +2112,127 @@ export class ParallClient {
1977
2112
  return this.request('GET', ENDPOINTS.SCHEDULE_RUN(orgId, runId));
1978
2113
  }
1979
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
+
1980
2236
  // ---- Wikis (org-scoped) ----
1981
2237
 
1982
2238
  async createWiki(orgId: string, data: CreateWikiRequest): Promise<Wiki> {
@@ -2000,12 +2256,43 @@ export class ParallClient {
2000
2256
  return this.request('GET', ENDPOINTS.WIKI_TREE(orgId, wikiId), undefined, params);
2001
2257
  }
2002
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
+
2003
2283
  async getWikiBlob(
2004
2284
  orgId: string,
2005
2285
  wikiId: string,
2006
2286
  params: { path: string; ref?: string },
2007
2287
  ): Promise<WikiBlob> {
2008
- 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;
2009
2296
  }
2010
2297
 
2011
2298
  async getWikiNodeSections(
@@ -2018,7 +2305,9 @@ export class ParallClient {
2018
2305
 
2019
2306
  async search(
2020
2307
  orgId: string,
2021
- params: { q: string; types?: string; chat_id?: string; limit?: number },
2308
+ // `types` selects entities (message/task/wiki); `wiki_type` is the distinct
2309
+ // wiki document-type facet (frontmatter `type`) — the two never overlap.
2310
+ params: { q: string; types?: string; chat_id?: string; limit?: number; wiki_type?: string },
2022
2311
  opts?: { signal?: AbortSignal },
2023
2312
  ): Promise<SearchResponse> {
2024
2313
  return this.request('GET', ENDPOINTS.SEARCH(orgId), undefined, params, false, opts);
@@ -2027,7 +2316,8 @@ export class ParallClient {
2027
2316
  async searchWiki(
2028
2317
  orgId: string,
2029
2318
  wikiId: string,
2030
- params: { q: string; limit?: number; path_prefix?: string; ref?: string },
2319
+ // `type` is the wiki document-type facet (frontmatter `type`).
2320
+ params: { q: string; limit?: number; path_prefix?: string; ref?: string; type?: string },
2031
2321
  ): Promise<WikiSearchResponse> {
2032
2322
  return this.request('GET', ENDPOINTS.WIKI_SEARCH(orgId, wikiId), undefined, params);
2033
2323
  }
@@ -2203,7 +2493,13 @@ export class ParallClient {
2203
2493
  wikiId: string,
2204
2494
  params: { path: string; ref?: string },
2205
2495
  ): Promise<WikiFilePreviewUrlResponse> {
2206
- 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;
2207
2503
  }
2208
2504
 
2209
2505
  // ---- Wiki Path Scopes (AFCS ACL) ----
@@ -2228,6 +2524,28 @@ export class ParallClient {
2228
2524
  await this.request('DELETE', ENDPOINTS.WIKI_PATH_SCOPE(orgId, wikiId, scopeId));
2229
2525
  }
2230
2526
 
2527
+ // ---- Wiki Path Restrictions (narrowing ACL — private subtrees) ----
2528
+
2529
+ async getWikiRestrictions(orgId: string, wikiId: string): Promise<WikiPathRestriction[]> {
2530
+ const res = await this.request<{ data: WikiPathRestriction[] }>(
2531
+ 'GET',
2532
+ ENDPOINTS.WIKI_RESTRICTIONS(orgId, wikiId),
2533
+ );
2534
+ return res.data;
2535
+ }
2536
+
2537
+ async createWikiRestriction(
2538
+ orgId: string,
2539
+ wikiId: string,
2540
+ data: CreateWikiPathRestrictionRequest,
2541
+ ): Promise<WikiPathRestriction> {
2542
+ return this.request('POST', ENDPOINTS.WIKI_RESTRICTIONS(orgId, wikiId), data);
2543
+ }
2544
+
2545
+ async deleteWikiRestriction(orgId: string, wikiId: string, restrictionId: string): Promise<void> {
2546
+ await this.request('DELETE', ENDPOINTS.WIKI_RESTRICTION(orgId, wikiId, restrictionId));
2547
+ }
2548
+
2231
2549
  async getWikiAccessStatus(
2232
2550
  orgId: string,
2233
2551
  wikiId: string,
@@ -2511,6 +2829,11 @@ export class ParallClient {
2511
2829
  return this.request('PATCH', ENDPOINTS.CLIP(orgId, clipId), req);
2512
2830
  }
2513
2831
 
2832
+ /** Atomically set display_name and/or description on multiple clip instances (application metadata). */
2833
+ async bulkUpdateClipMetadata(orgId: string, req: BulkUpdateClipMetadataRequest): Promise<Clip[]> {
2834
+ return this.request('POST', ENDPOINTS.CLIPS_BULK_METADATA(orgId), req);
2835
+ }
2836
+
2514
2837
  async deleteClip(orgId: string, clipId: string): Promise<void> {
2515
2838
  await this.request('DELETE', ENDPOINTS.CLIP(orgId, clipId));
2516
2839
  }
@@ -2554,8 +2877,11 @@ export class ParallClient {
2554
2877
  return resp.data;
2555
2878
  }
2556
2879
 
2557
- async listBrowserProfiles(orgId: string): Promise<BrowserProfile[]> {
2558
- 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[] }>(
2559
2885
  'GET',
2560
2886
  ENDPOINTS.BROWSER_PROFILES(orgId),
2561
2887
  );
package/src/constants.ts CHANGED
@@ -347,6 +347,7 @@ export const ENDPOINTS = {
347
347
  // Org-scoped
348
348
  ORG: (orgId: string) => `${API_BASE}/orgs/${orgId}`,
349
349
  ORG_MEMBERS: (orgId: string) => `${API_BASE}/orgs/${orgId}/members`,
350
+ TEAMS: (orgId: string) => `${API_BASE}/orgs/${orgId}/teams`,
350
351
  ORG_MEMBERS_ONLINE: (orgId: string) => `${API_BASE}/orgs/${orgId}/members/online`,
351
352
  ORG_MEMBER: (orgId: string, userId: string) => `${API_BASE}/orgs/${orgId}/members/${userId}`,
352
353
  ORG_MEMBER_CHATS: (orgId: string, memberId: string) =>
@@ -409,6 +410,8 @@ export const ENDPOINTS = {
409
410
  `${API_BASE}/orgs/${orgId}/agents/${agentId}/api-keys`,
410
411
  AGENT_API_KEY: (orgId: string, agentId: string, key: string) =>
411
412
  `${API_BASE}/orgs/${orgId}/agents/${agentId}/api-keys/${key}`,
413
+ AGENT_API_KEY_REGENERATE: (orgId: string, agentId: string) =>
414
+ `${API_BASE}/orgs/${orgId}/agents/${agentId}/api-keys/regenerate`,
412
415
  AGENT_AVATAR: (orgId: string, agentId: string) =>
413
416
  `${API_BASE}/orgs/${orgId}/agents/${agentId}/avatar`,
414
417
  AGENT_ACTIVITY: (orgId: string, agentId: string) =>
@@ -479,6 +482,8 @@ export const ENDPOINTS = {
479
482
  `${API_BASE}/orgs/${orgId}/machines/${machineId}/llm-source`,
480
483
  MACHINE_PROVIDER_ENABLED: (orgId: string, machineId: string) =>
481
484
  `${API_BASE}/orgs/${orgId}/machines/${machineId}/provider-enabled`,
485
+ MACHINE_CAPABILITIES: (orgId: string, machineId: string) =>
486
+ `${API_BASE}/orgs/${orgId}/machines/${machineId}/capabilities`,
482
487
  MACHINE_RUNTIME_AUTH: (orgId: string, machineId: string) =>
483
488
  `${API_BASE}/orgs/${orgId}/machines/${machineId}/runtime-auth`,
484
489
  MACHINE_KEYS: (orgId: string, machineId: string) =>
@@ -511,6 +516,13 @@ export const ENDPOINTS = {
511
516
  MACHINES_ME_WS_TICKET: `${API_BASE}/machines/me/ws/ticket`,
512
517
  MACHINES_ME_BROWSE_RESPONSE: (requestId: string) =>
513
518
  `${API_BASE}/machines/me/browse-response/${requestId}`,
519
+ // Hosted browser live-viewer control plane (api-server, NOT clip-service):
520
+ // the web client drives WebRTC signaling + tab nav through VIEWER_COMMAND;
521
+ // api-server brokers each command to the host daemon via the machine:{id}
522
+ // request/reply bridge (mirrors filesystem browse), and the daemon replies on
523
+ // VIEWER_RESPONSE. See docs/engineering-design/hosted-browser-provider-design.md.
524
+ MACHINES_ME_BROWSER_PROFILE_VIEWER_RESPONSE: (requestId: string) =>
525
+ `${API_BASE}/machines/me/browser-profiles/viewer-response/${requestId}`,
514
526
 
515
527
  // Tasks (org-scoped)
516
528
  TASKS: (orgId: string) => `${API_BASE}/orgs/${orgId}/tasks`,
@@ -554,6 +566,24 @@ export const ENDPOINTS = {
554
566
  SCHEDULE_RUN: (orgId: string, runId: string) =>
555
567
  `${API_BASE}/orgs/${orgId}/schedule_runs/${runId}`,
556
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
+
557
587
  // Invitations (org-scoped, admin)
558
588
  ORG_INVITATIONS: (orgId: string) => `${API_BASE}/orgs/${orgId}/invitations`,
559
589
  ORG_INVITATION: (orgId: string, invId: string) =>
@@ -617,6 +647,11 @@ export const ENDPOINTS = {
617
647
  `${WIKI_BASE}/orgs/${orgId}/wikis/${wikiId}/path-scopes`,
618
648
  WIKI_PATH_SCOPE: (orgId: string, wikiId: string, scopeId: string) =>
619
649
  `${WIKI_BASE}/orgs/${orgId}/wikis/${wikiId}/path-scopes/${scopeId}`,
650
+ // Wiki Path Restrictions (AFCS narrowing ACL — private subtrees)
651
+ WIKI_RESTRICTIONS: (orgId: string, wikiId: string) =>
652
+ `${WIKI_BASE}/orgs/${orgId}/wikis/${wikiId}/restrictions`,
653
+ WIKI_RESTRICTION: (orgId: string, wikiId: string, restrictionId: string) =>
654
+ `${WIKI_BASE}/orgs/${orgId}/wikis/${wikiId}/restrictions/${restrictionId}`,
620
655
  WIKI_ACCESS_STATUS: (orgId: string, wikiId: string) =>
621
656
  `${WIKI_BASE}/orgs/${orgId}/wikis/${wikiId}/access-status`,
622
657
  WIKI_ACCESS_REQUESTS: (orgId: string, wikiId: string) =>
@@ -705,6 +740,7 @@ export const ENDPOINTS = {
705
740
  // Clips (org-scoped, served by clip-service)
706
741
  CLIPS: (orgId: string) => `${CLIP_BASE}/orgs/${orgId}/clips`,
707
742
  CLIP: (orgId: string, clipId: string) => `${CLIP_BASE}/orgs/${orgId}/clips/${clipId}`,
743
+ CLIPS_BULK_METADATA: (orgId: string) => `${CLIP_BASE}/orgs/${orgId}/clips/bulk-metadata`,
708
744
  CLIP_AGENTS: (orgId: string, clipId: string) =>
709
745
  `${CLIP_BASE}/orgs/${orgId}/clips/${clipId}/agents`,
710
746
  AGENT_CLIPS: (orgId: string, agentId: string) =>
@@ -726,6 +762,10 @@ export const ENDPOINTS = {
726
762
  `${CLIP_BASE}/orgs/${orgId}/browser-profiles/${profileId}/consents`,
727
763
  BROWSER_PROFILE_CONSENT: (orgId: string, profileId: string, clipId: string) =>
728
764
  `${CLIP_BASE}/orgs/${orgId}/browser-profiles/${profileId}/consents/${clipId}`,
765
+ // Live viewer command — on api-server (API_BASE), not clip-service: it rides
766
+ // the machine control-plane request/reply bridge that lives in api-server.
767
+ BROWSER_PROFILE_VIEWER_COMMAND: (orgId: string, profileId: string) =>
768
+ `${API_BASE}/orgs/${orgId}/browser-profiles/${profileId}/viewer/command`,
729
769
 
730
770
  // Clip registry (global, served by clip-service → Pinix Hub proxy)
731
771
  CLIP_REGISTRY: () => `${CLIP_BASE}/registry/clips`,
@@ -805,6 +845,7 @@ export const WS_EVENTS = {
805
845
  MACHINE_CONFIG_UPDATED: 'machine.config.updated',
806
846
  MACHINE_CLIP_SYNC: 'machine.clip.sync',
807
847
  MACHINE_BROWSER_PROFILE_LIFECYCLE: 'machine.browser_profile.lifecycle',
848
+ MACHINE_BROWSER_PROFILE_VIEWER: 'machine.browser_profile.viewer',
808
849
  AGENT_NEW_SESSION: 'agent.new_session',
809
850
  CLIP_CREATED: 'clip.created',
810
851
  CLIP_REMOVED: 'clip.removed',