dsh-interconnect 0.11.16 → 0.11.17

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.
@@ -838,6 +838,7 @@ var PLUGIN_SOURCE = "dsh-interconnect";
838
838
  var MAX_LINK_FRAME_BYTES = 1024 * 1024;
839
839
  var LINK_HANDSHAKE_TIMEOUT_MS = 1e4;
840
840
  var LINK_CHANNEL = "/interconnect/link";
841
+ var MAX_INSTANCE_ID_CHARS = 256;
841
842
  var MAX_REQUEST_ID_CHARS = 256;
842
843
  var MAX_LISTED_SESSIONS = 100;
843
844
  var frameFitsLink = (frame) => Buffer.byteLength(JSON.stringify(frame), "utf8") <= MAX_LINK_FRAME_BYTES;
@@ -931,7 +932,7 @@ var linkFrameSchema = Schema.union([
931
932
  var InterconnectService = class extends Service {
932
933
  static inject = ["agents", "credentials"];
933
934
  static Config = Schema.object({
934
- instanceId: Schema.string().default("dsh"),
935
+ instanceId: Schema.string().max(MAX_INSTANCE_ID_CHARS).default("dsh"),
935
936
  requestTimeoutMs: Schema.natural().max(6e4).default(1e4),
936
937
  peers: Schema.dict(Schema.string()).default({}),
937
938
  delivery: Schema.union([Schema.const("followup"), Schema.const("steer"), Schema.const("inject")]).default("followup"),
@@ -941,7 +942,6 @@ var InterconnectService = class extends Service {
941
942
  requestTimeoutMs;
942
943
  delivery;
943
944
  allowResume;
944
- subscriptions = [];
945
945
  server = new WebSocketServer({ noServer: true, maxPayload: MAX_LINK_FRAME_BYTES });
946
946
  sockets = /* @__PURE__ */ new Set();
947
947
  /** Outbound peer links keyed by the peer's `instanceId`. */
@@ -998,28 +998,28 @@ var InterconnectService = class extends Service {
998
998
  socket.ping();
999
999
  }
1000
1000
  }, 3e4);
1001
- this.subscriptions.push(ctx.on("agent/status", ({ agent, status }) => {
1001
+ ctx.on("agent/status", ({ agent, status }) => {
1002
1002
  this.fanout({ kind: "agent/status", sessionId: String(agent.session.id), status });
1003
- }));
1004
- this.subscriptions.push(ctx.on("agent/created", ({ agent }) => {
1003
+ });
1004
+ ctx.on("agent/created", ({ agent }) => {
1005
1005
  this.fanout({ kind: "agent/created", sessionId: String(agent.session.id) });
1006
- }));
1007
- this.subscriptions.push(ctx.on("agent/disposed", ({ agent }) => {
1006
+ });
1007
+ ctx.on("agent/disposed", ({ agent }) => {
1008
1008
  this.fanout({ kind: "agent/disposed", sessionId: String(agent.session.id) });
1009
- }));
1010
- this.subscriptions.push(ctx.on("session/created", (session) => {
1009
+ });
1010
+ ctx.on("session/created", (session) => {
1011
1011
  const parentSessionId = session.header.parentSession === void 0 ? void 0 : String(session.header.parentSession);
1012
1012
  this.fanout({
1013
1013
  kind: "session/created",
1014
1014
  sessionId: String(session.id),
1015
1015
  ...parentSessionId === void 0 ? {} : { parentSessionId }
1016
1016
  });
1017
- }));
1018
- this.subscriptions.push(ctx.on("session/disposed", (session) => {
1017
+ });
1018
+ ctx.on("session/disposed", (session) => {
1019
1019
  this.senders.delete(String(session.id));
1020
1020
  this.fanout({ kind: "session/disposed", sessionId: String(session.id) });
1021
- }));
1022
- this.subscriptions.push(ctx.on("subagent/end", (info) => {
1021
+ });
1022
+ ctx.on("subagent/end", (info) => {
1023
1023
  if (!info.local) return;
1024
1024
  this.fanout({
1025
1025
  kind: "subagent/end",
@@ -1027,10 +1027,7 @@ var InterconnectService = class extends Service {
1027
1027
  childSessionId: String(info.id),
1028
1028
  stopReason: info.stopReason
1029
1029
  });
1030
- }));
1031
- ctx.effect(() => () => {
1032
- for (const dispose of this.subscriptions.splice(0)) dispose();
1033
- }, "interconnect: event subscriptions");
1030
+ });
1034
1031
  ctx.effect(() => () => {
1035
1032
  if (this.heartbeatTimer !== void 0) clearInterval(this.heartbeatTimer);
1036
1033
  for (const state of this.linkStates.values()) state.close();
@@ -1220,10 +1217,12 @@ var InterconnectService = class extends Service {
1220
1217
  * dialed link plus the peer's inbound one), so one event must leave over one
1221
1218
  * of them. Which one is decided by local ownership, not by the peer: a dialed
1222
1219
  * socket is a configured peer's link and always sends. An inbound socket is
1223
- * skipped only when a dialed link to the id that socket itself announced is
1224
- * open, so an announcement can suppress nothing but a duplicate the
1225
- * controlled link already carries — an impostor cannot drop an event for a
1226
- * peer, and without a dialed link every inbound socket receives it.
1220
+ * skipped only when an open dialed link covers the id that socket itself
1221
+ * announced — either because `Config.peers` lists that peer under the id, or
1222
+ * because the peer announced the id over that dialed link — so an
1223
+ * announcement can suppress nothing but a duplicate the controlled link
1224
+ * already carries. An impostor cannot drop an event for a peer, and without a
1225
+ * dialed link every inbound socket receives it.
1227
1226
  *
1228
1227
  * That holds once a peer's announcement has been seen. Between this
1229
1228
  * instance's dialed link opening and the peer's `hello` arriving on its
@@ -1242,7 +1241,10 @@ var InterconnectService = class extends Service {
1242
1241
  continue;
1243
1242
  }
1244
1243
  const dialedPeer = this.dialedPeerOf.get(socket);
1245
- if (dialedPeer !== void 0) covered.add(dialedPeer);
1244
+ if (dialedPeer === void 0) continue;
1245
+ covered.add(dialedPeer);
1246
+ const announced = this.peerOf.get(socket);
1247
+ if (announced !== void 0) covered.add(announced);
1246
1248
  }
1247
1249
  for (const socket of this.sockets) {
1248
1250
  if (this.dialedPeerOf.get(socket) === void 0) {
@@ -1274,12 +1276,7 @@ var InterconnectService = class extends Service {
1274
1276
  if (query.kind === "ping") {
1275
1277
  result = { pong: true, instance: this.instanceId };
1276
1278
  } else if (query.kind === "list") {
1277
- const rowBudget = this.listRowsBudgetBytes(reqId);
1278
- if (rowBudget < 0) {
1279
- this.ctx.logger.warn("interconnect: list answer cannot fit the link frame cap; not answering");
1280
- return;
1281
- }
1282
- result = this.listSessions(rowBudget);
1279
+ result = this.listSessions(this.listRowsBudgetBytes(reqId));
1283
1280
  } else {
1284
1281
  const payload = {
1285
1282
  sender: this.peerOf.get(socket) ?? "unknown-peer",
@@ -1311,10 +1308,11 @@ var InterconnectService = class extends Service {
1311
1308
  * Bytes one `list` answer may spend on its `sessions` rows: the frame cap
1312
1309
  * minus the serialized envelope that answer will carry. The envelope holds
1313
1310
  * the peer-supplied `reqId` and this instance's own id, so it is measured per
1314
- * answer instead of reserved with a fixed constant.
1311
+ * answer instead of reserved with a fixed constant. Both ids are bounded
1312
+ * ({@link MAX_REQUEST_ID_CHARS} and {@link MAX_INSTANCE_ID_CHARS} characters),
1313
+ * so the envelope always fits and the budget is always positive.
1315
1314
  * @param reqId - the request id the answer has to echo.
1316
- * @returns the byte budget for the rows, negative when even an empty answer
1317
- * exceeds the cap and so cannot be sent at all.
1315
+ * @returns the byte budget for the rows.
1318
1316
  */
1319
1317
  listRowsBudgetBytes(reqId) {
1320
1318
  const envelope = {