@itookit/dsht 0.5.1 → 0.5.2
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/README.i18n.yaml +2 -2
- package/README.md +4 -2
- package/README.zh.md +6 -4
- package/dist/catalog/controller.d.ts +26 -6
- package/dist/catalog/controller.js +73 -45
- package/dist/catalog/index.d.ts +1 -0
- package/dist/cli/dsht.js +4 -1
- package/dist/cli/startup.js +30 -11
- package/dist/cli/verifier.d.ts +4 -0
- package/dist/cli/verifier.js +28 -5
- package/dist/contracts.d.ts +20 -5
- package/dist/controller/connection-streams.d.ts +22 -0
- package/dist/controller/connection-streams.js +105 -0
- package/dist/controller/connection.d.ts +14 -3
- package/dist/controller/connection.js +40 -69
- package/dist/controller/controller.d.ts +17 -233
- package/dist/controller/controller.js +108 -811
- package/dist/controller/foreground.d.ts +44 -0
- package/dist/controller/foreground.js +79 -0
- package/dist/controller/loop-coordinator.d.ts +48 -0
- package/dist/controller/loop-coordinator.js +647 -0
- package/dist/controller/verifier.d.ts +4 -0
- package/dist/cost/controller.d.ts +1 -1
- package/dist/cost/controller.js +12 -5
- package/dist/cost/scanner.js +1 -0
- package/dist/session/controller.d.ts +23 -35
- package/dist/session/controller.js +113 -363
- package/dist/session/history-reader.d.ts +32 -0
- package/dist/session/history-reader.js +170 -0
- package/dist/session/index.d.ts +1 -1
- package/dist/session/info.d.ts +3 -38
- package/dist/session/info.js +14 -1
- package/dist/session/interactions.d.ts +26 -0
- package/dist/session/interactions.js +75 -0
- package/dist/session/navigator.d.ts +47 -0
- package/dist/session/navigator.js +158 -0
- package/dist/session/prompt-backfill.d.ts +23 -0
- package/dist/session/prompt-backfill.js +88 -0
- package/dist/session/state.d.ts +20 -0
- package/dist/session/state.js +1 -0
- package/dist/session/telemetry.d.ts +15 -6
- package/dist/session/telemetry.js +44 -7
- package/dist/session/transcript.d.ts +5 -1
- package/dist/slash/index.d.ts +1 -1
- package/dist/slash/parse.d.ts +2 -126
- package/dist/slash/registry.d.ts +1 -1
- package/dist/slash/types.d.ts +126 -0
- package/dist/slash/types.js +1 -0
- package/dist/state.d.ts +5 -17
- package/dist/state.js +1 -1
- package/dist/transport/client.d.ts +4 -3
- package/dist/transport/client.js +71 -25
- package/dist/ui/app.js +86 -301
- package/dist/ui/chat/shell-view.d.ts +2 -0
- package/dist/ui/chat/shell-view.js +8 -0
- package/dist/ui/chat/use-history-view.d.ts +69 -0
- package/dist/ui/chat/use-history-view.js +123 -0
- package/dist/ui/dialogs/use-panels.d.ts +53 -0
- package/dist/ui/dialogs/use-panels.js +51 -0
- package/dist/ui/input/use-composer.d.ts +35 -0
- package/dist/ui/input/use-composer.js +109 -0
- package/dist/ui/input/use-deferred-lines.d.ts +16 -0
- package/dist/ui/input/use-deferred-lines.js +54 -0
- package/dist/ui/input/use-history-recall.d.ts +20 -0
- package/dist/ui/input/use-history-recall.js +47 -0
- package/package.json +1 -1
package/dist/transport/client.js
CHANGED
|
@@ -22,7 +22,7 @@ export class RemoteError extends Error {
|
|
|
22
22
|
this.details = error.details;
|
|
23
23
|
}
|
|
24
24
|
}
|
|
25
|
-
/**
|
|
25
|
+
/** One client lifetime. Peer disconnects allow reconnect; close permanently ends the lifetime. */
|
|
26
26
|
export class Client {
|
|
27
27
|
timeoutMs;
|
|
28
28
|
base;
|
|
@@ -31,6 +31,7 @@ export class Client {
|
|
|
31
31
|
socket;
|
|
32
32
|
listeners = new Map();
|
|
33
33
|
lifetime = new AbortController();
|
|
34
|
+
closeTask;
|
|
34
35
|
constructor(base, timeoutMs = 15_000) {
|
|
35
36
|
this.timeoutMs = timeoutMs;
|
|
36
37
|
this.base = new URL(base);
|
|
@@ -117,13 +118,17 @@ export class Client {
|
|
|
117
118
|
}
|
|
118
119
|
/** Connect the physical mux. A disconnected instance may reconnect with its cookie. */
|
|
119
120
|
async connect() {
|
|
121
|
+
this.lifetime.signal.throwIfAborted();
|
|
120
122
|
if (this.socket)
|
|
121
123
|
throw new Error('Mux is already connected');
|
|
122
124
|
const url = new URL('/api/remote.mux', this.base);
|
|
123
125
|
url.protocol = url.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
124
126
|
const socket = new WebSocket(url, { headers: { cookie: this.cookie }, handshakeTimeout: this.timeoutMs });
|
|
125
127
|
this.socket = socket;
|
|
128
|
+
const closed = new Promise(resolve => socket.once('close', () => resolve()));
|
|
126
129
|
socket.on('message', (raw, binary) => {
|
|
130
|
+
if (this.socket !== socket || this.lifetime.signal.aborted)
|
|
131
|
+
return;
|
|
127
132
|
try {
|
|
128
133
|
if (binary)
|
|
129
134
|
throw new Error('Unexpected binary mux frame');
|
|
@@ -154,22 +159,40 @@ export class Client {
|
|
|
154
159
|
socket.terminate();
|
|
155
160
|
}
|
|
156
161
|
});
|
|
157
|
-
socket.on('error', error => this.
|
|
162
|
+
socket.on('error', error => { if (this.socket === socket)
|
|
163
|
+
this.fail(error); });
|
|
158
164
|
socket.on('close', () => {
|
|
159
|
-
if (this.socket
|
|
160
|
-
|
|
165
|
+
if (this.socket !== socket)
|
|
166
|
+
return;
|
|
167
|
+
this.socket = undefined;
|
|
161
168
|
this.fail(new Error('Connection closed'));
|
|
162
169
|
});
|
|
163
|
-
|
|
170
|
+
try {
|
|
171
|
+
await once(socket, 'open', { signal: this.lifetime.signal });
|
|
172
|
+
this.lifetime.signal.throwIfAborted();
|
|
173
|
+
}
|
|
174
|
+
catch (error) {
|
|
175
|
+
if (socket.readyState !== WebSocket.CLOSED)
|
|
176
|
+
socket.terminate();
|
|
177
|
+
await closed;
|
|
178
|
+
throw error;
|
|
179
|
+
}
|
|
164
180
|
}
|
|
165
181
|
/** Subscribe on the existing mux; each subscription has a fresh stream identity. */
|
|
166
182
|
subscribe(endpoint, args, listener) {
|
|
183
|
+
this.lifetime.signal.throwIfAborted();
|
|
167
184
|
const socket = this.socket;
|
|
168
185
|
if (socket?.readyState !== WebSocket.OPEN)
|
|
169
186
|
throw new Error('Mux is not connected');
|
|
170
187
|
const streamId = randomUUID();
|
|
171
188
|
this.listeners.set(streamId, listener);
|
|
172
|
-
|
|
189
|
+
try {
|
|
190
|
+
socket.send(JSON.stringify({ type: 'open', streamId, endpoint, payload: { args } }));
|
|
191
|
+
}
|
|
192
|
+
catch (error) {
|
|
193
|
+
this.listeners.delete(streamId);
|
|
194
|
+
throw error;
|
|
195
|
+
}
|
|
173
196
|
return { cancel: () => {
|
|
174
197
|
if (!this.listeners.delete(streamId))
|
|
175
198
|
return;
|
|
@@ -180,57 +203,80 @@ export class Client {
|
|
|
180
203
|
/** Archived session IDs from the latest authoritative workspace baseline. */
|
|
181
204
|
archivedSessionIds = new Set();
|
|
182
205
|
/** List workspaces by consuming and cancelling the authoritative opening baseline. */
|
|
183
|
-
async listWorkspaces() {
|
|
206
|
+
async listWorkspaces(signal) {
|
|
207
|
+
signal?.throwIfAborted();
|
|
184
208
|
return new Promise((resolve, reject) => {
|
|
185
209
|
let sub;
|
|
186
|
-
|
|
210
|
+
let settled = false;
|
|
211
|
+
const finish = (error, items) => {
|
|
212
|
+
if (settled)
|
|
213
|
+
return;
|
|
214
|
+
settled = true;
|
|
215
|
+
clearTimeout(timer);
|
|
216
|
+
signal?.removeEventListener('abort', cancel);
|
|
217
|
+
sub?.cancel();
|
|
218
|
+
if (error !== undefined)
|
|
219
|
+
reject(error);
|
|
220
|
+
else
|
|
221
|
+
resolve(items);
|
|
222
|
+
};
|
|
223
|
+
const cancel = () => finish(signal.reason);
|
|
224
|
+
const timer = setTimeout(() => finish(new Error('Workspace baseline timed out')), this.timeoutMs);
|
|
225
|
+
signal?.addEventListener('abort', cancel, { once: true });
|
|
187
226
|
try {
|
|
188
227
|
sub = this.subscribe('workspace/follow', {}, {
|
|
189
228
|
item: value => {
|
|
190
|
-
|
|
191
|
-
|
|
229
|
+
if (settled)
|
|
230
|
+
return;
|
|
192
231
|
try {
|
|
193
232
|
const frame = object(value);
|
|
194
233
|
if (frame.type !== 'baseline')
|
|
195
234
|
throw new Error('Workspace stream omitted its baseline');
|
|
196
235
|
this.archivedSessionIds = new Set(array(object(frame.value).archivedSessionIds).map(string));
|
|
197
|
-
|
|
236
|
+
finish(undefined, array(object(frame.value).items).map(object));
|
|
198
237
|
}
|
|
199
238
|
catch (error) {
|
|
200
|
-
|
|
239
|
+
finish(error);
|
|
201
240
|
}
|
|
202
241
|
},
|
|
203
|
-
end: error =>
|
|
242
|
+
end: error => finish(error ?? new Error('Workspace stream ended before baseline')),
|
|
204
243
|
});
|
|
244
|
+
if (settled)
|
|
245
|
+
sub.cancel();
|
|
205
246
|
}
|
|
206
247
|
catch (error) {
|
|
207
|
-
|
|
208
|
-
reject(error);
|
|
248
|
+
finish(error);
|
|
209
249
|
}
|
|
210
250
|
});
|
|
211
251
|
}
|
|
212
252
|
/** List visible sessions, optionally filtering by the workspace's accounted IDs. */
|
|
213
|
-
async listSessions(workspaceId) {
|
|
214
|
-
const sessions = array(object(await this.call('session/list', { _request: {} })).items).map(object);
|
|
253
|
+
async listSessions(workspaceId, signal) {
|
|
254
|
+
const sessions = array(object(await this.call('session/list', { _request: {} }, signal)).items).map(object);
|
|
215
255
|
if (!workspaceId)
|
|
216
256
|
return sessions;
|
|
217
|
-
const workspace = (await this.listWorkspaces()).find(item => item.workspaceId === workspaceId);
|
|
257
|
+
const workspace = (await this.listWorkspaces(signal)).find(item => item.workspaceId === workspaceId);
|
|
218
258
|
if (!workspace)
|
|
219
259
|
throw new Error(`Workspace not found: ${workspaceId}`);
|
|
220
260
|
const ids = new Set(array(workspace.sessionIds).map(string));
|
|
221
261
|
return sessions.filter(item => ids.has(string(item.sessionId)));
|
|
222
262
|
}
|
|
223
263
|
/** Close all streams, abort in-flight HTTP, and await the physical socket's closure. */
|
|
224
|
-
|
|
225
|
-
this.
|
|
264
|
+
close() {
|
|
265
|
+
if (this.closeTask)
|
|
266
|
+
return this.closeTask;
|
|
226
267
|
const socket = this.socket;
|
|
227
268
|
this.socket = undefined;
|
|
269
|
+
// Reserve before publishing cancellation: termination callbacks may themselves call close.
|
|
270
|
+
this.closeTask = Promise.resolve().then(async () => {
|
|
271
|
+
if (socket && socket.readyState !== WebSocket.CLOSED) {
|
|
272
|
+
const closed = new Promise(resolve => socket.once('close', () => resolve()));
|
|
273
|
+
socket.terminate();
|
|
274
|
+
await closed;
|
|
275
|
+
}
|
|
276
|
+
});
|
|
277
|
+
this.lifetime.abort();
|
|
228
278
|
this.fail(new Error('Client closed'));
|
|
229
|
-
|
|
230
|
-
const closed = new Promise(resolve => socket.once('close', () => resolve()));
|
|
231
|
-
socket.terminate();
|
|
232
|
-
await closed;
|
|
233
|
-
}
|
|
279
|
+
return this.closeTask;
|
|
234
280
|
}
|
|
235
281
|
signal(timeoutMs = this.timeoutMs) {
|
|
236
282
|
return timeoutMs === null ? this.lifetime.signal : AbortSignal.any([this.lifetime.signal, AbortSignal.timeout(timeoutMs)]);
|