@parall/sdk 1.37.0 → 1.39.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,5 +1,11 @@
1
1
  import { ENDPOINTS, WIKI_BASE } from './constants.js';
2
2
  import type {
3
+ ClaimDispatchRequest,
4
+ ClaimDispatchResponse,
5
+ SteerDispatchRequest,
6
+ SteerDispatchResponse,
7
+ CompleteDispatchRequest,
8
+ CompleteDispatchResult,
3
9
  AuthTokens,
4
10
  AvatarUploadResponse,
5
11
  RegisterRequest,
@@ -199,6 +205,10 @@ import type {
199
205
  BrowserViewerCommandRequest,
200
206
  BrowserViewerCommandResponse,
201
207
  GrantBrowserProfileConsentRequest,
208
+ EdgeDevice,
209
+ EdgeBrowserProfile,
210
+ ClipConnection,
211
+ EdgeOnboardingStatus,
202
212
  } from './types.js';
203
213
 
204
214
  export interface ParallClientOptions {
@@ -374,7 +384,13 @@ export class ParallClient {
374
384
  body?: unknown,
375
385
  query?: Record<string, string | number | boolean | undefined>,
376
386
  retried = false,
377
- opts?: { timeoutMs?: number; signal?: AbortSignal; keepalive?: boolean },
387
+ opts?: {
388
+ timeoutMs?: number;
389
+ signal?: AbortSignal;
390
+ keepalive?: boolean;
391
+ /** Observes the final HTTP status of a successful request (e.g. 200-idempotent-replay vs 201-created). */
392
+ onStatus?: (status: number) => void;
393
+ },
378
394
  ): Promise<T> {
379
395
  // Proactive refresh: block until token is fresh (no-op if still valid)
380
396
  if (!retried) {
@@ -441,6 +457,7 @@ export class ParallClient {
441
457
  throw buildApiError(res, rawErrorBody);
442
458
  }
443
459
 
460
+ opts?.onStatus?.(res.status);
444
461
  if (res.status === 204) return undefined as T;
445
462
  // 202 Accepted may have an empty body (e.g., async restart) or a JSON body.
446
463
  if (res.status === 202) {
@@ -969,6 +986,33 @@ export class ParallClient {
969
986
  return this.request('POST', ENDPOINTS.CHAT_MESSAGES(orgId, chatId), req);
970
987
  }
971
988
 
989
+ /**
990
+ * sendMessage variant that also reports whether the server answered with an
991
+ * idempotent replay (HTTP 200 — the message already existed for this
992
+ * idempotency/effect key) instead of a fresh create (201). Used by the CLI
993
+ * to surface "already sent by a previous run (deduplicated)".
994
+ */
995
+ async sendMessageDetailed(
996
+ orgId: string,
997
+ chatId: string,
998
+ req: SendMessageRequest,
999
+ ): Promise<{ message: Message; deduplicated: boolean }> {
1000
+ let status = 0;
1001
+ const message = await this.request<Message>(
1002
+ 'POST',
1003
+ ENDPOINTS.CHAT_MESSAGES(orgId, chatId),
1004
+ req,
1005
+ undefined,
1006
+ false,
1007
+ {
1008
+ onStatus: (s) => {
1009
+ status = s;
1010
+ },
1011
+ },
1012
+ );
1013
+ return { message, deduplicated: status === 200 };
1014
+ }
1015
+
972
1016
  async getMessages(
973
1017
  orgId: string,
974
1018
  chatId: string,
@@ -1821,6 +1865,41 @@ export class ParallClient {
1821
1865
  return this.request('POST', ENDPOINTS.DISPATCH_EXPIRE(orgId), undefined, params);
1822
1866
  }
1823
1867
 
1868
+ /**
1869
+ * Claim a dispatch lane (explicit consume endpoint). Occupies the
1870
+ * (agent, target, thread) lane and folds claimable WorkItems into it;
1871
+ * `claimed: false` means a healthy incumbent holds the lane.
1872
+ */
1873
+ async claimDispatch(orgId: string, req: ClaimDispatchRequest): Promise<ClaimDispatchResponse> {
1874
+ return this.request('POST', ENDPOINTS.DISPATCH_CLAIM(orgId), req);
1875
+ }
1876
+
1877
+ /** Fold a pending same-target WorkItem into a live lane (409 STALE_LANE when dethroned). */
1878
+ async steerDispatch(orgId: string, req: SteerDispatchRequest): Promise<SteerDispatchResponse> {
1879
+ return this.request('POST', ENDPOINTS.DISPATCH_STEER(orgId), req);
1880
+ }
1881
+
1882
+ /** End a turn: no_action sweep of the lane's members + lane release + re-drive check. */
1883
+ async completeDispatch(
1884
+ orgId: string,
1885
+ req: CompleteDispatchRequest,
1886
+ ): Promise<CompleteDispatchResult> {
1887
+ return this.request('POST', ENDPOINTS.DISPATCH_COMPLETE(orgId), req);
1888
+ }
1889
+
1890
+ /** Release a lane on graceful shutdown — members return to pending immediately. */
1891
+ async releaseDispatchLane(orgId: string, lane: string): Promise<void> {
1892
+ return this.request('POST', ENDPOINTS.DISPATCH_RELEASE(orgId), { lane });
1893
+ }
1894
+
1895
+ /** Renew a live lane's lease (long-turn keepalive). 409 STALE_LANE when dethroned. */
1896
+ async heartbeatDispatchLane(
1897
+ orgId: string,
1898
+ req: { lane: string; target_uri: string; thread_root_id?: string },
1899
+ ): Promise<{ lease_until?: string }> {
1900
+ return this.request('POST', ENDPOINTS.DISPATCH_HEARTBEAT(orgId), req);
1901
+ }
1902
+
1824
1903
  async getDispatchByMessages(
1825
1904
  orgId: string,
1826
1905
  chatId: string,
@@ -3186,6 +3265,44 @@ export class ParallClient {
3186
3265
  const resp = await this.request<{ data: RegistryClipInfo[] }>('GET', url);
3187
3266
  return resp.data;
3188
3267
  }
3268
+
3269
+ // ---- Edge devices ----
3270
+
3271
+ async listEdgeDevices(orgId: string): Promise<{ data: EdgeDevice[] }> {
3272
+ return this.request('GET', ENDPOINTS.ORG_EDGE_DEVICES(orgId));
3273
+ }
3274
+
3275
+ async getEdgeOnboarding(orgId: string): Promise<EdgeOnboardingStatus> {
3276
+ return this.request('GET', ENDPOINTS.ORG_EDGE_ONBOARDING(orgId));
3277
+ }
3278
+
3279
+ async listEdgeProfiles(orgId: string, edgeId: string): Promise<EdgeBrowserProfile[]> {
3280
+ return this.request('GET', ENDPOINTS.ORG_EDGE_PROFILES(orgId, edgeId));
3281
+ }
3282
+
3283
+ // ---- Clip connections ----
3284
+
3285
+ async listClipConnections(orgId: string, clipId: string): Promise<{ data: ClipConnection[] }> {
3286
+ return this.request('GET', ENDPOINTS.CLIP_CONNECTIONS(orgId, clipId));
3287
+ }
3288
+
3289
+ async createClipConnection(
3290
+ orgId: string,
3291
+ clipId: string,
3292
+ input: {
3293
+ device_id?: string;
3294
+ profile?: string;
3295
+ mcp_config_id?: string;
3296
+ alias?: string;
3297
+ is_default?: boolean;
3298
+ },
3299
+ ): Promise<ClipConnection> {
3300
+ return this.request('POST', ENDPOINTS.CLIP_CONNECTIONS(orgId, clipId), input);
3301
+ }
3302
+
3303
+ async deleteClipConnection(orgId: string, connId: string): Promise<void> {
3304
+ return this.request('DELETE', ENDPOINTS.CLIP_CONNECTION(orgId, connId));
3305
+ }
3189
3306
  }
3190
3307
 
3191
3308
  function normalizeWikiChangeset(changeset: WikiChangeset): WikiChangeset {
package/src/constants.ts CHANGED
@@ -749,6 +749,11 @@ export const ENDPOINTS = {
749
749
  DISPATCH_ACK_BY_ID: (orgId: string, id: string) => `${API_BASE}/orgs/${orgId}/dispatch/${id}/ack`,
750
750
  DISPATCH_EXPIRE: (orgId: string) => `${API_BASE}/orgs/${orgId}/dispatch/expire`,
751
751
  DISPATCH_BY_MESSAGES: (orgId: string) => `${API_BASE}/orgs/${orgId}/dispatch/by-messages`,
752
+ DISPATCH_CLAIM: (orgId: string) => `${API_BASE}/orgs/${orgId}/dispatch/claim`,
753
+ DISPATCH_STEER: (orgId: string) => `${API_BASE}/orgs/${orgId}/dispatch/steer`,
754
+ DISPATCH_COMPLETE: (orgId: string) => `${API_BASE}/orgs/${orgId}/dispatch/complete`,
755
+ DISPATCH_RELEASE: (orgId: string) => `${API_BASE}/orgs/${orgId}/dispatch/release`,
756
+ DISPATCH_HEARTBEAT: (orgId: string) => `${API_BASE}/orgs/${orgId}/dispatch/heartbeat`,
752
757
 
753
758
  // Unread
754
759
  UNREAD: `${API_BASE}/me/unread`,
@@ -838,6 +843,16 @@ export const ENDPOINTS = {
838
843
 
839
844
  // Clip registry (global, served by clip-service → Pinix Hub proxy)
840
845
  CLIP_REGISTRY: () => `${CLIP_BASE}/registry/clips`,
846
+
847
+ // Edge device endpoints
848
+ ORG_EDGE_DEVICES: (orgId: string) => `/api/v1/orgs/${orgId}/edge/devices`,
849
+ ORG_EDGE_ONBOARDING: (orgId: string) => `/api/v1/orgs/${orgId}/edge/onboarding`,
850
+ ORG_EDGE_PROFILES: (orgId: string, edgeId: string) =>
851
+ `/api/v1/orgs/${orgId}/edge/${edgeId}/profiles`,
852
+ CLIP_CONNECTIONS: (orgId: string, clipId: string) =>
853
+ `/api/v1/orgs/${orgId}/clip-registry/${clipId}/connections`,
854
+ CLIP_CONNECTION: (orgId: string, connId: string) =>
855
+ `/api/v1/orgs/${orgId}/clip-connections/${connId}`,
841
856
  } as const;
842
857
 
843
858
  /**
@@ -934,6 +949,7 @@ export const WS_EVENTS = {
934
949
  READ_POSITION_UPDATED: 'read_position.updated',
935
950
  DISPATCH_NEW: 'dispatch.new',
936
951
  DISPATCH_RECEIVED: 'dispatch.received',
952
+ DISPATCH_RESOLVED: 'dispatch.resolved',
937
953
  SCHEDULE_CREATED: 'schedule.created',
938
954
  SCHEDULE_UPDATED: 'schedule.updated',
939
955
  SCHEDULE_DELETED: 'schedule.deleted',
package/src/types.ts CHANGED
@@ -583,6 +583,13 @@ export interface SendMessageRequest {
583
583
  agent_step_id?: string;
584
584
  agent_session_id?: string;
585
585
  hints?: MessageHints;
586
+ /**
587
+ * Binds this send to a claimed dispatch lane (agent senders only). Every
588
+ * lane-bound write passes the server's lane incumbency check; when
589
+ * idempotency_key carries a dispatch effect key ("reply:<dsp_id>") the
590
+ * write is arbitrated by the scoped dispatch effects ledger.
591
+ */
592
+ dispatch_lane?: string;
586
593
  }
587
594
 
588
595
  export interface SendDirectMessageRequest {
@@ -1198,6 +1205,17 @@ export interface UpdateTaskRequest {
1198
1205
  parent_id?: string | null;
1199
1206
  project_id?: string | null;
1200
1207
  sort_order?: number;
1208
+ /**
1209
+ * Dispatch lane binding (agent senders during a typed dispatch turn).
1210
+ * dispatch_lane + dispatch_event_id bind the update to the claimed typed
1211
+ * lane (incumbency-checked); dispatch_effect_key — exactly
1212
+ * "task_update:<dispatch_event_id>" — additionally commits the update as
1213
+ * the dispatch's idempotent Effect and resolves the WorkItem in the same
1214
+ * transaction (first update only).
1215
+ */
1216
+ dispatch_lane?: string;
1217
+ dispatch_event_id?: string;
1218
+ dispatch_effect_key?: string;
1201
1219
  }
1202
1220
 
1203
1221
  export interface CreateTaskRelationRequest {
@@ -2181,7 +2199,15 @@ export interface UpdateTaskCommentRequest {
2181
2199
  export type TaskCreatedData = Task;
2182
2200
  export type TaskUpdatedData = Task;
2183
2201
  /** Sent to user:{agentID} channel when a task is assigned to an agent. Data is the full Task. */
2184
- export type TaskAssignedData = Task;
2202
+ export type TaskAssignedData = Task & {
2203
+ /**
2204
+ * Exact typed WorkItem this assignment enqueued. The runtime claims and
2205
+ * acks by it — never by the shared (task_activity, task_id) source tuple,
2206
+ * which sibling task_update WorkItems from the same PATCH may also carry.
2207
+ * Absent from older servers (fallback: claim by source).
2208
+ */
2209
+ dispatch_event_id?: string | null;
2210
+ };
2185
2211
  export interface TaskDeletedData {
2186
2212
  task_id: string;
2187
2213
  org_id: string;
@@ -2536,10 +2562,80 @@ export interface DispatchEvent {
2536
2562
  */
2537
2563
  delivery_reason: DispatchDeliveryReason | null;
2538
2564
  status: DispatchStatus;
2565
+ /**
2566
+ * Ledger fields (dispatch idempotency redesign). dedupe_key is the domain
2567
+ * identity of the source fact; lease_owner points at the lane the WorkItem
2568
+ * is folded into; target_uri / thread_root_id are queue-routing metadata
2569
+ * for message WorkItems; resolution_kind / resolved_by_effect record the
2570
+ * terminal outcome. Nullable — historical rows may predate the ledger.
2571
+ */
2572
+ dedupe_key?: string | null;
2573
+ lease_owner?: string | null;
2574
+ target_uri?: string | null;
2575
+ thread_root_id?: string | null;
2576
+ resolution_kind?: 'effect' | 'no_action' | 'cancelled' | 'legacy_ack' | null;
2577
+ resolved_by_effect?: string | null;
2578
+ /**
2579
+ * Reply-message provenance ("prll://msg_x") of the resolving Effect.
2580
+ * Populated only by GET /dispatch/by-messages (Effect join) on
2581
+ * effect-resolved rows; absent elsewhere.
2582
+ */
2583
+ result_uri?: string | null;
2539
2584
  acked_at: string | null;
2540
2585
  created_at: string;
2541
2586
  }
2542
2587
 
2588
+ /** POST /dispatch/claim — occupy a lane and fold claimable WorkItems into it. */
2589
+ export interface ClaimDispatchRequest {
2590
+ /** Message lane resource (e.g. "prll://cht_x"). Omit for typed claims. */
2591
+ target_uri?: string;
2592
+ thread_root_id?: string;
2593
+ /** Typed WorkItem claim: the lane resource is the WorkItem itself. */
2594
+ dispatch_event_id?: string;
2595
+ /**
2596
+ * Typed claim by source identity, for callers without the WorkItem id
2597
+ * (the live task.assigned event). The dedupe arbiter guarantees at most
2598
+ * one live WorkItem per source.
2599
+ */
2600
+ source_type?: string;
2601
+ source_id?: string;
2602
+ limit?: number;
2603
+ }
2604
+
2605
+ export interface ClaimDispatchResponse {
2606
+ claimed: boolean;
2607
+ lane?: string;
2608
+ lease_until?: string;
2609
+ events?: DispatchEvent[];
2610
+ }
2611
+
2612
+ /** POST /dispatch/steer — fold a pending same-target WorkItem into a live lane. */
2613
+ export interface SteerDispatchRequest {
2614
+ lane: string;
2615
+ target_uri: string;
2616
+ thread_root_id?: string;
2617
+ /** Either the WorkItem id or its (source_type, source_id) pair. */
2618
+ dispatch_event_id?: string;
2619
+ source_type?: string;
2620
+ source_id?: string;
2621
+ }
2622
+
2623
+ export interface SteerDispatchResponse {
2624
+ dispatch_event_id: string;
2625
+ }
2626
+
2627
+ /** POST /dispatch/complete — end a turn: no_action sweep + lane release + re-drive. */
2628
+ export interface CompleteDispatchRequest {
2629
+ lane: string;
2630
+ target_uri: string;
2631
+ thread_root_id?: string;
2632
+ }
2633
+
2634
+ export interface CompleteDispatchResult {
2635
+ swept_no_action: number;
2636
+ redriven: boolean;
2637
+ }
2638
+
2543
2639
  export type DispatchNewData = DispatchEvent;
2544
2640
 
2545
2641
  export interface DispatchExpiredGroup {
@@ -2566,6 +2662,28 @@ export interface DispatchReceivedData {
2566
2662
  org_id: string;
2567
2663
  }
2568
2664
 
2665
+ /**
2666
+ * Terminal counterpart of DispatchReceivedData: published on org:{orgId} when
2667
+ * a message WorkItem reaches a terminal resolution, so the message-level
2668
+ * Activity indicator flips active→done (or clears, for `cancelled`) without
2669
+ * inferring completion from agent session state. Message WorkItems only.
2670
+ */
2671
+ export interface DispatchResolvedData {
2672
+ agent_id: string;
2673
+ event_type: string;
2674
+ source_type: string;
2675
+ source_id: string;
2676
+ chat_id?: string | null;
2677
+ org_id: string;
2678
+ resolution_kind: 'effect' | 'no_action' | 'cancelled' | 'legacy_ack';
2679
+ /**
2680
+ * Reply-message provenance ("prll://msg_x") for effect resolutions —
2681
+ * the indicator's click-through resolves the agent session from this
2682
+ * message. Absent for no_action / legacy_ack.
2683
+ */
2684
+ result_uri?: string | null;
2685
+ }
2686
+
2569
2687
  export type InboxNewData = InboxItem;
2570
2688
 
2571
2689
  export interface InboxUpdateData {
@@ -2648,6 +2766,7 @@ export type WsEventMap = {
2648
2766
  'read_position.updated': ReadPositionUpdateData;
2649
2767
  'dispatch.new': DispatchNewData;
2650
2768
  'dispatch.received': DispatchReceivedData;
2769
+ 'dispatch.resolved': DispatchResolvedData;
2651
2770
  'schedule.created': ScheduleCreatedData;
2652
2771
  'schedule.updated': ScheduleUpdatedData;
2653
2772
  'schedule.deleted': ScheduleDeletedData;
@@ -3614,3 +3733,48 @@ export interface SearchResponse {
3614
3733
  /** Per-type flag: more results remain past the returned page (within the reachable pool). Lets a tabbed results UI page each type independently. Optional for resilience to older payloads; clients default to all-false. */
3615
3734
  has_more?: { messages: boolean; tasks: boolean; wiki: boolean };
3616
3735
  }
3736
+
3737
+ // ============================================================
3738
+ // Edge Device Types
3739
+ // ============================================================
3740
+
3741
+ export interface EdgeDevice {
3742
+ id: string;
3743
+ org_id: string;
3744
+ owner_user_id?: string;
3745
+ name: string;
3746
+ status: 'online' | 'offline';
3747
+ last_seen_at?: string;
3748
+ created_at: string;
3749
+ updated_at: string;
3750
+ }
3751
+
3752
+ export interface EdgeBrowserProfile {
3753
+ id: string;
3754
+ edge_id: string;
3755
+ name: string;
3756
+ is_default: boolean;
3757
+ created_at: string;
3758
+ updated_at: string;
3759
+ }
3760
+
3761
+ export interface ClipConnection {
3762
+ id: string;
3763
+ clip_id: string;
3764
+ org_id: string;
3765
+ device_id?: string;
3766
+ profile?: string;
3767
+ mcp_config_id?: string;
3768
+ alias?: string;
3769
+ is_default: boolean;
3770
+ created_at: string;
3771
+ updated_at: string;
3772
+ }
3773
+
3774
+ export interface EdgeOnboardingStatus {
3775
+ device_registered: boolean;
3776
+ device_online: boolean;
3777
+ clip_installed: boolean;
3778
+ connection_created: boolean;
3779
+ profile_created: boolean;
3780
+ }