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