@pnds/sdk 1.0.0 → 1.1.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
@@ -15,6 +15,8 @@ import type {
15
15
  CreateChatRequest,
16
16
  Message,
17
17
  SendMessageRequest,
18
+ SendDirectMessageRequest,
19
+ DirectMessageResponse,
18
20
  PatchMessageRequest,
19
21
  PaginatedResponse,
20
22
  Approval,
@@ -48,6 +50,22 @@ import type {
48
50
  OrgInvitation,
49
51
  InvitationPublicInfo,
50
52
  PlatformConfigResponse,
53
+ Wiki,
54
+ WikiMount,
55
+ WikiChangeset,
56
+ WikiTreeResponse,
57
+ WikiBlob,
58
+ WikiDiff,
59
+ CreateWikiRequest,
60
+ CreateWikiMountRequest,
61
+ CreateWikiChangesetRequest,
62
+ UpdateWikiChangesetRequest,
63
+ MintWikiRepoTokenRequest,
64
+ RefreshWikiRepoTokenRequest,
65
+ WikiMountManifestV2,
66
+ WikiRepoToken,
67
+ InboxItem,
68
+ InboxUnreadCountResponse,
51
69
  } from './types.js';
52
70
 
53
71
  export interface PondClientOptions {
@@ -68,10 +86,13 @@ export class PondClient {
68
86
 
69
87
  /** Auth endpoints excluded from automatic 401 refresh to prevent recursion. */
70
88
  private static readonly AUTH_PATHS = new Set([
71
- '/auth/login', '/auth/register', '/auth/refresh',
89
+ '/auth/login', '/auth/register', '/auth/refresh', '/auth/logout',
72
90
  '/auth/verify-email', '/auth/resend-code', '/auth/check-email',
73
91
  ]);
74
92
 
93
+ /** Proactive refresh when token expires within this window (seconds). */
94
+ private static readonly REFRESH_THRESHOLD_S = 5 * 60;
95
+
75
96
  constructor(options: PondClientOptions = {}) {
76
97
  this.baseUrl = options.baseUrl ?? '';
77
98
  this.token = options.token ?? null;
@@ -88,6 +109,38 @@ export class PondClient {
88
109
  return this.token;
89
110
  }
90
111
 
112
+ /** Decode the `exp` claim from a JWT without verifying the signature. */
113
+ private static decodeJwtExp(token: string): number | null {
114
+ try {
115
+ const parts = token.split('.');
116
+ if (parts.length !== 3) return null;
117
+ // base64url → standard base64
118
+ const b64 = parts[1].replace(/-/g, '+').replace(/_/g, '/');
119
+ const padded = b64.padEnd(b64.length + ((4 - (b64.length % 4)) % 4), '=');
120
+ const payload = JSON.parse(atob(padded));
121
+ return typeof payload.exp === 'number' ? payload.exp : null;
122
+ } catch {
123
+ return null;
124
+ }
125
+ }
126
+
127
+ /**
128
+ * Proactive refresh: if the current access token expires within
129
+ * REFRESH_THRESHOLD_S, refresh it **before** sending the request.
130
+ * No-op when the token is still fresh, missing, or un-parseable.
131
+ */
132
+ private async ensureFreshToken(path: string): Promise<void> {
133
+ if (!this.token || !this.getRefreshToken) return;
134
+ const pathSuffix = path.replace(/^\/api\/v1/, '');
135
+ if (PondClient.AUTH_PATHS.has(pathSuffix)) return;
136
+
137
+ const exp = PondClient.decodeJwtExp(this.token);
138
+ if (exp === null) return;
139
+ if (exp - Date.now() / 1000 > PondClient.REFRESH_THRESHOLD_S) return;
140
+
141
+ await this.tryRefresh();
142
+ }
143
+
91
144
  /**
92
145
  * Single-flight token refresh. Multiple concurrent 401s share one promise
93
146
  * so only one refresh request is made.
@@ -119,6 +172,11 @@ export class PondClient {
119
172
  query?: Record<string, string | number | boolean | undefined>,
120
173
  retried = false,
121
174
  ): Promise<T> {
175
+ // Proactive refresh: block until token is fresh (no-op if still valid)
176
+ if (!retried) {
177
+ await this.ensureFreshToken(path);
178
+ }
179
+
122
180
  let url = `${this.baseUrl}${path}`;
123
181
 
124
182
  if (query) {
@@ -233,10 +291,6 @@ export class PondClient {
233
291
  return this.request('GET', ENDPOINTS.USER(id));
234
292
  }
235
293
 
236
- async searchUsers(q: string): Promise<User[]> {
237
- const res = await this.request<{ data: User[] }>('GET', ENDPOINTS.USERS_SEARCH, undefined, { q });
238
- return res.data;
239
- }
240
294
 
241
295
  // ---- Organizations ----
242
296
 
@@ -310,6 +364,12 @@ export class PondClient {
310
364
  return this.request('GET', ENDPOINTS.INVITATION_BY_TOKEN(token));
311
365
  }
312
366
 
367
+ // ---- Direct Messages (org-scoped) ----
368
+
369
+ async sendDirectMessage(orgId: string, req: SendDirectMessageRequest): Promise<DirectMessageResponse> {
370
+ return this.request('POST', ENDPOINTS.DM(orgId), req);
371
+ }
372
+
313
373
  // ---- Chats (org-scoped) ----
314
374
 
315
375
  async createChat(orgId: string, req: Omit<CreateChatRequest, 'org_id'>): Promise<Chat> {
@@ -415,7 +475,8 @@ export class PondClient {
415
475
  }
416
476
 
417
477
  async getPendingApprovals(): Promise<Approval[]> {
418
- return this.request('GET', ENDPOINTS.APPROVALS_PENDING);
478
+ const res = await this.request<{ data: Approval[] }>('GET', ENDPOINTS.APPROVALS_PENDING);
479
+ return res.data;
419
480
  }
420
481
 
421
482
  // ---- Agents (org-scoped) ----
@@ -455,6 +516,12 @@ export class PondClient {
455
516
  return res.data;
456
517
  }
457
518
 
519
+ // ---- Agent Bootstrap (self) ----
520
+
521
+ async getAgentMe(orgId: string): Promise<AgentWithRuntime> {
522
+ return this.request('GET', ENDPOINTS.AGENT_ME(orgId));
523
+ }
524
+
458
525
  // ---- Agent Runs (org-scoped) ----
459
526
 
460
527
  async createAgentRun(orgId: string, agentId: string, req: CreateAgentRunRequest): Promise<AgentRunDB> {
@@ -518,6 +585,18 @@ export class PondClient {
518
585
  return this.request('PUT', ENDPOINTS.MACHINE_WORKSPACE(orgId, machineId), files);
519
586
  }
520
587
 
588
+ async getAgentWikiMountManifestV2(orgId: string, agentId: string): Promise<WikiMountManifestV2> {
589
+ return this.request('GET', ENDPOINTS.AGENT_WIKI_MOUNT_MANIFEST_V2(orgId, agentId));
590
+ }
591
+
592
+ async mintAgentWikiRepoToken(orgId: string, agentId: string, data: MintWikiRepoTokenRequest): Promise<WikiRepoToken> {
593
+ return this.request('POST', ENDPOINTS.AGENT_WIKI_REPO_TOKEN(orgId, agentId), data);
594
+ }
595
+
596
+ async refreshAgentWikiRepoToken(orgId: string, agentId: string, data: RefreshWikiRepoTokenRequest): Promise<WikiRepoToken> {
597
+ return this.request('POST', ENDPOINTS.AGENT_WIKI_REPO_TOKEN_REFRESH(orgId, agentId), data);
598
+ }
599
+
521
600
  // ---- Unread ----
522
601
 
523
602
  async getUnreadCounts(): Promise<Record<string, number>> {
@@ -529,6 +608,41 @@ export class PondClient {
529
608
  return this.request('POST', ENDPOINTS.CHAT_READ(orgId, chatId), { message_id: messageId });
530
609
  }
531
610
 
611
+ // ---- Inbox ----
612
+
613
+ async getInbox(orgId: string, params?: {
614
+ type?: string; reason?: string; read?: string;
615
+ limit?: number; cursor?: string;
616
+ }): Promise<PaginatedResponse<InboxItem>> {
617
+ return this.request('GET', ENDPOINTS.INBOX(orgId), undefined, params as Record<string, string>);
618
+ }
619
+
620
+ async getInboxUnreadCount(orgId: string): Promise<InboxUnreadCountResponse> {
621
+ return this.request('GET', ENDPOINTS.INBOX_UNREAD_COUNT(orgId));
622
+ }
623
+
624
+ async markInboxRead(orgId: string, id: string): Promise<void> {
625
+ return this.request('PATCH', ENDPOINTS.INBOX_ITEM_READ(orgId, id));
626
+ }
627
+
628
+ async archiveInboxItem(orgId: string, id: string): Promise<void> {
629
+ return this.request('PATCH', ENDPOINTS.INBOX_ITEM_ARCHIVE(orgId, id));
630
+ }
631
+
632
+ // snoozeInboxItem / unsnoozeInboxItem deferred until un-snooze cron worker is implemented
633
+
634
+ async markAllInboxRead(orgId: string): Promise<void> {
635
+ return this.request('POST', ENDPOINTS.INBOX_MARK_ALL_READ(orgId));
636
+ }
637
+
638
+ async archiveAllInbox(orgId: string): Promise<void> {
639
+ return this.request('POST', ENDPOINTS.INBOX_ARCHIVE_ALL(orgId));
640
+ }
641
+
642
+ async deleteInboxItem(orgId: string, id: string): Promise<void> {
643
+ return this.request('DELETE', ENDPOINTS.INBOX_ITEM(orgId, id));
644
+ }
645
+
532
646
  // ---- Platform Config ----
533
647
 
534
648
  /**
@@ -671,6 +785,75 @@ export class PondClient {
671
785
  async deleteProject(orgId: string, projectId: string): Promise<void> {
672
786
  return this.request('DELETE', ENDPOINTS.PROJECT(orgId, projectId));
673
787
  }
788
+
789
+ // ---- Wikis (org-scoped) ----
790
+
791
+ async createWiki(orgId: string, data: CreateWikiRequest): Promise<Wiki> {
792
+ return this.request('POST', ENDPOINTS.WIKIS(orgId), data);
793
+ }
794
+
795
+ async getWikis(orgId: string): Promise<Wiki[]> {
796
+ const res = await this.request<{ data: Wiki[] }>('GET', ENDPOINTS.WIKIS(orgId));
797
+ return res.data;
798
+ }
799
+
800
+ async getWiki(orgId: string, wikiId: string): Promise<Wiki> {
801
+ return this.request('GET', ENDPOINTS.WIKI(orgId, wikiId));
802
+ }
803
+
804
+ async getWikiTree(orgId: string, wikiId: string, params?: { ref?: string; path?: string }): Promise<WikiTreeResponse> {
805
+ return this.request('GET', ENDPOINTS.WIKI_TREE(orgId, wikiId), undefined, params);
806
+ }
807
+
808
+ async getWikiBlob(orgId: string, wikiId: string, params: { ref?: string; path: string }): Promise<WikiBlob> {
809
+ return this.request('GET', ENDPOINTS.WIKI_BLOB(orgId, wikiId), undefined, params);
810
+ }
811
+
812
+ async createWikiMount(orgId: string, wikiId: string, data: CreateWikiMountRequest): Promise<WikiMount> {
813
+ return this.request('POST', ENDPOINTS.WIKI_MOUNTS(orgId, wikiId), data);
814
+ }
815
+
816
+ async getWikiMounts(orgId: string, wikiId: string): Promise<WikiMount[]> {
817
+ const res = await this.request<{ data: WikiMount[] }>('GET', ENDPOINTS.WIKI_MOUNTS(orgId, wikiId));
818
+ return res.data;
819
+ }
820
+
821
+ async createWikiChangeset(orgId: string, wikiId: string, data: CreateWikiChangesetRequest): Promise<WikiChangeset> {
822
+ return normalizeWikiChangeset(await this.request('POST', ENDPOINTS.WIKI_CHANGESETS(orgId, wikiId), data));
823
+ }
824
+
825
+ async getWikiChangesets(orgId: string, wikiId: string): Promise<WikiChangeset[]> {
826
+ const res = await this.request<{ data: WikiChangeset[] }>('GET', ENDPOINTS.WIKI_CHANGESETS(orgId, wikiId));
827
+ return res.data.map(normalizeWikiChangeset);
828
+ }
829
+
830
+ async getWikiChangeset(orgId: string, wikiId: string, changesetId: string): Promise<WikiChangeset> {
831
+ return normalizeWikiChangeset(await this.request('GET', ENDPOINTS.WIKI_CHANGESET(orgId, wikiId, changesetId)));
832
+ }
833
+
834
+ async updateWikiChangeset(orgId: string, wikiId: string, changesetId: string, data: UpdateWikiChangesetRequest): Promise<WikiChangeset> {
835
+ return normalizeWikiChangeset(await this.request('PATCH', ENDPOINTS.WIKI_CHANGESET(orgId, wikiId, changesetId), data));
836
+ }
837
+
838
+ async getWikiChangesetDiff(orgId: string, wikiId: string, changesetId: string): Promise<WikiDiff> {
839
+ return this.request('GET', ENDPOINTS.WIKI_CHANGESET_DIFF(orgId, wikiId, changesetId));
840
+ }
841
+
842
+ async mergeWikiChangeset(orgId: string, wikiId: string, changesetId: string): Promise<WikiChangeset> {
843
+ return normalizeWikiChangeset(await this.request('POST', ENDPOINTS.WIKI_CHANGESET_MERGE(orgId, wikiId, changesetId)));
844
+ }
845
+
846
+ async closeWikiChangeset(orgId: string, wikiId: string, changesetId: string): Promise<WikiChangeset> {
847
+ return normalizeWikiChangeset(await this.request('POST', ENDPOINTS.WIKI_CHANGESET_CLOSE(orgId, wikiId, changesetId)));
848
+ }
849
+ }
850
+
851
+ function normalizeWikiChangeset(changeset: WikiChangeset): WikiChangeset {
852
+ return {
853
+ ...changeset,
854
+ changed_paths: changeset.changed_paths ?? [],
855
+ diff_files: changeset.diff_files ?? [],
856
+ };
674
857
  }
675
858
 
676
859
  export class ApiError extends Error {
package/src/constants.ts CHANGED
@@ -15,7 +15,6 @@ export const ENDPOINTS = {
15
15
  // Users
16
16
  USERS_ME: `${API_BASE}/users/me`,
17
17
  USER: (id: string) => `${API_BASE}/users/${id}`,
18
- USERS_SEARCH: `${API_BASE}/users/search`,
19
18
 
20
19
  // WebSocket ticket
21
20
  WS_TICKET: `${API_BASE}/ws/ticket`,
@@ -28,6 +27,9 @@ export const ENDPOINTS = {
28
27
  ORG_MEMBER: (orgId: string, userId: string) =>
29
28
  `${API_BASE}/orgs/${orgId}/members/${userId}`,
30
29
 
30
+ // Direct messages (org-scoped, atomic find-or-create + send)
31
+ DM: (orgId: string) => `${API_BASE}/orgs/${orgId}/dm`,
32
+
31
33
  // Chats (org-scoped)
32
34
  CHATS: (orgId: string) => `${API_BASE}/orgs/${orgId}/chats`,
33
35
  CHAT: (orgId: string, chatId: string) => `${API_BASE}/orgs/${orgId}/chats/${chatId}`,
@@ -63,6 +65,8 @@ export const ENDPOINTS = {
63
65
  `${API_BASE}/orgs/${orgId}/agents/${agentId}/activity`,
64
66
  AGENT_MONITOR: (orgId: string, agentId: string) =>
65
67
  `${API_BASE}/orgs/${orgId}/agents/${agentId}/monitor`,
68
+ AGENT_ME: (orgId: string) =>
69
+ `${API_BASE}/orgs/${orgId}/agents/me`,
66
70
  AGENT_RUNS: (orgId: string, agentId: string) =>
67
71
  `${API_BASE}/orgs/${orgId}/agents/${agentId}/runs`,
68
72
  AGENT_RUN: (orgId: string, agentId: string, runId: string) =>
@@ -83,6 +87,12 @@ export const ENDPOINTS = {
83
87
  `${API_BASE}/orgs/${orgId}/machines/${machineId}/logs`,
84
88
  MACHINE_WORKSPACE: (orgId: string, machineId: string) =>
85
89
  `${API_BASE}/orgs/${orgId}/machines/${machineId}/workspace`,
90
+ AGENT_WIKI_MOUNT_MANIFEST_V2: (orgId: string, agentId: string) =>
91
+ `${API_BASE}/orgs/${orgId}/agents/${agentId}/wiki-mounts/manifest-v2`,
92
+ AGENT_WIKI_REPO_TOKEN: (orgId: string, agentId: string) =>
93
+ `${API_BASE}/orgs/${orgId}/agents/${agentId}/wiki-repo-token`,
94
+ AGENT_WIKI_REPO_TOKEN_REFRESH: (orgId: string, agentId: string) =>
95
+ `${API_BASE}/orgs/${orgId}/agents/${agentId}/wiki-repo-token/refresh`,
86
96
 
87
97
  // Tasks (org-scoped)
88
98
  TASKS: (orgId: string) => `${API_BASE}/orgs/${orgId}/tasks`,
@@ -117,6 +127,32 @@ export const ENDPOINTS = {
117
127
  INVITATION_DECLINE: (id: string) => `${API_BASE}/invitations/${id}/decline`,
118
128
  INVITATION_BY_TOKEN: (token: string) => `${API_BASE}/invitations/by-token/${token}`,
119
129
 
130
+ // Wikis (org-scoped)
131
+ WIKIS: (orgId: string) => `${API_BASE}/orgs/${orgId}/wikis`,
132
+ WIKI: (orgId: string, wikiId: string) => `${API_BASE}/orgs/${orgId}/wikis/${wikiId}`,
133
+ WIKI_TREE: (orgId: string, wikiId: string) => `${API_BASE}/orgs/${orgId}/wikis/${wikiId}/tree`,
134
+ WIKI_BLOB: (orgId: string, wikiId: string) => `${API_BASE}/orgs/${orgId}/wikis/${wikiId}/blob`,
135
+ WIKI_MOUNTS: (orgId: string, wikiId: string) => `${API_BASE}/orgs/${orgId}/wikis/${wikiId}/mounts`,
136
+ WIKI_CHANGESETS: (orgId: string, wikiId: string) => `${API_BASE}/orgs/${orgId}/wikis/${wikiId}/changesets`,
137
+ WIKI_CHANGESET: (orgId: string, wikiId: string, changesetId: string) =>
138
+ `${API_BASE}/orgs/${orgId}/wikis/${wikiId}/changesets/${changesetId}`,
139
+ WIKI_CHANGESET_DIFF: (orgId: string, wikiId: string, changesetId: string) =>
140
+ `${API_BASE}/orgs/${orgId}/wikis/${wikiId}/changesets/${changesetId}/diff`,
141
+ WIKI_CHANGESET_MERGE: (orgId: string, wikiId: string, changesetId: string) =>
142
+ `${API_BASE}/orgs/${orgId}/wikis/${wikiId}/changesets/${changesetId}/merge`,
143
+ WIKI_CHANGESET_CLOSE: (orgId: string, wikiId: string, changesetId: string) =>
144
+ `${API_BASE}/orgs/${orgId}/wikis/${wikiId}/changesets/${changesetId}/close`,
145
+
146
+ // Inbox (org-scoped)
147
+ INBOX: (orgId: string) => `${API_BASE}/orgs/${orgId}/inbox`,
148
+ INBOX_UNREAD_COUNT: (orgId: string) => `${API_BASE}/orgs/${orgId}/inbox/unread-count`,
149
+ INBOX_ITEM_READ: (orgId: string, id: string) => `${API_BASE}/orgs/${orgId}/inbox/${id}/read`,
150
+ INBOX_ITEM_ARCHIVE: (orgId: string, id: string) => `${API_BASE}/orgs/${orgId}/inbox/${id}/archive`,
151
+ // Snooze/Unsnooze deferred until un-snooze cron worker is implemented
152
+ INBOX_MARK_ALL_READ: (orgId: string) => `${API_BASE}/orgs/${orgId}/inbox/mark-all-read`,
153
+ INBOX_ARCHIVE_ALL: (orgId: string) => `${API_BASE}/orgs/${orgId}/inbox/archive-all`,
154
+ INBOX_ITEM: (orgId: string, id: string) => `${API_BASE}/orgs/${orgId}/inbox/${id}`,
155
+
120
156
  // Unread
121
157
  UNREAD: `${API_BASE}/me/unread`,
122
158
  CHAT_READ: (orgId: string, chatId: string) =>
@@ -167,4 +203,10 @@ export const WS_EVENTS = {
167
203
  INVITATION_ACCEPTED: 'invitation.accepted',
168
204
  INVITATION_DECLINED: 'invitation.declined',
169
205
  AGENT_CONFIG_UPDATE: 'agent_config.update',
206
+ PRESENCE_UPDATE: 'presence.update',
207
+ WIKI_CHANGESET_CREATED: 'wiki.changeset.created',
208
+ WIKI_CHANGESET_UPDATED: 'wiki.changeset.updated',
209
+ NOTIFICATION_NEW: 'notification.new',
210
+ NOTIFICATION_UPDATE: 'notification.update',
211
+ NOTIFICATION_BULK_UPDATE: 'notification.bulk_update',
170
212
  } as const;