dsh-interconnect 0.11.2 → 0.11.4

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/dsh.plugin.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dsh-interconnect",
3
3
  "description": "Cross-instance message/event handoff for DeepSeek Harness (DSH): WebSocket link, model-facing tools, and companion skill.",
4
- "version": "0.11.2",
4
+ "version": "0.11.4",
5
5
  "entry": {
6
6
  "name": "dsh-interconnect/interconnect",
7
7
  "inject": ["agents", "credentials"]
package/lib/index.js CHANGED
@@ -853,14 +853,37 @@ var listResultSchema = Schema.object({
853
853
  status: Schema.string().required(false)
854
854
  })).required()
855
855
  });
856
- var acceptsQueryResult = (schema, result) => {
856
+ var withoutNullFields = (value) => {
857
+ if (Array.isArray(value)) return value.map((item) => withoutNullFields(item));
858
+ if (value === null || typeof value !== "object") return value;
859
+ return Object.fromEntries(Object.entries(value).filter(([, field]) => field !== null).map(([key, field]) => [key, withoutNullFields(field)]));
860
+ };
861
+ var projectMsgResult = (result) => result.delivered ? { delivered: true, instance: result.instance, ...result.delivery === void 0 ? {} : { delivery: result.delivery } } : { delivered: false, instance: result.instance, reason: result.reason };
862
+ var parseQueryResult = (schema, result) => {
857
863
  try {
858
864
  const [parsed] = Schema.resolve(result, schema, {});
859
- return parsed !== void 0 && parsed !== null;
865
+ return parsed;
860
866
  } catch {
861
- return false;
867
+ return void 0;
862
868
  }
863
869
  };
870
+ var projectPingResult = (result) => {
871
+ const parsed = parseQueryResult(pingResultSchema, result);
872
+ return parsed === void 0 ? void 0 : { pong: true, instance: parsed.instance };
873
+ };
874
+ var projectListResult = (result) => {
875
+ const parsed = parseQueryResult(listResultSchema, result);
876
+ if (parsed === void 0) return void 0;
877
+ const rows = parsed.sessions;
878
+ return {
879
+ instance: parsed.instance,
880
+ sessions: rows.map((row) => ({
881
+ sessionId: row.sessionId,
882
+ ...row.title === void 0 || row.title === null ? {} : { title: row.title },
883
+ ...row.status === void 0 || row.status === null ? {} : { status: row.status }
884
+ }))
885
+ };
886
+ };
864
887
  var queryResultSchema = Schema.union([
865
888
  pingResultSchema,
866
889
  listResultSchema,
@@ -1119,7 +1142,7 @@ var InterconnectService = class extends Service {
1119
1142
  const state = this.linkStates.get(instanceId);
1120
1143
  if (state === void 0 || !state.writable()) return void 0;
1121
1144
  const reqId = `q${++this.reqIdCounter}-${randomUUID()}`;
1122
- const accept = query.kind === "ping" ? (result) => acceptsQueryResult(pingResultSchema, result) : (result) => acceptsQueryResult(listResultSchema, result);
1145
+ const accept = query.kind === "ping" ? projectPingResult : projectListResult;
1123
1146
  try {
1124
1147
  return await this.waitForResult(reqId, "query", (failure) => {
1125
1148
  const wrote = state.sendFrame({ type: "query", reqId, query });
@@ -1136,16 +1159,12 @@ var InterconnectService = class extends Service {
1136
1159
  this.pendingMessages.delete(reqId);
1137
1160
  reject(new Error(`interconnect: no result for ${reqId} within ${this.requestTimeoutMs}ms`));
1138
1161
  }, this.requestTimeoutMs);
1139
- this.pendingMessages.set(reqId, {
1140
- timer,
1141
- kind,
1142
- ...accept === void 0 ? {} : { accept },
1143
- resolve: (result) => {
1144
- clearTimeout(timer);
1145
- this.pendingMessages.delete(reqId);
1146
- resolve2(result);
1147
- }
1148
- });
1162
+ const settle = (result) => {
1163
+ clearTimeout(timer);
1164
+ this.pendingMessages.delete(reqId);
1165
+ resolve2(result);
1166
+ };
1167
+ this.pendingMessages.set(reqId, kind === "query" ? { timer, kind, accept, resolve: settle } : { timer, kind, resolve: settle });
1149
1168
  send((error) => {
1150
1169
  clearTimeout(timer);
1151
1170
  this.pendingMessages.delete(reqId);
@@ -1200,7 +1219,7 @@ var InterconnectService = class extends Service {
1200
1219
  this.attachSocket(socket);
1201
1220
  },
1202
1221
  this.ctx.logger,
1203
- () => this.resolveTokenForDial(),
1222
+ () => this.resolveToken(),
1204
1223
  instanceId,
1205
1224
  trimBase(origin)
1206
1225
  );
@@ -1389,8 +1408,18 @@ var InterconnectService = class extends Service {
1389
1408
  const credential = await this.ctx.credentials.resolve(credentialRef(INTERCONNECT_TOKEN_REF));
1390
1409
  return credential === void 0 || credential.value.length === 0 ? void 0 : credential.value;
1391
1410
  }
1392
- resolveTokenForDial() {
1393
- return this.resolveToken();
1411
+ /**
1412
+ * Answer one refused upgrade and close its socket. `socket.end()` alone only
1413
+ * half-closes the connection: a client that keeps its own writing side open
1414
+ * (a socket created with `allowHalfOpen`) leaves the connection live, and a
1415
+ * refused socket never enters the link pool, so nothing else would reap it.
1416
+ * @param socket - the refused upgrade socket.
1417
+ * @param response - the complete HTTP response sent to the client.
1418
+ */
1419
+ refuseUpgrade(socket, response) {
1420
+ socket.end(response, () => {
1421
+ socket.destroy();
1422
+ });
1394
1423
  }
1395
1424
  /** Inbound WebSocket upgrade: authenticate the bearer header, then accept. */
1396
1425
  async handleUpgrade(req, socket, head) {
@@ -1399,21 +1428,21 @@ var InterconnectService = class extends Service {
1399
1428
  token = await this.resolveToken();
1400
1429
  } catch (error) {
1401
1430
  this.ctx.logger.warn(`interconnect: upgrade token read failed: ${error instanceof Error ? error.message : String(error)}`);
1402
- socket.end("HTTP/1.1 403 Forbidden\r\nConnection: close\r\nContent-Length: 9\r\n\r\nforbidden");
1431
+ this.refuseUpgrade(socket, "HTTP/1.1 403 Forbidden\r\nConnection: close\r\nContent-Length: 9\r\n\r\nforbidden");
1403
1432
  return;
1404
1433
  }
1405
1434
  if (token === void 0) {
1406
- socket.end("HTTP/1.1 403 Forbidden\r\nConnection: close\r\nContent-Length: 9\r\n\r\nforbidden");
1435
+ this.refuseUpgrade(socket, "HTTP/1.1 403 Forbidden\r\nConnection: close\r\nContent-Length: 9\r\n\r\nforbidden");
1407
1436
  return;
1408
1437
  }
1409
1438
  if (this.disposed) {
1410
- socket.end("HTTP/1.1 503 Service Unavailable\r\nConnection: close\r\nContent-Length: 11\r\n\r\nunavailable");
1439
+ this.refuseUpgrade(socket, "HTTP/1.1 503 Service Unavailable\r\nConnection: close\r\nContent-Length: 11\r\n\r\nunavailable");
1411
1440
  return;
1412
1441
  }
1413
1442
  const expected = `Bearer ${token}`;
1414
1443
  const header = req.headers.authorization;
1415
1444
  if (header === void 0 || !timingSafeEqual(header, expected)) {
1416
- socket.end("HTTP/1.1 401 Unauthorized\r\nConnection: close\r\nContent-Length: 12\r\n\r\nunauthorized");
1445
+ this.refuseUpgrade(socket, "HTTP/1.1 401 Unauthorized\r\nConnection: close\r\nContent-Length: 12\r\n\r\nunauthorized");
1417
1446
  return;
1418
1447
  }
1419
1448
  this.server.handleUpgrade(req, socket, head, (websocket) => {
@@ -1458,7 +1487,7 @@ var InterconnectService = class extends Service {
1458
1487
  const text = typeof data === "string" ? data : Array.isArray(data) ? Buffer.concat(data).toString("utf8") : Buffer.isBuffer(data) ? data.toString("utf8") : Buffer.from(data).toString("utf8");
1459
1488
  let parsed;
1460
1489
  try {
1461
- parsed = Schema.resolve(JSON.parse(text), linkFrameSchema, {})[0];
1490
+ parsed = withoutNullFields(Schema.resolve(JSON.parse(text), linkFrameSchema, {})[0]);
1462
1491
  } catch {
1463
1492
  this.ctx.logger.warn("interconnect: dropping malformed link frame");
1464
1493
  return;
@@ -1488,15 +1517,15 @@ var InterconnectService = class extends Service {
1488
1517
  const pending = this.pendingMessages.get(frame.reqId);
1489
1518
  if (pending !== void 0 && pending.kind === "msg") {
1490
1519
  ;
1491
- pending.resolve?.(frame.result);
1520
+ pending.resolve?.(projectMsgResult(frame.result));
1492
1521
  }
1493
1522
  return;
1494
1523
  }
1495
1524
  if (frame.type === "query-result") {
1496
1525
  const pending = this.pendingMessages.get(frame.reqId);
1497
- if (pending !== void 0 && pending.kind === "query" && (pending.accept === void 0 || pending.accept(frame.result))) {
1498
- ;
1499
- pending.resolve?.(frame.result);
1526
+ if (pending !== void 0 && pending.kind === "query") {
1527
+ const answer = pending.accept(frame.result);
1528
+ if (answer !== void 0) pending.resolve?.(answer);
1500
1529
  }
1501
1530
  return;
1502
1531
  }
@@ -853,14 +853,37 @@ var listResultSchema = Schema.object({
853
853
  status: Schema.string().required(false)
854
854
  })).required()
855
855
  });
856
- var acceptsQueryResult = (schema, result) => {
856
+ var withoutNullFields = (value) => {
857
+ if (Array.isArray(value)) return value.map((item) => withoutNullFields(item));
858
+ if (value === null || typeof value !== "object") return value;
859
+ return Object.fromEntries(Object.entries(value).filter(([, field]) => field !== null).map(([key, field]) => [key, withoutNullFields(field)]));
860
+ };
861
+ var projectMsgResult = (result) => result.delivered ? { delivered: true, instance: result.instance, ...result.delivery === void 0 ? {} : { delivery: result.delivery } } : { delivered: false, instance: result.instance, reason: result.reason };
862
+ var parseQueryResult = (schema, result) => {
857
863
  try {
858
864
  const [parsed] = Schema.resolve(result, schema, {});
859
- return parsed !== void 0 && parsed !== null;
865
+ return parsed;
860
866
  } catch {
861
- return false;
867
+ return void 0;
862
868
  }
863
869
  };
870
+ var projectPingResult = (result) => {
871
+ const parsed = parseQueryResult(pingResultSchema, result);
872
+ return parsed === void 0 ? void 0 : { pong: true, instance: parsed.instance };
873
+ };
874
+ var projectListResult = (result) => {
875
+ const parsed = parseQueryResult(listResultSchema, result);
876
+ if (parsed === void 0) return void 0;
877
+ const rows = parsed.sessions;
878
+ return {
879
+ instance: parsed.instance,
880
+ sessions: rows.map((row) => ({
881
+ sessionId: row.sessionId,
882
+ ...row.title === void 0 || row.title === null ? {} : { title: row.title },
883
+ ...row.status === void 0 || row.status === null ? {} : { status: row.status }
884
+ }))
885
+ };
886
+ };
864
887
  var queryResultSchema = Schema.union([
865
888
  pingResultSchema,
866
889
  listResultSchema,
@@ -1119,7 +1142,7 @@ var InterconnectService = class extends Service {
1119
1142
  const state = this.linkStates.get(instanceId);
1120
1143
  if (state === void 0 || !state.writable()) return void 0;
1121
1144
  const reqId = `q${++this.reqIdCounter}-${randomUUID()}`;
1122
- const accept = query.kind === "ping" ? (result) => acceptsQueryResult(pingResultSchema, result) : (result) => acceptsQueryResult(listResultSchema, result);
1145
+ const accept = query.kind === "ping" ? projectPingResult : projectListResult;
1123
1146
  try {
1124
1147
  return await this.waitForResult(reqId, "query", (failure) => {
1125
1148
  const wrote = state.sendFrame({ type: "query", reqId, query });
@@ -1136,16 +1159,12 @@ var InterconnectService = class extends Service {
1136
1159
  this.pendingMessages.delete(reqId);
1137
1160
  reject(new Error(`interconnect: no result for ${reqId} within ${this.requestTimeoutMs}ms`));
1138
1161
  }, this.requestTimeoutMs);
1139
- this.pendingMessages.set(reqId, {
1140
- timer,
1141
- kind,
1142
- ...accept === void 0 ? {} : { accept },
1143
- resolve: (result) => {
1144
- clearTimeout(timer);
1145
- this.pendingMessages.delete(reqId);
1146
- resolve2(result);
1147
- }
1148
- });
1162
+ const settle = (result) => {
1163
+ clearTimeout(timer);
1164
+ this.pendingMessages.delete(reqId);
1165
+ resolve2(result);
1166
+ };
1167
+ this.pendingMessages.set(reqId, kind === "query" ? { timer, kind, accept, resolve: settle } : { timer, kind, resolve: settle });
1149
1168
  send((error) => {
1150
1169
  clearTimeout(timer);
1151
1170
  this.pendingMessages.delete(reqId);
@@ -1200,7 +1219,7 @@ var InterconnectService = class extends Service {
1200
1219
  this.attachSocket(socket);
1201
1220
  },
1202
1221
  this.ctx.logger,
1203
- () => this.resolveTokenForDial(),
1222
+ () => this.resolveToken(),
1204
1223
  instanceId,
1205
1224
  trimBase(origin)
1206
1225
  );
@@ -1389,8 +1408,18 @@ var InterconnectService = class extends Service {
1389
1408
  const credential = await this.ctx.credentials.resolve(credentialRef(INTERCONNECT_TOKEN_REF));
1390
1409
  return credential === void 0 || credential.value.length === 0 ? void 0 : credential.value;
1391
1410
  }
1392
- resolveTokenForDial() {
1393
- return this.resolveToken();
1411
+ /**
1412
+ * Answer one refused upgrade and close its socket. `socket.end()` alone only
1413
+ * half-closes the connection: a client that keeps its own writing side open
1414
+ * (a socket created with `allowHalfOpen`) leaves the connection live, and a
1415
+ * refused socket never enters the link pool, so nothing else would reap it.
1416
+ * @param socket - the refused upgrade socket.
1417
+ * @param response - the complete HTTP response sent to the client.
1418
+ */
1419
+ refuseUpgrade(socket, response) {
1420
+ socket.end(response, () => {
1421
+ socket.destroy();
1422
+ });
1394
1423
  }
1395
1424
  /** Inbound WebSocket upgrade: authenticate the bearer header, then accept. */
1396
1425
  async handleUpgrade(req, socket, head) {
@@ -1399,21 +1428,21 @@ var InterconnectService = class extends Service {
1399
1428
  token = await this.resolveToken();
1400
1429
  } catch (error) {
1401
1430
  this.ctx.logger.warn(`interconnect: upgrade token read failed: ${error instanceof Error ? error.message : String(error)}`);
1402
- socket.end("HTTP/1.1 403 Forbidden\r\nConnection: close\r\nContent-Length: 9\r\n\r\nforbidden");
1431
+ this.refuseUpgrade(socket, "HTTP/1.1 403 Forbidden\r\nConnection: close\r\nContent-Length: 9\r\n\r\nforbidden");
1403
1432
  return;
1404
1433
  }
1405
1434
  if (token === void 0) {
1406
- socket.end("HTTP/1.1 403 Forbidden\r\nConnection: close\r\nContent-Length: 9\r\n\r\nforbidden");
1435
+ this.refuseUpgrade(socket, "HTTP/1.1 403 Forbidden\r\nConnection: close\r\nContent-Length: 9\r\n\r\nforbidden");
1407
1436
  return;
1408
1437
  }
1409
1438
  if (this.disposed) {
1410
- socket.end("HTTP/1.1 503 Service Unavailable\r\nConnection: close\r\nContent-Length: 11\r\n\r\nunavailable");
1439
+ this.refuseUpgrade(socket, "HTTP/1.1 503 Service Unavailable\r\nConnection: close\r\nContent-Length: 11\r\n\r\nunavailable");
1411
1440
  return;
1412
1441
  }
1413
1442
  const expected = `Bearer ${token}`;
1414
1443
  const header = req.headers.authorization;
1415
1444
  if (header === void 0 || !timingSafeEqual(header, expected)) {
1416
- socket.end("HTTP/1.1 401 Unauthorized\r\nConnection: close\r\nContent-Length: 12\r\n\r\nunauthorized");
1445
+ this.refuseUpgrade(socket, "HTTP/1.1 401 Unauthorized\r\nConnection: close\r\nContent-Length: 12\r\n\r\nunauthorized");
1417
1446
  return;
1418
1447
  }
1419
1448
  this.server.handleUpgrade(req, socket, head, (websocket) => {
@@ -1458,7 +1487,7 @@ var InterconnectService = class extends Service {
1458
1487
  const text = typeof data === "string" ? data : Array.isArray(data) ? Buffer.concat(data).toString("utf8") : Buffer.isBuffer(data) ? data.toString("utf8") : Buffer.from(data).toString("utf8");
1459
1488
  let parsed;
1460
1489
  try {
1461
- parsed = Schema.resolve(JSON.parse(text), linkFrameSchema, {})[0];
1490
+ parsed = withoutNullFields(Schema.resolve(JSON.parse(text), linkFrameSchema, {})[0]);
1462
1491
  } catch {
1463
1492
  this.ctx.logger.warn("interconnect: dropping malformed link frame");
1464
1493
  return;
@@ -1488,15 +1517,15 @@ var InterconnectService = class extends Service {
1488
1517
  const pending = this.pendingMessages.get(frame.reqId);
1489
1518
  if (pending !== void 0 && pending.kind === "msg") {
1490
1519
  ;
1491
- pending.resolve?.(frame.result);
1520
+ pending.resolve?.(projectMsgResult(frame.result));
1492
1521
  }
1493
1522
  return;
1494
1523
  }
1495
1524
  if (frame.type === "query-result") {
1496
1525
  const pending = this.pendingMessages.get(frame.reqId);
1497
- if (pending !== void 0 && pending.kind === "query" && (pending.accept === void 0 || pending.accept(frame.result))) {
1498
- ;
1499
- pending.resolve?.(frame.result);
1526
+ if (pending !== void 0 && pending.kind === "query") {
1527
+ const answer = pending.accept(frame.result);
1528
+ if (answer !== void 0) pending.resolve?.(answer);
1500
1529
  }
1501
1530
  return;
1502
1531
  }