@pstdio/sdk 0.7.0 → 0.8.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.
@@ -1 +1 @@
1
- export type { UpdateInstalledExtensionTemplateInput, UpdateInstalledExtensionTemplateResponse, } from "pstdio-api-contracts";
1
+ export type { CommandExecuteRequest, CommandExecuteResponse, DashboardExtensionMetadata, ExtensionCommandRecord, ExtensionDiagnostic, ExtensionMenuContribution, ExtensionNavigationRecord, ExtensionRecord, ExtensionRouteRecord, ExtensionSettingsPanelRecord, ExtensionViewRecord, ListExtensionAppearanceResponse, ListExtensionCommandsResponse, ListProjectExtensionsResponse, ProjectExtensionInstance, UpdateInstalledExtensionTemplateInput, UpdateInstalledExtensionTemplateResponse, } from "pstdio-api-contracts";
@@ -1,8 +1,9 @@
1
1
  export type { ActionResult, ExecuteActionInput } from "./actions";
2
2
  export type { SetupAgentInput, SetupAvailableAgentsInput, UpdateAgentInput } from "./agents";
3
- export type { UpdateInstalledExtensionTemplateInput, UpdateInstalledExtensionTemplateResponse } from "./extensions";
3
+ export type { CommandExecuteRequest, CommandExecuteResponse, DashboardExtensionMetadata, ExtensionCommandRecord, ExtensionDiagnostic, ExtensionMenuContribution, ExtensionNavigationRecord, ExtensionRecord, ExtensionRouteRecord, ExtensionSettingsPanelRecord, ExtensionViewRecord, ListExtensionAppearanceResponse, ListExtensionCommandsResponse, ListProjectExtensionsResponse, ProjectExtensionInstance, UpdateInstalledExtensionTemplateInput, UpdateInstalledExtensionTemplateResponse, } from "./extensions";
4
4
  export type { CreateProjectInput, RegisterRepoInput } from "./projects";
5
5
  export type { ApprovalInput, CreateSessionInput, FollowUpInput, ResolveSessionIdInput, ResolveSessionIdResponse, SessionConversationResponse, } from "./sessions";
6
+ export type { Settings, UpdateSettingsInput } from "./settings";
6
7
  export type { UpdateSkillInput } from "./skills";
7
8
  export type { CreateAttemptStatusInput, CreateStatusInput } from "./statuses";
8
9
  export type { CreateTagInput, CreateTagOptionInput, UpdateTagInput, UpdateTagOptionInput } from "./tags";
@@ -0,0 +1 @@
1
+ export type { Settings, UpdateSettingsInput } from "pstdio-api-contracts";
@@ -4,8 +4,10 @@ import { type ExtensionClient } from "./extensions";
4
4
  import { type ProjectClient } from "./projects";
5
5
  import type { ClientOptions } from "./request";
6
6
  import { type SessionClient } from "./sessions";
7
+ import { type SettingsClient } from "./settings";
7
8
  import { type SkillClient } from "./skills";
8
9
  import { type StatusClient } from "./statuses";
10
+ import { type SyncClient } from "./sync";
9
11
  import { type TagClient } from "./tags";
10
12
  import { type TemplateClient } from "./templates";
11
13
  import { type TicketClient } from "./tickets";
@@ -22,5 +24,7 @@ export type PstdioClient = {
22
24
  agents: AgentClient;
23
25
  actions: ActionClient;
24
26
  extensions: ExtensionClient;
27
+ settings: SettingsClient;
28
+ sync: SyncClient;
25
29
  };
26
30
  export declare const createClient: (options?: ClientOptions) => PstdioClient;
@@ -3,10 +3,13 @@ export type { AgentClient } from "./agents";
3
3
  export { createClient, type PstdioClient } from "./client";
4
4
  export type { ExtensionClient } from "./extensions";
5
5
  export type { ProjectClient } from "./projects";
6
- export { type ClientOptions, createRequest, PstdioApiError, type RequestFn } from "./request";
7
- export type { SessionClient } from "./sessions";
6
+ export { type ClientOptions, createRequest, PstdioApiError, type RequestFn, type RequestOptions, } from "./request";
7
+ export type { ListSessionsInput, SessionClient, SessionStreamConnection, SessionStreamHandlers } from "./sessions";
8
+ export type { SettingsClient } from "./settings";
8
9
  export type { SkillClient } from "./skills";
10
+ export type { SseEvent } from "./sse";
9
11
  export type { StatusClient } from "./statuses";
12
+ export { applySyncEvent, parseSyncDeleteEvent, type StartSyncInput, type SyncClient, type SyncConnection, type SyncRow, type SyncWriter, type SyncWriterProvider, } from "./sync";
10
13
  export type { TagClient } from "./tags";
11
14
  export type { TemplateClient } from "./templates";
12
15
  export type { TicketClient } from "./tickets";
@@ -107,20 +107,32 @@ ${hookOutput}` : message;
107
107
  }
108
108
  return `Request failed: ${status}`;
109
109
  };
110
+ var resolveBaseUrl = (options) => options.baseUrl ?? (typeof process !== "undefined" ? process.env.PSTDIO_API_URL : undefined) ?? "http://localhost:19840";
111
+ var resolveClientUrl = (baseUrl, path) => path.startsWith("http://") || path.startsWith("https://") ? path : `${baseUrl}${path}`;
112
+ var resolveFetch = (options) => options.fetch ?? globalThis.fetch;
113
+ var createRequestHeaders = (options, reqOpts = {}) => {
114
+ const headers = new Headers(reqOpts.headers);
115
+ if (reqOpts.hasJsonBody && !headers.has("content-type"))
116
+ headers.set("content-type", "application/json");
117
+ if (options.token)
118
+ headers.set("authorization", `Bearer ${options.token}`);
119
+ return headers;
120
+ };
110
121
  var createRequest = (options) => {
111
- const baseUrl = options.baseUrl ?? (typeof process !== "undefined" ? process.env.PSTDIO_API_URL : undefined) ?? "http://localhost:19840";
112
- const fetchFn = options.fetch ?? globalThis.fetch;
122
+ const baseUrl = resolveBaseUrl(options);
123
+ const fetchFn = resolveFetch(options);
113
124
  return async (path, reqOpts = {}) => {
114
- const headers = {};
115
- if (reqOpts.body !== undefined)
116
- headers["content-type"] = "application/json";
117
- if (options.token)
118
- headers.authorization = `Bearer ${options.token}`;
119
- const response = await fetchFn(`${baseUrl}${path}`, {
125
+ const headers = createRequestHeaders(options, {
126
+ headers: reqOpts.headers,
127
+ hasJsonBody: reqOpts.body !== undefined
128
+ });
129
+ const url = resolveClientUrl(baseUrl, path);
130
+ const response = await fetchFn(url, {
120
131
  method: reqOpts.method ?? "GET",
121
- headers,
132
+ headers: Object.fromEntries(headers.entries()),
122
133
  body: reqOpts.body !== undefined ? JSON.stringify(reqOpts.body) : undefined,
123
- signal: reqOpts.signal
134
+ signal: reqOpts.signal,
135
+ cache: reqOpts.cache
124
136
  });
125
137
  if (!response.ok) {
126
138
  const errorBody = await response.json().catch(() => null);
@@ -133,8 +145,105 @@ var createRequest = (options) => {
133
145
  };
134
146
  };
135
147
 
148
+ // src/client/sse.ts
149
+ var parseSseEvent = (part) => {
150
+ let event = "message";
151
+ const data = [];
152
+ for (const line of part.split(`
153
+ `)) {
154
+ if (line.startsWith("event: ")) {
155
+ event = line.slice(7);
156
+ continue;
157
+ }
158
+ if (line.startsWith("data: ")) {
159
+ data.push(line.slice(6));
160
+ }
161
+ }
162
+ if (!event || data.length === 0)
163
+ return null;
164
+ return { event, data: data.join(`
165
+ `) };
166
+ };
167
+ var consumeSseBuffer = (buffer) => {
168
+ const parts = buffer.split(`
169
+
170
+ `);
171
+ const remainder = parts.pop() ?? "";
172
+ const events = parts.map((part) => parseSseEvent(part)).filter((event) => event !== null);
173
+ return { remainder, events };
174
+ };
175
+ var readSseStream = async (body, onEvent, options = {}) => {
176
+ const reader = body.getReader();
177
+ const decoder = new TextDecoder;
178
+ let buffer = "";
179
+ const abort = () => {
180
+ reader.cancel();
181
+ };
182
+ options.signal?.addEventListener("abort", abort, { once: true });
183
+ try {
184
+ while (!options.signal?.aborted) {
185
+ const { done, value } = await reader.read();
186
+ if (done)
187
+ break;
188
+ buffer += decoder.decode(value, { stream: true });
189
+ const { remainder, events } = consumeSseBuffer(buffer);
190
+ buffer = remainder;
191
+ for (const event of events) {
192
+ onEvent(event);
193
+ }
194
+ }
195
+ } finally {
196
+ options.signal?.removeEventListener("abort", abort);
197
+ }
198
+ };
199
+
136
200
  // src/client/sessions.ts
137
- var createSessionClient = (request) => ({
201
+ var buildSessionsQuery = (projectId, input = {}) => {
202
+ const params = new URLSearchParams({ project_id: projectId });
203
+ if (input.status)
204
+ params.append("status", input.status);
205
+ if (input.agent)
206
+ params.append("agent", input.agent);
207
+ if (input.archived)
208
+ params.append("archived", "true");
209
+ return params.toString();
210
+ };
211
+ var buildSessionStreamPath = (sessionId, attempt) => {
212
+ const path = `/v1/sessions/${sessionId}/stream`;
213
+ return attempt === undefined ? path : `${path}?attempt=${attempt}`;
214
+ };
215
+ var parseEventData = (data) => {
216
+ if (data.length === 0)
217
+ return null;
218
+ return JSON.parse(data);
219
+ };
220
+ var streamSessionEvents = async (clientOptions, path, onEvent, options = {}) => {
221
+ const response = await resolveFetch(clientOptions)(resolveClientUrl(resolveBaseUrl(clientOptions), path), {
222
+ headers: Object.fromEntries(createRequestHeaders(clientOptions).entries()),
223
+ signal: options.signal
224
+ });
225
+ if (!response.ok || !response.body)
226
+ throw new Error(`Connection failed: ${response.status}`);
227
+ await readSseStream(response.body, onEvent, { signal: options.signal });
228
+ };
229
+ var dispatchSessionStreamEvent = (event, handlers) => {
230
+ if (event.event === "ready") {
231
+ handlers.onReady?.(parseEventData(event.data));
232
+ return;
233
+ }
234
+ if (event.event === "patch") {
235
+ handlers.onPatch?.(parseEventData(event.data));
236
+ return;
237
+ }
238
+ if (event.event === "approval_request") {
239
+ handlers.onApprovalRequest?.(parseEventData(event.data));
240
+ return;
241
+ }
242
+ if (event.event === "end") {
243
+ handlers.onEnd?.(parseEventData(event.data));
244
+ }
245
+ };
246
+ var createSessionClient = (request, clientOptions) => ({
138
247
  listActivity: (sessionId, input = {}) => {
139
248
  const params = new URLSearchParams;
140
249
  if (input.event_type)
@@ -150,7 +259,7 @@ var createSessionClient = (request) => ({
150
259
  const query = params.toString();
151
260
  return request(`/v1/sessions/${sessionId}/activity${query ? `?${query}` : ""}`);
152
261
  },
153
- list: (projectId) => request(`/v1/sessions?project_id=${projectId}`),
262
+ list: (projectId, input) => request(`/v1/sessions?${buildSessionsQuery(projectId, input)}`),
154
263
  get: (sessionId) => request(`/v1/sessions/${sessionId}`),
155
264
  create: (input) => request("/v1/sessions", { method: "POST", body: input }),
156
265
  archive: (sessionId) => request(`/v1/sessions/${sessionId}/archive`, { method: "POST" }),
@@ -158,7 +267,29 @@ var createSessionClient = (request) => ({
158
267
  approve: (sessionId, input) => request(`/v1/sessions/${sessionId}/approve`, { method: "POST", body: input }),
159
268
  getConversation: (sessionId) => request(`/v1/sessions/${sessionId}/conversation`),
160
269
  resolveSessionId: (input) => request("/v1/sessions/resolve-session-id", { method: "POST", body: input }),
161
- updateStatus: (sessionId, status) => request(`/v1/sessions/${sessionId}/status`, { method: "PATCH", body: { status } })
270
+ updateStatus: (sessionId, status) => request(`/v1/sessions/${sessionId}/status`, { method: "PATCH", body: { status } }),
271
+ stream: (sessionId, onEvent, options = {}) => streamSessionEvents(clientOptions, buildSessionStreamPath(sessionId), onEvent, options),
272
+ connectStream: (sessionId, handlers, options = {}) => {
273
+ const abortController = new AbortController;
274
+ let closed = false;
275
+ streamSessionEvents(clientOptions, buildSessionStreamPath(sessionId, options.attempt), (event) => dispatchSessionStreamEvent(event, handlers), { signal: abortController.signal }).catch((error) => {
276
+ if (closed || error.name === "AbortError")
277
+ return;
278
+ handlers.onError?.(error);
279
+ });
280
+ return {
281
+ close: () => {
282
+ closed = true;
283
+ abortController.abort();
284
+ }
285
+ };
286
+ }
287
+ });
288
+
289
+ // src/client/settings.ts
290
+ var createSettingsClient = (request) => ({
291
+ get: () => request("/v1/settings"),
292
+ update: (input) => request("/v1/settings", { method: "PATCH", body: input })
162
293
  });
163
294
 
164
295
  // src/client/skills.ts
@@ -181,6 +312,137 @@ var createStatusClient = (request) => ({
181
312
  deleteAttemptStatus: (projectId, statusId) => request(`/v1/projects/${projectId}/attempt-statuses/${statusId}`, { method: "DELETE" })
182
313
  });
183
314
 
315
+ // src/client/sync.ts
316
+ var DEFAULT_HEARTBEAT_INTERVAL_MS = 1e4;
317
+ var DEFAULT_HEARTBEAT_THRESHOLD = 3;
318
+ var DEFAULT_RECONNECT_DELAY_MS = 1000;
319
+ var parseSyncDeleteEvent = (data) => {
320
+ const parsed = JSON.parse(data);
321
+ const id = parsed.id ?? parsed.data?.id;
322
+ if (!id) {
323
+ throw new Error("Missing id in sync:delete event.");
324
+ }
325
+ return {
326
+ table: parsed.table,
327
+ id,
328
+ seq: parsed.seq
329
+ };
330
+ };
331
+ var applySyncEvent = (event, data, writers) => {
332
+ if (event === "init") {
333
+ const parsed = JSON.parse(data);
334
+ for (const [table, rows] of Object.entries(parsed.tables)) {
335
+ writers.getWriter(table)?.truncateAndWrite(rows);
336
+ }
337
+ return parsed.seq;
338
+ }
339
+ if (event === "sync:set") {
340
+ const parsed = JSON.parse(data);
341
+ writers.getWriter(parsed.table)?.upsert(parsed.data);
342
+ return parsed.seq;
343
+ }
344
+ if (event === "sync:delete") {
345
+ const parsed = parseSyncDeleteEvent(data);
346
+ writers.getWriter(parsed.table)?.remove(parsed.id);
347
+ return parsed.seq;
348
+ }
349
+ if (event === "heartbeat") {
350
+ return JSON.parse(data).seq;
351
+ }
352
+ return null;
353
+ };
354
+ var createHeartbeatMonitor = (input) => {
355
+ let missedHeartbeats = 0;
356
+ let timer = null;
357
+ return {
358
+ recordHeartbeat: () => {
359
+ missedHeartbeats = 0;
360
+ },
361
+ start: () => {
362
+ timer = setInterval(() => {
363
+ missedHeartbeats += 1;
364
+ if (missedHeartbeats >= input.threshold) {
365
+ input.onConnectionLost?.();
366
+ }
367
+ }, input.intervalMs);
368
+ },
369
+ stop: () => {
370
+ if (timer)
371
+ clearInterval(timer);
372
+ timer = null;
373
+ }
374
+ };
375
+ };
376
+ var buildSyncStreamPath = (lastSeq) => lastSeq > 0 ? `/v1/sync/stream?since=${lastSeq}` : "/v1/sync/stream";
377
+ var createSyncClient = (clientOptions) => ({
378
+ start: (input) => {
379
+ let lastSeq = 0;
380
+ let abortController = null;
381
+ let reconnectTimer = null;
382
+ let closed = false;
383
+ const state = { close: () => {}, connected: false };
384
+ const heartbeatMonitor = createHeartbeatMonitor({
385
+ intervalMs: input.heartbeatIntervalMs ?? DEFAULT_HEARTBEAT_INTERVAL_MS,
386
+ threshold: input.heartbeatThreshold ?? DEFAULT_HEARTBEAT_THRESHOLD,
387
+ onConnectionLost: input.onConnectionLost
388
+ });
389
+ const setConnected = () => {
390
+ const wasConnected = state.connected;
391
+ state.connected = true;
392
+ if (!wasConnected)
393
+ input.onConnected?.();
394
+ };
395
+ const setDisconnected = () => {
396
+ const wasConnected = state.connected;
397
+ state.connected = false;
398
+ if (wasConnected)
399
+ input.onDisconnected?.();
400
+ };
401
+ const handleEvent = (event, data) => {
402
+ heartbeatMonitor.recordHeartbeat();
403
+ const nextSeq = applySyncEvent(event, data, input);
404
+ if (nextSeq !== null)
405
+ lastSeq = Math.max(lastSeq, nextSeq);
406
+ if (event === "init")
407
+ setConnected();
408
+ };
409
+ const connect = async () => {
410
+ if (closed)
411
+ return;
412
+ abortController = new AbortController;
413
+ try {
414
+ const response = await resolveFetch(clientOptions)(resolveClientUrl(resolveBaseUrl(clientOptions), buildSyncStreamPath(lastSeq)), {
415
+ headers: Object.fromEntries(createRequestHeaders(clientOptions).entries()),
416
+ signal: abortController.signal
417
+ });
418
+ if (!response.ok || !response.body)
419
+ throw new Error("SSE connection failed");
420
+ await readSseStream(response.body, ({ event, data }) => handleEvent(event, data), {
421
+ signal: abortController.signal
422
+ });
423
+ } catch (err) {
424
+ if (closed || err.name === "AbortError")
425
+ return;
426
+ }
427
+ if (closed)
428
+ return;
429
+ setDisconnected();
430
+ reconnectTimer = setTimeout(connect, input.reconnectDelayMs ?? DEFAULT_RECONNECT_DELAY_MS);
431
+ };
432
+ heartbeatMonitor.start();
433
+ connect();
434
+ state.close = () => {
435
+ closed = true;
436
+ state.connected = false;
437
+ heartbeatMonitor.stop();
438
+ abortController?.abort();
439
+ if (reconnectTimer)
440
+ clearTimeout(reconnectTimer);
441
+ };
442
+ return state;
443
+ }
444
+ });
445
+
184
446
  // src/client/tags.ts
185
447
  var createTagClient = (request) => ({
186
448
  list: (projectId) => request(`/v1/projects/${projectId}/ticket-tags`),
@@ -202,7 +464,7 @@ var createTemplateClient = (request) => ({
202
464
  });
203
465
 
204
466
  // src/client/tickets.ts
205
- var resolveBaseUrl = (options) => options.baseUrl ?? (typeof process !== "undefined" ? process.env.PSTDIO_API_URL : undefined) ?? "http://localhost:19840";
467
+ var resolveBaseUrl2 = (options) => options.baseUrl ?? (typeof process !== "undefined" ? process.env.PSTDIO_API_URL : undefined) ?? "http://localhost:19840";
206
468
  var readErrorMessage2 = async (response) => {
207
469
  const errorBody = await response.json().catch(() => null);
208
470
  if (errorBody && typeof errorBody === "object" && errorBody !== null && "error" in errorBody) {
@@ -265,13 +527,13 @@ var createTicketClient = (request, options = {}) => ({
265
527
  },
266
528
  updateWhenAttemptStatus: (ticketId, input) => request(`/v1/tickets/${ticketId}/update-when-attempt-status`, { method: "POST", body: input }),
267
529
  listFiles: (ticketId) => request(`/v1/tickets/${ticketId}/files`),
268
- getFileContent: async (ticketId, fileId) => {
269
- const baseUrl = resolveBaseUrl(options);
530
+ getFileContent: async (ticketId, fileId, requestOptions = {}) => {
531
+ const baseUrl = resolveBaseUrl2(options);
270
532
  const fetchFn = options.fetch ?? globalThis.fetch;
271
533
  const headers = {};
272
534
  if (options.token)
273
535
  headers.authorization = `Bearer ${options.token}`;
274
- const response = await fetchFn(`${baseUrl}/v1/tickets/${encodeURIComponent(ticketId)}/files/${encodeURIComponent(fileId)}/content`, { headers });
536
+ const response = await fetchFn(`${baseUrl}/v1/tickets/${encodeURIComponent(ticketId)}/files/${encodeURIComponent(fileId)}/content`, { cache: requestOptions.cache, headers, signal: requestOptions.signal });
275
537
  if (!response.ok) {
276
538
  throw new PstdioApiError(await readErrorMessage2(response), response.status);
277
539
  }
@@ -313,18 +575,22 @@ var createClient = (options = {}) => {
313
575
  projects: createProjectClient(request),
314
576
  tickets: createTicketClient(request, options),
315
577
  workspaces: createWorkspaceClient(request),
316
- sessions: createSessionClient(request),
578
+ sessions: createSessionClient(request, options),
317
579
  statuses: createStatusClient(request),
318
580
  tags: createTagClient(request),
319
581
  templates: createTemplateClient(request),
320
582
  skills: createSkillClient(request),
321
583
  agents: createAgentClient(request),
322
584
  actions: createActionClient(request),
323
- extensions: createExtensionClient(request)
585
+ extensions: createExtensionClient(request),
586
+ settings: createSettingsClient(request),
587
+ sync: createSyncClient(options)
324
588
  };
325
589
  };
326
590
  export {
591
+ parseSyncDeleteEvent,
327
592
  createRequest,
328
593
  createClient,
594
+ applySyncEvent,
329
595
  PstdioApiError
330
596
  };
@@ -11,6 +11,15 @@ export type RequestOptions = {
11
11
  method?: string;
12
12
  body?: unknown;
13
13
  signal?: AbortSignal;
14
+ headers?: HeadersInit;
15
+ cache?: RequestCache;
14
16
  };
15
17
  export type RequestFn = <T>(path: string, options?: RequestOptions) => Promise<T>;
18
+ export declare const resolveBaseUrl: (options: ClientOptions) => string;
19
+ export declare const resolveClientUrl: (baseUrl: string, path: string) => string;
20
+ export declare const resolveFetch: (options: ClientOptions) => typeof fetch;
21
+ export declare const createRequestHeaders: (options: ClientOptions, reqOpts?: {
22
+ headers?: HeadersInit;
23
+ hasJsonBody?: boolean;
24
+ }) => Headers;
16
25
  export declare const createRequest: (options: ClientOptions) => RequestFn;
@@ -1,8 +1,14 @@
1
1
  import type { ApprovalInput, CreateSessionInput, FollowUpInput, FollowUpResponse, ListSessionActivityInput, ListSessionActivityResponse, ResolveSessionIdInput, ResolveSessionIdResponse, SessionConversationResponse } from "pstdio-api-contracts";
2
2
  import type { Session } from "../resources";
3
- import type { RequestFn } from "./request";
3
+ import { type ClientOptions, type RequestFn } from "./request";
4
+ import { type SseEvent } from "./sse";
5
+ export type ListSessionsInput = {
6
+ status?: string;
7
+ agent?: string;
8
+ archived?: boolean;
9
+ };
4
10
  export type SessionClient = {
5
- list(projectId: string): Promise<Session[]>;
11
+ list(projectId: string, input?: ListSessionsInput): Promise<Session[]>;
6
12
  get(sessionId: string): Promise<Session>;
7
13
  create(input: CreateSessionInput): Promise<Session>;
8
14
  archive(sessionId: string): Promise<void>;
@@ -12,5 +18,21 @@ export type SessionClient = {
12
18
  resolveSessionId(input: ResolveSessionIdInput): Promise<ResolveSessionIdResponse>;
13
19
  updateStatus(sessionId: string, status: string): Promise<Session>;
14
20
  listActivity(sessionId: string, input?: ListSessionActivityInput): Promise<ListSessionActivityResponse>;
21
+ stream(sessionId: string, onEvent: (event: SseEvent) => void, options?: {
22
+ signal?: AbortSignal;
23
+ }): Promise<void>;
24
+ connectStream(sessionId: string, handlers: SessionStreamHandlers, options?: {
25
+ attempt?: number;
26
+ }): SessionStreamConnection;
27
+ };
28
+ export type SessionStreamHandlers = {
29
+ onReady?: (data: unknown) => void;
30
+ onPatch?: (data: unknown) => void;
31
+ onApprovalRequest?: (data: unknown) => void;
32
+ onEnd?: (data: unknown) => void;
33
+ onError?: (error: unknown) => void;
34
+ };
35
+ export type SessionStreamConnection = {
36
+ close: () => void;
15
37
  };
16
- export declare const createSessionClient: (request: RequestFn) => SessionClient;
38
+ export declare const createSessionClient: (request: RequestFn, clientOptions: ClientOptions) => SessionClient;
@@ -0,0 +1,7 @@
1
+ import type { Settings, UpdateSettingsInput } from "../api/settings";
2
+ import type { RequestFn } from "./request";
3
+ export type SettingsClient = {
4
+ get(): Promise<Settings>;
5
+ update(input: UpdateSettingsInput): Promise<Settings>;
6
+ };
7
+ export declare const createSettingsClient: (request: RequestFn) => SettingsClient;
@@ -0,0 +1,15 @@
1
+ export type SseEvent = {
2
+ event: string;
3
+ data: string;
4
+ };
5
+ export declare const parseSseEvent: (part: string) => {
6
+ event: string;
7
+ data: string;
8
+ } | null;
9
+ export declare const consumeSseBuffer: (buffer: string) => {
10
+ remainder: string;
11
+ events: SseEvent[];
12
+ };
13
+ export declare const readSseStream: (body: ReadableStream<Uint8Array>, onEvent: (event: SseEvent) => void, options?: {
14
+ signal?: AbortSignal;
15
+ }) => Promise<void>;
@@ -0,0 +1,35 @@
1
+ import { type ClientOptions } from "./request";
2
+ export type SyncRow = {
3
+ id: string;
4
+ [key: string]: unknown;
5
+ };
6
+ export type SyncWriter = {
7
+ truncateAndWrite: (rows: SyncRow[]) => void;
8
+ upsert: (row: SyncRow) => void;
9
+ remove: (id: string) => void;
10
+ };
11
+ export type SyncWriterProvider = {
12
+ getWriter: (table: string) => SyncWriter | undefined;
13
+ };
14
+ export type SyncConnection = {
15
+ close: () => void;
16
+ connected: boolean;
17
+ };
18
+ export type StartSyncInput = SyncWriterProvider & {
19
+ onConnected?: () => void;
20
+ onDisconnected?: () => void;
21
+ onConnectionLost?: () => void;
22
+ heartbeatIntervalMs?: number;
23
+ heartbeatThreshold?: number;
24
+ reconnectDelayMs?: number;
25
+ };
26
+ export type SyncClient = {
27
+ start: (input: StartSyncInput) => SyncConnection;
28
+ };
29
+ export declare const parseSyncDeleteEvent: (data: string) => {
30
+ table: string;
31
+ id: string;
32
+ seq: number;
33
+ };
34
+ export declare const applySyncEvent: (event: string, data: string, writers: SyncWriterProvider) => number | null;
35
+ export declare const createSyncClient: (clientOptions: ClientOptions) => SyncClient;
@@ -12,7 +12,10 @@ export type TicketClient = {
12
12
  listProjectActivity(projectId: string, input?: ListProjectActivityForTicketsInput): Promise<ListTicketActivityResponse>;
13
13
  updateWhenAttemptStatus(ticketId: string, input: UpdateWhenAttemptStatusInput): Promise<UpdateWhenAttemptStatusResponse>;
14
14
  listFiles(ticketId: string): Promise<TicketFile[]>;
15
- getFileContent(ticketId: string, fileId: string): Promise<Uint8Array>;
15
+ getFileContent(ticketId: string, fileId: string, options?: {
16
+ cache?: RequestCache;
17
+ signal?: AbortSignal;
18
+ }): Promise<Uint8Array>;
16
19
  uploadFile(ticketId: string, input: UploadTicketFileInput): Promise<TicketFile>;
17
20
  deleteFile(ticketId: string, fileId: string): Promise<void>;
18
21
  };
@@ -1,7 +1,7 @@
1
1
  export { defineCommand, defineHook, defineMiddleware } from "./define-command";
2
2
  export { defineExtension } from "./define-extension";
3
3
  export { defineExtensionView, type ExtensionViewModule, type ExtensionViewRender, type ExtensionViewRenderContext, type GuestHost, type PropsStore, } from "./define-extension-view";
4
- export { projectEvents, projectSlots, sessionEvents, sessionSlots, workspaceEvents, workspaceSlots, } from "./kernel-slots";
4
+ export { attemptStatusEvents, gitEvents, projectEvents, projectSlots, sessionEvents, sessionSlots, ticketEvents, workspaceCommands, workspaceEvents, workspaceSlots, worktreeEvents, } from "./kernel-slots";
5
5
  export { packageAsset } from "./package-asset";
6
6
  export { params } from "./params";
7
7
  export { commandEvent, commandRef, commandsOf, eventRef } from "./refs";
@@ -60,13 +60,39 @@ var projectEvents = {
60
60
  };
61
61
  var sessionEvents = {
62
62
  started: eventRef("session.started"),
63
+ resumed: eventRef("session.resumed"),
64
+ awaitingInput: eventRef("session.awaitingInput"),
65
+ succeeded: eventRef("session.succeeded"),
66
+ failed: eventRef("session.failed"),
63
67
  completed: eventRef("session.completed")
64
68
  };
69
+ var ticketEvents = {
70
+ created: eventRef("ticket.created"),
71
+ statusChanged: eventRef("ticket.statusChanged"),
72
+ archived: eventRef("ticket.archived"),
73
+ deleted: eventRef("ticket.deleted")
74
+ };
65
75
  var workspaceEvents = {
66
76
  created: eventRef("workspace.created"),
67
77
  archived: eventRef("workspace.archived"),
68
78
  deleted: eventRef("workspace.deleted")
69
79
  };
80
+ var worktreeEvents = {
81
+ created: eventRef("worktree.created"),
82
+ removed: eventRef("worktree.removed")
83
+ };
84
+ var gitEvents = {
85
+ committed: eventRef("git.committed"),
86
+ rebased: eventRef("git.rebased"),
87
+ merged: eventRef("git.merged"),
88
+ conflicted: eventRef("git.conflicted")
89
+ };
90
+ var attemptStatusEvents = {
91
+ changed: eventRef("attemptStatus.changed")
92
+ };
93
+ var workspaceCommands = {
94
+ setAttemptStatus: commandRef("kernel.workspace.setAttemptStatus")
95
+ };
70
96
  // src/extensions/package-asset.ts
71
97
  var packageAsset = (path, baseUrl) => ({
72
98
  kind: "package-asset",
@@ -115,14 +141,18 @@ var WEBVIEW_HOST_CAPABILITIES = [
115
141
  ...ALWAYS_AVAILABLE_WEBVIEW_CAPABILITIES
116
142
  ];
117
143
  export {
144
+ worktreeEvents,
118
145
  workspaceSlots,
119
146
  workspaceEvents,
147
+ workspaceCommands,
148
+ ticketEvents,
120
149
  sessionSlots,
121
150
  sessionEvents,
122
151
  projectSlots,
123
152
  projectEvents,
124
153
  params,
125
154
  packageAsset,
155
+ gitEvents,
126
156
  eventRef,
127
157
  defineSlot,
128
158
  defineMiddleware,
@@ -133,6 +163,7 @@ export {
133
163
  commandsOf,
134
164
  commandRef,
135
165
  commandEvent,
166
+ attemptStatusEvents,
136
167
  WEBVIEW_HOST_CAPABILITY_VERSION,
137
168
  WEBVIEW_HOST_CAPABILITIES,
138
169
  WEBVIEW_DECLARABLE_CAPABILITIES,
@@ -1,5 +1,101 @@
1
+ import type { SetAttemptStatusInput, SetAttemptStatusResult } from "./types/context";
1
2
  import type { JsonObject } from "./types/json";
2
3
  import type { ResourceAnchor } from "./types/resources";
4
+ export interface TicketArchivedEventPayload {
5
+ projectId: string;
6
+ ticket: {
7
+ id: string;
8
+ shorthand: string;
9
+ displayTitle: string | null;
10
+ userPrompt: string | null;
11
+ parentId: string | null;
12
+ draft: boolean;
13
+ archived: boolean;
14
+ status: string | null;
15
+ tagIds: string[];
16
+ tagNames: string[];
17
+ fileIds: string[];
18
+ };
19
+ }
20
+ export interface WorktreeCreatedEventPayload {
21
+ projectId: string;
22
+ repoPath: string;
23
+ worktreePath: string;
24
+ branch: string;
25
+ workspace: string;
26
+ workspaceId: string;
27
+ ticket: string;
28
+ }
29
+ export interface TicketLifecyclePayload {
30
+ projectId: string;
31
+ ticket: JsonObject;
32
+ }
33
+ export interface TicketStatusChangePayload {
34
+ projectId: string;
35
+ ticket: JsonObject;
36
+ fromStatus: string | null;
37
+ toStatus: string;
38
+ }
39
+ export interface AttemptStatusChangePayload {
40
+ projectId: string;
41
+ workspaceId: string;
42
+ ticket: JsonObject | null;
43
+ fromStatus: string | null;
44
+ toStatus: string;
45
+ statusChangeId?: string;
46
+ sessionId: string | null;
47
+ originalSessionId: string | null;
48
+ worktreePath: string | null;
49
+ workspace: JsonObject;
50
+ }
51
+ export interface SessionLifecyclePayload {
52
+ projectId: string;
53
+ sessionId: string;
54
+ sessionStatus?: string;
55
+ originalSessionId?: string;
56
+ workspace?: JsonObject;
57
+ workspaceId?: string;
58
+ worktreePath?: string;
59
+ branch?: string;
60
+ ticket?: JsonObject;
61
+ attemptStatus?: string;
62
+ }
63
+ export interface WorktreeRemovedPayload {
64
+ projectId: string;
65
+ repoPath?: string;
66
+ worktreePath: string;
67
+ workspace?: JsonObject;
68
+ workspaceId?: string;
69
+ ticket?: JsonObject | string;
70
+ }
71
+ export interface CommitPayload {
72
+ projectId: string;
73
+ repoPath?: string;
74
+ worktreePath?: string;
75
+ branch?: string;
76
+ commitSha?: string;
77
+ workspace?: JsonObject;
78
+ ticket?: JsonObject | string;
79
+ }
80
+ export interface RebasePayload {
81
+ projectId: string;
82
+ repoPath?: string;
83
+ worktreePath?: string;
84
+ branch?: string;
85
+ workspace?: JsonObject;
86
+ ticket?: JsonObject | string;
87
+ }
88
+ export interface MergePayload extends CommitPayload {
89
+ }
90
+ export interface ConflictPayload {
91
+ projectId: string;
92
+ operation: "rebase" | "merge";
93
+ repoPath?: string;
94
+ worktreePath?: string;
95
+ branch?: string;
96
+ workspace?: JsonObject;
97
+ ticket?: JsonObject | string;
98
+ }
3
99
  export declare const projectSlots: {
4
100
  sidebarNav: import("./types").SlotRef<object, "navigation">;
5
101
  sidebar: import("./types").SlotRef<object, "view">;
@@ -25,15 +121,23 @@ export declare const projectEvents: {
25
121
  }>;
26
122
  };
27
123
  export declare const sessionEvents: {
28
- started: import("./types").EventRef<{
29
- sessionId: string;
124
+ started: import("./types").EventRef<SessionLifecyclePayload & {
30
125
  anchors?: ResourceAnchor[];
31
126
  }>;
32
- completed: import("./types").EventRef<{
33
- sessionId: string;
127
+ resumed: import("./types").EventRef<SessionLifecyclePayload>;
128
+ awaitingInput: import("./types").EventRef<SessionLifecyclePayload>;
129
+ succeeded: import("./types").EventRef<SessionLifecyclePayload>;
130
+ failed: import("./types").EventRef<SessionLifecyclePayload>;
131
+ completed: import("./types").EventRef<SessionLifecyclePayload & {
34
132
  anchors?: ResourceAnchor[];
35
133
  }>;
36
134
  };
135
+ export declare const ticketEvents: {
136
+ created: import("./types").EventRef<TicketLifecyclePayload>;
137
+ statusChanged: import("./types").EventRef<TicketStatusChangePayload>;
138
+ archived: import("./types").EventRef<TicketArchivedEventPayload>;
139
+ deleted: import("./types").EventRef<TicketLifecyclePayload>;
140
+ };
37
141
  export declare const workspaceEvents: {
38
142
  created: import("./types").EventRef<{
39
143
  workspace: JsonObject;
@@ -45,3 +149,19 @@ export declare const workspaceEvents: {
45
149
  workspace: JsonObject;
46
150
  }>;
47
151
  };
152
+ export declare const worktreeEvents: {
153
+ created: import("./types").EventRef<WorktreeCreatedEventPayload>;
154
+ removed: import("./types").EventRef<WorktreeRemovedPayload>;
155
+ };
156
+ export declare const gitEvents: {
157
+ committed: import("./types").EventRef<CommitPayload>;
158
+ rebased: import("./types").EventRef<RebasePayload>;
159
+ merged: import("./types").EventRef<MergePayload>;
160
+ conflicted: import("./types").EventRef<ConflictPayload>;
161
+ };
162
+ export declare const attemptStatusEvents: {
163
+ changed: import("./types").EventRef<AttemptStatusChangePayload>;
164
+ };
165
+ export declare const workspaceCommands: {
166
+ setAttemptStatus: import("./types").CommandRef<SetAttemptStatusInput, SetAttemptStatusResult>;
167
+ };
@@ -82,11 +82,36 @@ export interface ExtensionSessionsApi {
82
82
  vars?: JsonObject;
83
83
  }): Promise<void>;
84
84
  }
85
+ export interface SetAttemptStatusInput {
86
+ workspaceId: string;
87
+ status: string;
88
+ sessionId?: string;
89
+ }
90
+ export interface SetAttemptStatusResult {
91
+ id: string;
92
+ attempt_status_id: string | null;
93
+ from_status: string | null;
94
+ to_status: string;
95
+ status_change_id: string;
96
+ }
85
97
  export interface ExtensionWorkspacesApi {
86
98
  get(id: string): Promise<unknown>;
87
99
  create(input: JsonObject): Promise<unknown>;
88
100
  archive(id: string): Promise<void>;
89
101
  delete(id: string): Promise<void>;
102
+ setAttemptStatus(input: SetAttemptStatusInput): Promise<SetAttemptStatusResult>;
103
+ }
104
+ export interface BootstrapWorktreeInput {
105
+ repoPath: string;
106
+ worktreePath: string;
107
+ ticketId?: string;
108
+ }
109
+ export interface RemoveWorktreesForTicketInput {
110
+ ticketId?: string;
111
+ }
112
+ export interface ExtensionWorktreesApi {
113
+ bootstrap(input: BootstrapWorktreeInput): Promise<void>;
114
+ removeAllForTicket(input: RemoveWorktreesForTicketInput): Promise<number>;
90
115
  }
91
116
  export interface ExtensionReposApi {
92
117
  list(): Promise<RepoContext[]>;
@@ -117,13 +142,15 @@ export interface ProcessRunResult {
117
142
  stdout: string;
118
143
  stderr: string;
119
144
  }
145
+ export interface ProcessRunInput {
146
+ command: string[];
147
+ cwd?: string;
148
+ env?: Record<string, string>;
149
+ timeoutMs?: number;
150
+ }
120
151
  export interface ExtensionProcessApi {
121
- run(input: {
122
- command: string[];
123
- cwd?: string;
124
- env?: Record<string, string>;
125
- timeoutMs?: number;
126
- }): Promise<ProcessRunResult>;
152
+ run(input: ProcessRunInput): Promise<ProcessRunResult>;
153
+ runOrThrow(input: ProcessRunInput): Promise<ProcessRunResult>;
127
154
  spawnDetached(input: {
128
155
  command: string[];
129
156
  cwd?: string;
@@ -160,6 +187,7 @@ export interface ExtensionContextBase {
160
187
  files: ExtensionFilesApi;
161
188
  sessions: ExtensionSessionsApi;
162
189
  workspaces: ExtensionWorkspacesApi;
190
+ worktrees: ExtensionWorktreesApi;
163
191
  repos: ExtensionReposApi;
164
192
  commands: CommandHelpersApi;
165
193
  events: ExtensionEventsApi;
@@ -164,5 +164,5 @@ export interface ExtensionLifecycle {
164
164
  export interface ExtensionDefinition extends UiContributions, BehaviourContributions, AssetContributions, ProviderContributions, ExtensionLifecycle {
165
165
  settings?: ParamObjectSchema;
166
166
  }
167
- export type ExtensionSourceKind = "local" | "package" | "builtin";
167
+ export type ExtensionSourceKind = "local" | "package";
168
168
  export {};
@@ -1,8 +1,11 @@
1
1
  export type { Repo } from "pstdio-api-contracts";
2
2
  export type { AgentAvailabilityType, AgentConfig, AgentInfo, AgentModel } from "./agent";
3
3
  export type { FileRecord } from "./file";
4
+ export type { KnownAgent, KnownAgentId } from "./known-agents";
5
+ export { findAgent, isKnownAgentId, KNOWN_AGENT_IDS, KNOWN_AGENTS } from "./known-agents";
4
6
  export type { Project } from "./project";
5
7
  export type { Session, SessionStatus } from "./session";
8
+ export type { Settings } from "./settings";
6
9
  export type { Skill, SkillFile, SkillWithContent } from "./skill";
7
10
  export type { AttemptStatus, Status } from "./status";
8
11
  export type { Tag, TagOption } from "./tag";
@@ -0,0 +1,26 @@
1
+ // src/resources/known-agents.ts
2
+ var KNOWN_AGENTS = [
3
+ {
4
+ id: "claude-code",
5
+ name: "Claude Code",
6
+ binary: "claude",
7
+ skillsDir: ".claude/skills",
8
+ globalSkillsDir: ".claude/skills"
9
+ },
10
+ {
11
+ id: "opencode",
12
+ name: "OpenCode",
13
+ binary: "opencode",
14
+ skillsDir: ".opencode/skills",
15
+ globalSkillsDir: ".opencode/skills"
16
+ }
17
+ ];
18
+ var KNOWN_AGENT_IDS = KNOWN_AGENTS.map((agent) => agent.id);
19
+ var findAgent = (id) => KNOWN_AGENTS.find((agent) => agent.id === id) ?? null;
20
+ var isKnownAgentId = (id) => KNOWN_AGENT_IDS.includes(id);
21
+ export {
22
+ isKnownAgentId,
23
+ findAgent,
24
+ KNOWN_AGENT_IDS,
25
+ KNOWN_AGENTS
26
+ };
@@ -0,0 +1,12 @@
1
+ export type KnownAgentId = "claude-code" | "opencode" | "fake";
2
+ export type KnownAgent = {
3
+ id: KnownAgentId;
4
+ name: string;
5
+ binary: string;
6
+ skillsDir: string;
7
+ globalSkillsDir: string;
8
+ };
9
+ export declare const KNOWN_AGENTS: KnownAgent[];
10
+ export declare const KNOWN_AGENT_IDS: KnownAgentId[];
11
+ export declare const findAgent: (id: string) => KnownAgent | null;
12
+ export declare const isKnownAgentId: (id: string) => id is KnownAgentId;
@@ -0,0 +1 @@
1
+ export type { Settings } from "pstdio-api-contracts";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pstdio/sdk",
3
- "version": "0.7.0",
3
+ "version": "0.8.0",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/pufflyai/prompt-studio"