@camerontaylor/paseo-client 0.8.0-fork.1 → 0.9.0-beta.2.fork.1
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/connection/index.d.ts +28 -21
- package/dist/connection/index.js +90 -85
- package/dist/connection/legacy-workspaces.d.ts +19 -0
- package/dist/connection/legacy-workspaces.js +160 -0
- package/dist/connection/legacy.d.ts +31 -0
- package/dist/connection/legacy.js +245 -0
- package/dist/connection/owned.d.ts +36 -0
- package/dist/connection/owned.js +210 -0
- package/dist/creation/index.d.ts +2 -9
- package/dist/creation/index.js +37 -56
- package/dist/daemon-client.d.ts +104 -43
- package/dist/daemon-client.js +408 -376
- package/dist/index.d.ts +37 -3
- package/dist/index.js +235 -104
- package/dist/provider-snapshots/index.d.ts +1 -3
- package/dist/provider-snapshots/index.js +23 -27
- package/dist/terminal-stream-router.d.ts +7 -10
- package/dist/terminal-stream-router.js +41 -88
- package/dist/timeline-subscription/index.d.ts +25 -0
- package/dist/timeline-subscription/index.js +56 -0
- package/package.json +7 -3
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
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
5
|
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";
|
|
@@ -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[];
|
|
@@ -125,6 +129,12 @@ export interface PaseoProjectActions {
|
|
|
125
129
|
subscribe(handler: PaseoProjectUpdateHandler): () => void;
|
|
126
130
|
}
|
|
127
131
|
export interface PaseoWorkspaceActions {
|
|
132
|
+
list(options: PaseoWorkspaceListOptions & {
|
|
133
|
+
subscribe: {};
|
|
134
|
+
}): Promise<PaseoWorkspaceListResult & {
|
|
135
|
+
subscriptionId: string;
|
|
136
|
+
subscription: OwnedSubscription<PaseoWorkspaceListResult>;
|
|
137
|
+
}>;
|
|
128
138
|
list(options?: PaseoWorkspaceListOptions): Promise<PaseoWorkspaceListResult>;
|
|
129
139
|
ref(workspace: string | PaseoWorkspace): PaseoWorkspaceHandle;
|
|
130
140
|
open(input: string | PaseoWorkspaceOpenOptions, requestId?: string): Promise<PaseoWorkspaceHandle>;
|
|
@@ -217,6 +227,18 @@ export type PaseoAgentTimelineEvent = PaseoAgentStream | {
|
|
|
217
227
|
type: "replacement";
|
|
218
228
|
epoch: string;
|
|
219
229
|
};
|
|
230
|
+
} | {
|
|
231
|
+
agentId: string;
|
|
232
|
+
subscriptionId: string;
|
|
233
|
+
event: {
|
|
234
|
+
type: "subscription_restored";
|
|
235
|
+
};
|
|
236
|
+
} | {
|
|
237
|
+
agentId: string;
|
|
238
|
+
event: {
|
|
239
|
+
type: "error";
|
|
240
|
+
error: string;
|
|
241
|
+
};
|
|
220
242
|
};
|
|
221
243
|
export type PaseoAgentTimelineSubscription = ReturnType<DaemonClient["subscribeAgentTimeline"]>;
|
|
222
244
|
export interface PaseoAgentTimelineHandle {
|
|
@@ -231,8 +253,10 @@ export interface PaseoAgentTimelineHandle {
|
|
|
231
253
|
*/
|
|
232
254
|
refetch(options?: PaseoAgentTimelineRefetchOptions): Promise<FetchAgentTimelinePayload>;
|
|
233
255
|
/**
|
|
234
|
-
*
|
|
235
|
-
*
|
|
256
|
+
* Delivers live events only. After reconnect, subscription_restored precedes
|
|
257
|
+
* subsequent updates. History may have been missed; use refetch() to request
|
|
258
|
+
* the range you need. No history is fetched automatically. A replacement event
|
|
259
|
+
* invalidates the previous epoch. Subscription errors release this observation.
|
|
236
260
|
* Await the returned unsubscribe function's `ready` promise before starting
|
|
237
261
|
* work that must be observed. It rejects if establishment fails.
|
|
238
262
|
*/
|
|
@@ -283,6 +307,12 @@ export interface PaseoAgentHandle {
|
|
|
283
307
|
subscribe(handler: (update: PaseoAgentUpdate) => void): () => void;
|
|
284
308
|
}
|
|
285
309
|
export interface PaseoAgentActions {
|
|
310
|
+
list(options: PaseoAgentListOptions & {
|
|
311
|
+
subscribe: {};
|
|
312
|
+
}): Promise<PaseoAgentListResult & {
|
|
313
|
+
subscriptionId: string;
|
|
314
|
+
subscription: OwnedSubscription<PaseoAgentListResult>;
|
|
315
|
+
}>;
|
|
286
316
|
list(options?: PaseoAgentListOptions): Promise<PaseoAgentListResult>;
|
|
287
317
|
ref(agent: string | PaseoAgent): PaseoAgentHandle;
|
|
288
318
|
create(options: PaseoAgentCreateOptions): Promise<PaseoAgentHandle>;
|
|
@@ -365,6 +395,8 @@ export interface PaseoConfigActions {
|
|
|
365
395
|
}>;
|
|
366
396
|
}
|
|
367
397
|
export interface PaseoApi {
|
|
398
|
+
dispose(): Promise<void>;
|
|
399
|
+
observeEvents: DaemonClient["observeEvents"];
|
|
368
400
|
readonly terminals: PaseoTerminalActions;
|
|
369
401
|
readonly workspaces: PaseoWorkspaceActions;
|
|
370
402
|
readonly projects: PaseoProjectActions;
|
|
@@ -379,5 +411,7 @@ export interface PaseoClient extends PaseoApi {
|
|
|
379
411
|
getConnectionState(): ConnectionState;
|
|
380
412
|
}
|
|
381
413
|
export declare function createPaseoClient(config: PaseoClientConfig): PaseoClient;
|
|
382
|
-
export declare function createPaseoApi(daemonClient: DaemonClient
|
|
414
|
+
export declare function createPaseoApi(daemonClient: DaemonClient, scopeOptions?: {
|
|
415
|
+
signal?: AbortSignal;
|
|
416
|
+
}): PaseoApi;
|
|
383
417
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
CHANGED
|
@@ -11,10 +11,18 @@ 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
|
-
...
|
|
16
|
+
...api,
|
|
16
17
|
connect: () => daemonClient.connect(),
|
|
17
|
-
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
|
};
|
|
@@ -38,8 +46,40 @@ function toDaemonAgentCreateOptions(options, placement) {
|
|
|
38
46
|
...(prompt !== undefined ? { initialPrompt: prompt } : {}),
|
|
39
47
|
};
|
|
40
48
|
}
|
|
41
|
-
export function createPaseoApi(daemonClient) {
|
|
42
|
-
const
|
|
49
|
+
export function createPaseoApi(daemonClient, scopeOptions) {
|
|
50
|
+
const handles = new Set();
|
|
51
|
+
const agentListeners = new Set();
|
|
52
|
+
const workspaceListeners = new Set();
|
|
53
|
+
const lifetime = new AbortController();
|
|
54
|
+
const own = (create) => {
|
|
55
|
+
if (lifetime.signal.aborted)
|
|
56
|
+
throw new Error("Paseo API is disposed");
|
|
57
|
+
const handle = create();
|
|
58
|
+
handles.add(handle);
|
|
59
|
+
const release = handle.release.bind(handle);
|
|
60
|
+
handle.release = async () => {
|
|
61
|
+
await release();
|
|
62
|
+
handles.delete(handle);
|
|
63
|
+
};
|
|
64
|
+
return handle;
|
|
65
|
+
};
|
|
66
|
+
const listenAgents = (handler) => {
|
|
67
|
+
if (lifetime.signal.aborted)
|
|
68
|
+
throw new Error("Paseo API is disposed");
|
|
69
|
+
agentListeners.add(handler);
|
|
70
|
+
return () => {
|
|
71
|
+
agentListeners.delete(handler);
|
|
72
|
+
};
|
|
73
|
+
};
|
|
74
|
+
const listenWorkspaces = (handler) => {
|
|
75
|
+
if (lifetime.signal.aborted)
|
|
76
|
+
throw new Error("Paseo API is disposed");
|
|
77
|
+
workspaceListeners.add(handler);
|
|
78
|
+
return () => {
|
|
79
|
+
workspaceListeners.delete(handler);
|
|
80
|
+
};
|
|
81
|
+
};
|
|
82
|
+
const createAgentHandle = createAgentHandleFactory(daemonClient, listenAgents, (agentId, handler) => own(() => daemonClient.subscribeAgentTimeline(agentId, handler)));
|
|
43
83
|
const createAgent = async (options, placement) => {
|
|
44
84
|
const agent = await daemonClient.createAgent(toDaemonAgentCreateOptions(options, placement));
|
|
45
85
|
return createAgentHandle(agent);
|
|
@@ -51,17 +91,88 @@ export function createPaseoApi(daemonClient) {
|
|
|
51
91
|
}
|
|
52
92
|
return workspace.workspaceDirectory;
|
|
53
93
|
});
|
|
54
|
-
const createWorkspaceHandle = createWorkspaceHandleFactory(daemonClient, createAgent, terminals);
|
|
94
|
+
const createWorkspaceHandle = createWorkspaceHandleFactory(daemonClient, createAgent, terminals, listenWorkspaces);
|
|
95
|
+
let disposal = null;
|
|
96
|
+
const dispose = () => {
|
|
97
|
+
if (disposal)
|
|
98
|
+
return disposal;
|
|
99
|
+
lifetime.abort();
|
|
100
|
+
scopeOptions?.signal?.removeEventListener("abort", abort);
|
|
101
|
+
agentListeners.clear();
|
|
102
|
+
workspaceListeners.clear();
|
|
103
|
+
disposal = Promise.allSettled([...handles].map((handle) => handle.release())).then((results) => {
|
|
104
|
+
handles.clear();
|
|
105
|
+
const failures = results.flatMap((result) => result.status === "rejected" ? [result.reason] : []);
|
|
106
|
+
if (failures.length)
|
|
107
|
+
throw new AggregateError(failures, "Failed to release API subscriptions");
|
|
108
|
+
return undefined;
|
|
109
|
+
});
|
|
110
|
+
return disposal;
|
|
111
|
+
};
|
|
112
|
+
const abort = () => {
|
|
113
|
+
void dispose().catch((error) => console.error("API subscription cleanup failed", error));
|
|
114
|
+
};
|
|
115
|
+
if (scopeOptions?.signal?.aborted)
|
|
116
|
+
abort();
|
|
117
|
+
else
|
|
118
|
+
scopeOptions?.signal?.addEventListener("abort", abort, { once: true });
|
|
119
|
+
const observeEvents = (events, options) => own(() => daemonClient.observeEvents(events, options));
|
|
120
|
+
const subscribeEvent = (event, update) => {
|
|
121
|
+
const observation = observeEvents([event]);
|
|
122
|
+
observation.subscribe({ snapshot: () => { }, update });
|
|
123
|
+
return () => {
|
|
124
|
+
void observation
|
|
125
|
+
.release()
|
|
126
|
+
.catch((error) => console.error("Event subscription cleanup failed", error));
|
|
127
|
+
};
|
|
128
|
+
};
|
|
129
|
+
async function listWorkspaces(options) {
|
|
130
|
+
if (!options?.subscribe)
|
|
131
|
+
return daemonClient.fetchWorkspaces(options);
|
|
132
|
+
if (options.subscribe.subscriptionId !== undefined)
|
|
133
|
+
throw new Error("Subscription IDs are assigned by the host");
|
|
134
|
+
const subscription = own(() => daemonClient.observeWorkspaces(options));
|
|
135
|
+
subscription.subscribe({
|
|
136
|
+
snapshot: () => { },
|
|
137
|
+
update: (message) => {
|
|
138
|
+
if (message.type === "workspace_update")
|
|
139
|
+
for (const listener of workspaceListeners)
|
|
140
|
+
listener(message.payload);
|
|
141
|
+
},
|
|
142
|
+
});
|
|
143
|
+
return { ...(await subscription.ready), subscription };
|
|
144
|
+
}
|
|
145
|
+
async function listAgents(options) {
|
|
146
|
+
if (!options?.subscribe)
|
|
147
|
+
return daemonClient.fetchAgents(options);
|
|
148
|
+
if (options.subscribe.subscriptionId !== undefined)
|
|
149
|
+
throw new Error("Subscription IDs are assigned by the host");
|
|
150
|
+
const subscription = own(() => daemonClient.observeAgents(options));
|
|
151
|
+
subscription.subscribe({
|
|
152
|
+
snapshot: () => { },
|
|
153
|
+
update: (message) => {
|
|
154
|
+
if (message.type === "agent_update")
|
|
155
|
+
for (const listener of agentListeners)
|
|
156
|
+
listener(message.payload);
|
|
157
|
+
},
|
|
158
|
+
});
|
|
159
|
+
return { ...(await subscription.ready), subscription };
|
|
160
|
+
}
|
|
55
161
|
return {
|
|
162
|
+
dispose,
|
|
163
|
+
observeEvents,
|
|
56
164
|
terminals,
|
|
57
165
|
projects: {
|
|
58
166
|
list: (options) => daemonClient.listProjects(options),
|
|
59
|
-
subscribe: (handler) =>
|
|
60
|
-
|
|
61
|
-
|
|
167
|
+
subscribe: (handler) => {
|
|
168
|
+
return subscribeEvent("project.update", (message) => {
|
|
169
|
+
if (message.type === "project.update")
|
|
170
|
+
handler(message.payload);
|
|
171
|
+
});
|
|
172
|
+
},
|
|
62
173
|
},
|
|
63
174
|
workspaces: {
|
|
64
|
-
list:
|
|
175
|
+
list: listWorkspaces,
|
|
65
176
|
ref: (workspace) => createWorkspaceHandle(workspace),
|
|
66
177
|
open: (input, requestId) => openWorkspace(daemonClient, createWorkspaceHandle, input, requestId),
|
|
67
178
|
create: async ({ requestId, agent, ...options }) => {
|
|
@@ -72,17 +183,13 @@ export function createPaseoApi(daemonClient) {
|
|
|
72
183
|
return createWorkspaceHandle(result.workspace);
|
|
73
184
|
},
|
|
74
185
|
archive: (workspace, requestId) => daemonClient.archiveWorkspace(resolveWorkspaceId(workspace), requestId),
|
|
75
|
-
subscribe:
|
|
76
|
-
handler(message.payload);
|
|
77
|
-
}),
|
|
186
|
+
subscribe: listenWorkspaces,
|
|
78
187
|
},
|
|
79
188
|
agents: {
|
|
80
|
-
list:
|
|
189
|
+
list: listAgents,
|
|
81
190
|
ref: (agent) => createAgentHandle(agent),
|
|
82
191
|
create: (options) => createAgent(options),
|
|
83
|
-
subscribe:
|
|
84
|
-
handler(message.payload);
|
|
85
|
-
}),
|
|
192
|
+
subscribe: listenAgents,
|
|
86
193
|
},
|
|
87
194
|
providers: {
|
|
88
195
|
listModels: (provider, options) => daemonClient.listProviderModels(provider, options),
|
|
@@ -93,13 +200,16 @@ export function createPaseoApi(daemonClient) {
|
|
|
93
200
|
},
|
|
94
201
|
listAvailable: (options) => daemonClient.listAvailableProviders(options),
|
|
95
202
|
snapshot: (options) => daemonClient.getProvidersSnapshot(options),
|
|
96
|
-
waitForReady: (options) => waitForProvidersReady(daemonClient, options),
|
|
203
|
+
waitForReady: (options) => waitForProvidersReady(daemonClient, observeEvents(["providers_snapshot_update"]), lifetime.signal, options),
|
|
97
204
|
refresh: (options) => daemonClient.refreshProvidersSnapshot(options),
|
|
98
205
|
diagnostic: (provider, options) => daemonClient.getProviderDiagnostic(provider, options),
|
|
99
206
|
listUsage: (options) => listProviderUsage(daemonClient, options),
|
|
100
|
-
subscribe: (handler) =>
|
|
101
|
-
|
|
102
|
-
|
|
207
|
+
subscribe: (handler) => {
|
|
208
|
+
return subscribeEvent("providers_snapshot_update", (message) => {
|
|
209
|
+
if (message.type === "providers_snapshot_update")
|
|
210
|
+
handler(message.payload);
|
|
211
|
+
});
|
|
212
|
+
},
|
|
103
213
|
},
|
|
104
214
|
config: {
|
|
105
215
|
get: (requestId) => daemonClient.getDaemonConfig(requestId),
|
|
@@ -107,7 +217,7 @@ export function createPaseoApi(daemonClient) {
|
|
|
107
217
|
},
|
|
108
218
|
};
|
|
109
219
|
}
|
|
110
|
-
function createWorkspaceHandleFactory(daemonClient, createAgent, terminals) {
|
|
220
|
+
function createWorkspaceHandleFactory(daemonClient, createAgent, terminals, listen) {
|
|
111
221
|
return (workspace) => {
|
|
112
222
|
const id = typeof workspace === "string" ? workspace : workspace.id;
|
|
113
223
|
let current = typeof workspace === "string" ? null : workspace;
|
|
@@ -167,21 +277,19 @@ function createWorkspaceHandleFactory(daemonClient, createAgent, terminals) {
|
|
|
167
277
|
}
|
|
168
278
|
return result;
|
|
169
279
|
},
|
|
170
|
-
subscribe: (handler) =>
|
|
171
|
-
const update = message.payload;
|
|
280
|
+
subscribe: (handler) => listen((update) => {
|
|
172
281
|
if (update.kind === "upsert" && update.workspace.id === id) {
|
|
173
282
|
current = update.workspace;
|
|
174
283
|
handler(update);
|
|
175
284
|
}
|
|
176
285
|
if (update.kind === "remove" && update.id === id) {
|
|
177
|
-
current = null;
|
|
178
286
|
handler(update);
|
|
179
287
|
}
|
|
180
288
|
}),
|
|
181
289
|
};
|
|
182
290
|
};
|
|
183
291
|
}
|
|
184
|
-
function createAgentHandleFactory(daemonClient) {
|
|
292
|
+
function createAgentHandleFactory(daemonClient, listen, subscribeTimeline) {
|
|
185
293
|
return (agent) => {
|
|
186
294
|
const id = typeof agent === "string" ? agent : agent.id;
|
|
187
295
|
let current = typeof agent === "string" ? null : agent;
|
|
@@ -196,13 +304,27 @@ function createAgentHandleFactory(daemonClient) {
|
|
|
196
304
|
}
|
|
197
305
|
return result;
|
|
198
306
|
},
|
|
199
|
-
subscribe: (handler) =>
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
307
|
+
subscribe: (handler) => subscribeTimeline(id, (message) => {
|
|
308
|
+
switch (message.type) {
|
|
309
|
+
case "agent_stream":
|
|
310
|
+
return handler(message.payload);
|
|
311
|
+
case "agent.timeline.subscription_restored":
|
|
312
|
+
return handler({
|
|
313
|
+
agentId: id,
|
|
314
|
+
subscriptionId: message.payload.subscriptionId,
|
|
315
|
+
event: { type: "subscription_restored" },
|
|
316
|
+
});
|
|
317
|
+
case "agent.timeline.error":
|
|
318
|
+
return handler({
|
|
319
|
+
agentId: id,
|
|
320
|
+
event: { type: "error", error: message.payload.error },
|
|
321
|
+
});
|
|
322
|
+
case "agent.timeline.replacement":
|
|
323
|
+
return handler({
|
|
324
|
+
agentId: id,
|
|
325
|
+
event: { type: "replacement", epoch: message.payload.epoch },
|
|
326
|
+
});
|
|
327
|
+
}
|
|
206
328
|
}),
|
|
207
329
|
},
|
|
208
330
|
get workspaceId() {
|
|
@@ -280,14 +402,12 @@ function createAgentHandleFactory(daemonClient) {
|
|
|
280
402
|
detach: async () => {
|
|
281
403
|
await daemonClient.detachAgent(id);
|
|
282
404
|
},
|
|
283
|
-
subscribe: (handler) =>
|
|
284
|
-
const update = message.payload;
|
|
405
|
+
subscribe: (handler) => listen((update) => {
|
|
285
406
|
if (update.kind === "upsert" && update.agent.id === id) {
|
|
286
407
|
current = update.agent;
|
|
287
408
|
handler(update);
|
|
288
409
|
}
|
|
289
410
|
if (update.kind === "remove" && update.agentId === id) {
|
|
290
|
-
current = null;
|
|
291
411
|
handler(update);
|
|
292
412
|
}
|
|
293
413
|
}),
|
|
@@ -326,77 +446,88 @@ function listProviderUsage(daemonClient, options) {
|
|
|
326
446
|
}
|
|
327
447
|
return daemonClient.listProviderUsage(options);
|
|
328
448
|
}
|
|
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
|
-
}
|
|
449
|
+
async function waitForProvidersReady(daemonClient, observation, signal, options = {}) {
|
|
334
450
|
const { timeoutMs = 60000, ...snapshotOptions } = options;
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
451
|
+
try {
|
|
452
|
+
await observation.ready;
|
|
453
|
+
signal.throwIfAborted();
|
|
454
|
+
return await new Promise((resolve, reject) => {
|
|
455
|
+
let settled = false;
|
|
456
|
+
let requestId = null;
|
|
457
|
+
let snapshotCwd;
|
|
458
|
+
const pendingUpdates = new Map();
|
|
459
|
+
let latestEntries = [];
|
|
460
|
+
const cleanup = () => {
|
|
461
|
+
clearTimeout(timeout);
|
|
462
|
+
unsubscribe();
|
|
463
|
+
signal.removeEventListener("abort", abort);
|
|
464
|
+
};
|
|
465
|
+
const finish = (snapshot) => {
|
|
466
|
+
if (settled)
|
|
467
|
+
return;
|
|
468
|
+
settled = true;
|
|
469
|
+
cleanup();
|
|
470
|
+
resolve(snapshot);
|
|
471
|
+
};
|
|
472
|
+
const fail = (error) => {
|
|
473
|
+
if (settled)
|
|
474
|
+
return;
|
|
475
|
+
settled = true;
|
|
476
|
+
cleanup();
|
|
477
|
+
reject(error instanceof Error ? error : new Error(String(error)));
|
|
478
|
+
};
|
|
479
|
+
const updateMatches = (update) => update.cwd === snapshotCwd;
|
|
480
|
+
const unsubscribe = observation.subscribe({
|
|
481
|
+
snapshot: () => { },
|
|
482
|
+
update: (message) => {
|
|
483
|
+
if (message.type !== "providers_snapshot_update")
|
|
484
|
+
return;
|
|
485
|
+
const update = message.payload;
|
|
486
|
+
if (!requestId) {
|
|
487
|
+
pendingUpdates.set(update.cwd, update);
|
|
488
|
+
return;
|
|
489
|
+
}
|
|
490
|
+
if (!updateMatches(update))
|
|
491
|
+
return;
|
|
492
|
+
latestEntries = update.entries;
|
|
493
|
+
if (update.entries.some((entry) => entry.status === "loading"))
|
|
494
|
+
return;
|
|
495
|
+
finish({ ...update, requestId });
|
|
496
|
+
},
|
|
497
|
+
});
|
|
498
|
+
const abort = () => fail(new Error("Paseo API is disposed"));
|
|
499
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
500
|
+
const timeout = setTimeout(() => {
|
|
501
|
+
const loading = latestEntries
|
|
502
|
+
.filter((entry) => entry.status === "loading")
|
|
503
|
+
.map((entry) => entry.provider)
|
|
504
|
+
.join(", ");
|
|
505
|
+
fail(new Error(loading
|
|
506
|
+
? `Timed out waiting for providers: ${loading}`
|
|
507
|
+
: "Timed out waiting for provider discovery"));
|
|
508
|
+
}, timeoutMs);
|
|
509
|
+
void daemonClient
|
|
510
|
+
.getProvidersSnapshot(snapshotOptions)
|
|
511
|
+
.then((snapshot) => {
|
|
512
|
+
requestId = snapshot.requestId;
|
|
513
|
+
snapshotCwd = snapshot.cwd;
|
|
514
|
+
latestEntries = snapshot.entries;
|
|
515
|
+
if (!snapshot.entries.some((entry) => entry.status === "loading")) {
|
|
516
|
+
finish(snapshot);
|
|
517
|
+
return;
|
|
518
|
+
}
|
|
519
|
+
const pendingUpdate = pendingUpdates.get(snapshotCwd);
|
|
520
|
+
if (pendingUpdate && !pendingUpdate.entries.some((entry) => entry.status === "loading")) {
|
|
521
|
+
finish({ ...pendingUpdate, requestId });
|
|
522
|
+
}
|
|
523
|
+
return undefined;
|
|
524
|
+
})
|
|
525
|
+
.catch(fail);
|
|
372
526
|
});
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
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
|
-
});
|
|
527
|
+
}
|
|
528
|
+
finally {
|
|
529
|
+
await observation.release();
|
|
530
|
+
}
|
|
400
531
|
}
|
|
401
532
|
function createGeneratedClientId() {
|
|
402
533
|
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
|
}
|
|
@@ -3,57 +3,53 @@ export class ProviderSnapshotUpdates {
|
|
|
3
3
|
constructor(host) {
|
|
4
4
|
this.host = host;
|
|
5
5
|
this.pending = new Map();
|
|
6
|
-
this.paused = false;
|
|
7
6
|
}
|
|
8
7
|
clear() {
|
|
9
8
|
this.pending.clear();
|
|
10
9
|
}
|
|
11
|
-
pause() {
|
|
12
|
-
this.paused = true;
|
|
13
|
-
// Invalidate old responses while retaining the announcements they would resolve.
|
|
14
|
-
for (const [cwd, pending] of this.pending) {
|
|
15
|
-
this.pending.set(cwd, { latest: pending.latest, running: false });
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
resume() {
|
|
19
|
-
this.paused = false;
|
|
20
|
-
for (const pending of this.pending.values())
|
|
21
|
-
this.receive(pending.latest);
|
|
22
|
-
}
|
|
23
10
|
receive(update) {
|
|
24
|
-
|
|
25
|
-
|
|
11
|
+
if (!this.host.active(update))
|
|
12
|
+
return;
|
|
13
|
+
const key = JSON.stringify([update.payload.subscriptionId, update.payload.cwd]);
|
|
14
|
+
const pending = this.pending.get(key) ?? { latest: update, running: false };
|
|
26
15
|
pending.latest = update;
|
|
27
|
-
this.pending.set(
|
|
28
|
-
if (
|
|
16
|
+
this.pending.set(key, pending);
|
|
17
|
+
if (pending.running)
|
|
29
18
|
return;
|
|
30
19
|
pending.running = true;
|
|
31
|
-
void this.resolve(
|
|
20
|
+
void this.resolve(key, pending);
|
|
32
21
|
}
|
|
33
|
-
async resolve(
|
|
22
|
+
async resolve(key, pending) {
|
|
34
23
|
try {
|
|
35
|
-
while (this.pending.get(
|
|
24
|
+
while (this.pending.get(key) === pending) {
|
|
36
25
|
const announced = pending.latest;
|
|
37
|
-
|
|
38
|
-
|
|
26
|
+
if (!this.host.active(announced))
|
|
27
|
+
return;
|
|
28
|
+
const snapshot = await this.host.fetch(announced.payload.cwd);
|
|
29
|
+
if (!this.host.active(announced))
|
|
30
|
+
return;
|
|
31
|
+
if (this.pending.get(key) !== pending)
|
|
39
32
|
return;
|
|
40
33
|
// A change arriving while the body was in flight needs the current body.
|
|
41
34
|
// If that response already covers it, no second fetch is necessary.
|
|
42
35
|
if (pending.latest !== announced &&
|
|
43
36
|
pending.latest.payload.snapshotHash !== snapshot.snapshotHash)
|
|
44
37
|
continue;
|
|
45
|
-
this.pending.delete(
|
|
46
|
-
this.host.emit({
|
|
38
|
+
this.pending.delete(key);
|
|
39
|
+
this.host.emit({
|
|
40
|
+
type: "providers_snapshot_update",
|
|
41
|
+
payload: { ...snapshot, subscriptionId: announced.payload.subscriptionId },
|
|
42
|
+
});
|
|
47
43
|
return;
|
|
48
44
|
}
|
|
49
45
|
}
|
|
50
46
|
catch (error) {
|
|
51
|
-
if (this.pending.get(
|
|
47
|
+
if (this.pending.get(key) === pending)
|
|
52
48
|
this.host.failed(error);
|
|
53
49
|
}
|
|
54
50
|
finally {
|
|
55
|
-
if (this.pending.get(
|
|
56
|
-
this.pending.delete(
|
|
51
|
+
if (this.pending.get(key) === pending)
|
|
52
|
+
this.pending.delete(key);
|
|
57
53
|
}
|
|
58
54
|
}
|
|
59
55
|
}
|
|
@@ -1,28 +1,25 @@
|
|
|
1
1
|
import { type TerminalStreamFrame } from "@camerontaylor/paseo-protocol/binary-frames/index";
|
|
2
|
-
import type {
|
|
2
|
+
import type { TerminalState } from "@camerontaylor/paseo-protocol/messages";
|
|
3
3
|
export type TerminalStreamEvent = {
|
|
4
4
|
terminalId: string;
|
|
5
|
+
subscriptionId: string;
|
|
6
|
+
} & ({
|
|
5
7
|
type: "output";
|
|
6
8
|
data: Uint8Array;
|
|
7
9
|
} | {
|
|
8
|
-
terminalId: string;
|
|
9
10
|
type: "snapshot";
|
|
10
11
|
state: TerminalState;
|
|
11
12
|
} | {
|
|
12
|
-
terminalId: string;
|
|
13
13
|
type: "restore";
|
|
14
14
|
data: Uint8Array;
|
|
15
|
-
};
|
|
15
|
+
});
|
|
16
16
|
export declare class TerminalStreamRouter {
|
|
17
|
-
private readonly
|
|
18
|
-
private readonly slotTerminals;
|
|
17
|
+
private readonly slots;
|
|
19
18
|
private readonly listeners;
|
|
19
|
+
/** Passive wire diagnostics. App state consumes the registration's callback. */
|
|
20
20
|
onEvent(handler: (event: TerminalStreamEvent) => void): () => void;
|
|
21
|
-
|
|
22
|
-
removeTerminal(terminalId: string): void;
|
|
21
|
+
attach(subscriptionId: string, terminalId: string, slot: number, receive: (event: TerminalStreamEvent) => void): () => void;
|
|
23
22
|
clearSlots(): void;
|
|
24
|
-
encodeInput(terminalId: string, message: TerminalInput["message"]): Uint8Array | null;
|
|
25
23
|
handleFrame(frame: TerminalStreamFrame): void;
|
|
26
|
-
private emit;
|
|
27
24
|
}
|
|
28
25
|
//# sourceMappingURL=terminal-stream-router.d.ts.map
|