@pnds/sdk 0.1.1 → 0.2.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/dist/ws.d.ts CHANGED
@@ -1,6 +1,30 @@
1
1
  import type { WsEventMap, WsTicketResponse } from './types.js';
2
2
  export type WsEventType = keyof WsEventMap;
3
3
  export type WsEventHandler<T extends WsEventType> = (data: WsEventMap[T], seq?: number) => void;
4
+ export type WsClientEventMap = {
5
+ 'ping': {
6
+ ts: number;
7
+ };
8
+ 'typing': {
9
+ chat_id: string;
10
+ thread_root_id: string | null;
11
+ action: 'start' | 'stop';
12
+ };
13
+ 'ack': {
14
+ message_id: string;
15
+ };
16
+ 'read': {
17
+ chat_id: string;
18
+ last_read_id: string;
19
+ };
20
+ 'watch': {
21
+ chat_ids: string[];
22
+ };
23
+ 'agent.heartbeat': {
24
+ session_id: string;
25
+ telemetry?: Record<string, unknown>;
26
+ };
27
+ };
4
28
  export type WsConnectionState = 'connecting' | 'connected' | 'disconnected' | 'reconnecting';
5
29
  export interface PondWsOptions {
6
30
  /** Async function that fetches a one-time WS ticket from the API. */
package/dist/ws.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"ws.d.ts","sourceRoot":"","sources":["../src/ws.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAW,UAAU,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAExE,MAAM,MAAM,WAAW,GAAG,MAAM,UAAU,CAAC;AAC3C,MAAM,MAAM,cAAc,CAAC,CAAC,SAAS,WAAW,IAAI,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;AAEhG,MAAM,MAAM,iBAAiB,GAAG,YAAY,GAAG,WAAW,GAAG,cAAc,GAAG,cAAc,CAAC;AAE7F,MAAM,WAAW,aAAa;IAC5B,qEAAqE;IACrE,SAAS,EAAE,MAAM,OAAO,CAAC,gBAAgB,CAAC,CAAC;IAC3C,oEAAoE;IACpE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,oBAAoB,CAAC,EAAE,MAAM,CAAC;CAC/B;AAED,qBAAa,MAAM;IACjB,OAAO,CAAC,EAAE,CAA0B;IACpC,OAAO,CAAC,OAAO,CAAkG;IACjH,OAAO,CAAC,SAAS,CAAuD;IACxE,OAAO,CAAC,cAAc,CAAiD;IACvE,OAAO,CAAC,cAAc,CAA+C;IACrE,OAAO,CAAC,cAAc,CAA8C;IACpE,OAAO,CAAC,iBAAiB,CAAK;IAC9B,OAAO,CAAC,OAAO,CAAK;IACpB,OAAO,CAAC,MAAM,CAAqC;IACnD,OAAO,CAAC,gBAAgB,CAAS;gBAErB,OAAO,EAAE,aAAa;IAUlC,IAAI,KAAK,IAAI,iBAAiB,CAE7B;IAEK,OAAO;IA+Db,UAAU;IAaV,wFAAwF;IACxF,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE;IAIvB,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,GAAG,MAAM,EAAE,YAAY,CAAC,EAAE,MAAM;IAO1E,OAAO,CAAC,SAAS,EAAE,MAAM;IAIzB,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM;IAI3C,mDAAmD;IACnD,kBAAkB,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAOzE,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO;IAMrC,EAAE,CAAC,CAAC,SAAS,WAAW,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,cAAc,CAAC,CAAC,CAAC;IAY9D,GAAG,CAAC,CAAC,SAAS,WAAW,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,cAAc,CAAC,CAAC,CAAC;IAI/D,aAAa,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,iBAAiB,KAAK,IAAI;IASzD,OAAO,CAAC,IAAI;IAMZ,OAAO,CAAC,WAAW;IAqBnB,OAAO,CAAC,cAAc;IAOtB,OAAO,CAAC,aAAa;IAOrB,OAAO,CAAC,iBAAiB;IAezB,OAAO,CAAC,cAAc;IAOtB,OAAO,CAAC,QAAQ;CAMjB"}
1
+ {"version":3,"file":"ws.d.ts","sourceRoot":"","sources":["../src/ws.ts"],"names":[],"mappings":"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"}
package/dist/ws.js CHANGED
@@ -50,7 +50,33 @@ export class PondWs {
50
50
  params.set('last_seq', String(this.lastSeq));
51
51
  }
52
52
  this.ws = new WebSocket(`${wsUrl}?${params.toString()}`);
53
+ // Force-reconnect if not connected within 15s.
54
+ // Handles edge case where Node 22 WebSocket transitions to CLOSED without
55
+ // firing onclose (e.g. TCP reset during Cloudflare rolling update).
56
+ const ws = this.ws;
57
+ const connectTimeout = setTimeout(() => {
58
+ if (this._state === 'connected' || this.intentionalClose)
59
+ return;
60
+ ws.onclose = null;
61
+ ws.onopen = null;
62
+ ws.onerror = null;
63
+ try {
64
+ ws.close();
65
+ }
66
+ catch { /* ignore */ }
67
+ // Only reconnect if this socket is still the active one.
68
+ // If connect() was called again, a newer socket owns the lifecycle.
69
+ if (ws !== this.ws)
70
+ return;
71
+ if (this.options.reconnect) {
72
+ this.scheduleReconnect();
73
+ }
74
+ else {
75
+ this.setState('disconnected');
76
+ }
77
+ }, 15_000);
53
78
  this.ws.onopen = () => {
79
+ clearTimeout(connectTimeout);
54
80
  this.reconnectAttempts = 0;
55
81
  this.setState('connected');
56
82
  };
@@ -64,6 +90,7 @@ export class PondWs {
64
90
  }
65
91
  };
66
92
  this.ws.onclose = () => {
93
+ clearTimeout(connectTimeout);
67
94
  this.stopHeartbeat();
68
95
  if (this.intentionalClose) {
69
96
  this.setState('disconnected');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pnds/sdk",
3
- "version": "0.1.1",
3
+ "version": "0.2.0",
4
4
  "description": "TypeScript client SDK for Pond — REST + WebSocket client, shared types",
5
5
  "license": "MIT",
6
6
  "repository": {
package/src/client.ts CHANGED
@@ -2,6 +2,7 @@ import { ENDPOINTS } from './constants.js';
2
2
  import type {
3
3
  AuthTokens,
4
4
  RegisterRequest,
5
+ RegisterResponse,
5
6
  LoginRequest,
6
7
  CheckEmailResponse,
7
8
  User,
@@ -19,35 +20,64 @@ import type {
19
20
  Approval,
20
21
  PresignResponse,
21
22
  PresignUploadRequest,
23
+ FileUrlResponse,
22
24
  CreateAgentRequest,
23
25
  CreateAgentResponse,
24
26
  AgentWithRuntime,
25
27
  ApiKey,
26
28
  WsTicketResponse,
27
- HostedStatus,
29
+ Machine,
28
30
  WorkspaceFiles,
29
31
  Task,
30
32
  CreateTaskRequest,
31
33
  UpdateTaskRequest,
32
34
  TaskRelation,
33
35
  CreateTaskRelationRequest,
36
+ TaskComment,
37
+ CreateTaskCommentRequest,
38
+ UpdateTaskCommentRequest,
39
+ TaskActivity,
40
+ Project,
41
+ CreateProjectRequest,
42
+ UpdateProjectRequest,
43
+ AgentRunDB,
44
+ AgentStep,
45
+ CreateAgentRunRequest,
46
+ CreateAgentStepRequest,
47
+ UpdateAgentRunRequest,
48
+ OrgInvitation,
49
+ InvitationPublicInfo,
50
+ PlatformConfigResponse,
34
51
  } from './types.js';
35
52
 
36
53
  export interface PondClientOptions {
37
54
  baseUrl?: string;
38
55
  token?: string;
39
56
  onTokenExpired?: () => void;
57
+ getRefreshToken?: () => string | null;
58
+ setTokens?: (tokens: { access_token: string; refresh_token: string }) => void;
40
59
  }
41
60
 
42
61
  export class PondClient {
43
62
  private baseUrl: string;
44
63
  private token: string | null;
45
64
  private onTokenExpired?: () => void;
65
+ private getRefreshToken?: () => string | null;
66
+ private setTokens?: (tokens: { access_token: string; refresh_token: string }) => void;
67
+ private refreshPromise: Promise<AuthTokens> | null = null;
68
+
69
+ /** Auth endpoints excluded from automatic 401 refresh to prevent recursion. */
70
+ private static readonly AUTH_PATHS = new Set([
71
+ '/auth/login', '/auth/register', '/auth/refresh',
72
+ '/auth/verify-email', '/auth/resend-code', '/auth/check-email',
73
+ ]);
46
74
 
47
75
  constructor(options: PondClientOptions = {}) {
48
76
  this.baseUrl = options.baseUrl ?? '';
49
77
  this.token = options.token ?? null;
50
78
  this.onTokenExpired = options.onTokenExpired;
79
+ this.getRefreshToken = options.getRefreshToken;
80
+ this.setTokens = options.setTokens;
51
81
  }
52
82
 
53
83
  setToken(token: string | null) {
@@ -58,11 +88,36 @@ export class PondClient {
58
88
  return this.token;
59
89
  }
60
90
 
91
+ /**
92
+ * Single-flight token refresh. Multiple concurrent 401s share one promise
93
+ * so only one refresh request is made.
94
+ */
95
+ private async tryRefresh(): Promise<boolean> {
96
+ const rt = this.getRefreshToken?.();
97
+ if (!rt) return false;
98
+
99
+ if (!this.refreshPromise) {
100
+ this.refreshPromise = this.refreshToken(rt);
101
+ }
102
+
103
+ try {
104
+ const tokens = await this.refreshPromise;
105
+ this.setToken(tokens.access_token);
106
+ this.setTokens?.(tokens);
107
+ return true;
108
+ } catch {
109
+ return false;
110
+ } finally {
111
+ this.refreshPromise = null;
112
+ }
113
+ }
114
+
61
115
  private async request<T>(
62
116
  method: string,
63
117
  path: string,
64
118
  body?: unknown,
65
119
  query?: Record<string, string | number | boolean | undefined>,
120
+ retried = false,
66
121
  ): Promise<T> {
67
122
  let url = `${this.baseUrl}${path}`;
68
123
 
@@ -84,14 +139,36 @@ export class PondClient {
84
139
  headers['Authorization'] = `Bearer ${this.token}`;
85
140
  }
86
141
 
87
- const res = await fetch(url, {
88
- method,
89
- headers,
90
- body: body ? JSON.stringify(body) : undefined,
91
- });
142
+ let res: Response;
143
+ try {
144
+ res = await fetch(url, {
145
+ method,
146
+ headers,
147
+ body: body ? JSON.stringify(body) : undefined,
148
+ signal: AbortSignal.timeout(15_000),
149
+ });
150
+ } catch (err) {
151
+ if (err instanceof DOMException && err.name === 'TimeoutError') {
152
+ throw new ApiError(0, 'Request timed out', 'REQUEST_TIMEOUT');
153
+ }
154
+ throw err;
155
+ }
92
156
 
93
- if (res.status === 401 && this.onTokenExpired) {
94
- this.onTokenExpired();
157
+ if (res.status === 401) {
158
+ const pathSuffix = path.replace(/^\/api\/v1/, '');
159
+ const isAuthPath = PondClient.AUTH_PATHS.has(pathSuffix);
160
+ if (!retried && !isAuthPath && this.getRefreshToken) {
161
+ const refreshed = await this.tryRefresh();
162
+ if (refreshed) {
163
+ // Retry once with the new token
164
+ return this.request<T>(method, path, body, query, true);
165
+ }
166
+ }
167
+ // Only fire for non-auth paths — auth endpoints (e.g. /auth/refresh)
168
+ // just throw; the original caller's path handles the notification.
169
+ if (this.onTokenExpired && !isAuthPath) {
170
+ this.onTokenExpired();
171
+ }
95
172
  }
96
173
 
97
174
  if (!res.ok) {
@@ -108,7 +185,7 @@ export class PondClient {
108
185
 
109
186
  // ---- Auth ----
110
187
 
111
- async register(req: RegisterRequest): Promise<AuthTokens> {
188
+ async register(req: RegisterRequest): Promise<RegisterResponse> {
112
189
  return this.request('POST', ENDPOINTS.AUTH_REGISTER, req);
113
190
  }
114
191
 
@@ -128,7 +205,7 @@ export class PondClient {
128
205
  return this.request('POST', ENDPOINTS.AUTH_CHECK_EMAIL, { email });
129
206
  }
130
207
 
131
- async verifyEmail(email: string, code: string): Promise<void> {
208
+ async verifyEmail(email: string, code: string): Promise<AuthTokens> {
132
209
  return this.request('POST', ENDPOINTS.AUTH_VERIFY_EMAIL, { email, code });
133
210
  }
134
211
 
@@ -189,10 +266,6 @@ export class PondClient {
189
266
  return res.data;
190
267
  }
191
268
 
192
- async addOrgMember(orgId: string, userId: string, role?: OrgMemberRole): Promise<void> {
193
- return this.request('POST', ENDPOINTS.ORG_MEMBERS(orgId), { user_id: userId, role: role ?? 'member' });
194
- }
195
-
196
269
  async removeOrgMember(orgId: string, userId: string): Promise<void> {
197
270
  return this.request('DELETE', ENDPOINTS.ORG_MEMBER(orgId, userId));
198
271
  }
@@ -201,6 +274,42 @@ export class PondClient {
201
274
  return this.request('PATCH', ENDPOINTS.ORG_MEMBER(orgId, userId), { role });
202
275
  }
203
276
 
277
+ // ---- Invitations ----
278
+
279
+ async createInvitation(orgId: string, email: string, role?: OrgMemberRole): Promise<OrgInvitation> {
280
+ return this.request('POST', ENDPOINTS.ORG_INVITATIONS(orgId), { email, role });
281
+ }
282
+
283
+ async getOrgInvitations(orgId: string): Promise<OrgInvitation[]> {
284
+ const res = await this.request<{ data: OrgInvitation[] }>('GET', ENDPOINTS.ORG_INVITATIONS(orgId));
285
+ return res.data;
286
+ }
287
+
288
+ async revokeInvitation(orgId: string, invId: string): Promise<void> {
289
+ return this.request('DELETE', ENDPOINTS.ORG_INVITATION(orgId, invId));
290
+ }
291
+
292
+ async resendInvitation(orgId: string, invId: string): Promise<void> {
293
+ return this.request('POST', ENDPOINTS.ORG_INVITATION_RESEND(orgId, invId));
294
+ }
295
+
296
+ async getMyInvitations(): Promise<OrgInvitation[]> {
297
+ const res = await this.request<{ data: OrgInvitation[] }>('GET', ENDPOINTS.MY_INVITATIONS);
298
+ return res.data;
299
+ }
300
+
301
+ async acceptInvitation(id: string): Promise<OrgMember> {
302
+ return this.request('POST', ENDPOINTS.INVITATION_ACCEPT(id));
303
+ }
304
+
305
+ async declineInvitation(id: string): Promise<void> {
306
+ return this.request('POST', ENDPOINTS.INVITATION_DECLINE(id));
307
+ }
308
+
309
+ async getInvitationByToken(token: string): Promise<InvitationPublicInfo> {
310
+ return this.request('GET', ENDPOINTS.INVITATION_BY_TOKEN(token));
311
+ }
312
+
204
313
  // ---- Chats (org-scoped) ----
205
314
 
206
315
  async createChat(orgId: string, req: Omit<CreateChatRequest, 'org_id'>): Promise<Chat> {
@@ -287,12 +396,16 @@ export class PondClient {
287
396
 
288
397
  // ---- File Upload ----
289
398
 
290
- async getUploadPresignUrl(req: PresignUploadRequest): Promise<PresignResponse> {
291
- return this.request('POST', ENDPOINTS.UPLOAD_PRESIGN, req);
399
+ async getUploadPresignUrl(orgId: string, req: PresignUploadRequest): Promise<PresignResponse> {
400
+ return this.request('POST', ENDPOINTS.UPLOAD_PRESIGN(orgId), req);
401
+ }
402
+
403
+ async completeUpload(orgId: string, attachmentId: string): Promise<{ attachment_id: string }> {
404
+ return this.request('POST', ENDPOINTS.UPLOAD_COMPLETE(orgId), { attachment_id: attachmentId });
292
405
  }
293
406
 
294
- async completeUpload(attachmentId: string): Promise<{ attachment_id: string }> {
295
- return this.request('POST', ENDPOINTS.UPLOAD_COMPLETE, { attachment_id: attachmentId });
407
+ async getFileUrl(id: string): Promise<FileUrlResponse> {
408
+ return this.request('GET', ENDPOINTS.FILE(id));
296
409
  }
297
410
 
298
411
  // ---- Approvals ----
@@ -342,24 +455,67 @@ export class PondClient {
342
455
  return res.data;
343
456
  }
344
457
 
345
- async startAgent(orgId: string, agentId: string): Promise<void> {
346
- return this.request('POST', ENDPOINTS.AGENT_START(orgId, agentId));
458
+ // ---- Agent Runs (org-scoped) ----
459
+
460
+ async createAgentRun(orgId: string, agentId: string, req: CreateAgentRunRequest): Promise<AgentRunDB> {
461
+ return this.request('POST', ENDPOINTS.AGENT_RUNS(orgId, agentId), req);
462
+ }
463
+
464
+ async getAgentRuns(orgId: string, agentId: string, params?: { limit?: number }): Promise<AgentRunDB[]> {
465
+ const res = await this.request<{ data: AgentRunDB[] }>('GET', ENDPOINTS.AGENT_RUNS(orgId, agentId), undefined, params);
466
+ return res.data;
467
+ }
468
+
469
+ async getAgentRun(orgId: string, agentId: string, runId: string): Promise<AgentRunDB> {
470
+ return this.request('GET', ENDPOINTS.AGENT_RUN(orgId, agentId, runId));
471
+ }
472
+
473
+ async updateAgentRun(orgId: string, agentId: string, runId: string, req: UpdateAgentRunRequest): Promise<AgentRunDB> {
474
+ return this.request('PATCH', ENDPOINTS.AGENT_RUN(orgId, agentId, runId), req);
475
+ }
476
+
477
+ async createAgentStep(orgId: string, agentId: string, runId: string, req: CreateAgentStepRequest): Promise<AgentStep> {
478
+ return this.request('POST', ENDPOINTS.AGENT_RUN_STEPS(orgId, agentId, runId), req);
479
+ }
480
+
481
+ async getAgentRunSteps(orgId: string, agentId: string, runId: string, params?: { limit?: number }): Promise<AgentStep[]> {
482
+ const res = await this.request<{ data: AgentStep[] }>('GET', ENDPOINTS.AGENT_RUN_STEPS(orgId, agentId, runId), undefined, params);
483
+ return res.data;
484
+ }
485
+
486
+ // ---- Machines (org-scoped) ----
487
+
488
+ async getMachines(orgId: string): Promise<Machine[]> {
489
+ const res = await this.request<{ data: Machine[] }>('GET', ENDPOINTS.MACHINES(orgId));
490
+ return res.data;
347
491
  }
348
492
 
349
- async stopAgent(orgId: string, agentId: string): Promise<void> {
350
- return this.request('POST', ENDPOINTS.AGENT_STOP(orgId, agentId));
493
+ async getMachine(orgId: string, machineId: string): Promise<Machine> {
494
+ return this.request('GET', ENDPOINTS.MACHINE(orgId, machineId));
351
495
  }
352
496
 
353
- async getHostedStatus(orgId: string, agentId: string): Promise<HostedStatus> {
354
- return this.request('GET', ENDPOINTS.AGENT_HOSTED_STATUS(orgId, agentId));
497
+ async deleteMachine(orgId: string, machineId: string): Promise<void> {
498
+ return this.request('DELETE', ENDPOINTS.MACHINE(orgId, machineId));
355
499
  }
356
500
 
357
- async updateWorkspace(orgId: string, agentId: string, files: WorkspaceFiles): Promise<void> {
358
- return this.request('PUT', ENDPOINTS.AGENT_WORKSPACE(orgId, agentId), files);
501
+ async startMachine(orgId: string, machineId: string): Promise<void> {
502
+ return this.request('POST', ENDPOINTS.MACHINE_START(orgId, machineId));
359
503
  }
360
504
 
361
- async getAgentLogs(orgId: string, agentId: string, lines = 100): Promise<{ data: string[] }> {
362
- return this.request('GET', ENDPOINTS.AGENT_LOGS(orgId, agentId), undefined, { lines });
505
+ async stopMachine(orgId: string, machineId: string): Promise<void> {
506
+ return this.request('POST', ENDPOINTS.MACHINE_STOP(orgId, machineId));
507
+ }
508
+
509
+ async getMachineStatus(orgId: string, machineId: string): Promise<{ state: string }> {
510
+ return this.request('GET', ENDPOINTS.MACHINE_STATUS(orgId, machineId));
511
+ }
512
+
513
+ async getMachineLogs(orgId: string, machineId: string, lines = 100): Promise<{ data: string[] }> {
514
+ return this.request('GET', ENDPOINTS.MACHINE_LOGS(orgId, machineId), undefined, { lines });
515
+ }
516
+
517
+ async updateMachineWorkspace(orgId: string, machineId: string, files: WorkspaceFiles): Promise<void> {
518
+ return this.request('PUT', ENDPOINTS.MACHINE_WORKSPACE(orgId, machineId), files);
363
519
  }
364
520
 
365
521
  // ---- Unread ----
@@ -373,6 +529,44 @@ export class PondClient {
373
529
  return this.request('POST', ENDPOINTS.CHAT_READ(orgId, chatId), { message_id: messageId });
374
530
  }
375
531
 
532
+ // ---- Platform Config ----
533
+
534
+ /**
535
+ * Fetch platform config for the authenticated agent.
536
+ * Pass currentVersion to enable conditional fetch (If-None-Match / 304).
537
+ * Returns null when the server responds with 304 (config unchanged).
538
+ */
539
+ async getPlatformConfig(currentVersion?: string): Promise<PlatformConfigResponse | null> {
540
+ const url = `${this.baseUrl}${ENDPOINTS.PLATFORM_CONFIG}`;
541
+ const headers: Record<string, string> = {
542
+ 'Content-Type': 'application/json',
543
+ };
544
+ if (this.token) {
545
+ headers['Authorization'] = `Bearer ${this.token}`;
546
+ }
547
+ if (currentVersion !== undefined) {
548
+ headers['If-None-Match'] = currentVersion;
549
+ }
550
+
551
+ const res = await fetch(url, {
552
+ method: 'GET',
553
+ headers,
554
+ signal: AbortSignal.timeout(15_000),
555
+ });
556
+
557
+ if (res.status === 304) return null;
558
+
559
+ if (!res.ok) {
560
+ const errorBody = await res.json().catch(() => ({}));
561
+ const errorObj = errorBody?.error && typeof errorBody.error === 'object' ? errorBody.error as { message?: string; code?: string } : undefined;
562
+ const errMsg = errorObj?.message ?? (typeof errorBody?.error === 'string' ? errorBody.error : undefined);
563
+ const errCode = errorObj?.code ?? (typeof errorBody?.code === 'string' ? errorBody.code : undefined);
564
+ throw new ApiError(res.status, errMsg ?? res.statusText, errCode);
565
+ }
566
+
567
+ return res.json() as Promise<PlatformConfigResponse>;
568
+ }
569
+
376
570
  // ---- Tasks (org-scoped) ----
377
571
 
378
572
  async createTask(orgId: string, data: CreateTaskRequest): Promise<Task> {
@@ -384,6 +578,7 @@ export class PondClient {
384
578
  priority?: string;
385
579
  assignee_id?: string;
386
580
  parent_id?: string;
581
+ project_id?: string;
387
582
  limit?: number;
388
583
  cursor?: string;
389
584
  sort?: string;
@@ -421,6 +616,61 @@ export class PondClient {
421
616
  async deleteTaskRelation(orgId: string, taskId: string, relationId: string): Promise<void> {
422
617
  return this.request('DELETE', ENDPOINTS.TASK_RELATION(orgId, taskId, relationId));
423
618
  }
619
+
620
+ // ---- Task Comments ----
621
+
622
+ async getTaskComments(orgId: string, taskId: string, params?: {
623
+ limit?: number; cursor?: string; order?: string;
624
+ }): Promise<PaginatedResponse<TaskComment>> {
625
+ return this.request('GET', ENDPOINTS.TASK_COMMENTS(orgId, taskId), undefined, params);
626
+ }
627
+
628
+ async getTaskComment(orgId: string, taskId: string, commentId: string): Promise<TaskComment> {
629
+ return this.request('GET', ENDPOINTS.TASK_COMMENT(orgId, taskId, commentId));
630
+ }
631
+
632
+ async createTaskComment(orgId: string, taskId: string, data: CreateTaskCommentRequest): Promise<TaskComment> {
633
+ return this.request('POST', ENDPOINTS.TASK_COMMENTS(orgId, taskId), data);
634
+ }
635
+
636
+ async updateTaskComment(orgId: string, taskId: string, commentId: string, data: UpdateTaskCommentRequest): Promise<TaskComment> {
637
+ return this.request('PATCH', ENDPOINTS.TASK_COMMENT(orgId, taskId, commentId), data);
638
+ }
639
+
640
+ async deleteTaskComment(orgId: string, taskId: string, commentId: string): Promise<void> {
641
+ return this.request('DELETE', ENDPOINTS.TASK_COMMENT(orgId, taskId, commentId));
642
+ }
643
+
644
+ // ---- Task Activities ----
645
+
646
+ async getTaskActivities(orgId: string, taskId: string, params?: {
647
+ limit?: number; cursor?: string;
648
+ }): Promise<PaginatedResponse<TaskActivity>> {
649
+ return this.request('GET', ENDPOINTS.TASK_ACTIVITIES(orgId, taskId), undefined, params);
650
+ }
651
+
652
+ // ---- Projects (org-scoped) ----
653
+
654
+ async createProject(orgId: string, data: CreateProjectRequest): Promise<Project> {
655
+ return this.request('POST', ENDPOINTS.PROJECTS(orgId), data);
656
+ }
657
+
658
+ async getProjects(orgId: string): Promise<Project[]> {
659
+ const res = await this.request<{ data: Project[] }>('GET', ENDPOINTS.PROJECTS(orgId));
660
+ return res.data;
661
+ }
662
+
663
+ async getProject(orgId: string, projectId: string): Promise<Project> {
664
+ return this.request('GET', ENDPOINTS.PROJECT(orgId, projectId));
665
+ }
666
+
667
+ async updateProject(orgId: string, projectId: string, data: UpdateProjectRequest): Promise<Project> {
668
+ return this.request('PATCH', ENDPOINTS.PROJECT(orgId, projectId), data);
669
+ }
670
+
671
+ async deleteProject(orgId: string, projectId: string): Promise<void> {
672
+ return this.request('DELETE', ENDPOINTS.PROJECT(orgId, projectId));
673
+ }
424
674
  }
425
675
 
426
676
  export class ApiError extends Error {
package/src/constants.ts CHANGED
@@ -43,9 +43,10 @@ export const ENDPOINTS = {
43
43
  MESSAGE_PATCHES: (id: string) => `${API_BASE}/messages/${id}/patches`,
44
44
  MESSAGE_REPLIES: (id: string) => `${API_BASE}/messages/${id}/replies`,
45
45
 
46
- // Upload (global)
47
- UPLOAD_PRESIGN: `${API_BASE}/upload/presign`,
48
- UPLOAD_COMPLETE: `${API_BASE}/upload/complete`,
46
+ // Upload (org-scoped)
47
+ UPLOAD_PRESIGN: (orgId: string) => `${API_BASE}/orgs/${orgId}/upload/presign`,
48
+ UPLOAD_COMPLETE: (orgId: string) => `${API_BASE}/orgs/${orgId}/upload/complete`,
49
+ FILE: (id: string) => `${API_BASE}/files/${id}`,
49
50
 
50
51
  // Approvals (global)
51
52
  APPROVAL_DECIDE: (id: string) => `${API_BASE}/approvals/${id}/decide`,
@@ -62,16 +63,26 @@ export const ENDPOINTS = {
62
63
  `${API_BASE}/orgs/${orgId}/agents/${agentId}/activity`,
63
64
  AGENT_MONITOR: (orgId: string, agentId: string) =>
64
65
  `${API_BASE}/orgs/${orgId}/agents/${agentId}/monitor`,
65
- AGENT_START: (orgId: string, agentId: string) =>
66
- `${API_BASE}/orgs/${orgId}/agents/${agentId}/start`,
67
- AGENT_STOP: (orgId: string, agentId: string) =>
68
- `${API_BASE}/orgs/${orgId}/agents/${agentId}/stop`,
69
- AGENT_HOSTED_STATUS: (orgId: string, agentId: string) =>
70
- `${API_BASE}/orgs/${orgId}/agents/${agentId}/hosted-status`,
71
- AGENT_WORKSPACE: (orgId: string, agentId: string) =>
72
- `${API_BASE}/orgs/${orgId}/agents/${agentId}/workspace`,
73
- AGENT_LOGS: (orgId: string, agentId: string) =>
74
- `${API_BASE}/orgs/${orgId}/agents/${agentId}/logs`,
66
+ AGENT_RUNS: (orgId: string, agentId: string) =>
67
+ `${API_BASE}/orgs/${orgId}/agents/${agentId}/runs`,
68
+ AGENT_RUN: (orgId: string, agentId: string, runId: string) =>
69
+ `${API_BASE}/orgs/${orgId}/agents/${agentId}/runs/${runId}`,
70
+ AGENT_RUN_STEPS: (orgId: string, agentId: string, runId: string) =>
71
+ `${API_BASE}/orgs/${orgId}/agents/${agentId}/runs/${runId}/steps`,
72
+ // Machines (org-scoped)
73
+ MACHINES: (orgId: string) => `${API_BASE}/orgs/${orgId}/machines`,
74
+ MACHINE: (orgId: string, machineId: string) =>
75
+ `${API_BASE}/orgs/${orgId}/machines/${machineId}`,
76
+ MACHINE_START: (orgId: string, machineId: string) =>
77
+ `${API_BASE}/orgs/${orgId}/machines/${machineId}/start`,
78
+ MACHINE_STOP: (orgId: string, machineId: string) =>
79
+ `${API_BASE}/orgs/${orgId}/machines/${machineId}/stop`,
80
+ MACHINE_STATUS: (orgId: string, machineId: string) =>
81
+ `${API_BASE}/orgs/${orgId}/machines/${machineId}/status`,
82
+ MACHINE_LOGS: (orgId: string, machineId: string) =>
83
+ `${API_BASE}/orgs/${orgId}/machines/${machineId}/logs`,
84
+ MACHINE_WORKSPACE: (orgId: string, machineId: string) =>
85
+ `${API_BASE}/orgs/${orgId}/machines/${machineId}/workspace`,
75
86
 
76
87
  // Tasks (org-scoped)
77
88
  TASKS: (orgId: string) => `${API_BASE}/orgs/${orgId}/tasks`,
@@ -82,12 +93,38 @@ export const ENDPOINTS = {
82
93
  `${API_BASE}/orgs/${orgId}/tasks/${taskId}/relations`,
83
94
  TASK_RELATION: (orgId: string, taskId: string, relId: string) =>
84
95
  `${API_BASE}/orgs/${orgId}/tasks/${taskId}/relations/${relId}`,
96
+ TASK_COMMENTS: (orgId: string, taskId: string) =>
97
+ `${API_BASE}/orgs/${orgId}/tasks/${taskId}/comments`,
98
+ TASK_COMMENT: (orgId: string, taskId: string, commentId: string) =>
99
+ `${API_BASE}/orgs/${orgId}/tasks/${taskId}/comments/${commentId}`,
100
+ TASK_ACTIVITIES: (orgId: string, taskId: string) =>
101
+ `${API_BASE}/orgs/${orgId}/tasks/${taskId}/activities`,
102
+
103
+ // Projects (org-scoped)
104
+ PROJECTS: (orgId: string) => `${API_BASE}/orgs/${orgId}/projects`,
105
+ PROJECT: (orgId: string, projectId: string) => `${API_BASE}/orgs/${orgId}/projects/${projectId}`,
106
+
107
+ // Invitations (org-scoped, admin)
108
+ ORG_INVITATIONS: (orgId: string) => `${API_BASE}/orgs/${orgId}/invitations`,
109
+ ORG_INVITATION: (orgId: string, invId: string) =>
110
+ `${API_BASE}/orgs/${orgId}/invitations/${invId}`,
111
+ ORG_INVITATION_RESEND: (orgId: string, invId: string) =>
112
+ `${API_BASE}/orgs/${orgId}/invitations/${invId}/resend`,
113
+
114
+ // Invitations (invitee)
115
+ MY_INVITATIONS: `${API_BASE}/invitations/pending`,
116
+ INVITATION_ACCEPT: (id: string) => `${API_BASE}/invitations/${id}/accept`,
117
+ INVITATION_DECLINE: (id: string) => `${API_BASE}/invitations/${id}/decline`,
118
+ INVITATION_BY_TOKEN: (token: string) => `${API_BASE}/invitations/by-token/${token}`,
85
119
 
86
120
  // Unread
87
121
  UNREAD: `${API_BASE}/me/unread`,
88
122
  CHAT_READ: (orgId: string, chatId: string) =>
89
123
  `${API_BASE}/orgs/${orgId}/chats/${chatId}/read`,
90
124
 
125
+ // Platform config (agent-scoped, not org-scoped)
126
+ PLATFORM_CONFIG: `${API_BASE}/agents/platform-config`,
127
+
91
128
  } as const;
92
129
 
93
130
  // WebSocket event types
@@ -110,10 +147,24 @@ export const WS_EVENTS = {
110
147
  MESSAGE_DELETE: 'message.delete',
111
148
  TYPING_UPDATE: 'typing.update',
112
149
  CHAT_UPDATE: 'chat.update',
113
- APPROVAL_UPDATE: 'approval.update',
150
+ APPROVAL_UPDATE: 'approval.update', // deprecated
151
+ CARD_UPDATE: 'card.update',
114
152
  RECOVERY_OVERFLOW: 'recovery.overflow',
115
153
  MEMBERSHIP_CHANGED: 'membership.changed',
116
154
  TASK_CREATED: 'task.created',
117
155
  TASK_UPDATED: 'task.updated',
118
156
  TASK_DELETED: 'task.deleted',
157
+ TASK_COMMENT_CREATED: 'task.comment.created',
158
+ TASK_COMMENT_UPDATED: 'task.comment.updated',
159
+ TASK_COMMENT_DELETED: 'task.comment.deleted',
160
+ PROJECT_CREATED: 'project.created',
161
+ PROJECT_UPDATED: 'project.updated',
162
+ PROJECT_DELETED: 'project.deleted',
163
+ AGENT_RUN_NEW: 'agent_run.new',
164
+ AGENT_RUN_UPDATE: 'agent_run.update',
165
+ AGENT_STEP_NEW: 'agent_step.new',
166
+ INVITATION_NEW: 'invitation.new',
167
+ INVITATION_ACCEPTED: 'invitation.accepted',
168
+ INVITATION_DECLINED: 'invitation.declined',
169
+ AGENT_CONFIG_UPDATE: 'agent_config.update',
119
170
  } as const;
package/src/index.ts CHANGED
@@ -3,4 +3,4 @@ export * from './constants.js';
3
3
  export { PondClient, ApiError } from './client.js';
4
4
  export type { PondClientOptions } from './client.js';
5
5
  export { PondWs } from './ws.js';
6
- export type { PondWsOptions, WsConnectionState, WsEventType, WsEventHandler } from './ws.js';
6
+ export type { PondWsOptions, WsConnectionState, WsEventType, WsEventHandler, WsClientEventMap } from './ws.js';