@parall/sdk 1.35.0 → 1.36.1

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,
@@ -91,6 +104,7 @@ import type {
91
104
  WikiNodeSectionArtifact,
92
105
  WikiSearchResponse,
93
106
  WikiPageIndex,
107
+ SearchParams,
94
108
  SearchResponse,
95
109
  WikiRefsResponse,
96
110
  WikiRefsCheckResponse,
@@ -119,6 +133,7 @@ import type {
119
133
  DispatchExpireResult,
120
134
  ResolveRefsResponse,
121
135
  BacklinksResponse,
136
+ RefGraphResponse,
122
137
  BrokenRefsResponse,
123
138
  PushSubscribeRequest,
124
139
  NotifPrefs,
@@ -160,6 +175,8 @@ import type {
160
175
  RegistryClipInfo,
161
176
  MachineClip,
162
177
  BrowserProfile,
178
+ MachineBrowserProfile,
179
+ BrowserProfileListItem,
163
180
  BrowserProfileStatus,
164
181
  BrowserProfileConsent,
165
182
  CreateBrowserProfileRequest,
@@ -172,6 +189,15 @@ import type {
172
189
 
173
190
  export interface ParallClientOptions {
174
191
  baseUrl?: string;
192
+ /**
193
+ * Base URL for wiki-service endpoints (paths under `/wiki/v1`). Defaults to
194
+ * `baseUrl`. Wiki endpoints are served by a separate service from the rest of
195
+ * the API; in staging/prod one gateway fronts both (so `baseUrl` alone is
196
+ * correct), but local dev runs api-server (:8080) and wiki-service (:8090)
197
+ * separately. Set this so wiki calls reach wiki-service while everything else
198
+ * (chat, tasks, comments, refs) keeps using `baseUrl`.
199
+ */
200
+ wikiBaseUrl?: string;
175
201
  token?: string;
176
202
  onTokenExpired?: () => void;
177
203
  getRefreshToken?: () => string | null;
@@ -181,6 +207,7 @@ export interface ParallClientOptions {
181
207
 
182
208
  export class ParallClient {
183
209
  private baseUrl: string;
210
+ private wikiBaseUrl: string;
184
211
  private token: string | null;
185
212
  private onTokenExpired?: () => void;
186
213
  private getRefreshToken?: () => string | null;
@@ -242,6 +269,10 @@ export class ParallClient {
242
269
 
243
270
  constructor(options: ParallClientOptions = {}) {
244
271
  this.baseUrl = options.baseUrl ?? '';
272
+ // Defaults to baseUrl so existing single-origin consumers (web, desktop,
273
+ // hosted agents behind one gateway) are unchanged; only split api/wiki
274
+ // deployments (local dev) need to set it.
275
+ this.wikiBaseUrl = options.wikiBaseUrl ?? this.baseUrl;
245
276
  this.token = options.token ?? null;
246
277
  this.onTokenExpired = options.onTokenExpired;
247
278
  this.getRefreshToken = options.getRefreshToken;
@@ -249,6 +280,16 @@ export class ParallClient {
249
280
  this.swimlaneName = options.swimlaneName;
250
281
  }
251
282
 
283
+ /**
284
+ * Pick the origin for a request path: wiki-service base for `/wiki/v1`
285
+ * endpoints, api base for everything else. The path itself (from ENDPOINTS)
286
+ * is authoritative, so wiki vs api routing can't drift from how a caller
287
+ * happens to invoke the client.
288
+ */
289
+ private baseUrlFor(path: string): string {
290
+ return path.startsWith(WIKI_BASE) ? this.wikiBaseUrl : this.baseUrl;
291
+ }
292
+
252
293
  setToken(token: string | null) {
253
294
  this.token = token;
254
295
  }
@@ -326,7 +367,7 @@ export class ParallClient {
326
367
  await this.ensureFreshToken(path);
327
368
  }
328
369
 
329
- let url = `${this.baseUrl}${path}`;
370
+ let url = `${this.baseUrlFor(path)}${path}`;
330
371
 
331
372
  if (query) {
332
373
  const params = new URLSearchParams();
@@ -419,7 +460,7 @@ export class ParallClient {
419
460
 
420
461
  let res: Response;
421
462
  try {
422
- res = await fetch(`${this.baseUrl}${path}`, {
463
+ res = await fetch(`${this.baseUrlFor(path)}${path}`, {
423
464
  method,
424
465
  headers,
425
466
  body,
@@ -654,6 +695,22 @@ export class ParallClient {
654
695
  );
655
696
  }
656
697
 
698
+ // Auto-paginated variant of getMemberTasks: fetches ALL pending tasks
699
+ // (todo + in_progress) assigned to a member, including subtasks (the
700
+ // endpoint does not filter parent_id). Powers the CLI `tasks assigned`
701
+ // command so an agent answering "what's on X's plate" sees the full
702
+ // backlog, not just the first page.
703
+ async getMemberTasksAll(orgId: string, memberId: string): Promise<Task[]> {
704
+ const all: Task[] = [];
705
+ let cursor: string | undefined;
706
+ do {
707
+ const res = await this.getMemberTasks(orgId, memberId, { cursor, limit: 100 });
708
+ all.push(...res.data);
709
+ cursor = res.has_more ? res.next_cursor : undefined;
710
+ } while (cursor);
711
+ return all;
712
+ }
713
+
657
714
  // ---- Invitations ----
658
715
 
659
716
  async createInvitation(
@@ -896,6 +953,8 @@ export class ParallClient {
896
953
  limit?: number;
897
954
  thread_root_id?: string;
898
955
  top_level?: boolean;
956
+ /** RFC3339 or YYYY-MM-DD; filters messages created at/after this instant. Independent of before/after cursors. */
957
+ since?: string;
899
958
  },
900
959
  ): Promise<PaginatedResponse<Message>> {
901
960
  return this.request(
@@ -1441,9 +1500,10 @@ export class ParallClient {
1441
1500
  return res.data;
1442
1501
  }
1443
1502
 
1444
- /** `GET /machines/me/browser-profiles` — browser profiles hosted by this machine. */
1445
- async listMachineBrowserProfiles(): Promise<BrowserProfile[]> {
1446
- const res = await this.request<{ data: BrowserProfile[] }>(
1503
+ /** `GET /machines/me/browser-profiles` — browser profiles hosted by this machine.
1504
+ * Returns the daemon DTO (carries proxy_password for bb-browser replay). */
1505
+ async listMachineBrowserProfiles(): Promise<MachineBrowserProfile[]> {
1506
+ const res = await this.request<{ data: MachineBrowserProfile[] }>(
1447
1507
  'GET',
1448
1508
  ENDPOINTS.MACHINES_ME_BROWSER_PROFILES,
1449
1509
  );
@@ -1747,7 +1807,7 @@ export class ParallClient {
1747
1807
  * Returns null when the server responds with 304 (config unchanged).
1748
1808
  */
1749
1809
  async getPlatformConfig(currentVersion?: string): Promise<PlatformConfigResponse | null> {
1750
- const url = `${this.baseUrl}${ENDPOINTS.PLATFORM_CONFIG}`;
1810
+ const url = `${this.baseUrlFor(ENDPOINTS.PLATFORM_CONFIG)}${ENDPOINTS.PLATFORM_CONFIG}`;
1751
1811
  const extra: Record<string, string> = {};
1752
1812
  if (currentVersion !== undefined) {
1753
1813
  extra['If-None-Match'] = currentVersion;
@@ -2058,6 +2118,127 @@ export class ParallClient {
2058
2118
  return this.request('GET', ENDPOINTS.SCHEDULE_RUN(orgId, runId));
2059
2119
  }
2060
2120
 
2121
+ // ---- External triggers (org-scoped) ----
2122
+
2123
+ async createExternalConnection(
2124
+ orgId: string,
2125
+ input: CreateExternalConnectionInput,
2126
+ ): Promise<ExternalConnection> {
2127
+ return this.request('POST', ENDPOINTS.EXTERNAL_CONNECTIONS(orgId), input);
2128
+ }
2129
+
2130
+ async listExternalConnections(
2131
+ orgId: string,
2132
+ filters?: ExternalConnectionFilters,
2133
+ ): Promise<PaginatedResponse<ExternalConnection>> {
2134
+ return this.request(
2135
+ 'GET',
2136
+ ENDPOINTS.EXTERNAL_CONNECTIONS(orgId),
2137
+ undefined,
2138
+ filters as Record<string, string | number | undefined>,
2139
+ );
2140
+ }
2141
+
2142
+ async getExternalConnection(orgId: string, connectionId: string): Promise<ExternalConnection> {
2143
+ return this.request('GET', ENDPOINTS.EXTERNAL_CONNECTION(orgId, connectionId));
2144
+ }
2145
+
2146
+ async updateExternalConnection(
2147
+ orgId: string,
2148
+ connectionId: string,
2149
+ patch: UpdateExternalConnectionInput,
2150
+ ): Promise<ExternalConnection> {
2151
+ return this.request('PATCH', ENDPOINTS.EXTERNAL_CONNECTION(orgId, connectionId), patch);
2152
+ }
2153
+
2154
+ async regenerateExternalConnectionIngressToken(
2155
+ orgId: string,
2156
+ connectionId: string,
2157
+ ): Promise<ExternalConnection> {
2158
+ return this.request(
2159
+ 'POST',
2160
+ ENDPOINTS.EXTERNAL_CONNECTION_INGRESS_TOKEN_REGENERATE(orgId, connectionId),
2161
+ );
2162
+ }
2163
+
2164
+ async deleteExternalConnection(orgId: string, connectionId: string): Promise<void> {
2165
+ return this.request('DELETE', ENDPOINTS.EXTERNAL_CONNECTION(orgId, connectionId));
2166
+ }
2167
+
2168
+ async getExternalTriggerSchema(
2169
+ orgId: string,
2170
+ connectionId: string,
2171
+ ): Promise<ExternalTriggerSchema> {
2172
+ return this.request('GET', ENDPOINTS.EXTERNAL_TRIGGER_SCHEMA(orgId, connectionId));
2173
+ }
2174
+
2175
+ async listExternalIngressEvents(
2176
+ orgId: string,
2177
+ filters?: ExternalIngressEventFilters,
2178
+ ): Promise<PaginatedResponse<ExternalIngressEvent>> {
2179
+ return this.request(
2180
+ 'GET',
2181
+ ENDPOINTS.EXTERNAL_INGRESS_EVENTS(orgId),
2182
+ undefined,
2183
+ filters as Record<string, string | number | undefined>,
2184
+ );
2185
+ }
2186
+
2187
+ async getExternalIngressEvent(orgId: string, eventId: string): Promise<ExternalIngressEvent> {
2188
+ return this.request('GET', ENDPOINTS.EXTERNAL_INGRESS_EVENT(orgId, eventId));
2189
+ }
2190
+
2191
+ async createExternalTrigger(
2192
+ orgId: string,
2193
+ input: CreateExternalTriggerInput,
2194
+ ): Promise<ExternalTrigger> {
2195
+ return this.request('POST', ENDPOINTS.EXTERNAL_TRIGGERS(orgId), input);
2196
+ }
2197
+
2198
+ async listExternalTriggers(
2199
+ orgId: string,
2200
+ filters?: ExternalTriggerFilters,
2201
+ ): Promise<PaginatedResponse<ExternalTrigger>> {
2202
+ return this.request(
2203
+ 'GET',
2204
+ ENDPOINTS.EXTERNAL_TRIGGERS(orgId),
2205
+ undefined,
2206
+ filters as Record<string, string | number | undefined>,
2207
+ );
2208
+ }
2209
+
2210
+ async getExternalTrigger(orgId: string, triggerId: string): Promise<ExternalTrigger> {
2211
+ return this.request('GET', ENDPOINTS.EXTERNAL_TRIGGER(orgId, triggerId));
2212
+ }
2213
+
2214
+ async updateExternalTrigger(
2215
+ orgId: string,
2216
+ triggerId: string,
2217
+ patch: UpdateExternalTriggerInput,
2218
+ ): Promise<ExternalTrigger> {
2219
+ return this.request('PATCH', ENDPOINTS.EXTERNAL_TRIGGER(orgId, triggerId), patch);
2220
+ }
2221
+
2222
+ async deleteExternalTrigger(orgId: string, triggerId: string): Promise<void> {
2223
+ return this.request('DELETE', ENDPOINTS.EXTERNAL_TRIGGER(orgId, triggerId));
2224
+ }
2225
+
2226
+ async listExternalTriggerRuns(
2227
+ orgId: string,
2228
+ filters?: ExternalTriggerRunFilters,
2229
+ ): Promise<PaginatedResponse<ExternalTriggerRun>> {
2230
+ return this.request(
2231
+ 'GET',
2232
+ ENDPOINTS.EXTERNAL_TRIGGER_RUNS(orgId),
2233
+ undefined,
2234
+ filters as Record<string, string | number | undefined>,
2235
+ );
2236
+ }
2237
+
2238
+ async getExternalTriggerRun(orgId: string, runId: string): Promise<ExternalTriggerRun> {
2239
+ return this.request('GET', ENDPOINTS.EXTERNAL_TRIGGER_RUN(orgId, runId));
2240
+ }
2241
+
2061
2242
  // ---- Wikis (org-scoped) ----
2062
2243
 
2063
2244
  async createWiki(orgId: string, data: CreateWikiRequest): Promise<Wiki> {
@@ -2081,12 +2262,43 @@ export class ParallClient {
2081
2262
  return this.request('GET', ENDPOINTS.WIKI_TREE(orgId, wikiId), undefined, params);
2082
2263
  }
2083
2264
 
2265
+ /**
2266
+ * Resolve a server-returned, host-relative media URL (a wiki `signed_url`
2267
+ * like `/wiki/v1/signed/files?token=...`) against this client's base origin,
2268
+ * so it can be dropped straight into a browser `<img>`/`<video>`/`<iframe>`
2269
+ * `src`.
2270
+ *
2271
+ * wiki-service returns these relative on purpose — it doesn't know its own
2272
+ * public origin. A relative `src` resolves against the *page* origin, which
2273
+ * only works when the page and wiki-service share an origin (local dev:
2274
+ * same-origin + Next.js `/wiki/*` proxy). In deployed envs the app
2275
+ * (app.parall.com) and wiki-service (api.parall.com) are different origins,
2276
+ * so `app.parall.com/wiki/v1/signed/files` hits the SPA's own `/wiki/[...]`
2277
+ * catch-all route — an `<iframe>` then recursively renders the whole app
2278
+ * instead of the file. Prefixing with the wiki base (the exact origin every
2279
+ * wiki API request already uses — `baseUrlFor` resolves `/wiki/v1` paths to
2280
+ * `wikiBaseUrl`) makes the URL absolute against the origin that actually
2281
+ * serves the bytes. An empty base (local dev, same-origin proxy) leaves it
2282
+ * relative, preserving the proxy path.
2283
+ */
2284
+ private absoluteMediaUrl(url: string): string {
2285
+ if (/^https?:\/\//i.test(url)) return url; // already absolute — leave as-is
2286
+ return `${this.baseUrlFor(url)}${url}`;
2287
+ }
2288
+
2084
2289
  async getWikiBlob(
2085
2290
  orgId: string,
2086
2291
  wikiId: string,
2087
2292
  params: { path: string; ref?: string },
2088
2293
  ): Promise<WikiBlob> {
2089
- return this.request('GET', ENDPOINTS.WIKI_BLOB(orgId, wikiId), undefined, params);
2294
+ const blob = await this.request<WikiBlob>(
2295
+ 'GET',
2296
+ ENDPOINTS.WIKI_BLOB(orgId, wikiId),
2297
+ undefined,
2298
+ params,
2299
+ );
2300
+ if (blob.signed_url) blob.signed_url = this.absoluteMediaUrl(blob.signed_url);
2301
+ return blob;
2090
2302
  }
2091
2303
 
2092
2304
  async getWikiNodeSections(
@@ -2099,9 +2311,7 @@ export class ParallClient {
2099
2311
 
2100
2312
  async search(
2101
2313
  orgId: string,
2102
- // `types` selects entities (message/task/wiki); `wiki_type` is the distinct
2103
- // wiki document-type facet (frontmatter `type`) — the two never overlap.
2104
- params: { q: string; types?: string; chat_id?: string; limit?: number; wiki_type?: string },
2314
+ params: SearchParams,
2105
2315
  opts?: { signal?: AbortSignal },
2106
2316
  ): Promise<SearchResponse> {
2107
2317
  return this.request('GET', ENDPOINTS.SEARCH(orgId), undefined, params, false, opts);
@@ -2287,7 +2497,13 @@ export class ParallClient {
2287
2497
  wikiId: string,
2288
2498
  params: { path: string; ref?: string },
2289
2499
  ): Promise<WikiFilePreviewUrlResponse> {
2290
- return this.request('POST', ENDPOINTS.WIKI_FILE_PREVIEW_URL(orgId, wikiId), params);
2500
+ const res = await this.request<WikiFilePreviewUrlResponse>(
2501
+ 'POST',
2502
+ ENDPOINTS.WIKI_FILE_PREVIEW_URL(orgId, wikiId),
2503
+ params,
2504
+ );
2505
+ if (res.url) res.url = this.absoluteMediaUrl(res.url);
2506
+ return res;
2291
2507
  }
2292
2508
 
2293
2509
  // ---- Wiki Path Scopes (AFCS ACL) ----
@@ -2468,6 +2684,20 @@ export class ParallClient {
2468
2684
  return this.request('GET', ENDPOINTS.REFS_BACKLINKS(orgId), undefined, params);
2469
2685
  }
2470
2686
 
2687
+ /**
2688
+ * Bounded multi-hop walk of the prll:// reference graph around `uri`. `uri`
2689
+ * must be an entity-level prll:// URI — a refined URI (path/query/fragment) is
2690
+ * rejected with 400 UNSUPPORTED_REFINED_URI. `depth` is clamped server-side to
2691
+ * [1, 4]; breadth (node/edge counts) is capped server-side and surfaced via
2692
+ * `truncated`. ACL is applied per hop (chat membership + wiki path scope).
2693
+ */
2694
+ async getRefsGraph(
2695
+ orgId: string,
2696
+ params: { uri: string; depth?: number },
2697
+ ): Promise<RefGraphResponse> {
2698
+ return this.request('GET', ENDPOINTS.REFS_GRAPH(orgId), undefined, params);
2699
+ }
2700
+
2471
2701
  async checkBrokenRefs(orgId: string): Promise<BrokenRefsResponse> {
2472
2702
  return this.request('GET', ENDPOINTS.REFS_CHECK(orgId));
2473
2703
  }
@@ -2665,8 +2895,11 @@ export class ParallClient {
2665
2895
  return resp.data;
2666
2896
  }
2667
2897
 
2668
- async listBrowserProfiles(orgId: string): Promise<BrowserProfile[]> {
2669
- const resp = await this.request<{ data: BrowserProfile[] }>(
2898
+ /** Org-wide browser-profile discovery list. Returns the sanitized
2899
+ * {@link BrowserProfileListItem} shape (not the full domain model), each row
2900
+ * carrying a per-viewer `can_open` control hint. */
2901
+ async listBrowserProfiles(orgId: string): Promise<BrowserProfileListItem[]> {
2902
+ const resp = await this.request<{ data: BrowserProfileListItem[] }>(
2670
2903
  'GET',
2671
2904
  ENDPOINTS.BROWSER_PROFILES(orgId),
2672
2905
  );
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) =>
@@ -688,6 +706,7 @@ export const ENDPOINTS = {
688
706
  // References (org-scoped)
689
707
  REFS_RESOLVE: (orgId: string) => `${API_BASE}/orgs/${orgId}/refs/resolve`,
690
708
  REFS_BACKLINKS: (orgId: string) => `${API_BASE}/orgs/${orgId}/refs/backlinks`,
709
+ REFS_GRAPH: (orgId: string) => `${API_BASE}/orgs/${orgId}/refs/graph`,
691
710
  REFS_CHECK: (orgId: string) => `${API_BASE}/orgs/${orgId}/refs/check`,
692
711
 
693
712
  // Platform config (agent-scoped, not org-scoped)