dsh-interconnect 0.11.23 → 0.13.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.
- package/README.md +164 -27
- package/assets/dsh-interconnect.md +60 -24
- package/dsh.plugin.json +1 -1
- package/lib/index.js +28 -13
- package/lib/interconnect/index.js +28 -13
- package/lib/interconnect/index.js.map +3 -3
- package/lib/tool-interconnect/index.js +106 -27
- package/lib/tool-interconnect/index.js.map +3 -3
- package/lib/types/interconnect/index.d.ts +32 -6
- package/lib/types/interconnect/index.d.ts.map +1 -1
- package/lib/types/interconnect/types.d.ts +57 -18
- package/lib/types/interconnect/types.d.ts.map +1 -1
- package/lib/types/tool-interconnect/index.d.ts +1 -1
- package/lib/types/tool-interconnect/index.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -8,7 +8,7 @@ import { credentialRef } from "@deepseek-ai/dsh-credentials";
|
|
|
8
8
|
import { remoteErrorOf } from "@deepseek-ai/dsh-typert-protocol";
|
|
9
9
|
import { boundContextSummary, createUserMessage } from "@deepseek-ai/dsh-llm";
|
|
10
10
|
|
|
11
|
-
//
|
|
11
|
+
// ../dsh-wt-ic-merge/vendor/cosmokit/src/misc.ts
|
|
12
12
|
function isNullable(value) {
|
|
13
13
|
return value === null || value === void 0;
|
|
14
14
|
}
|
|
@@ -30,7 +30,7 @@ function pick(source, keys, forced) {
|
|
|
30
30
|
return result;
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
//
|
|
33
|
+
// ../dsh-wt-ic-merge/vendor/cosmokit/src/types.ts
|
|
34
34
|
function is(type, value) {
|
|
35
35
|
if (arguments.length === 1) return (value2) => is(type, value2);
|
|
36
36
|
return type in globalThis && value instanceof globalThis[type] || Object.prototype.toString.call(value).slice(8, -1) === type;
|
|
@@ -139,7 +139,7 @@ function deepEqual(a, b, strict) {
|
|
|
139
139
|
}) ?? Object.keys({ ...a, ...b }).every((key) => deepEqual(a[key], b[key], strict));
|
|
140
140
|
}
|
|
141
141
|
|
|
142
|
-
//
|
|
142
|
+
// ../dsh-wt-ic-merge/vendor/cosmokit/src/time.ts
|
|
143
143
|
var Time;
|
|
144
144
|
((Time2) => {
|
|
145
145
|
Time2.millisecond = 1;
|
|
@@ -219,7 +219,7 @@ var Time;
|
|
|
219
219
|
Time2.template = template;
|
|
220
220
|
})(Time || (Time = {}));
|
|
221
221
|
|
|
222
|
-
//
|
|
222
|
+
// ../dsh-wt-ic-merge/vendor/schemastery/lib/index.mjs
|
|
223
223
|
var kSchema = Symbol.for("schemastery");
|
|
224
224
|
var kValidationError = Symbol.for("ValidationError");
|
|
225
225
|
globalThis.__schemastery_index__ ??= 0;
|
|
@@ -834,7 +834,6 @@ function assertNever(value) {
|
|
|
834
834
|
throw new Error(`interconnect: unhandled variant ${JSON.stringify(value)}`);
|
|
835
835
|
}
|
|
836
836
|
var INTERCONNECT_TOKEN_REF = "DSH_INTERCONNECT_TOKEN";
|
|
837
|
-
var PLUGIN_SOURCE = "dsh-interconnect";
|
|
838
837
|
var MAX_LINK_FRAME_BYTES = 1024 * 1024;
|
|
839
838
|
var DEFAULT_INSTANCE_ID = "dsh";
|
|
840
839
|
var DEFAULT_REQUEST_TIMEOUT_MS = 1e4;
|
|
@@ -1112,7 +1111,7 @@ var InterconnectService = class extends Service {
|
|
|
1112
1111
|
* Returns the peer identity when reachable, or undefined when the link is
|
|
1113
1112
|
* not up or no answer arrives.
|
|
1114
1113
|
* @param instanceId - the peer's `instanceId` as configured under {@link Config.peers}.
|
|
1115
|
-
* @returns the peer's identity, or undefined when
|
|
1114
|
+
* @returns the peer's identity, or undefined when there is no usable answer.
|
|
1116
1115
|
*/
|
|
1117
1116
|
async ping(instanceId) {
|
|
1118
1117
|
return this.queryRequest(instanceId, { kind: "ping" });
|
|
@@ -1126,16 +1125,33 @@ var InterconnectService = class extends Service {
|
|
|
1126
1125
|
async list(instanceId) {
|
|
1127
1126
|
return this.queryRequest(instanceId, { kind: "list" });
|
|
1128
1127
|
}
|
|
1128
|
+
/**
|
|
1129
|
+
* The peers this instance configures, each with the current state of the link
|
|
1130
|
+
* it dials, in configuration order. This is how a caller tells an id this
|
|
1131
|
+
* instance cannot reach at all (`peer-not-configured`) from one whose link is
|
|
1132
|
+
* merely down (`unreachable`), which the bare `undefined` of `ping`/`list`
|
|
1133
|
+
* cannot express. A configured peer whose link is `down` is still a valid
|
|
1134
|
+
* target: its reconnect runs on its own backoff, so it may answer later.
|
|
1135
|
+
* @returns one row per configured peer.
|
|
1136
|
+
*/
|
|
1137
|
+
peers() {
|
|
1138
|
+
return [...this.linkStates].map(([instanceId, state]) => ({
|
|
1139
|
+
instanceId,
|
|
1140
|
+
origin: state.peer,
|
|
1141
|
+
link: state.writable() ? "up" : "down"
|
|
1142
|
+
}));
|
|
1143
|
+
}
|
|
1129
1144
|
/**
|
|
1130
1145
|
* Send a `msg` frame over the live link to a peer and resolve with its
|
|
1131
|
-
* correlated `msg-result`.
|
|
1132
|
-
*
|
|
1133
|
-
*
|
|
1134
|
-
*
|
|
1146
|
+
* correlated `msg-result`. A `peer-not-configured` result when this instance
|
|
1147
|
+
* configures no route for that id, undefined when the route exists but its
|
|
1148
|
+
* link is down, and a `message-too-large` result when the encoded frame would
|
|
1149
|
+
* not fit the link.
|
|
1135
1150
|
*/
|
|
1136
1151
|
async msgRequest(instanceId, kind, payload) {
|
|
1137
1152
|
const state = this.linkStates.get(instanceId);
|
|
1138
|
-
if (state === void 0
|
|
1153
|
+
if (state === void 0) return { delivered: false, instance: instanceId, reason: "peer-not-configured" };
|
|
1154
|
+
if (!state.writable()) return void 0;
|
|
1139
1155
|
const reqId = `m${++this.reqIdCounter}-${randomUUID()}`;
|
|
1140
1156
|
const frame = {
|
|
1141
1157
|
type: "msg",
|
|
@@ -1443,8 +1459,7 @@ var InterconnectService = class extends Service {
|
|
|
1443
1459
|
})();
|
|
1444
1460
|
const message = createUserMessage({
|
|
1445
1461
|
source: {
|
|
1446
|
-
kind: "plugin",
|
|
1447
|
-
plugin: PLUGIN_SOURCE,
|
|
1462
|
+
kind: "plugin:dsh-interconnect",
|
|
1448
1463
|
form: "notice",
|
|
1449
1464
|
summary: boundContextSummary(
|
|
1450
1465
|
sender === void 0 ? `interconnect handoff delivered on instance ${this.instanceId}` : `interconnect handoff from ${sender.instanceId} (session ${sender.sessionId}) delivered on instance ${this.instanceId}`
|
|
@@ -1790,11 +1805,13 @@ var deliveryOutcome = (result) => ({
|
|
|
1790
1805
|
...result.delivery === void 0 ? {} : { delivery: result.delivery },
|
|
1791
1806
|
...result.reason === void 0 ? {} : { reason: result.reason }
|
|
1792
1807
|
});
|
|
1808
|
+
var configures = (interconnect, instanceId) => interconnect.peers().some((peer) => peer.instanceId === instanceId);
|
|
1809
|
+
var unansweredText = (instanceId, configured) => configured === false ? `not configured: this instance has no peer "${instanceId}" \u2014 add it to the interconnect peers mapping (interconnect_peers lists what is configured)` : `unreachable: peer "${instanceId}" gave no usable answer (its link is down, or the shared token differs); retrying may succeed`;
|
|
1793
1810
|
function apply(ctx) {
|
|
1794
1811
|
const interconnect = ctx.interconnect;
|
|
1795
1812
|
ctx.tools.register(defineTool({
|
|
1796
1813
|
name: "interconnect_send",
|
|
1797
|
-
description: 'Deliver one text message to a
|
|
1814
|
+
description: 'Deliver one text message to a session on another DSH instance (same machine, another machine, or another session). Returns whether the peer instance received it and which instance answered. A live session receives directly; a persisted session with no live agent is woken by default (resume), and when delivery fails the result reports the reason (for example "session-not-live" or "resume-refused"). The sending instance and session ids are attached automatically, so the receiver can reply with interconnect_reply; do not pass a sender parameter.',
|
|
1798
1815
|
parameters: {
|
|
1799
1816
|
instanceId: {
|
|
1800
1817
|
type: "string",
|
|
@@ -1818,7 +1835,7 @@ function apply(ctx) {
|
|
|
1818
1835
|
},
|
|
1819
1836
|
resume: {
|
|
1820
1837
|
type: "boolean",
|
|
1821
|
-
description: `Wake the target session if it is persisted but not live.
|
|
1838
|
+
description: `Wake the target session if it is persisted but not live. Defaults to true: waking starts a real agent turn \u2014 a billed model call with that session's full toolset \u2014 on the receiver, which is what makes an offline target reachable. Set false to leave it asleep and report reason "session-not-live". The receiver may still refuse, answering reason "resume-refused".`
|
|
1822
1839
|
}
|
|
1823
1840
|
},
|
|
1824
1841
|
output: {
|
|
@@ -1832,6 +1849,8 @@ function apply(ctx) {
|
|
|
1832
1849
|
}
|
|
1833
1850
|
const text = (() => {
|
|
1834
1851
|
switch (value.reason) {
|
|
1852
|
+
case "peer-not-configured":
|
|
1853
|
+
return `not delivered: this instance configures no peer "${value.instance}" \u2014 add it to the interconnect peers mapping (interconnect_peers lists what is configured)`;
|
|
1835
1854
|
case "unreachable":
|
|
1836
1855
|
return `not delivered: ${value.instance} did not answer (unreachable); retrying may succeed`;
|
|
1837
1856
|
case "resume-refused":
|
|
@@ -1843,7 +1862,7 @@ function apply(ctx) {
|
|
|
1843
1862
|
case "message-too-large":
|
|
1844
1863
|
return "not delivered: the message is too large for the peer link (limit 1 MiB); shorten the text and try again";
|
|
1845
1864
|
default:
|
|
1846
|
-
return `not delivered: no live session "${_args.sessionId}" on ${value.instance}` + (_args.resume ===
|
|
1865
|
+
return `not delivered: no live session "${_args.sessionId}" on ${value.instance}` + (_args.resume === false ? " \u2014 waking was disabled (resume: false); the message was not delivered to any other session" : " \u2014 it could not be woken there (the receiver has no resuming agent lookup); the message was not delivered to any other session");
|
|
1847
1866
|
}
|
|
1848
1867
|
})();
|
|
1849
1868
|
return [{ type: "text", text }];
|
|
@@ -1861,7 +1880,9 @@ function apply(ctx) {
|
|
|
1861
1880
|
// as a reply target.
|
|
1862
1881
|
...sessionId === void 0 ? {} : { sender: interconnect.selfSender(String(sessionId)) },
|
|
1863
1882
|
...args.delivery === void 0 ? {} : { delivery: args.delivery },
|
|
1864
|
-
|
|
1883
|
+
// Waking is the default: a persisted target with no live agent is
|
|
1884
|
+
// otherwise unreachable, and a caller that wants it left asleep passes false.
|
|
1885
|
+
resume: args.resume ?? true
|
|
1865
1886
|
}));
|
|
1866
1887
|
return deliveryOutcome(result);
|
|
1867
1888
|
}
|
|
@@ -1882,17 +1903,20 @@ function apply(ctx) {
|
|
|
1882
1903
|
additionalProperties: false,
|
|
1883
1904
|
properties: {
|
|
1884
1905
|
reachable: { type: "boolean", required: true },
|
|
1885
|
-
instance: { type: "string" }
|
|
1906
|
+
instance: { type: "string" },
|
|
1907
|
+
// Whether this instance configures that peer at all: absent/false means
|
|
1908
|
+
// the id cannot be reached by any retry, true means the link is down.
|
|
1909
|
+
configured: { type: "boolean" }
|
|
1886
1910
|
}
|
|
1887
1911
|
},
|
|
1888
|
-
render: (
|
|
1912
|
+
render: (args, value) => [{
|
|
1889
1913
|
type: "text",
|
|
1890
|
-
text: value.reachable ? `reachable: ${value.instance ?? "(unknown instance)"}` :
|
|
1914
|
+
text: value.reachable ? `reachable: ${value.instance ?? "(unknown instance)"}` : unansweredText(args.instanceId, value.configured)
|
|
1891
1915
|
}]
|
|
1892
1916
|
},
|
|
1893
1917
|
async execute(args, exec) {
|
|
1894
1918
|
const result = await raceSignal(exec.signal, interconnect.ping(args.instanceId));
|
|
1895
|
-
if (result === void 0) return { reachable: false };
|
|
1919
|
+
if (result === void 0) return { reachable: false, configured: configures(interconnect, args.instanceId) };
|
|
1896
1920
|
return { reachable: true, instance: result.instance };
|
|
1897
1921
|
}
|
|
1898
1922
|
}));
|
|
@@ -1913,6 +1937,8 @@ function apply(ctx) {
|
|
|
1913
1937
|
properties: {
|
|
1914
1938
|
reachable: { type: "boolean", required: true },
|
|
1915
1939
|
instance: { type: "string" },
|
|
1940
|
+
// Whether this instance configures that peer at all, as in interconnect_ping.
|
|
1941
|
+
configured: { type: "boolean" },
|
|
1916
1942
|
sessions: {
|
|
1917
1943
|
type: "array",
|
|
1918
1944
|
items: {
|
|
@@ -1927,8 +1953,8 @@ function apply(ctx) {
|
|
|
1927
1953
|
}
|
|
1928
1954
|
}
|
|
1929
1955
|
},
|
|
1930
|
-
render: (
|
|
1931
|
-
if (!value.reachable) return [{ type: "text", text:
|
|
1956
|
+
render: (args, value) => {
|
|
1957
|
+
if (!value.reachable) return [{ type: "text", text: unansweredText(args.instanceId, value.configured) }];
|
|
1932
1958
|
const sessions = value.sessions ?? [];
|
|
1933
1959
|
if (sessions.length === 0) {
|
|
1934
1960
|
return [{ type: "text", text: `no live sessions on ${value.instance ?? "(unknown instance)"}` }];
|
|
@@ -1946,7 +1972,7 @@ function apply(ctx) {
|
|
|
1946
1972
|
},
|
|
1947
1973
|
async execute(args, exec) {
|
|
1948
1974
|
const result = await raceSignal(exec.signal, interconnect.list(args.instanceId));
|
|
1949
|
-
if (result === void 0) return { reachable: false };
|
|
1975
|
+
if (result === void 0) return { reachable: false, configured: configures(interconnect, args.instanceId) };
|
|
1950
1976
|
return {
|
|
1951
1977
|
reachable: true,
|
|
1952
1978
|
instance: result.instance,
|
|
@@ -1960,6 +1986,55 @@ function apply(ctx) {
|
|
|
1960
1986
|
};
|
|
1961
1987
|
}
|
|
1962
1988
|
}));
|
|
1989
|
+
ctx.tools.register(defineTool({
|
|
1990
|
+
name: "interconnect_peers",
|
|
1991
|
+
description: `List the peer DSH instances this instance is configured to reach, with each peer's dialed link state. Call this first when you do not already know the peer ids available here: every listed instanceId is a valid target for interconnect_send, interconnect_ping, and interconnect_list. A peer listed with link "down" is configured but currently cannot carry frames, so it answers nothing until its reconnect succeeds; an id that is not listed at all cannot be reached and reports reason "peer-not-configured". A message that arrives from a peer this instance does not configure can be received but not replied to.`,
|
|
1992
|
+
parameters: {},
|
|
1993
|
+
output: {
|
|
1994
|
+
schema: {
|
|
1995
|
+
type: "object",
|
|
1996
|
+
additionalProperties: false,
|
|
1997
|
+
properties: {
|
|
1998
|
+
peers: {
|
|
1999
|
+
type: "array",
|
|
2000
|
+
items: {
|
|
2001
|
+
type: "object",
|
|
2002
|
+
additionalProperties: false,
|
|
2003
|
+
properties: {
|
|
2004
|
+
instanceId: { type: "string", required: true },
|
|
2005
|
+
origin: { type: "string", required: true },
|
|
2006
|
+
link: { type: "string", required: true }
|
|
2007
|
+
}
|
|
2008
|
+
}
|
|
2009
|
+
}
|
|
2010
|
+
}
|
|
2011
|
+
},
|
|
2012
|
+
render: (_args, value) => {
|
|
2013
|
+
const peers = value.peers ?? [];
|
|
2014
|
+
if (peers.length === 0) {
|
|
2015
|
+
return [{
|
|
2016
|
+
type: "text",
|
|
2017
|
+
text: "no peers configured: this instance can receive messages but cannot send or reply. Add entries to the interconnect row's peers mapping (a peer id keyed to the origin this instance dials); the change applies when that row's config is re-activated."
|
|
2018
|
+
}];
|
|
2019
|
+
}
|
|
2020
|
+
return [{
|
|
2021
|
+
type: "text",
|
|
2022
|
+
text: peers.map((peer) => `${peer.instanceId} ${peer.origin} [${peer.link}]`).join("\n")
|
|
2023
|
+
}];
|
|
2024
|
+
}
|
|
2025
|
+
},
|
|
2026
|
+
execute(_args, _exec) {
|
|
2027
|
+
return Promise.resolve({
|
|
2028
|
+
peers: interconnect.peers().map((peer) => ({
|
|
2029
|
+
instanceId: peer.instanceId,
|
|
2030
|
+
// The origin is this instance's own dial target, read locally: it is
|
|
2031
|
+
// what a caller needs to diagnose a peer whose link will not come up.
|
|
2032
|
+
origin: peer.origin,
|
|
2033
|
+
link: peer.link
|
|
2034
|
+
}))
|
|
2035
|
+
});
|
|
2036
|
+
}
|
|
2037
|
+
}));
|
|
1963
2038
|
ctx.tools.register(defineTool({
|
|
1964
2039
|
name: "interconnect_reply",
|
|
1965
2040
|
description: `Deliver one text message back to the peer that this session last received an interconnect message from. Only the reply text is needed: the replying session is this agent's own, and the outbound target (the sending instance id and session id) is recalled from the message this session received, so you do not ask for an address twice. The recalled target is the most recent sender only: a later message from another peer replaces it, and restarting this instance forgets it until a new message arrives, so name the intended recipient in the reply text when more than one peer may message this session. A session that never received a message through interconnect \u2014 or received one without a sender identity \u2014 reports reason "no-sender-known".`,
|
|
@@ -1976,7 +2051,7 @@ function apply(ctx) {
|
|
|
1976
2051
|
},
|
|
1977
2052
|
resume: {
|
|
1978
2053
|
type: "boolean",
|
|
1979
|
-
description:
|
|
2054
|
+
description: `Wake the recorded sender's session if it is persisted but not live. Defaults to true: a reply completes an interchange the sender started, so the session is woken by a real billed agent turn on the receiver. Set false to leave it asleep and report reason "session-not-live". The receiver may still refuse, answering "resume-refused".`
|
|
1980
2055
|
}
|
|
1981
2056
|
},
|
|
1982
2057
|
output: {
|
|
@@ -1998,6 +2073,9 @@ function apply(ctx) {
|
|
|
1998
2073
|
if (value.reason === "session-owned-by-subagent") {
|
|
1999
2074
|
return "not delivered: the recorded sender's session belongs to a subagent and its parent agent owns delivery";
|
|
2000
2075
|
}
|
|
2076
|
+
if (value.reason === "peer-not-configured") {
|
|
2077
|
+
return `not delivered: this instance configures no peer "${value.instance}" \u2014 the sender can be messaged, but this instance cannot dial it back until it is added to the interconnect peers mapping`;
|
|
2078
|
+
}
|
|
2001
2079
|
if (value.reason === "unreachable") {
|
|
2002
2080
|
return "not delivered: the recorded sender did not answer (unreachable); retrying may succeed";
|
|
2003
2081
|
}
|
|
@@ -2007,7 +2085,7 @@ function apply(ctx) {
|
|
|
2007
2085
|
if (value.reason === "resume-failed") {
|
|
2008
2086
|
return "not delivered: could not wake the recorded sender's session";
|
|
2009
2087
|
}
|
|
2010
|
-
return "not delivered: the recorded sender's session is not live";
|
|
2088
|
+
return "not delivered: the recorded sender's session is not live" + (_args.resume === false ? " \u2014 waking was disabled (resume: false); the reply was not sent to any other session" : " \u2014 it could not be woken there (the receiver has no resuming agent lookup); the reply was not sent to any other session");
|
|
2011
2089
|
})();
|
|
2012
2090
|
return [{ type: "text", text }];
|
|
2013
2091
|
}
|
|
@@ -2023,7 +2101,8 @@ function apply(ctx) {
|
|
|
2023
2101
|
sessionId: String(exec.agent.session.id),
|
|
2024
2102
|
text: args.text,
|
|
2025
2103
|
...args.delivery === void 0 ? {} : { delivery: args.delivery },
|
|
2026
|
-
|
|
2104
|
+
// Replying to a sender that has gone offline should still reach it.
|
|
2105
|
+
resume: args.resume ?? true
|
|
2027
2106
|
}));
|
|
2028
2107
|
return deliveryOutcome(result);
|
|
2029
2108
|
}
|