dsh-interconnect 0.11.24 → 0.15.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;
@@ -823,12 +823,8 @@ defineMethod("transform", [
823
823
 
824
824
  // src/interconnect/index.ts
825
825
  import WebSocket, { WebSocketServer } from "ws";
826
- function isSessionOwnedBySubagent(ctx, session, agent) {
827
- if (session.header.origin === "subagent") return true;
828
- const parentId = session.header.parentSession;
829
- if (parentId === void 0 || agent === void 0) return false;
830
- const parent = ctx.agents.get(parentId);
831
- return parent !== void 0 && ctx.agents.isOwnedBy(agent.id, parent);
826
+ function isSessionOwnedBySubagent(session) {
827
+ return session.header.origin === "subagent";
832
828
  }
833
829
  function assertNever(value) {
834
830
  throw new Error(`interconnect: unhandled variant ${JSON.stringify(value)}`);
@@ -839,6 +835,7 @@ var DEFAULT_INSTANCE_ID = "dsh";
839
835
  var DEFAULT_REQUEST_TIMEOUT_MS = 1e4;
840
836
  var LINK_HANDSHAKE_TIMEOUT_MS = 1e4;
841
837
  var LINK_CHANNEL = "/interconnect/link";
838
+ var SELF_PEER_ORIGIN = "self";
842
839
  var MAX_INSTANCE_ID_CHARS = 256;
843
840
  var MAX_REQUEST_ID_CHARS = 256;
844
841
  var MAX_LISTED_SESSIONS = 100;
@@ -966,11 +963,19 @@ var InterconnectService = class extends Service {
966
963
  this.requestTimeoutMs = config.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
967
964
  this.delivery = config.delivery ?? "followup";
968
965
  this.allowResume = config.allowResume ?? true;
969
- for (const origin of Object.values(config.peers ?? {})) {
966
+ for (const [peerInstanceId, origin] of Object.entries(config.peers ?? {})) {
967
+ if (origin === SELF_PEER_ORIGIN) {
968
+ if (peerInstanceId !== this.instanceId) {
969
+ throw new Error(
970
+ `interconnect: peer "${peerInstanceId}" is configured as self, but this instance announces "${this.instanceId}"; key a self peer by the instanceId`
971
+ );
972
+ }
973
+ continue;
974
+ }
970
975
  linkUrl(trimBase(origin));
971
976
  }
972
977
  for (const [peerInstanceId, origin] of Object.entries(config.peers ?? {})) {
973
- this.link(peerInstanceId, origin);
978
+ this.link(peerInstanceId, origin === SELF_PEER_ORIGIN ? () => this.selfOrigin() : origin);
974
979
  }
975
980
  const upgrade = {
976
981
  path: LINK_CHANNEL,
@@ -1111,7 +1116,7 @@ var InterconnectService = class extends Service {
1111
1116
  * Returns the peer identity when reachable, or undefined when the link is
1112
1117
  * not up or no answer arrives.
1113
1118
  * @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.
1119
+ * @returns the peer's identity, or undefined when there is no usable answer.
1115
1120
  */
1116
1121
  async ping(instanceId) {
1117
1122
  return this.queryRequest(instanceId, { kind: "ping" });
@@ -1125,16 +1130,33 @@ var InterconnectService = class extends Service {
1125
1130
  async list(instanceId) {
1126
1131
  return this.queryRequest(instanceId, { kind: "list" });
1127
1132
  }
1133
+ /**
1134
+ * The peers this instance configures, each with the current state of the link
1135
+ * it dials, in configuration order. This is how a caller tells an id this
1136
+ * instance cannot reach at all (`peer-not-configured`) from one whose link is
1137
+ * merely down (`unreachable`), which the bare `undefined` of `ping`/`list`
1138
+ * cannot express. A configured peer whose link is `down` is still a valid
1139
+ * target: its reconnect runs on its own backoff, so it may answer later.
1140
+ * @returns one row per configured peer.
1141
+ */
1142
+ peers() {
1143
+ return [...this.linkStates].map(([instanceId, state]) => ({
1144
+ instanceId,
1145
+ origin: state.peer,
1146
+ link: state.writable() ? "up" : "down"
1147
+ }));
1148
+ }
1128
1149
  /**
1129
1150
  * 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.
1151
+ * correlated `msg-result`. A `peer-not-configured` result when this instance
1152
+ * configures no route for that id, undefined when the route exists but its
1153
+ * link is down, and a `message-too-large` result when the encoded frame would
1154
+ * not fit the link.
1134
1155
  */
1135
1156
  async msgRequest(instanceId, kind, payload) {
1136
1157
  const state = this.linkStates.get(instanceId);
1137
- if (state === void 0 || !state.writable()) return void 0;
1158
+ if (state === void 0) return { delivered: false, instance: instanceId, reason: "peer-not-configured" };
1159
+ if (!state.writable()) return void 0;
1138
1160
  const reqId = `m${++this.reqIdCounter}-${randomUUID()}`;
1139
1161
  const frame = {
1140
1162
  type: "msg",
@@ -1196,7 +1218,8 @@ var InterconnectService = class extends Service {
1196
1218
  * `Config.peers` entry; the resulting state owns reconnection for the life of
1197
1219
  * the service fiber.
1198
1220
  * @param instanceId - the peer's `instanceId`.
1199
- * @param origin - receiver origin this instance dials, e.g. `http://127.0.0.1:13080`.
1221
+ * @param origin - receiver origin this instance dials (e.g.
1222
+ * `http://127.0.0.1:13080`), or a resolver for a `self` route.
1200
1223
  */
1201
1224
  link(instanceId, origin) {
1202
1225
  const state = new LinkState(
@@ -1206,11 +1229,28 @@ var InterconnectService = class extends Service {
1206
1229
  this.ctx.logger,
1207
1230
  () => this.resolveToken(),
1208
1231
  instanceId,
1209
- trimBase(origin)
1232
+ typeof origin === "string" ? trimBase(origin) : origin
1210
1233
  );
1211
1234
  this.linkStates.set(instanceId, state);
1212
1235
  state.dial();
1213
1236
  }
1237
+ /**
1238
+ * The origin of this instance's own host webserver, or undefined while no
1239
+ * listening port exists. Read on every dial rather than cached: the
1240
+ * webserver may activate after this service, its port is assigned only in
1241
+ * its listen callback (`--port 0` picks a fresh one per start), and a
1242
+ * replacement webserver would move it again. Loopback is used regardless of
1243
+ * the bind host: a server bound to `0.0.0.0` also accepts `127.0.0.1`, while
1244
+ * `0.0.0.0` itself is a bind address, not a dialable origin.
1245
+ * @returns `http://127.0.0.1:<port>`, or undefined without a listening server.
1246
+ */
1247
+ selfOrigin() {
1248
+ const server = this.ctx.get("webServer");
1249
+ if (server === void 0) return void 0;
1250
+ const port = server.port;
1251
+ if (typeof port !== "number" || port <= 0) return void 0;
1252
+ return `http://127.0.0.1:${String(port)}`;
1253
+ }
1214
1254
  /** Push one serialized lifecycle fact out to every linked peer over WS. */
1215
1255
  fanout(notification) {
1216
1256
  this.broadcast(notification);
@@ -1345,7 +1385,7 @@ var InterconnectService = class extends Service {
1345
1385
  * @returns the bounded listing.
1346
1386
  */
1347
1387
  listSessions(rowBudgetBytes) {
1348
- const reachable = this.ctx.agents.list().filter((agent) => !isSessionOwnedBySubagent(this.ctx, agent.session, agent));
1388
+ const reachable = this.ctx.agents.list().filter((agent) => !isSessionOwnedBySubagent(agent.session));
1349
1389
  const sessions = [];
1350
1390
  let used = 0;
1351
1391
  for (const agent of reachable.slice(0, MAX_LISTED_SESSIONS)) {
@@ -1372,7 +1412,8 @@ var InterconnectService = class extends Service {
1372
1412
  let title;
1373
1413
  try {
1374
1414
  const snapshot = this.ctx.get("sessionProjections")?.snapshot(agent.session);
1375
- const value = snapshot?.values.title;
1415
+ const values = snapshot?.values;
1416
+ const value = values?.title;
1376
1417
  if (typeof value === "string" && value !== "") title = value;
1377
1418
  } catch {
1378
1419
  title = void 0;
@@ -1430,7 +1471,7 @@ var InterconnectService = class extends Service {
1430
1471
  }
1431
1472
  agent = woken.agent;
1432
1473
  }
1433
- if (isSessionOwnedBySubagent(this.ctx, agent.session, agent)) {
1474
+ if (isSessionOwnedBySubagent(agent.session)) {
1434
1475
  return { delivered: false, instance: this.instanceId, reason: "session-owned-by-subagent" };
1435
1476
  }
1436
1477
  const sender = (() => {
@@ -1639,24 +1680,40 @@ var LinkState = class {
1639
1680
  this.logger = logger;
1640
1681
  this.resolveToken = resolveToken;
1641
1682
  this.instanceId = instanceId;
1642
- this.peer = origin;
1683
+ this.origin = origin;
1643
1684
  }
1644
1685
  dialed;
1645
1686
  closed = false;
1646
1687
  retry = 0;
1647
1688
  reconnectTimer;
1648
1689
  warnedNoToken = false;
1649
- /** Receiver origin this link dials. */
1650
- peer;
1690
+ warnedNoOrigin = false;
1651
1691
  instanceId;
1692
+ origin;
1693
+ /** The origin this link dials now; for an unresolved `self` route, the sentinel is shown. */
1694
+ get peer() {
1695
+ return typeof this.origin === "string" ? this.origin : this.origin() ?? SELF_PEER_ORIGIN;
1696
+ }
1652
1697
  /** Open the socket; reconnect is scheduled by the close handler. */
1653
1698
  dial() {
1654
1699
  if (this.closed) return;
1700
+ const origin = typeof this.origin === "string" ? this.origin : this.origin();
1701
+ if (origin === void 0) {
1702
+ if (this.retry >= 1 && !this.warnedNoOrigin) {
1703
+ this.warnedNoOrigin = true;
1704
+ this.logger.warn(
1705
+ `interconnect: peer ${this.instanceId} is configured as self but this instance has no listening webServer port yet; the link stays down and retries`
1706
+ );
1707
+ }
1708
+ this.scheduleReconnect();
1709
+ return;
1710
+ }
1711
+ this.warnedNoOrigin = false;
1655
1712
  let url;
1656
1713
  try {
1657
- url = linkUrl(this.peer);
1714
+ url = linkUrl(origin);
1658
1715
  } catch {
1659
- this.logger.warn(`interconnect: invalid peer origin ${this.peer}; link to ${this.instanceId} stays down`);
1716
+ this.logger.warn(`interconnect: invalid peer origin ${origin}; link to ${this.instanceId} stays down`);
1660
1717
  return;
1661
1718
  }
1662
1719
  void this.resolveToken().then((token) => {
@@ -1788,11 +1845,13 @@ var deliveryOutcome = (result) => ({
1788
1845
  ...result.delivery === void 0 ? {} : { delivery: result.delivery },
1789
1846
  ...result.reason === void 0 ? {} : { reason: result.reason }
1790
1847
  });
1848
+ var configures = (interconnect, instanceId) => interconnect.peers().some((peer) => peer.instanceId === instanceId);
1849
+ 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
1850
  function apply(ctx) {
1792
1851
  const interconnect = ctx.interconnect;
1793
1852
  ctx.tools.register(defineTool({
1794
1853
  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.',
1854
+ 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
1855
  parameters: {
1797
1856
  instanceId: {
1798
1857
  type: "string",
@@ -1816,7 +1875,7 @@ function apply(ctx) {
1816
1875
  },
1817
1876
  resume: {
1818
1877
  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".`
1878
+ 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
1879
  }
1821
1880
  },
1822
1881
  output: {
@@ -1830,6 +1889,8 @@ function apply(ctx) {
1830
1889
  }
1831
1890
  const text = (() => {
1832
1891
  switch (value.reason) {
1892
+ case "peer-not-configured":
1893
+ 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
1894
  case "unreachable":
1834
1895
  return `not delivered: ${value.instance} did not answer (unreachable); retrying may succeed`;
1835
1896
  case "resume-refused":
@@ -1841,7 +1902,7 @@ function apply(ctx) {
1841
1902
  case "message-too-large":
1842
1903
  return "not delivered: the message is too large for the peer link (limit 1 MiB); shorten the text and try again";
1843
1904
  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");
1905
+ 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
1906
  }
1846
1907
  })();
1847
1908
  return [{ type: "text", text }];
@@ -1859,7 +1920,9 @@ function apply(ctx) {
1859
1920
  // as a reply target.
1860
1921
  ...sessionId === void 0 ? {} : { sender: interconnect.selfSender(String(sessionId)) },
1861
1922
  ...args.delivery === void 0 ? {} : { delivery: args.delivery },
1862
- ...args.resume === void 0 ? {} : { resume: args.resume }
1923
+ // Waking is the default: a persisted target with no live agent is
1924
+ // otherwise unreachable, and a caller that wants it left asleep passes false.
1925
+ resume: args.resume ?? true
1863
1926
  }));
1864
1927
  return deliveryOutcome(result);
1865
1928
  }
@@ -1880,17 +1943,20 @@ function apply(ctx) {
1880
1943
  additionalProperties: false,
1881
1944
  properties: {
1882
1945
  reachable: { type: "boolean", required: true },
1883
- instance: { type: "string" }
1946
+ instance: { type: "string" },
1947
+ // Whether this instance configures that peer at all: absent/false means
1948
+ // the id cannot be reached by any retry, true means the link is down.
1949
+ configured: { type: "boolean" }
1884
1950
  }
1885
1951
  },
1886
- render: (_args, value) => [{
1952
+ render: (args, value) => [{
1887
1953
  type: "text",
1888
- text: value.reachable ? `reachable: ${value.instance ?? "(unknown instance)"}` : "unreachable"
1954
+ text: value.reachable ? `reachable: ${value.instance ?? "(unknown instance)"}` : unansweredText(args.instanceId, value.configured)
1889
1955
  }]
1890
1956
  },
1891
1957
  async execute(args, exec) {
1892
1958
  const result = await raceSignal(exec.signal, interconnect.ping(args.instanceId));
1893
- if (result === void 0) return { reachable: false };
1959
+ if (result === void 0) return { reachable: false, configured: configures(interconnect, args.instanceId) };
1894
1960
  return { reachable: true, instance: result.instance };
1895
1961
  }
1896
1962
  }));
@@ -1911,6 +1977,8 @@ function apply(ctx) {
1911
1977
  properties: {
1912
1978
  reachable: { type: "boolean", required: true },
1913
1979
  instance: { type: "string" },
1980
+ // Whether this instance configures that peer at all, as in interconnect_ping.
1981
+ configured: { type: "boolean" },
1914
1982
  sessions: {
1915
1983
  type: "array",
1916
1984
  items: {
@@ -1925,8 +1993,8 @@ function apply(ctx) {
1925
1993
  }
1926
1994
  }
1927
1995
  },
1928
- render: (_args, value) => {
1929
- if (!value.reachable) return [{ type: "text", text: "unreachable" }];
1996
+ render: (args, value) => {
1997
+ if (!value.reachable) return [{ type: "text", text: unansweredText(args.instanceId, value.configured) }];
1930
1998
  const sessions = value.sessions ?? [];
1931
1999
  if (sessions.length === 0) {
1932
2000
  return [{ type: "text", text: `no live sessions on ${value.instance ?? "(unknown instance)"}` }];
@@ -1944,7 +2012,7 @@ function apply(ctx) {
1944
2012
  },
1945
2013
  async execute(args, exec) {
1946
2014
  const result = await raceSignal(exec.signal, interconnect.list(args.instanceId));
1947
- if (result === void 0) return { reachable: false };
2015
+ if (result === void 0) return { reachable: false, configured: configures(interconnect, args.instanceId) };
1948
2016
  return {
1949
2017
  reachable: true,
1950
2018
  instance: result.instance,
@@ -1958,6 +2026,55 @@ function apply(ctx) {
1958
2026
  };
1959
2027
  }
1960
2028
  }));
2029
+ ctx.tools.register(defineTool({
2030
+ name: "interconnect_peers",
2031
+ 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.`,
2032
+ parameters: {},
2033
+ output: {
2034
+ schema: {
2035
+ type: "object",
2036
+ additionalProperties: false,
2037
+ properties: {
2038
+ peers: {
2039
+ type: "array",
2040
+ items: {
2041
+ type: "object",
2042
+ additionalProperties: false,
2043
+ properties: {
2044
+ instanceId: { type: "string", required: true },
2045
+ origin: { type: "string", required: true },
2046
+ link: { type: "string", required: true }
2047
+ }
2048
+ }
2049
+ }
2050
+ }
2051
+ },
2052
+ render: (_args, value) => {
2053
+ const peers = value.peers ?? [];
2054
+ if (peers.length === 0) {
2055
+ return [{
2056
+ type: "text",
2057
+ 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."
2058
+ }];
2059
+ }
2060
+ return [{
2061
+ type: "text",
2062
+ text: peers.map((peer) => `${peer.instanceId} ${peer.origin} [${peer.link}]`).join("\n")
2063
+ }];
2064
+ }
2065
+ },
2066
+ execute(_args, _exec) {
2067
+ return Promise.resolve({
2068
+ peers: interconnect.peers().map((peer) => ({
2069
+ instanceId: peer.instanceId,
2070
+ // The origin is this instance's own dial target, read locally: it is
2071
+ // what a caller needs to diagnose a peer whose link will not come up.
2072
+ origin: peer.origin,
2073
+ link: peer.link
2074
+ }))
2075
+ });
2076
+ }
2077
+ }));
1961
2078
  ctx.tools.register(defineTool({
1962
2079
  name: "interconnect_reply",
1963
2080
  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 +2091,7 @@ function apply(ctx) {
1974
2091
  },
1975
2092
  resume: {
1976
2093
  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."
2094
+ 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
2095
  }
1979
2096
  },
1980
2097
  output: {
@@ -1996,6 +2113,9 @@ function apply(ctx) {
1996
2113
  if (value.reason === "session-owned-by-subagent") {
1997
2114
  return "not delivered: the recorded sender's session belongs to a subagent and its parent agent owns delivery";
1998
2115
  }
2116
+ if (value.reason === "peer-not-configured") {
2117
+ 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`;
2118
+ }
1999
2119
  if (value.reason === "unreachable") {
2000
2120
  return "not delivered: the recorded sender did not answer (unreachable); retrying may succeed";
2001
2121
  }
@@ -2005,7 +2125,7 @@ function apply(ctx) {
2005
2125
  if (value.reason === "resume-failed") {
2006
2126
  return "not delivered: could not wake the recorded sender's session";
2007
2127
  }
2008
- return "not delivered: the recorded sender's session is not live";
2128
+ 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
2129
  })();
2010
2130
  return [{ type: "text", text }];
2011
2131
  }
@@ -2021,7 +2141,8 @@ function apply(ctx) {
2021
2141
  sessionId: String(exec.agent.session.id),
2022
2142
  text: args.text,
2023
2143
  ...args.delivery === void 0 ? {} : { delivery: args.delivery },
2024
- ...args.resume === void 0 ? {} : { resume: args.resume }
2144
+ // Replying to a sender that has gone offline should still reach it.
2145
+ resume: args.resume ?? true
2025
2146
  }));
2026
2147
  return deliveryOutcome(result);
2027
2148
  }