@camerontaylor/paseo-client 0.8.0-fork.1 → 0.8.0-fork.3

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/index.d.ts CHANGED
@@ -1,6 +1,8 @@
1
+ import type { OwnedSubscription } from "./connection/index.js";
2
+ export type { OwnedSubscription, SubscriptionObserver } from "./connection/index.js";
1
3
  import type { DaemonClientConfig } from "./daemon-client.js";
2
4
  import type { AgentPermissionResponse } from "@camerontaylor/paseo-protocol/agent-types";
3
- import type { AgentSnapshotPayload, CreationSnapshot, CreateAgentRequestMessage, FetchWorkspacesRequestMessage, FetchWorkspacesResponseMessage, GetProvidersSnapshotResponseMessage, ListAvailableProvidersResponse, ListCommandsResponse, ListProviderFeaturesRequestMessage, ListProviderFeaturesResponseMessage, ListProviderModelsResponseMessage, ProjectListRequestMessage, ProjectListResponseMessage, ListProviderModesResponseMessage, MutableDaemonConfig, MutableDaemonConfigPatch, ProviderDiagnosticResponseMessage, ProviderUsageListResponseMessage, ProjectPlacementPayload, WorkspaceProjectDescriptorPayload, RefreshProvidersSnapshotResponseMessage, SendAgentMessageRequest, SessionOutboundMessage, WorkspaceDescriptorPayload, WorkspaceCreateRequest } from "@camerontaylor/paseo-protocol/messages";
5
+ import type { AgentSnapshotPayload, CreateAgentRequestMessage, FetchWorkspacesRequestMessage, FetchWorkspacesResponseMessage, GetProvidersSnapshotResponseMessage, ListAvailableProvidersResponse, ListCommandsResponse, ListProviderFeaturesRequestMessage, ListProviderFeaturesResponseMessage, ListProviderModelsResponseMessage, ProjectListRequestMessage, ProjectListResponseMessage, ListProviderModesResponseMessage, MutableDaemonConfig, MutableDaemonConfigPatch, ProviderDiagnosticResponseMessage, ProviderUsageListResponseMessage, ProjectPlacementPayload, WorkspaceProjectDescriptorPayload, RefreshProvidersSnapshotResponseMessage, SendAgentMessageRequest, SessionOutboundMessage, WorkspaceDescriptorPayload, WorkspaceCreateRequest } from "@camerontaylor/paseo-protocol/messages";
4
6
  import { DaemonClient } from "./daemon-client.js";
5
7
  import { type PaseoTerminalActions, type PaseoWorkspaceTerminalActions } from "./terminals/index.js";
6
8
  export type { PaseoTerminal, PaseoTerminalActions, PaseoTerminalHandle, PaseoTerminalCreateOptions, PaseoTerminalListOptions, PaseoTerminalListResult, PaseoTerminalCaptureOptions, PaseoTerminalCaptureResult, PaseoWorkspaceTerminalActions, } from "./terminals/index.js";
@@ -61,6 +63,7 @@ export type PaseoProjectUpdate = Extract<SessionOutboundMessage, {
61
63
  }>["payload"];
62
64
  export type PaseoProjectUpdateHandler = (update: PaseoProjectUpdate) => void;
63
65
  export interface PaseoAgentListResult {
66
+ subscription?: OwnedSubscription<PaseoAgentListResult>;
64
67
  requestId: string;
65
68
  subscriptionId?: string | null;
66
69
  entries: FetchAgentsEntry[];
@@ -70,6 +73,7 @@ export type PaseoWorkspaceListOptions = Omit<FetchWorkspacesRequestMessage, "typ
70
73
  requestId?: string;
71
74
  };
72
75
  export interface PaseoWorkspaceListResult {
76
+ subscription?: OwnedSubscription<PaseoWorkspaceListResult>;
73
77
  requestId: string;
74
78
  subscriptionId?: string | null;
75
79
  entries: PaseoWorkspace[];
@@ -79,10 +83,8 @@ export interface PaseoWorkspaceOpenOptions {
79
83
  cwd: string;
80
84
  requestId?: string;
81
85
  }
82
- export type PaseoWorkspaceCreateOptions = Omit<WorkspaceCreateRequest, "type" | "requestId" | "agent" | "subscribe"> & {
86
+ export type PaseoWorkspaceCreateOptions = Omit<WorkspaceCreateRequest, "type" | "requestId"> & {
83
87
  requestId?: string;
84
- agent?: Omit<PaseoAgentCreateOptions, "worktree" | "git" | "onEvent" | "idempotencyKey" | "requestId">;
85
- onEvent?: (snapshot: CreationSnapshot) => void;
86
88
  };
87
89
  export interface PaseoWorkspaceArchiveResult {
88
90
  requestId: string;
@@ -125,6 +127,12 @@ export interface PaseoProjectActions {
125
127
  subscribe(handler: PaseoProjectUpdateHandler): () => void;
126
128
  }
127
129
  export interface PaseoWorkspaceActions {
130
+ list(options: PaseoWorkspaceListOptions & {
131
+ subscribe: {};
132
+ }): Promise<PaseoWorkspaceListResult & {
133
+ subscriptionId: string;
134
+ subscription: OwnedSubscription<PaseoWorkspaceListResult>;
135
+ }>;
128
136
  list(options?: PaseoWorkspaceListOptions): Promise<PaseoWorkspaceListResult>;
129
137
  ref(workspace: string | PaseoWorkspace): PaseoWorkspaceHandle;
130
138
  open(input: string | PaseoWorkspaceOpenOptions, requestId?: string): Promise<PaseoWorkspaceHandle>;
@@ -152,9 +160,6 @@ export interface PaseoAgentConfig {
152
160
  mcpServers?: PaseoAgentSessionConfig["mcpServers"];
153
161
  }
154
162
  export interface PaseoAgentCreateOptions {
155
- idempotencyKey?: string;
156
- agentId?: string;
157
- onEvent?: (snapshot: CreationSnapshot) => void;
158
163
  config: PaseoAgentConfig;
159
164
  cwd: string;
160
165
  parent?: string | PaseoAgentHandle;
@@ -217,6 +222,18 @@ export type PaseoAgentTimelineEvent = PaseoAgentStream | {
217
222
  type: "replacement";
218
223
  epoch: string;
219
224
  };
225
+ } | {
226
+ agentId: string;
227
+ subscriptionId: string;
228
+ event: {
229
+ type: "subscription_restored";
230
+ };
231
+ } | {
232
+ agentId: string;
233
+ event: {
234
+ type: "error";
235
+ error: string;
236
+ };
220
237
  };
221
238
  export type PaseoAgentTimelineSubscription = ReturnType<DaemonClient["subscribeAgentTimeline"]>;
222
239
  export interface PaseoAgentTimelineHandle {
@@ -231,8 +248,10 @@ export interface PaseoAgentTimelineHandle {
231
248
  */
232
249
  refetch(options?: PaseoAgentTimelineRefetchOptions): Promise<FetchAgentTimelinePayload>;
233
250
  /**
234
- * Subscribe to this agent and restore demand after reconnect. A replacement
235
- * event invalidates previously fetched history; refetch the page you need.
251
+ * Delivers live events only. After reconnect, subscription_restored precedes
252
+ * subsequent updates. History may have been missed; use refetch() to request
253
+ * the range you need. No history is fetched automatically. A replacement event
254
+ * invalidates the previous epoch. Subscription errors release this observation.
236
255
  * Await the returned unsubscribe function's `ready` promise before starting
237
256
  * work that must be observed. It rejects if establishment fails.
238
257
  */
@@ -283,6 +302,12 @@ export interface PaseoAgentHandle {
283
302
  subscribe(handler: (update: PaseoAgentUpdate) => void): () => void;
284
303
  }
285
304
  export interface PaseoAgentActions {
305
+ list(options: PaseoAgentListOptions & {
306
+ subscribe: {};
307
+ }): Promise<PaseoAgentListResult & {
308
+ subscriptionId: string;
309
+ subscription: OwnedSubscription<PaseoAgentListResult>;
310
+ }>;
286
311
  list(options?: PaseoAgentListOptions): Promise<PaseoAgentListResult>;
287
312
  ref(agent: string | PaseoAgent): PaseoAgentHandle;
288
313
  create(options: PaseoAgentCreateOptions): Promise<PaseoAgentHandle>;
@@ -365,6 +390,8 @@ export interface PaseoConfigActions {
365
390
  }>;
366
391
  }
367
392
  export interface PaseoApi {
393
+ dispose(): Promise<void>;
394
+ observeEvents: DaemonClient["observeEvents"];
368
395
  readonly terminals: PaseoTerminalActions;
369
396
  readonly workspaces: PaseoWorkspaceActions;
370
397
  readonly projects: PaseoProjectActions;
@@ -379,5 +406,7 @@ export interface PaseoClient extends PaseoApi {
379
406
  getConnectionState(): ConnectionState;
380
407
  }
381
408
  export declare function createPaseoClient(config: PaseoClientConfig): PaseoClient;
382
- export declare function createPaseoApi(daemonClient: DaemonClient): PaseoApi;
409
+ export declare function createPaseoApi(daemonClient: DaemonClient, scopeOptions?: {
410
+ signal?: AbortSignal;
411
+ }): PaseoApi;
383
412
  //# sourceMappingURL=index.d.ts.map
package/dist/index.js CHANGED
@@ -11,37 +11,75 @@ export function createPaseoClient(config) {
11
11
  clientId: config.clientId ?? createGeneratedClientId(),
12
12
  clientType: "cli",
13
13
  });
14
+ const api = createPaseoApi(daemonClient);
14
15
  return {
15
- ...createPaseoApi(daemonClient),
16
+ ...api,
16
17
  connect: () => daemonClient.connect(),
17
- close: () => daemonClient.close(),
18
+ close: async () => {
19
+ try {
20
+ await api.dispose();
21
+ }
22
+ finally {
23
+ await daemonClient.close();
24
+ }
25
+ },
18
26
  ensureConnected: () => daemonClient.ensureConnected(),
19
27
  getConnectionState: () => daemonClient.getConnectionState(),
20
28
  };
21
29
  }
22
- function toDaemonAgentCreateOptions(options, placement) {
23
- const { config: agentConfig, cwd, parent, title, prompt, ...requestOptions } = options;
24
- const { provider: providerModel, options: providerOptions, ...runtimeConfig } = agentConfig;
25
- const { provider, model } = parseProviderModel(providerModel);
26
- return {
27
- ...requestOptions,
28
- config: {
29
- ...runtimeConfig,
30
- provider,
31
- model,
32
- cwd: placement?.cwd ?? cwd,
33
- ...(title !== undefined ? { title } : {}),
34
- ...(providerOptions !== undefined ? { providerOptions } : {}),
35
- },
36
- ...(placement ? { workspaceId: placement.workspaceId } : {}),
37
- ...(parent ? { callerAgentId: resolveAgentId(parent) } : {}),
38
- ...(prompt !== undefined ? { initialPrompt: prompt } : {}),
30
+ export function createPaseoApi(daemonClient, scopeOptions) {
31
+ const handles = new Set();
32
+ const agentListeners = new Set();
33
+ const workspaceListeners = new Set();
34
+ const lifetime = new AbortController();
35
+ const own = (create) => {
36
+ if (lifetime.signal.aborted)
37
+ throw new Error("Paseo API is disposed");
38
+ const handle = create();
39
+ handles.add(handle);
40
+ const release = handle.release.bind(handle);
41
+ handle.release = async () => {
42
+ await release();
43
+ handles.delete(handle);
44
+ };
45
+ return handle;
39
46
  };
40
- }
41
- export function createPaseoApi(daemonClient) {
42
- const createAgentHandle = createAgentHandleFactory(daemonClient);
47
+ const listenAgents = (handler) => {
48
+ if (lifetime.signal.aborted)
49
+ throw new Error("Paseo API is disposed");
50
+ agentListeners.add(handler);
51
+ return () => {
52
+ agentListeners.delete(handler);
53
+ };
54
+ };
55
+ const listenWorkspaces = (handler) => {
56
+ if (lifetime.signal.aborted)
57
+ throw new Error("Paseo API is disposed");
58
+ workspaceListeners.add(handler);
59
+ return () => {
60
+ workspaceListeners.delete(handler);
61
+ };
62
+ };
63
+ const createAgentHandle = createAgentHandleFactory(daemonClient, listenAgents, (agentId, handler) => own(() => daemonClient.subscribeAgentTimeline(agentId, handler)));
43
64
  const createAgent = async (options, placement) => {
44
- const agent = await daemonClient.createAgent(toDaemonAgentCreateOptions(options, placement));
65
+ const { config: agentConfig, cwd, parent, title, prompt, ...requestOptions } = options;
66
+ const { provider: providerModel, options: providerOptions, ...runtimeConfig } = agentConfig;
67
+ const { provider, model } = parseProviderModel(providerModel);
68
+ const effectiveCwd = placement?.cwd ?? cwd;
69
+ const agent = await daemonClient.createAgent({
70
+ ...requestOptions,
71
+ config: {
72
+ ...runtimeConfig,
73
+ provider,
74
+ model,
75
+ cwd: effectiveCwd,
76
+ ...(title !== undefined ? { title } : {}),
77
+ ...(providerOptions !== undefined ? { providerOptions } : {}),
78
+ },
79
+ ...(placement ? { workspaceId: placement.workspaceId } : {}),
80
+ ...(parent ? { callerAgentId: resolveAgentId(parent) } : {}),
81
+ ...(prompt !== undefined ? { initialPrompt: prompt } : {}),
82
+ });
45
83
  return createAgentHandle(agent);
46
84
  };
47
85
  const terminals = createTerminalActions(daemonClient, async (workspaceId) => {
@@ -51,38 +89,105 @@ export function createPaseoApi(daemonClient) {
51
89
  }
52
90
  return workspace.workspaceDirectory;
53
91
  });
54
- const createWorkspaceHandle = createWorkspaceHandleFactory(daemonClient, createAgent, terminals);
92
+ const createWorkspaceHandle = createWorkspaceHandleFactory(daemonClient, createAgent, terminals, listenWorkspaces);
93
+ let disposal = null;
94
+ const dispose = () => {
95
+ if (disposal)
96
+ return disposal;
97
+ lifetime.abort();
98
+ scopeOptions?.signal?.removeEventListener("abort", abort);
99
+ agentListeners.clear();
100
+ workspaceListeners.clear();
101
+ disposal = Promise.allSettled([...handles].map((handle) => handle.release())).then((results) => {
102
+ handles.clear();
103
+ const failures = results.flatMap((result) => result.status === "rejected" ? [result.reason] : []);
104
+ if (failures.length)
105
+ throw new AggregateError(failures, "Failed to release API subscriptions");
106
+ return undefined;
107
+ });
108
+ return disposal;
109
+ };
110
+ const abort = () => {
111
+ void dispose().catch((error) => console.error("API subscription cleanup failed", error));
112
+ };
113
+ if (scopeOptions?.signal?.aborted)
114
+ abort();
115
+ else
116
+ scopeOptions?.signal?.addEventListener("abort", abort, { once: true });
117
+ const observeEvents = (events, options) => own(() => daemonClient.observeEvents(events, options));
118
+ const subscribeEvent = (event, update) => {
119
+ const observation = observeEvents([event]);
120
+ observation.subscribe({ snapshot: () => { }, update });
121
+ return () => {
122
+ void observation
123
+ .release()
124
+ .catch((error) => console.error("Event subscription cleanup failed", error));
125
+ };
126
+ };
127
+ async function listWorkspaces(options) {
128
+ if (!options?.subscribe)
129
+ return daemonClient.fetchWorkspaces(options);
130
+ if (options.subscribe.subscriptionId !== undefined)
131
+ throw new Error("Subscription IDs are assigned by the host");
132
+ const subscription = own(() => daemonClient.observeWorkspaces(options));
133
+ subscription.subscribe({
134
+ snapshot: () => { },
135
+ update: (message) => {
136
+ if (message.type === "workspace_update")
137
+ for (const listener of workspaceListeners)
138
+ listener(message.payload);
139
+ },
140
+ });
141
+ return { ...(await subscription.ready), subscription };
142
+ }
143
+ async function listAgents(options) {
144
+ if (!options?.subscribe)
145
+ return daemonClient.fetchAgents(options);
146
+ if (options.subscribe.subscriptionId !== undefined)
147
+ throw new Error("Subscription IDs are assigned by the host");
148
+ const subscription = own(() => daemonClient.observeAgents(options));
149
+ subscription.subscribe({
150
+ snapshot: () => { },
151
+ update: (message) => {
152
+ if (message.type === "agent_update")
153
+ for (const listener of agentListeners)
154
+ listener(message.payload);
155
+ },
156
+ });
157
+ return { ...(await subscription.ready), subscription };
158
+ }
55
159
  return {
160
+ dispose,
161
+ observeEvents,
56
162
  terminals,
57
163
  projects: {
58
164
  list: (options) => daemonClient.listProjects(options),
59
- subscribe: (handler) => daemonClient.on("project.update", (message) => {
60
- handler(message.payload);
61
- }),
165
+ subscribe: (handler) => {
166
+ return subscribeEvent("project.update", (message) => {
167
+ if (message.type === "project.update")
168
+ handler(message.payload);
169
+ });
170
+ },
62
171
  },
63
172
  workspaces: {
64
- list: (options) => daemonClient.fetchWorkspaces(options),
173
+ list: listWorkspaces,
65
174
  ref: (workspace) => createWorkspaceHandle(workspace),
66
175
  open: (input, requestId) => openWorkspace(daemonClient, createWorkspaceHandle, input, requestId),
67
- create: async ({ requestId, agent, ...options }) => {
68
- const result = await daemonClient.createWorkspace({ ...options, ...(agent ? { agent: toDaemonAgentCreateOptions(agent) } : {}) }, requestId);
176
+ create: async ({ requestId, ...options }) => {
177
+ const result = await daemonClient.createWorkspace(options, requestId);
69
178
  if (result.error || !result.workspace) {
70
179
  throw new Error(result.error ?? "The daemon did not create a workspace");
71
180
  }
72
181
  return createWorkspaceHandle(result.workspace);
73
182
  },
74
183
  archive: (workspace, requestId) => daemonClient.archiveWorkspace(resolveWorkspaceId(workspace), requestId),
75
- subscribe: (handler) => daemonClient.on("workspace_update", (message) => {
76
- handler(message.payload);
77
- }),
184
+ subscribe: listenWorkspaces,
78
185
  },
79
186
  agents: {
80
- list: (options) => daemonClient.fetchAgents(options),
187
+ list: listAgents,
81
188
  ref: (agent) => createAgentHandle(agent),
82
189
  create: (options) => createAgent(options),
83
- subscribe: (handler) => daemonClient.on("agent_update", (message) => {
84
- handler(message.payload);
85
- }),
190
+ subscribe: listenAgents,
86
191
  },
87
192
  providers: {
88
193
  listModels: (provider, options) => daemonClient.listProviderModels(provider, options),
@@ -93,13 +198,16 @@ export function createPaseoApi(daemonClient) {
93
198
  },
94
199
  listAvailable: (options) => daemonClient.listAvailableProviders(options),
95
200
  snapshot: (options) => daemonClient.getProvidersSnapshot(options),
96
- waitForReady: (options) => waitForProvidersReady(daemonClient, options),
201
+ waitForReady: (options) => waitForProvidersReady(daemonClient, observeEvents(["providers_snapshot_update"]), lifetime.signal, options),
97
202
  refresh: (options) => daemonClient.refreshProvidersSnapshot(options),
98
203
  diagnostic: (provider, options) => daemonClient.getProviderDiagnostic(provider, options),
99
204
  listUsage: (options) => listProviderUsage(daemonClient, options),
100
- subscribe: (handler) => daemonClient.on("providers_snapshot_update", (message) => {
101
- handler(message.payload);
102
- }),
205
+ subscribe: (handler) => {
206
+ return subscribeEvent("providers_snapshot_update", (message) => {
207
+ if (message.type === "providers_snapshot_update")
208
+ handler(message.payload);
209
+ });
210
+ },
103
211
  },
104
212
  config: {
105
213
  get: (requestId) => daemonClient.getDaemonConfig(requestId),
@@ -107,7 +215,7 @@ export function createPaseoApi(daemonClient) {
107
215
  },
108
216
  };
109
217
  }
110
- function createWorkspaceHandleFactory(daemonClient, createAgent, terminals) {
218
+ function createWorkspaceHandleFactory(daemonClient, createAgent, terminals, listen) {
111
219
  return (workspace) => {
112
220
  const id = typeof workspace === "string" ? workspace : workspace.id;
113
221
  let current = typeof workspace === "string" ? null : workspace;
@@ -167,21 +275,19 @@ function createWorkspaceHandleFactory(daemonClient, createAgent, terminals) {
167
275
  }
168
276
  return result;
169
277
  },
170
- subscribe: (handler) => daemonClient.on("workspace_update", (message) => {
171
- const update = message.payload;
278
+ subscribe: (handler) => listen((update) => {
172
279
  if (update.kind === "upsert" && update.workspace.id === id) {
173
280
  current = update.workspace;
174
281
  handler(update);
175
282
  }
176
283
  if (update.kind === "remove" && update.id === id) {
177
- current = null;
178
284
  handler(update);
179
285
  }
180
286
  }),
181
287
  };
182
288
  };
183
289
  }
184
- function createAgentHandleFactory(daemonClient) {
290
+ function createAgentHandleFactory(daemonClient, listen, subscribeTimeline) {
185
291
  return (agent) => {
186
292
  const id = typeof agent === "string" ? agent : agent.id;
187
293
  let current = typeof agent === "string" ? null : agent;
@@ -196,13 +302,27 @@ function createAgentHandleFactory(daemonClient) {
196
302
  }
197
303
  return result;
198
304
  },
199
- subscribe: (handler) => daemonClient.subscribeAgentTimeline(id, (message) => {
200
- handler(message.type === "agent_stream"
201
- ? message.payload
202
- : {
203
- agentId: message.payload.agentId,
204
- event: { type: "replacement", epoch: message.payload.epoch },
205
- });
305
+ subscribe: (handler) => subscribeTimeline(id, (message) => {
306
+ switch (message.type) {
307
+ case "agent_stream":
308
+ return handler(message.payload);
309
+ case "agent.timeline.subscription_restored":
310
+ return handler({
311
+ agentId: id,
312
+ subscriptionId: message.payload.subscriptionId,
313
+ event: { type: "subscription_restored" },
314
+ });
315
+ case "agent.timeline.error":
316
+ return handler({
317
+ agentId: id,
318
+ event: { type: "error", error: message.payload.error },
319
+ });
320
+ case "agent.timeline.replacement":
321
+ return handler({
322
+ agentId: id,
323
+ event: { type: "replacement", epoch: message.payload.epoch },
324
+ });
325
+ }
206
326
  }),
207
327
  },
208
328
  get workspaceId() {
@@ -280,14 +400,12 @@ function createAgentHandleFactory(daemonClient) {
280
400
  detach: async () => {
281
401
  await daemonClient.detachAgent(id);
282
402
  },
283
- subscribe: (handler) => daemonClient.on("agent_update", (message) => {
284
- const update = message.payload;
403
+ subscribe: (handler) => listen((update) => {
285
404
  if (update.kind === "upsert" && update.agent.id === id) {
286
405
  current = update.agent;
287
406
  handler(update);
288
407
  }
289
408
  if (update.kind === "remove" && update.agentId === id) {
290
- current = null;
291
409
  handler(update);
292
410
  }
293
411
  }),
@@ -326,77 +444,88 @@ function listProviderUsage(daemonClient, options) {
326
444
  }
327
445
  return daemonClient.listProviderUsage(options);
328
446
  }
329
- function waitForProvidersReady(daemonClient, options = {}) {
330
- // COMPAT(providersSnapshotCwd): added in v0.3.2, remove gate after 2027-02-10.
331
- if (daemonClient.getLastServerInfoMessage()?.features?.providersSnapshotCwd !== true) {
332
- return Promise.reject(new Error("Update the host to wait for provider discovery."));
333
- }
447
+ async function waitForProvidersReady(daemonClient, observation, signal, options = {}) {
334
448
  const { timeoutMs = 60000, ...snapshotOptions } = options;
335
- return new Promise((resolve, reject) => {
336
- let settled = false;
337
- let requestId = null;
338
- let snapshotCwd;
339
- const pendingUpdates = new Map();
340
- let latestEntries = [];
341
- const cleanup = () => {
342
- clearTimeout(timeout);
343
- unsubscribe();
344
- };
345
- const finish = (snapshot) => {
346
- if (settled)
347
- return;
348
- settled = true;
349
- cleanup();
350
- resolve(snapshot);
351
- };
352
- const fail = (error) => {
353
- if (settled)
354
- return;
355
- settled = true;
356
- cleanup();
357
- reject(error instanceof Error ? error : new Error(String(error)));
358
- };
359
- const updateMatches = (update) => update.cwd === snapshotCwd;
360
- const unsubscribe = daemonClient.on("providers_snapshot_update", (message) => {
361
- const update = message.payload;
362
- if (!requestId) {
363
- pendingUpdates.set(update.cwd, update);
364
- return;
365
- }
366
- if (!updateMatches(update))
367
- return;
368
- latestEntries = update.entries;
369
- if (update.entries.some((entry) => entry.status === "loading"))
370
- return;
371
- finish({ ...update, requestId });
449
+ try {
450
+ await observation.ready;
451
+ signal.throwIfAborted();
452
+ return await new Promise((resolve, reject) => {
453
+ let settled = false;
454
+ let requestId = null;
455
+ let snapshotCwd;
456
+ const pendingUpdates = new Map();
457
+ let latestEntries = [];
458
+ const cleanup = () => {
459
+ clearTimeout(timeout);
460
+ unsubscribe();
461
+ signal.removeEventListener("abort", abort);
462
+ };
463
+ const finish = (snapshot) => {
464
+ if (settled)
465
+ return;
466
+ settled = true;
467
+ cleanup();
468
+ resolve(snapshot);
469
+ };
470
+ const fail = (error) => {
471
+ if (settled)
472
+ return;
473
+ settled = true;
474
+ cleanup();
475
+ reject(error instanceof Error ? error : new Error(String(error)));
476
+ };
477
+ const updateMatches = (update) => update.cwd === snapshotCwd;
478
+ const unsubscribe = observation.subscribe({
479
+ snapshot: () => { },
480
+ update: (message) => {
481
+ if (message.type !== "providers_snapshot_update")
482
+ return;
483
+ const update = message.payload;
484
+ if (!requestId) {
485
+ pendingUpdates.set(update.cwd, update);
486
+ return;
487
+ }
488
+ if (!updateMatches(update))
489
+ return;
490
+ latestEntries = update.entries;
491
+ if (update.entries.some((entry) => entry.status === "loading"))
492
+ return;
493
+ finish({ ...update, requestId });
494
+ },
495
+ });
496
+ const abort = () => fail(new Error("Paseo API is disposed"));
497
+ signal.addEventListener("abort", abort, { once: true });
498
+ const timeout = setTimeout(() => {
499
+ const loading = latestEntries
500
+ .filter((entry) => entry.status === "loading")
501
+ .map((entry) => entry.provider)
502
+ .join(", ");
503
+ fail(new Error(loading
504
+ ? `Timed out waiting for providers: ${loading}`
505
+ : "Timed out waiting for provider discovery"));
506
+ }, timeoutMs);
507
+ void daemonClient
508
+ .getProvidersSnapshot(snapshotOptions)
509
+ .then((snapshot) => {
510
+ requestId = snapshot.requestId;
511
+ snapshotCwd = snapshot.cwd;
512
+ latestEntries = snapshot.entries;
513
+ if (!snapshot.entries.some((entry) => entry.status === "loading")) {
514
+ finish(snapshot);
515
+ return;
516
+ }
517
+ const pendingUpdate = pendingUpdates.get(snapshotCwd);
518
+ if (pendingUpdate && !pendingUpdate.entries.some((entry) => entry.status === "loading")) {
519
+ finish({ ...pendingUpdate, requestId });
520
+ }
521
+ return undefined;
522
+ })
523
+ .catch(fail);
372
524
  });
373
- const timeout = setTimeout(() => {
374
- const loading = latestEntries
375
- .filter((entry) => entry.status === "loading")
376
- .map((entry) => entry.provider)
377
- .join(", ");
378
- fail(new Error(loading
379
- ? `Timed out waiting for providers: ${loading}`
380
- : "Timed out waiting for provider discovery"));
381
- }, timeoutMs);
382
- void daemonClient
383
- .getProvidersSnapshot(snapshotOptions)
384
- .then((snapshot) => {
385
- requestId = snapshot.requestId;
386
- snapshotCwd = snapshot.cwd;
387
- latestEntries = snapshot.entries;
388
- if (!snapshot.entries.some((entry) => entry.status === "loading")) {
389
- finish(snapshot);
390
- return;
391
- }
392
- const pendingUpdate = pendingUpdates.get(snapshotCwd);
393
- if (pendingUpdate && !pendingUpdate.entries.some((entry) => entry.status === "loading")) {
394
- finish({ ...pendingUpdate, requestId });
395
- }
396
- return undefined;
397
- })
398
- .catch(fail);
399
- });
525
+ }
526
+ finally {
527
+ await observation.release();
528
+ }
400
529
  }
401
530
  function createGeneratedClientId() {
402
531
  const randomId = typeof globalThis.crypto?.randomUUID === "function"
@@ -7,15 +7,13 @@ type Snapshot = GetProvidersSnapshotResponseMessage["payload"];
7
7
  export declare class ProviderSnapshotUpdates {
8
8
  private readonly host;
9
9
  private pending;
10
- private paused;
11
10
  constructor(host: {
11
+ active(update: Update): boolean;
12
12
  fetch(cwd: string | undefined): Promise<Snapshot>;
13
13
  emit(update: Update): void;
14
14
  failed(error: unknown): void;
15
15
  });
16
16
  clear(): void;
17
- pause(): void;
18
- resume(): void;
19
17
  receive(update: Update): void;
20
18
  private resolve;
21
19
  }