dsh-interconnect 0.11.16 → 0.11.18
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 +34 -30
- package/lib/interconnect/index.js +34 -30
- package/lib/interconnect/index.js.map +2 -2
- package/lib/tool-interconnect/index.js +34 -30
- package/lib/tool-interconnect/index.js.map +2 -2
- package/lib/types/interconnect/index.d.ts +12 -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.18",
|
|
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`. */
|
|
@@ -948,6 +948,8 @@ var InterconnectService = class extends Service {
|
|
|
948
948
|
peerOf = /* @__PURE__ */ new WeakMap();
|
|
949
949
|
/** Configured peer an outbound dial owns a socket for; absent on inbound sockets. */
|
|
950
950
|
dialedPeerOf = /* @__PURE__ */ new WeakMap();
|
|
951
|
+
/** Dialed sockets already reported as announcing an id other than their configured key. */
|
|
952
|
+
mismatchWarned = /* @__PURE__ */ new WeakSet();
|
|
951
953
|
/** Sender each local session last received a send from, keyed by local session id. */
|
|
952
954
|
senders = /* @__PURE__ */ new Map();
|
|
953
955
|
/** In-flight frames sent over a peer link, keyed by `reqId`, awaiting a correlated result. */
|
|
@@ -995,28 +997,28 @@ var InterconnectService = class extends Service {
|
|
|
995
997
|
socket.ping();
|
|
996
998
|
}
|
|
997
999
|
}, 3e4);
|
|
998
|
-
|
|
1000
|
+
ctx.on("agent/status", ({ agent, status }) => {
|
|
999
1001
|
this.fanout({ kind: "agent/status", sessionId: String(agent.session.id), status });
|
|
1000
|
-
})
|
|
1001
|
-
|
|
1002
|
+
});
|
|
1003
|
+
ctx.on("agent/created", ({ agent }) => {
|
|
1002
1004
|
this.fanout({ kind: "agent/created", sessionId: String(agent.session.id) });
|
|
1003
|
-
})
|
|
1004
|
-
|
|
1005
|
+
});
|
|
1006
|
+
ctx.on("agent/disposed", ({ agent }) => {
|
|
1005
1007
|
this.fanout({ kind: "agent/disposed", sessionId: String(agent.session.id) });
|
|
1006
|
-
})
|
|
1007
|
-
|
|
1008
|
+
});
|
|
1009
|
+
ctx.on("session/created", (session) => {
|
|
1008
1010
|
const parentSessionId = session.header.parentSession === void 0 ? void 0 : String(session.header.parentSession);
|
|
1009
1011
|
this.fanout({
|
|
1010
1012
|
kind: "session/created",
|
|
1011
1013
|
sessionId: String(session.id),
|
|
1012
1014
|
...parentSessionId === void 0 ? {} : { parentSessionId }
|
|
1013
1015
|
});
|
|
1014
|
-
})
|
|
1015
|
-
|
|
1016
|
+
});
|
|
1017
|
+
ctx.on("session/disposed", (session) => {
|
|
1016
1018
|
this.senders.delete(String(session.id));
|
|
1017
1019
|
this.fanout({ kind: "session/disposed", sessionId: String(session.id) });
|
|
1018
|
-
})
|
|
1019
|
-
|
|
1020
|
+
});
|
|
1021
|
+
ctx.on("subagent/end", (info) => {
|
|
1020
1022
|
if (!info.local) return;
|
|
1021
1023
|
this.fanout({
|
|
1022
1024
|
kind: "subagent/end",
|
|
@@ -1024,10 +1026,7 @@ var InterconnectService = class extends Service {
|
|
|
1024
1026
|
childSessionId: String(info.id),
|
|
1025
1027
|
stopReason: info.stopReason
|
|
1026
1028
|
});
|
|
1027
|
-
})
|
|
1028
|
-
ctx.effect(() => () => {
|
|
1029
|
-
for (const dispose of this.subscriptions.splice(0)) dispose();
|
|
1030
|
-
}, "interconnect: event subscriptions");
|
|
1029
|
+
});
|
|
1031
1030
|
ctx.effect(() => () => {
|
|
1032
1031
|
if (this.heartbeatTimer !== void 0) clearInterval(this.heartbeatTimer);
|
|
1033
1032
|
for (const state of this.linkStates.values()) state.close();
|
|
@@ -1217,10 +1216,12 @@ var InterconnectService = class extends Service {
|
|
|
1217
1216
|
* dialed link plus the peer's inbound one), so one event must leave over one
|
|
1218
1217
|
* of them. Which one is decided by local ownership, not by the peer: a dialed
|
|
1219
1218
|
* socket is a configured peer's link and always sends. An inbound socket is
|
|
1220
|
-
* skipped only when
|
|
1221
|
-
*
|
|
1222
|
-
*
|
|
1223
|
-
* peer
|
|
1219
|
+
* skipped only when this instance has an open dialed link configured under
|
|
1220
|
+
* the id that socket itself announced, so a self-reported announcement can
|
|
1221
|
+
* never silence another peer's link; with no such dialed link every inbound
|
|
1222
|
+
* socket receives the event. A peer listed under a key other than the id it
|
|
1223
|
+
* announces is warned about once per link, because then its own inbound
|
|
1224
|
+
* socket is not attributable and the peer receives each event twice.
|
|
1224
1225
|
*
|
|
1225
1226
|
* That holds once a peer's announcement has been seen. Between this
|
|
1226
1227
|
* instance's dialed link opening and the peer's `hello` arriving on its
|
|
@@ -1271,12 +1272,7 @@ var InterconnectService = class extends Service {
|
|
|
1271
1272
|
if (query.kind === "ping") {
|
|
1272
1273
|
result = { pong: true, instance: this.instanceId };
|
|
1273
1274
|
} 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);
|
|
1275
|
+
result = this.listSessions(this.listRowsBudgetBytes(reqId));
|
|
1280
1276
|
} else {
|
|
1281
1277
|
const payload = {
|
|
1282
1278
|
sender: this.peerOf.get(socket) ?? "unknown-peer",
|
|
@@ -1308,10 +1304,11 @@ var InterconnectService = class extends Service {
|
|
|
1308
1304
|
* Bytes one `list` answer may spend on its `sessions` rows: the frame cap
|
|
1309
1305
|
* minus the serialized envelope that answer will carry. The envelope holds
|
|
1310
1306
|
* the peer-supplied `reqId` and this instance's own id, so it is measured per
|
|
1311
|
-
* answer instead of reserved with a fixed constant.
|
|
1307
|
+
* answer instead of reserved with a fixed constant. Both ids are bounded
|
|
1308
|
+
* ({@link MAX_REQUEST_ID_CHARS} and {@link MAX_INSTANCE_ID_CHARS} characters),
|
|
1309
|
+
* so the envelope always fits and the budget is always positive.
|
|
1312
1310
|
* @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.
|
|
1311
|
+
* @returns the byte budget for the rows.
|
|
1315
1312
|
*/
|
|
1316
1313
|
listRowsBudgetBytes(reqId) {
|
|
1317
1314
|
const envelope = {
|
|
@@ -1576,6 +1573,13 @@ var InterconnectService = class extends Service {
|
|
|
1576
1573
|
const frame = parsed;
|
|
1577
1574
|
if (frame.type === "hello") {
|
|
1578
1575
|
this.peerOf.set(socket, frame.sender);
|
|
1576
|
+
const dialedPeer = this.dialedPeerOf.get(socket);
|
|
1577
|
+
if (dialedPeer !== void 0 && dialedPeer !== frame.sender && !this.mismatchWarned.has(socket)) {
|
|
1578
|
+
this.mismatchWarned.add(socket);
|
|
1579
|
+
this.ctx.logger.warn(
|
|
1580
|
+
`interconnect: peer configured as ${dialedPeer} announces ${frame.sender}; list a peer under the instanceId it announces, or the duplicate inbound link is not recognised`
|
|
1581
|
+
);
|
|
1582
|
+
}
|
|
1579
1583
|
return;
|
|
1580
1584
|
}
|
|
1581
1585
|
if (frame.type === "event") {
|
|
@@ -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`. */
|
|
@@ -948,6 +948,8 @@ var InterconnectService = class extends Service {
|
|
|
948
948
|
peerOf = /* @__PURE__ */ new WeakMap();
|
|
949
949
|
/** Configured peer an outbound dial owns a socket for; absent on inbound sockets. */
|
|
950
950
|
dialedPeerOf = /* @__PURE__ */ new WeakMap();
|
|
951
|
+
/** Dialed sockets already reported as announcing an id other than their configured key. */
|
|
952
|
+
mismatchWarned = /* @__PURE__ */ new WeakSet();
|
|
951
953
|
/** Sender each local session last received a send from, keyed by local session id. */
|
|
952
954
|
senders = /* @__PURE__ */ new Map();
|
|
953
955
|
/** In-flight frames sent over a peer link, keyed by `reqId`, awaiting a correlated result. */
|
|
@@ -995,28 +997,28 @@ var InterconnectService = class extends Service {
|
|
|
995
997
|
socket.ping();
|
|
996
998
|
}
|
|
997
999
|
}, 3e4);
|
|
998
|
-
|
|
1000
|
+
ctx.on("agent/status", ({ agent, status }) => {
|
|
999
1001
|
this.fanout({ kind: "agent/status", sessionId: String(agent.session.id), status });
|
|
1000
|
-
})
|
|
1001
|
-
|
|
1002
|
+
});
|
|
1003
|
+
ctx.on("agent/created", ({ agent }) => {
|
|
1002
1004
|
this.fanout({ kind: "agent/created", sessionId: String(agent.session.id) });
|
|
1003
|
-
})
|
|
1004
|
-
|
|
1005
|
+
});
|
|
1006
|
+
ctx.on("agent/disposed", ({ agent }) => {
|
|
1005
1007
|
this.fanout({ kind: "agent/disposed", sessionId: String(agent.session.id) });
|
|
1006
|
-
})
|
|
1007
|
-
|
|
1008
|
+
});
|
|
1009
|
+
ctx.on("session/created", (session) => {
|
|
1008
1010
|
const parentSessionId = session.header.parentSession === void 0 ? void 0 : String(session.header.parentSession);
|
|
1009
1011
|
this.fanout({
|
|
1010
1012
|
kind: "session/created",
|
|
1011
1013
|
sessionId: String(session.id),
|
|
1012
1014
|
...parentSessionId === void 0 ? {} : { parentSessionId }
|
|
1013
1015
|
});
|
|
1014
|
-
})
|
|
1015
|
-
|
|
1016
|
+
});
|
|
1017
|
+
ctx.on("session/disposed", (session) => {
|
|
1016
1018
|
this.senders.delete(String(session.id));
|
|
1017
1019
|
this.fanout({ kind: "session/disposed", sessionId: String(session.id) });
|
|
1018
|
-
})
|
|
1019
|
-
|
|
1020
|
+
});
|
|
1021
|
+
ctx.on("subagent/end", (info) => {
|
|
1020
1022
|
if (!info.local) return;
|
|
1021
1023
|
this.fanout({
|
|
1022
1024
|
kind: "subagent/end",
|
|
@@ -1024,10 +1026,7 @@ var InterconnectService = class extends Service {
|
|
|
1024
1026
|
childSessionId: String(info.id),
|
|
1025
1027
|
stopReason: info.stopReason
|
|
1026
1028
|
});
|
|
1027
|
-
})
|
|
1028
|
-
ctx.effect(() => () => {
|
|
1029
|
-
for (const dispose of this.subscriptions.splice(0)) dispose();
|
|
1030
|
-
}, "interconnect: event subscriptions");
|
|
1029
|
+
});
|
|
1031
1030
|
ctx.effect(() => () => {
|
|
1032
1031
|
if (this.heartbeatTimer !== void 0) clearInterval(this.heartbeatTimer);
|
|
1033
1032
|
for (const state of this.linkStates.values()) state.close();
|
|
@@ -1217,10 +1216,12 @@ var InterconnectService = class extends Service {
|
|
|
1217
1216
|
* dialed link plus the peer's inbound one), so one event must leave over one
|
|
1218
1217
|
* of them. Which one is decided by local ownership, not by the peer: a dialed
|
|
1219
1218
|
* socket is a configured peer's link and always sends. An inbound socket is
|
|
1220
|
-
* skipped only when
|
|
1221
|
-
*
|
|
1222
|
-
*
|
|
1223
|
-
* peer
|
|
1219
|
+
* skipped only when this instance has an open dialed link configured under
|
|
1220
|
+
* the id that socket itself announced, so a self-reported announcement can
|
|
1221
|
+
* never silence another peer's link; with no such dialed link every inbound
|
|
1222
|
+
* socket receives the event. A peer listed under a key other than the id it
|
|
1223
|
+
* announces is warned about once per link, because then its own inbound
|
|
1224
|
+
* socket is not attributable and the peer receives each event twice.
|
|
1224
1225
|
*
|
|
1225
1226
|
* That holds once a peer's announcement has been seen. Between this
|
|
1226
1227
|
* instance's dialed link opening and the peer's `hello` arriving on its
|
|
@@ -1271,12 +1272,7 @@ var InterconnectService = class extends Service {
|
|
|
1271
1272
|
if (query.kind === "ping") {
|
|
1272
1273
|
result = { pong: true, instance: this.instanceId };
|
|
1273
1274
|
} 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);
|
|
1275
|
+
result = this.listSessions(this.listRowsBudgetBytes(reqId));
|
|
1280
1276
|
} else {
|
|
1281
1277
|
const payload = {
|
|
1282
1278
|
sender: this.peerOf.get(socket) ?? "unknown-peer",
|
|
@@ -1308,10 +1304,11 @@ var InterconnectService = class extends Service {
|
|
|
1308
1304
|
* Bytes one `list` answer may spend on its `sessions` rows: the frame cap
|
|
1309
1305
|
* minus the serialized envelope that answer will carry. The envelope holds
|
|
1310
1306
|
* the peer-supplied `reqId` and this instance's own id, so it is measured per
|
|
1311
|
-
* answer instead of reserved with a fixed constant.
|
|
1307
|
+
* answer instead of reserved with a fixed constant. Both ids are bounded
|
|
1308
|
+
* ({@link MAX_REQUEST_ID_CHARS} and {@link MAX_INSTANCE_ID_CHARS} characters),
|
|
1309
|
+
* so the envelope always fits and the budget is always positive.
|
|
1312
1310
|
* @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.
|
|
1311
|
+
* @returns the byte budget for the rows.
|
|
1315
1312
|
*/
|
|
1316
1313
|
listRowsBudgetBytes(reqId) {
|
|
1317
1314
|
const envelope = {
|
|
@@ -1576,6 +1573,13 @@ var InterconnectService = class extends Service {
|
|
|
1576
1573
|
const frame = parsed;
|
|
1577
1574
|
if (frame.type === "hello") {
|
|
1578
1575
|
this.peerOf.set(socket, frame.sender);
|
|
1576
|
+
const dialedPeer = this.dialedPeerOf.get(socket);
|
|
1577
|
+
if (dialedPeer !== void 0 && dialedPeer !== frame.sender && !this.mismatchWarned.has(socket)) {
|
|
1578
|
+
this.mismatchWarned.add(socket);
|
|
1579
|
+
this.ctx.logger.warn(
|
|
1580
|
+
`interconnect: peer configured as ${dialedPeer} announces ${frame.sender}; list a peer under the instanceId it announces, or the duplicate inbound link is not recognised`
|
|
1581
|
+
);
|
|
1582
|
+
}
|
|
1579
1583
|
return;
|
|
1580
1584
|
}
|
|
1581
1585
|
if (frame.type === "event") {
|