@xmanrui/dsh-im 0.15.0 → 0.17.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.
@@ -1223,6 +1223,125 @@ export class HarnessClient {
1223
1223
  });
1224
1224
  }
1225
1225
 
1226
+ /**
1227
+ * Watch the global Harness event mux (all sessions) until `signal`
1228
+ * aborts, reconnecting on drop. The Desktop host serves the mux as a
1229
+ * WebSocket downlink; frames are `server-request` envelopes whose payload
1230
+ * is a `session/event` — only those are forwarded. `onReconnect` (when
1231
+ * provided) fires after every (re)connection so callers can compensate
1232
+ * for events missed while offline.
1233
+ */
1234
+ async watchHarnessEvents({ signal, onSessionEvent, onReconnect } = {}) {
1235
+ if (typeof onSessionEvent !== 'function') {
1236
+ throw new TypeError('watchHarnessEvents requires onSessionEvent');
1237
+ }
1238
+ if (!signal || typeof signal.addEventListener !== 'function') {
1239
+ throw new TypeError('watchHarnessEvents requires an AbortSignal');
1240
+ }
1241
+ if (onReconnect !== undefined && typeof onReconnect !== 'function') {
1242
+ throw new TypeError('onReconnect must be a function');
1243
+ }
1244
+ const url = new URL('/api/events.mux', this.#baseUrl);
1245
+ url.protocol = url.protocol === 'https:' ? 'wss:' : 'ws:';
1246
+ while (!signal.aborted) {
1247
+ try {
1248
+ await this.#watchHarnessEventSocket(url.toString(), {
1249
+ signal,
1250
+ onSessionEvent,
1251
+ onReconnect,
1252
+ });
1253
+ } catch (error) {
1254
+ if (signal.aborted) return;
1255
+ console.warn(`[${this.#logPrefix}] Harness event mux disconnected:`, error.message);
1256
+ }
1257
+ if (signal.aborted) return;
1258
+ try {
1259
+ await sleep(this.#interactionReconnectDelayMs, signal);
1260
+ } catch {
1261
+ if (signal.aborted) return;
1262
+ throw new Error('Harness event mux reconnect wait failed');
1263
+ }
1264
+ }
1265
+ }
1266
+
1267
+ #watchHarnessEventSocket(url, { signal, onSessionEvent, onReconnect }) {
1268
+ return new Promise((resolve, reject) => {
1269
+ let socket;
1270
+ try {
1271
+ socket = this.#createWebSocket(url);
1272
+ } catch (error) {
1273
+ reject(error);
1274
+ return;
1275
+ }
1276
+ let opened = false;
1277
+ let finished = false;
1278
+ const close = () => {
1279
+ try {
1280
+ socket.close();
1281
+ } catch {
1282
+ // Already closed.
1283
+ }
1284
+ };
1285
+ const finish = (error) => {
1286
+ if (finished) return;
1287
+ finished = true;
1288
+ socket.removeEventListener('open', handleOpen);
1289
+ socket.removeEventListener('message', handleMessage);
1290
+ socket.removeEventListener('close', handleClose);
1291
+ socket.removeEventListener('error', handleError);
1292
+ signal.removeEventListener('abort', handleAbort);
1293
+ if (error) reject(error);
1294
+ else resolve();
1295
+ };
1296
+ const handleOpen = () => {
1297
+ opened = true;
1298
+ try {
1299
+ onReconnect?.();
1300
+ } catch (error) {
1301
+ console.warn(`[${this.#logPrefix}] mux reconnect hook failed:`, error.message);
1302
+ }
1303
+ };
1304
+ const handleMessage = (event) => {
1305
+ try {
1306
+ if (typeof event.data !== 'string') return;
1307
+ const envelope = JSON.parse(event.data);
1308
+ const payload = envelope?.payload;
1309
+ if (envelope?.type !== 'server-request'
1310
+ || !payload
1311
+ || typeof payload !== 'object'
1312
+ || envelope.method !== payload.type
1313
+ || payload.type !== 'session/event'
1314
+ || typeof payload.sessionId !== 'string'
1315
+ || !payload.event
1316
+ || typeof payload.event !== 'object') return;
1317
+ onSessionEvent({ sessionId: payload.sessionId, event: payload.event });
1318
+ } catch (error) {
1319
+ console.warn(`[${this.#logPrefix}] ignored a malformed global mux frame:`, error.message);
1320
+ }
1321
+ };
1322
+ const handleClose = () => finish(opened ? null : new Error(
1323
+ 'Harness event mux WebSocket closed before opening',
1324
+ ));
1325
+ const handleError = () => {
1326
+ finish(new Error(opened
1327
+ ? 'Harness event mux WebSocket failed'
1328
+ : 'Harness event mux WebSocket failed before opening'));
1329
+ close();
1330
+ };
1331
+ const handleAbort = () => {
1332
+ close();
1333
+ finish();
1334
+ };
1335
+
1336
+ socket.addEventListener('open', handleOpen);
1337
+ socket.addEventListener('message', handleMessage);
1338
+ socket.addEventListener('close', handleClose, { once: true });
1339
+ socket.addEventListener('error', handleError, { once: true });
1340
+ signal.addEventListener('abort', handleAbort, { once: true });
1341
+ if (signal.aborted) handleAbort();
1342
+ });
1343
+ }
1344
+
1226
1345
  stopManagedProcess() {
1227
1346
  if (this.#managedProcess?.exitCode === null) this.#managedProcess.kill('SIGTERM');
1228
1347
  }
@@ -85,7 +85,7 @@ async function selectedWorkspacePath(value) {
85
85
  }
86
86
  }
87
87
 
88
- async function workspacePathSnapshot(harness) {
88
+ export async function workspacePathSnapshot(harness) {
89
89
  const listed = await harness.listWorkspaces();
90
90
  const currentValue = typeof harness?.currentWorkspace === 'function'
91
91
  ? harness.currentWorkspace()
@@ -148,7 +148,7 @@ async function runWorkspaceListCommand(match, harness) {
148
148
  }
149
149
  }
150
150
 
151
- async function resolveSessionListWorkspace(selector, harness) {
151
+ export async function resolveSessionListWorkspace(selector, harness) {
152
152
  if (!selector) {
153
153
  if (typeof harness?.currentWorkspace !== 'function') {
154
154
  return { error: '当前机器人没有可用的工作区。' };
@@ -184,7 +184,7 @@ function authenticatedHeaders(token) {
184
184
  function baseInfo() {
185
185
  return {
186
186
  channel_version: WEIXIN_PROTOCOL_VERSION,
187
- bot_agent: 'DeepSeekHarness/0.15.0',
187
+ bot_agent: 'DeepSeekHarness/0.17.0',
188
188
  };
189
189
  }
190
190