dsh-interconnect 0.11.24 → 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.
@@ -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
- // ../.dsh/source/staging-20260807T143236Z/vendor/cosmokit/src/misc.ts
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
- // ../.dsh/source/staging-20260807T143236Z/vendor/cosmokit/src/types.ts
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
- // ../.dsh/source/staging-20260807T143236Z/vendor/cosmokit/src/time.ts
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
- // ../.dsh/source/staging-20260807T143236Z/vendor/schemastery/lib/index.mjs
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;
@@ -1111,7 +1111,7 @@ var InterconnectService = class extends Service {
1111
1111
  * Returns the peer identity when reachable, or undefined when the link is
1112
1112
  * not up or no answer arrives.
1113
1113
  * @param instanceId - the peer's `instanceId` as configured under {@link Config.peers}.
1114
- * @returns the peer's identity, or undefined when the link is not up or no answer arrives.
1114
+ * @returns the peer's identity, or undefined when there is no usable answer.
1115
1115
  */
1116
1116
  async ping(instanceId) {
1117
1117
  return this.queryRequest(instanceId, { kind: "ping" });
@@ -1125,16 +1125,33 @@ var InterconnectService = class extends Service {
1125
1125
  async list(instanceId) {
1126
1126
  return this.queryRequest(instanceId, { kind: "list" });
1127
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
+ }
1128
1144
  /**
1129
1145
  * Send a `msg` frame over the live link to a peer and resolve with its
1130
- * correlated `msg-result`. Undefined when the peer has no live link here
1131
- * (not configured, or the link is down) — sends can only address configured+
1132
- * connected peers by design — and a `message-too-large` result when the
1133
- * encoded frame would not fit the link.
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.
1134
1150
  */
1135
1151
  async msgRequest(instanceId, kind, payload) {
1136
1152
  const state = this.linkStates.get(instanceId);
1137
- if (state === void 0 || !state.writable()) return void 0;
1153
+ if (state === void 0) return { delivered: false, instance: instanceId, reason: "peer-not-configured" };
1154
+ if (!state.writable()) return void 0;
1138
1155
  const reqId = `m${++this.reqIdCounter}-${randomUUID()}`;
1139
1156
  const frame = {
1140
1157
  type: "msg",
@@ -1788,11 +1805,13 @@ var deliveryOutcome = (result) => ({
1788
1805
  ...result.delivery === void 0 ? {} : { delivery: result.delivery },
1789
1806
  ...result.reason === void 0 ? {} : { reason: result.reason }
1790
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`;
1791
1810
  function apply(ctx) {
1792
1811
  const interconnect = ctx.interconnect;
1793
1812
  ctx.tools.register(defineTool({
1794
1813
  name: "interconnect_send",
1795
- description: 'Deliver one text message to a live session on another DSH instance (same machine, another machine, or another session). Returns whether the peer instance received it and which instance answered. Only a live session receives directly; a persisted one can be woken by setting resume, and when delivery fails the result reports the reason (for example "session-not-live") and interconnect_list shows which sessions are live there. The sending instance and session ids are attached automatically, so the receiver can reply with interconnect_reply; do not pass a sender parameter.',
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.',
1796
1815
  parameters: {
1797
1816
  instanceId: {
1798
1817
  type: "string",
@@ -1816,7 +1835,7 @@ function apply(ctx) {
1816
1835
  },
1817
1836
  resume: {
1818
1837
  type: "boolean",
1819
- description: `Wake the target session if it is persisted but not live. Off by default because delivery to a woken session starts a real agent turn \u2014 a billed model call with that session's full toolset \u2014 in a conversation nobody is watching. Prefer interconnect_list and an already-live target; set this only when that specific session must be reached. The receiver may refuse, answering reason "resume-refused".`
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".`
1820
1839
  }
1821
1840
  },
1822
1841
  output: {
@@ -1830,6 +1849,8 @@ function apply(ctx) {
1830
1849
  }
1831
1850
  const text = (() => {
1832
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)`;
1833
1854
  case "unreachable":
1834
1855
  return `not delivered: ${value.instance} did not answer (unreachable); retrying may succeed`;
1835
1856
  case "resume-refused":
@@ -1841,7 +1862,7 @@ function apply(ctx) {
1841
1862
  case "message-too-large":
1842
1863
  return "not delivered: the message is too large for the peer link (limit 1 MiB); shorten the text and try again";
1843
1864
  default:
1844
- return `not delivered: no live session "${_args.sessionId}" on ${value.instance}` + (_args.resume === true ? " \u2014 use interconnect_list to see which sessions are live there" : " \u2014 use interconnect_list to see which sessions are live there, or set resume to wake this one");
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");
1845
1866
  }
1846
1867
  })();
1847
1868
  return [{ type: "text", text }];
@@ -1859,7 +1880,9 @@ function apply(ctx) {
1859
1880
  // as a reply target.
1860
1881
  ...sessionId === void 0 ? {} : { sender: interconnect.selfSender(String(sessionId)) },
1861
1882
  ...args.delivery === void 0 ? {} : { delivery: args.delivery },
1862
- ...args.resume === void 0 ? {} : { resume: args.resume }
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
1863
1886
  }));
1864
1887
  return deliveryOutcome(result);
1865
1888
  }
@@ -1880,17 +1903,20 @@ function apply(ctx) {
1880
1903
  additionalProperties: false,
1881
1904
  properties: {
1882
1905
  reachable: { type: "boolean", required: true },
1883
- 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" }
1884
1910
  }
1885
1911
  },
1886
- render: (_args, value) => [{
1912
+ render: (args, value) => [{
1887
1913
  type: "text",
1888
- text: value.reachable ? `reachable: ${value.instance ?? "(unknown instance)"}` : "unreachable"
1914
+ text: value.reachable ? `reachable: ${value.instance ?? "(unknown instance)"}` : unansweredText(args.instanceId, value.configured)
1889
1915
  }]
1890
1916
  },
1891
1917
  async execute(args, exec) {
1892
1918
  const result = await raceSignal(exec.signal, interconnect.ping(args.instanceId));
1893
- if (result === void 0) return { reachable: false };
1919
+ if (result === void 0) return { reachable: false, configured: configures(interconnect, args.instanceId) };
1894
1920
  return { reachable: true, instance: result.instance };
1895
1921
  }
1896
1922
  }));
@@ -1911,6 +1937,8 @@ function apply(ctx) {
1911
1937
  properties: {
1912
1938
  reachable: { type: "boolean", required: true },
1913
1939
  instance: { type: "string" },
1940
+ // Whether this instance configures that peer at all, as in interconnect_ping.
1941
+ configured: { type: "boolean" },
1914
1942
  sessions: {
1915
1943
  type: "array",
1916
1944
  items: {
@@ -1925,8 +1953,8 @@ function apply(ctx) {
1925
1953
  }
1926
1954
  }
1927
1955
  },
1928
- render: (_args, value) => {
1929
- if (!value.reachable) return [{ type: "text", text: "unreachable" }];
1956
+ render: (args, value) => {
1957
+ if (!value.reachable) return [{ type: "text", text: unansweredText(args.instanceId, value.configured) }];
1930
1958
  const sessions = value.sessions ?? [];
1931
1959
  if (sessions.length === 0) {
1932
1960
  return [{ type: "text", text: `no live sessions on ${value.instance ?? "(unknown instance)"}` }];
@@ -1944,7 +1972,7 @@ function apply(ctx) {
1944
1972
  },
1945
1973
  async execute(args, exec) {
1946
1974
  const result = await raceSignal(exec.signal, interconnect.list(args.instanceId));
1947
- if (result === void 0) return { reachable: false };
1975
+ if (result === void 0) return { reachable: false, configured: configures(interconnect, args.instanceId) };
1948
1976
  return {
1949
1977
  reachable: true,
1950
1978
  instance: result.instance,
@@ -1958,6 +1986,55 @@ function apply(ctx) {
1958
1986
  };
1959
1987
  }
1960
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
+ }));
1961
2038
  ctx.tools.register(defineTool({
1962
2039
  name: "interconnect_reply",
1963
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".`,
@@ -1974,7 +2051,7 @@ function apply(ctx) {
1974
2051
  },
1975
2052
  resume: {
1976
2053
  type: "boolean",
1977
- description: "Wake the target session if it is persisted but not live, as in interconnect_send. Off by default; the receiver may refuse."
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".`
1978
2055
  }
1979
2056
  },
1980
2057
  output: {
@@ -1996,6 +2073,9 @@ function apply(ctx) {
1996
2073
  if (value.reason === "session-owned-by-subagent") {
1997
2074
  return "not delivered: the recorded sender's session belongs to a subagent and its parent agent owns delivery";
1998
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
+ }
1999
2079
  if (value.reason === "unreachable") {
2000
2080
  return "not delivered: the recorded sender did not answer (unreachable); retrying may succeed";
2001
2081
  }
@@ -2005,7 +2085,7 @@ function apply(ctx) {
2005
2085
  if (value.reason === "resume-failed") {
2006
2086
  return "not delivered: could not wake the recorded sender's session";
2007
2087
  }
2008
- 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");
2009
2089
  })();
2010
2090
  return [{ type: "text", text }];
2011
2091
  }
@@ -2021,7 +2101,8 @@ function apply(ctx) {
2021
2101
  sessionId: String(exec.agent.session.id),
2022
2102
  text: args.text,
2023
2103
  ...args.delivery === void 0 ? {} : { delivery: args.delivery },
2024
- ...args.resume === void 0 ? {} : { resume: args.resume }
2104
+ // Replying to a sender that has gone offline should still reach it.
2105
+ resume: args.resume ?? true
2025
2106
  }));
2026
2107
  return deliveryOutcome(result);
2027
2108
  }