@parall/sdk 1.55.0 → 1.55.2

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/constants.ts CHANGED
@@ -604,6 +604,18 @@ export const ENDPOINTS = {
604
604
  PROJECTS: (orgId: string) => `${API_BASE}/orgs/${orgId}/projects`,
605
605
  PROJECT_TASK_SUMMARY: (orgId: string) => `${API_BASE}/orgs/${orgId}/projects/task-summary`,
606
606
  PROJECT: (orgId: string, projectId: string) => `${API_BASE}/orgs/${orgId}/projects/${projectId}`,
607
+ PROJECT_MEMBERS: (orgId: string, projectId: string) =>
608
+ `${API_BASE}/orgs/${orgId}/projects/${projectId}/members`,
609
+ PROJECT_READERS: (orgId: string, projectId: string) =>
610
+ `${API_BASE}/orgs/${orgId}/projects/${projectId}/readers`,
611
+ PROJECT_LIBRARY: (orgId: string) => `${API_BASE}/orgs/${orgId}/projects/library`,
612
+ PROJECT_JOIN: (orgId: string, projectId: string) =>
613
+ `${API_BASE}/orgs/${orgId}/projects/${projectId}/join`,
614
+ PROJECT_JOIN_REQUESTS: (orgId: string, projectId: string) =>
615
+ `${API_BASE}/orgs/${orgId}/projects/${projectId}/join-requests`,
616
+ // subject is a bare user ID or a bare team ID; both are path-segment safe.
617
+ PROJECT_MEMBER: (orgId: string, projectId: string, subject: string) =>
618
+ `${API_BASE}/orgs/${orgId}/projects/${projectId}/members/${subject}`,
607
619
 
608
620
  // Schedules (org-scoped, platform time trigger primitive)
609
621
  SCHEDULES: (orgId: string) => `${API_BASE}/orgs/${orgId}/schedules`,
@@ -925,6 +937,10 @@ export const ENDPOINTS = {
925
937
  // Per-profile egress proxy (hosted Cloud Profiles; manager-only, human-only).
926
938
  ORG_EDGE_PROFILE_PROXY: (orgId: string, edgeId: string, profileName: string) =>
927
939
  `/api/v1/orgs/${orgId}/edge/${edgeId}/profiles/${encodeURIComponent(profileName)}/proxy`,
940
+ // Per-profile one-shot cookie seed (hosted Cloud Profiles; manager-only,
941
+ // human-only, idle-only). Injected at the next cold start, then consumed.
942
+ ORG_EDGE_PROFILE_COOKIES: (orgId: string, edgeId: string, profileName: string) =>
943
+ `/api/v1/orgs/${orgId}/edge/${edgeId}/profiles/${encodeURIComponent(profileName)}/cookies`,
928
944
  ORG_EDGE_EXEC: (orgId: string) => `/api/v1/orgs/${orgId}/edge/exec`,
929
945
  // Cloud Edge live viewer command (V1b) — api-server, gated on cap:edge-viewer.
930
946
  // Same request/reply shape as the v2 browser-profile viewer, on the v3 edge
@@ -1020,6 +1036,7 @@ export const WS_EVENTS = {
1020
1036
  PONG: 'pong',
1021
1037
  WATCHING: 'watching',
1022
1038
  MESSAGE_NEW: 'message.new',
1039
+ /** @deprecated `message.patch` is retired and never published; kept for compile compatibility. */
1023
1040
  MESSAGE_PATCH: 'message.patch',
1024
1041
  MESSAGE_EDIT: 'message.edit',
1025
1042
  MESSAGE_DELETE: 'message.delete',
@@ -1,11 +1,16 @@
1
1
  import { ENDPOINTS } from './constants.js';
2
2
  import type {
3
+ AddProjectMemberRequest,
4
+ Approval,
3
5
  CreateProjectRequest,
4
6
  CreateTaskCommentRequest,
5
7
  CreateTaskRelationRequest,
6
8
  CreateTaskRequest,
7
9
  PaginatedResponse,
8
10
  Project,
11
+ ProjectJoinRequest,
12
+ ProjectLibraryEntry,
13
+ ProjectMember,
9
14
  ProjectTaskSummaryResponse,
10
15
  Task,
11
16
  TaskActivity,
@@ -15,6 +20,7 @@ import type {
15
20
  TaskSubtaskSummaryResponse,
16
21
  TaskWatcher,
17
22
  ThreadWatcher,
23
+ UpdateProjectMemberRequest,
18
24
  UpdateProjectRequest,
19
25
  UpdateTaskCommentRequest,
20
26
  UpdateTaskRequest,
@@ -246,4 +252,91 @@ export abstract class ProjectTaskClient {
246
252
  async deleteProject(orgId: string, projectId: string): Promise<void> {
247
253
  return this.request('DELETE', ENDPOINTS.PROJECT(orgId, projectId));
248
254
  }
255
+
256
+ /** Roster with display info; readable by anyone who can read the project. */
257
+ async getProjectMembers(orgId: string, projectId: string): Promise<ProjectMember[]> {
258
+ const res = await this.request<{ data: ProjectMember[] }>(
259
+ 'GET',
260
+ ENDPOINTS.PROJECT_MEMBERS(orgId, projectId),
261
+ );
262
+ return res.data;
263
+ }
264
+
265
+ /**
266
+ * User IDs who may read the project — the assignee-eligibility set. Every
267
+ * tier answers the roster expansion (direct, team-reached, org
268
+ * owners/admins); visibility only shapes admission, never reach.
269
+ */
270
+ async getProjectReaders(orgId: string, projectId: string): Promise<string[]> {
271
+ const res = await this.request<{ data: string[] }>(
272
+ 'GET',
273
+ ENDPOINTS.PROJECT_READERS(orgId, projectId),
274
+ );
275
+ return res.data;
276
+ }
277
+
278
+ /** The join library: every discoverable project (public + restricted, plus
279
+ * private for org owners/admins) with the caller's admission state. */
280
+ async getProjectLibrary(orgId: string): Promise<ProjectLibraryEntry[]> {
281
+ const res = await this.request<{ data: ProjectLibraryEntry[] }>(
282
+ 'GET',
283
+ ENDPOINTS.PROJECT_LIBRARY(orgId),
284
+ );
285
+ return res.data;
286
+ }
287
+
288
+ /** Public-tier self-admission: writes the caller's own member row.
289
+ * Other tiers answer `404 PROJECT_NOT_FOUND`; a duplicate answers
290
+ * `409 MEMBER_EXISTS`. */
291
+ async joinProject(orgId: string, projectId: string): Promise<ProjectMember> {
292
+ return this.request('POST', ENDPOINTS.PROJECT_JOIN(orgId, projectId));
293
+ }
294
+
295
+ /** Restricted-tier admission petition. A pending duplicate answers
296
+ * `409 REQUEST_EXISTS`; membership answers `409 MEMBER_EXISTS`. */
297
+ async createProjectJoinRequest(orgId: string, projectId: string): Promise<Approval> {
298
+ return this.request('POST', ENDPOINTS.PROJECT_JOIN_REQUESTS(orgId, projectId));
299
+ }
300
+
301
+ /** Pending petitions for one project — manager standing required. */
302
+ async listProjectJoinRequests(orgId: string, projectId: string): Promise<ProjectJoinRequest[]> {
303
+ const res = await this.request<{ data: ProjectJoinRequest[] }>(
304
+ 'GET',
305
+ ENDPOINTS.PROJECT_JOIN_REQUESTS(orgId, projectId),
306
+ );
307
+ return res.data;
308
+ }
309
+
310
+ /** Withdraws the caller's own pending petition. */
311
+ async cancelProjectJoinRequest(orgId: string, projectId: string): Promise<void> {
312
+ return this.request('DELETE', ENDPOINTS.PROJECT_JOIN_REQUESTS(orgId, projectId));
313
+ }
314
+
315
+ /**
316
+ * Adds one subject (user ID or team ID) to the roster. Manager-only,
317
+ * create-only: an existing row answers `409 MEMBER_EXISTS` — change roles
318
+ * through updateProjectMember instead.
319
+ */
320
+ async addProjectMember(
321
+ orgId: string,
322
+ projectId: string,
323
+ req: AddProjectMemberRequest,
324
+ ): Promise<ProjectMember> {
325
+ return this.request('POST', ENDPOINTS.PROJECT_MEMBERS(orgId, projectId), req);
326
+ }
327
+
328
+ /** Sets one roster entry's role. Demoting the last manager answers `400 LAST_MANAGER`. */
329
+ async updateProjectMember(
330
+ orgId: string,
331
+ projectId: string,
332
+ subject: string,
333
+ req: UpdateProjectMemberRequest,
334
+ ): Promise<ProjectMember> {
335
+ return this.request('PATCH', ENDPOINTS.PROJECT_MEMBER(orgId, projectId, subject), req);
336
+ }
337
+
338
+ /** Removes one roster entry. Removing the last manager answers `400 LAST_MANAGER`. */
339
+ async removeProjectMember(orgId: string, projectId: string, subject: string): Promise<void> {
340
+ return this.request('DELETE', ENDPOINTS.PROJECT_MEMBER(orgId, projectId, subject));
341
+ }
249
342
  }
package/src/subject.ts CHANGED
@@ -4,37 +4,32 @@ export type SubjectKind = 'wildcard' | 'user' | 'team';
4
4
 
5
5
  export interface ParsedSubject {
6
6
  kind: SubjectKind;
7
- /** Empty for wildcard, user ID for user, and slug without `@` for team. */
7
+ /** Empty for wildcard, user ID for user, and the full team ID for team. */
8
8
  value: string;
9
9
  }
10
10
 
11
- const TEAM_SLUG = /^[a-z0-9][a-z0-9-]*$/;
11
+ /**
12
+ * Team subjects are stored as bare team IDs (`team_...`), mirroring the
13
+ * server's `pkg/subject`: the ID prefix is what makes a token a team
14
+ * reference, so no marker syntax exists and a rename never invalidates a
15
+ * stored reference.
16
+ */
17
+ const TEAM_ID_PREFIX = 'team_';
12
18
 
13
- export function isValidTeamSlug(slug: string): boolean {
14
- return TEAM_SLUG.test(slug);
15
- }
16
-
17
- export function formatTeamSubject(slug: string): string {
18
- if (!isValidTeamSlug(slug)) {
19
- throw new Error(`Invalid team slug: ${slug}`);
20
- }
21
- return `@${slug}`;
19
+ export function isTeamSubject(token: string): boolean {
20
+ return token.startsWith(TEAM_ID_PREFIX);
22
21
  }
23
22
 
24
23
  export function parseSubjectToken(token: string): ParsedSubject {
25
24
  if (token === '*') {
26
25
  return { kind: 'wildcard', value: '' };
27
26
  }
28
- if (token.startsWith('@')) {
29
- const slug = token.slice(1);
30
- if (!isValidTeamSlug(slug)) {
31
- throw new Error(`Invalid team subject: ${token}`);
32
- }
33
- return { kind: 'team', value: slug };
34
- }
35
27
  if (!token) {
36
28
  throw new Error('Subject token is empty');
37
29
  }
30
+ if (isTeamSubject(token)) {
31
+ return { kind: 'team', value: token };
32
+ }
38
33
  return { kind: 'user', value: token };
39
34
  }
40
35
 
@@ -45,22 +40,18 @@ export function parseSubjectToken(token: string): ParsedSubject {
45
40
  export const MAX_TEAM_SUBJECTS_PER_WRITE = 200;
46
41
 
47
42
  /**
48
- * Returns unique team slugs in first-seen order for batched write validation.
49
- *
50
- * Syntactically invalid slugs are included rather than skipped, matching the
51
- * server's `subject.TeamSlugs`: a mistyped `@Finance` must reach the existence
52
- * check and be reported missing, so the write fails with a legible error
53
- * instead of storing a token that matches nobody.
43
+ * Returns unique team IDs in first-seen order for batched write validation.
44
+ * An ID that names no team comes back from the server's existence check
45
+ * reported missing, so the write fails with a legible error instead of
46
+ * storing a token that matches nobody.
54
47
  */
55
- export function teamSlugsFromSubjects(subjects: readonly string[]): string[] {
48
+ export function teamIdsFromSubjects(subjects: readonly string[]): string[] {
56
49
  const seen = new Set<string>();
57
- const slugs: string[] = [];
50
+ const ids: string[] = [];
58
51
  for (const token of subjects) {
59
- if (!token.startsWith('@')) continue;
60
- const slug = token.slice(1);
61
- if (!slug || seen.has(slug)) continue;
62
- seen.add(slug);
63
- slugs.push(slug);
52
+ if (!isTeamSubject(token) || seen.has(token)) continue;
53
+ seen.add(token);
54
+ ids.push(token);
64
55
  }
65
- return slugs;
56
+ return ids;
66
57
  }
package/src/types.ts CHANGED
@@ -486,14 +486,15 @@ export interface UpdateAgentInstructionsRequest {
486
486
  expected_version: number;
487
487
  }
488
488
 
489
- // Team — a named group of org members, referenced in wiki ACLs as `@slug`.
489
+ // Team — a named group of org members, referenced in ACL rosters by its ID.
490
490
  export type TeamRole = 'member' | 'manager';
491
491
 
492
492
  export interface Team {
493
493
  id: string;
494
494
  org_id: string;
495
495
  name: string;
496
- slug: string;
496
+ /** @deprecated Removed since migration 176 — teams have no slug anymore. */
497
+ slug?: string;
497
498
  created_by: string;
498
499
  created_at: string;
499
500
  updated_at: string;
@@ -526,13 +527,12 @@ export interface TeamMember {
526
527
 
527
528
  export interface CreateTeamRequest {
528
529
  name: string;
529
- slug: string;
530
+ /** @deprecated Accepted and ignored by the server since migration 176. */
531
+ slug?: string;
530
532
  }
531
533
 
532
534
  export interface UpdateTeamRequest {
533
535
  name: string;
534
- /** Team slugs are immutable because wiki ACL rows store them as @slug. */
535
- slug?: never;
536
536
  }
537
537
 
538
538
  export interface AddTeamMemberRequest {
@@ -1028,12 +1028,6 @@ export interface DirectMessageResponse {
1028
1028
  message: Message;
1029
1029
  }
1030
1030
 
1031
- export interface JsonPatchOp {
1032
- op: 'replace' | 'add';
1033
- path: string;
1034
- value: unknown;
1035
- }
1036
-
1037
1031
  export interface CreateAgentRequest {
1038
1032
  display_name: string;
1039
1033
  agent_provider?: string;
@@ -1559,7 +1553,8 @@ export interface Task {
1559
1553
  assignee_id: string | null;
1560
1554
  creator_id: string;
1561
1555
  parent_id: string | null;
1562
- project_id: string | null;
1556
+ /** Every Task belongs to exactly one Project; immutable after creation. */
1557
+ project_id: string;
1563
1558
  seq_number: number | null;
1564
1559
  identifier: string | null;
1565
1560
  sort_order: number;
@@ -1583,7 +1578,7 @@ export interface Task {
1583
1578
  deleted_at?: string | null;
1584
1579
  /**
1585
1580
  * Per-viewer: whether the requesting user may manage OTHER members' comment
1586
- * subscriptions for this task (creator / assignee / project lead / org
1581
+ * subscriptions for this task (creator / assignee / project manager / org
1587
1582
  * owner-admin). Populated on single-task GET; undefined elsewhere. Read this to
1588
1583
  * gate subscriber-management UI — do not re-derive the rule client-side.
1589
1584
  */
@@ -1659,7 +1654,13 @@ export interface CreateTaskRequest {
1659
1654
  priority?: TaskPriority;
1660
1655
  assignee_id?: string;
1661
1656
  parent_id?: string;
1662
- project_id?: string;
1657
+ /**
1658
+ * Required: every Task is created inside a Project (missing → 400
1659
+ * MISSING_FIELDS with `error.details.available_projects`; archived project →
1660
+ * 400 PROJECT_ARCHIVED). A parent task's subtasks must use the parent's
1661
+ * project (400 INVALID_PARENT otherwise). Immutable after creation.
1662
+ */
1663
+ project_id: string;
1663
1664
  source_chat_id?: string;
1664
1665
  sort_order?: number;
1665
1666
  /** Planned start date, YYYY-MM-DD. Must be <= due_date when both are set. */
@@ -1676,7 +1677,9 @@ export interface UpdateTaskRequest {
1676
1677
  priority?: TaskPriority;
1677
1678
  assignee_id?: string | null;
1678
1679
  parent_id?: string | null;
1679
- project_id?: string | null;
1680
+ // project_id is deliberately absent: a Task's Project is immutable after
1681
+ // creation (server answers 400 PROJECT_IMMUTABLE; resubmitting the current
1682
+ // value is an idempotent no-op).
1680
1683
  sort_order?: number;
1681
1684
  /**
1682
1685
  * Ordering intent resolved server-side in the update transaction:
@@ -1723,36 +1726,123 @@ export interface CreateTaskRelationRequest {
1723
1726
 
1724
1727
  export type ProjectStatus = 'active' | 'paused' | 'completed' | 'archived';
1725
1728
 
1729
+ /** Visibility only decides how someone becomes a member (self-join /
1730
+ * request+approval / invite-only); membership rights are identical on every
1731
+ * tier, and reads are always roster-scoped. */
1732
+ export type ProjectVisibility = 'public' | 'restricted' | 'private';
1733
+
1734
+ /** A role held on a project roster. Managers administer the project; members
1735
+ * only reach it. Org owners and admins are implicit managers everywhere, which
1736
+ * is why a project can never become unmanageable. */
1737
+ export type ProjectRole = 'member' | 'manager';
1738
+
1726
1739
  export interface Project {
1727
1740
  id: string;
1728
1741
  org_id: string;
1729
1742
  name: string;
1730
1743
  key: string;
1731
1744
  description: string | null;
1732
- lead_id: string | null;
1733
1745
  status: ProjectStatus;
1746
+ visibility: ProjectVisibility;
1734
1747
  color: string | null;
1735
1748
  sort_order: number;
1736
1749
  created_at: string;
1737
1750
  updated_at: string;
1751
+ /** The requesting user's own role, computed per request rather than stored.
1752
+ * Absent means they hold no membership (direct or team-derived). */
1753
+ my_role?: ProjectRole;
1754
+ /** True when the requesting user holds their own roster row — the fact
1755
+ * Leave acts on, as opposed to team-derived or org-admin standing. */
1756
+ my_direct_member?: boolean;
1757
+ /**
1758
+ * @deprecated Project authority now comes from the project roster. Retained
1759
+ * only for source compatibility during the pre-GA migration window; current
1760
+ * API responses omit it.
1761
+ */
1762
+ lead_id?: string | null;
1763
+ }
1764
+
1765
+ /** One join-library row: the minimal metadata a non-member may see about a
1766
+ * discoverable project, plus the caller's own admission state. */
1767
+ export interface ProjectLibraryEntry {
1768
+ id: string;
1769
+ name: string;
1770
+ key: string;
1771
+ color?: string | null;
1772
+ visibility: ProjectVisibility;
1773
+ joined: boolean;
1774
+ requested: boolean;
1775
+ }
1776
+
1777
+ /** One pending join request, as the Collaborators pending tab renders it. */
1778
+ export interface ProjectJoinRequest {
1779
+ id: string;
1780
+ requester_id: string;
1781
+ created_at: string;
1782
+ requester?: User;
1783
+ }
1784
+
1785
+ /** One roster entry. `subject` is a bare user ID or a bare team ID; exactly one
1786
+ * of `user` / `team` is filled in for display, according to which kind it is. */
1787
+ export interface ProjectMember {
1788
+ project_id: string;
1789
+ subject: string;
1790
+ role: ProjectRole;
1791
+ added_by?: string;
1792
+ added_at: string;
1793
+ user?: User;
1794
+ team?: Team;
1795
+ }
1796
+
1797
+ /**
1798
+ * Adds one subject to a project's roster. Create-only: an existing row answers
1799
+ * `409 MEMBER_EXISTS` rather than being mutated — role changes go through the
1800
+ * member role endpoint. A subject naming nobody reachable answers
1801
+ * `400 UNKNOWN_SUBJECT`. Member rows are storable on every tier.
1802
+ */
1803
+ export interface AddProjectMemberRequest {
1804
+ /** A bare user ID, or a bare team ID (`team_...`) for a team. */
1805
+ subject: string;
1806
+ /** Defaults to `member` when omitted. */
1807
+ role?: ProjectRole;
1808
+ }
1809
+
1810
+ /**
1811
+ * Changes one roster entry's role. Demoting the last manager answers
1812
+ * `400 LAST_MANAGER`; a concurrent roster change answers `409 CONFLICT`.
1813
+ */
1814
+ export interface UpdateProjectMemberRequest {
1815
+ role: ProjectRole;
1738
1816
  }
1739
1817
 
1740
1818
  export interface CreateProjectRequest {
1741
1819
  name: string;
1742
1820
  key?: string;
1743
1821
  description?: string;
1744
- lead_id?: string;
1822
+ /** Defaults to `public` when omitted. The creator is always written as the
1823
+ * first manager, which is what naming yourself lead used to mean. */
1824
+ visibility?: ProjectVisibility;
1745
1825
  color?: string;
1826
+ /**
1827
+ * @deprecated Use the project roster after creation. Retained only for
1828
+ * source compatibility; the current API ignores this retired field.
1829
+ */
1830
+ lead_id?: string;
1746
1831
  }
1747
1832
 
1748
1833
  export interface UpdateProjectRequest {
1749
1834
  name?: string;
1750
1835
  key?: string;
1751
1836
  description?: string | null;
1752
- lead_id?: string | null;
1753
1837
  status?: ProjectStatus;
1838
+ visibility?: ProjectVisibility;
1754
1839
  color?: string | null;
1755
1840
  sort_order?: number;
1841
+ /**
1842
+ * @deprecated Use the project roster. Retained only for source
1843
+ * compatibility; the current API ignores this retired field.
1844
+ */
1845
+ lead_id?: string | null;
1756
1846
  }
1757
1847
 
1758
1848
  export interface ProjectTaskStatusCounts {
@@ -2623,6 +2713,17 @@ export interface MessageNewData {
2623
2713
  attachments?: Attachment[];
2624
2714
  }
2625
2715
 
2716
+ /** @deprecated The `message.patch` event is retired — the server no longer
2717
+ * publishes it (content updates arrive as full-content `message.edit`). Kept
2718
+ * only so existing consumers keep compiling through the deprecation window;
2719
+ * a handler typed with this will never fire. */
2720
+ export interface JsonPatchOp {
2721
+ op: 'replace' | 'add';
2722
+ path: string;
2723
+ value: unknown;
2724
+ }
2725
+
2726
+ /** @deprecated See {@link JsonPatchOp} — `message.patch` is never published. */
2626
2727
  export interface MessagePatchData {
2627
2728
  message_id: string;
2628
2729
  chat_id: string;
@@ -2630,11 +2731,20 @@ export interface MessagePatchData {
2630
2731
  ops: JsonPatchOp[];
2631
2732
  }
2632
2733
 
2734
+ /**
2735
+ * Unified message-content-update event: every persisted content write (author
2736
+ * edit, server link-preview write-back) publishes it with the full content and
2737
+ * post-write version. Apply only when `version` is greater than the local one;
2738
+ * the "(edited)" marker keys off `edited_at` alone — a preview write-back
2739
+ * never creates or changes edited state (it carries the pre-existing value,
2740
+ * absent when the message was never author-edited).
2741
+ */
2633
2742
  export interface MessageEditData {
2634
2743
  message_id: string;
2635
2744
  chat_id: string;
2636
2745
  content: MessageContent;
2637
- edited_at: string;
2746
+ version: number;
2747
+ edited_at?: string;
2638
2748
  }
2639
2749
 
2640
2750
  export interface MessageDeleteData {
@@ -3700,6 +3810,7 @@ export interface NotificationAlertData {
3700
3810
  export type WsEventMap = {
3701
3811
  hello: HelloData;
3702
3812
  'message.new': MessageNewData;
3813
+ /** @deprecated Never published anymore — kept so existing `ws.on('message.patch', …)` registrations keep compiling. */
3703
3814
  'message.patch': MessagePatchData;
3704
3815
  'message.edit': MessageEditData;
3705
3816
  'message.delete': MessageDeleteData;
@@ -3898,6 +4009,14 @@ export interface ResolvedRef {
3898
4009
  created_at?: string;
3899
4010
  attachments?: Attachment[];
3900
4011
 
4012
+ // Partial-quote fields (msg_ refs with a ?v= + #t= anchor): the exact text
4013
+ // sliced from the body that was live at the anchor's content version.
4014
+ // quote_from_edited marks that the body was edited after the quote was
4015
+ // taken. Absent on bounds/validity failure — degrade to the whole-message
4016
+ // reference.
4017
+ quoted_text?: string;
4018
+ quote_from_edited?: boolean;
4019
+
3901
4020
  // Range fields (cht_ + range anchor)
3902
4021
  range_messages?: Array<{
3903
4022
  id: string;
@@ -4894,6 +5013,54 @@ export interface SetEdgeProfileProxyRequest {
4894
5013
  password?: string;
4895
5014
  }
4896
5015
 
5016
+ /**
5017
+ * One normalized cookie in a hosted-profile cookie-seed import — the exact shape
5018
+ * the pod-side `session.cookies.set()` consumes (mirrors the desktop
5019
+ * cookie-import output). Web parses the manager's paste (a raw `Cookie` header +
5020
+ * target domain, or a Cookie-Editor JSON export) into an array of these before
5021
+ * the PUT. Values are write-only; the sanitized status never echoes them.
5022
+ */
5023
+ export interface EdgeCookie {
5024
+ url: string;
5025
+ name: string;
5026
+ value: string;
5027
+ domain: string;
5028
+ path: string;
5029
+ secure: boolean;
5030
+ httpOnly: boolean;
5031
+ /** Unix epoch seconds; null for a session cookie (no persisted expiry). */
5032
+ expirationDate: number | null;
5033
+ sameSite: 'no_restriction' | 'lax' | 'strict' | null;
5034
+ }
5035
+
5036
+ /**
5037
+ * PUT body for a hosted profile's one-shot cookie seed: the full normalized
5038
+ * cookie array. Injected at the next cold start and consumed once. Clearing is
5039
+ * DELETE, never an empty PUT.
5040
+ */
5041
+ export interface SetEdgeCookieSeedRequest {
5042
+ cookies: EdgeCookie[];
5043
+ }
5044
+
5045
+ /**
5046
+ * Sanitized cookie-seed status (hosted Cloud Profiles) — the GET response and
5047
+ * the PUT/DELETE result. NEVER carries cookie values: only the count and the
5048
+ * distinct target domains are exposed.
5049
+ */
5050
+ export interface EdgeCookieSeedStatus {
5051
+ configured: boolean;
5052
+ cookie_count: number;
5053
+ domains: string[];
5054
+ /** Opaque compare-and-swap token for conditional PUT/DELETE (`cookieseed-<version>` ETag). */
5055
+ version: string;
5056
+ /** Authoritative mutation gate; false whenever a non-released lease exists. */
5057
+ can_mutate: boolean;
5058
+ /** Actual lease state blocking a mutation (never inferred from device status). */
5059
+ lease_status?: 'pending' | 'assigned' | 'active' | 'releasing' | 'repair';
5060
+ /** Suggested delay before re-reading authoritative state. */
5061
+ retry_after_seconds?: number | null;
5062
+ }
5063
+
4897
5064
  export interface ClipConnection {
4898
5065
  id: string;
4899
5066
  clip_id: string;
@@ -4916,7 +5083,18 @@ export interface ClipConnection {
4916
5083
  * would make `putClipMCPConfig({auth_type: 'oauth'})` type-legal while its
4917
5084
  * declared return type is wrong. Reads use MCPConfigAuthType.
4918
5085
  */
4919
- export type MCPAuthType = 'none' | 'bearer' | 'api_key';
5086
+ export type MCPAuthType = 'none' | 'bearer' | 'api_key' | 'basic';
5087
+
5088
+ /**
5089
+ * One declared credential input of an `api_key` clip (manifest
5090
+ * `mcp.auth_headers`): the header the value is delivered in and, for
5091
+ * `Authorization`, the `Bearer` scheme prefix. `bearer` remains the legacy
5092
+ * alias for the single `Authorization: Bearer` shape.
5093
+ */
5094
+ export interface MCPAuthHeaderSlot {
5095
+ name: string;
5096
+ scheme?: 'Bearer';
5097
+ }
4920
5098
 
4921
5099
  /**
4922
5100
  * MCP clip auth modes a config can REPORT. A config authorized through the
@@ -5044,9 +5222,20 @@ export interface MCPConfigResponse {
5044
5222
  * persists nothing.
5045
5223
  */
5046
5224
  export interface MCPConfigPutRequest {
5225
+ /**
5226
+ * Required on the wire, but when the clip's manifest declares the server
5227
+ * an empty string means "use the declaration" — the server fills it in and
5228
+ * refuses a contradicting value. (The Console sends '' for declared clips.)
5229
+ */
5047
5230
  server_url: string;
5048
5231
  auth_type: MCPAuthType;
5049
5232
  credential?: string;
5233
+ /**
5234
+ * Slot-aligned multi-value form: one value per declared `auth_headers`
5235
+ * slot, or `[username, password]` for `basic`. Mutually exclusive with
5236
+ * `credential`.
5237
+ */
5238
+ credentials?: string[];
5050
5239
  }
5051
5240
 
5052
5241
  /**
@@ -5065,6 +5254,8 @@ export interface MCPConfigCreateRequest {
5065
5254
  server_url?: string;
5066
5255
  auth_type: MCPAuthType | 'oauth';
5067
5256
  credential?: string;
5257
+ /** See MCPConfigPutRequest.credentials. */
5258
+ credentials?: string[];
5068
5259
  alias?: string;
5069
5260
  oauth_client?: MCPOAuthClientParams;
5070
5261
  }
@@ -5175,13 +5366,33 @@ export interface PublishRegistryClipRequest {
5175
5366
  files: Record<string, string>;
5176
5367
  }
5177
5368
 
5369
+ /**
5370
+ * One granted agent's entry under `selected_agents` (ADR-019).
5371
+ *
5372
+ * - `connection_scope: 'all'` — the grant covers every connection of the clip
5373
+ * (what a legacy `agent_ids` write means). `connection_ids` must be [].
5374
+ * - `connection_scope: 'selected'` — the grant admits only the connections in
5375
+ * `connection_ids` (non-empty on write; a grant can still READ back with []
5376
+ * when its granted connections were deleted, and then admits nothing).
5377
+ */
5378
+ export interface ClipAgentExecGrant {
5379
+ agent_id: string;
5380
+ connection_scope: 'all' | 'selected';
5381
+ /** Granted connection ids (`ccn_…`) under `selected`, sorted; [] under `all`. */
5382
+ connection_ids: string[];
5383
+ }
5384
+
5178
5385
  /**
5179
5386
  * A clip's per-org agent exec access (`GET/PUT /orgs/{orgId}/clips/{clipId}/exec-access`).
5180
5387
  *
5181
5388
  * - `all_agents` (default): every org agent may exec the clip.
5182
- * - `selected_agents`: only the agents in `agent_ids` may exec; an empty list
5183
- * blocks every agent. Denied agents also stop seeing the clip in
5184
- * `clips/installed`, and exec answers `403 CLIP_AGENT_NOT_ALLOWED`.
5389
+ * - `selected_agents`: only the agents in `grants` may exec, each through the
5390
+ * connections its `connection_scope` admits; an empty list blocks every
5391
+ * agent. Denied agents also stop seeing the clip in `clips/installed`; exec
5392
+ * answers `403 CLIP_AGENT_NOT_ALLOWED` (no grant at all) or
5393
+ * `403 CLIP_AGENT_CONNECTION_NOT_ALLOWED` (grant does not cover the named
5394
+ * connection). Agents must always exec through a connection — a
5395
+ * connectionless agent exec answers `400 AGENT_CONNECTION_REQUIRED`.
5185
5396
  *
5186
5397
  * Decided by the CALLING org (an installer restricts its own agents, never the
5187
5398
  * publisher's). Humans are never restricted; the PUT is human-only (an agent
@@ -5189,8 +5400,24 @@ export interface PublishRegistryClipRequest {
5189
5400
  */
5190
5401
  export interface ClipAgentExecAccess {
5191
5402
  mode: 'all_agents' | 'selected_agents';
5192
- /** Granted agent user ids under `selected_agents`; always [] under `all_agents`. */
5403
+ /**
5404
+ * Granted agent user ids under `selected_agents` — the flat projection of
5405
+ * `grants`, kept for older readers; always [] under `all_agents`.
5406
+ */
5193
5407
  agent_ids: string[];
5408
+ /** Per-agent connection scope under `selected_agents`, sorted by agent id. */
5409
+ grants: ClipAgentExecGrant[];
5410
+ }
5411
+
5412
+ /**
5413
+ * The PUT body for a clip's agent exec access. Two shapes, one semantic
5414
+ * space — pass EITHER `agent_ids` (legacy flat form; every listed agent gets
5415
+ * `connection_scope: 'all'`) OR `grants` (connection-scoped form), never both.
5416
+ */
5417
+ export interface ClipAgentExecAccessUpdate {
5418
+ mode: 'all_agents' | 'selected_agents';
5419
+ agent_ids?: string[];
5420
+ grants?: ClipAgentExecGrant[];
5194
5421
  }
5195
5422
 
5196
5423
  /**
@@ -5205,6 +5432,10 @@ export interface ClipAgentExecAccess {
5205
5432
  * here is refused (`400 HOSTED_CONNECTION_REQUIRED`).
5206
5433
  * - neither: legacy BYOC fallback — resolves only to the caller's OWN online
5207
5434
  * desktop device, never to a hosted one.
5435
+ *
5436
+ * AGENT principals must always pass `connection` (ADR-019): the device paths
5437
+ * answer `400 AGENT_CONNECTION_REQUIRED`, and a connection outside the agent's
5438
+ * granted scope answers `403 CLIP_AGENT_CONNECTION_NOT_ALLOWED`.
5208
5439
  */
5209
5440
  export interface ExecEdgeClipRequest {
5210
5441
  /**