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.
- package/README.md +177 -31
- package/assets/dsh-interconnect.md +60 -24
- package/dsh.plugin.json +1 -1
- package/lib/index.js +85 -28
- package/lib/interconnect/index.js +85 -28
- package/lib/interconnect/index.js.map +3 -3
- package/lib/tool-interconnect/index.js +163 -42
- package/lib/tool-interconnect/index.js.map +3 -3
- package/lib/types/interconnect/index.d.ts +29 -7
- package/lib/types/interconnect/index.d.ts.map +1 -1
- package/lib/types/interconnect/types.d.ts +54 -14
- 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 -7
- package/lib/interconnect/invariant.js +0 -25
- package/lib/interconnect/invariant.js.map +0 -7
- package/lib/skill-interconnect/invariant.js +0 -13
- package/lib/skill-interconnect/invariant.js.map +0 -7
- package/lib/tool-interconnect/invariant.js +0 -13
- package/lib/tool-interconnect/invariant.js.map +0 -7
- package/lib/types/interconnect/invariant.d.ts +0 -16
- package/lib/types/interconnect/invariant.d.ts.map +0 -1
- package/lib/types/skill-interconnect/invariant.d.ts +0 -16
- package/lib/types/skill-interconnect/invariant.d.ts.map +0 -1
- package/lib/types/tool-interconnect/invariant.d.ts +0 -16
- package/lib/types/tool-interconnect/invariant.d.ts.map +0 -1
package/lib/index.js
CHANGED
|
@@ -5,7 +5,7 @@ import { credentialRef } from "@deepseek-ai/dsh-credentials";
|
|
|
5
5
|
import { remoteErrorOf } from "@deepseek-ai/dsh-typert-protocol";
|
|
6
6
|
import { boundContextSummary, createUserMessage } from "@deepseek-ai/dsh-llm";
|
|
7
7
|
|
|
8
|
-
//
|
|
8
|
+
// ../dsh-wt-ic-merge/vendor/cosmokit/src/misc.ts
|
|
9
9
|
function isNullable(value) {
|
|
10
10
|
return value === null || value === void 0;
|
|
11
11
|
}
|
|
@@ -27,7 +27,7 @@ function pick(source, keys, forced) {
|
|
|
27
27
|
return result;
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
//
|
|
30
|
+
// ../dsh-wt-ic-merge/vendor/cosmokit/src/types.ts
|
|
31
31
|
function is(type, value) {
|
|
32
32
|
if (arguments.length === 1) return (value2) => is(type, value2);
|
|
33
33
|
return type in globalThis && value instanceof globalThis[type] || Object.prototype.toString.call(value).slice(8, -1) === type;
|
|
@@ -136,7 +136,7 @@ function deepEqual(a, b, strict) {
|
|
|
136
136
|
}) ?? Object.keys({ ...a, ...b }).every((key) => deepEqual(a[key], b[key], strict));
|
|
137
137
|
}
|
|
138
138
|
|
|
139
|
-
//
|
|
139
|
+
// ../dsh-wt-ic-merge/vendor/cosmokit/src/time.ts
|
|
140
140
|
var Time;
|
|
141
141
|
((Time2) => {
|
|
142
142
|
Time2.millisecond = 1;
|
|
@@ -216,7 +216,7 @@ var Time;
|
|
|
216
216
|
Time2.template = template;
|
|
217
217
|
})(Time || (Time = {}));
|
|
218
218
|
|
|
219
|
-
//
|
|
219
|
+
// ../dsh-wt-ic-merge/vendor/schemastery/lib/index.mjs
|
|
220
220
|
var kSchema = Symbol.for("schemastery");
|
|
221
221
|
var kValidationError = Symbol.for("ValidationError");
|
|
222
222
|
globalThis.__schemastery_index__ ??= 0;
|
|
@@ -820,12 +820,8 @@ defineMethod("transform", [
|
|
|
820
820
|
|
|
821
821
|
// src/interconnect/index.ts
|
|
822
822
|
import WebSocket, { WebSocketServer } from "ws";
|
|
823
|
-
function isSessionOwnedBySubagent(
|
|
824
|
-
|
|
825
|
-
const parentId = session.header.parentSession;
|
|
826
|
-
if (parentId === void 0 || agent === void 0) return false;
|
|
827
|
-
const parent = ctx.agents.get(parentId);
|
|
828
|
-
return parent !== void 0 && ctx.agents.isOwnedBy(agent.id, parent);
|
|
823
|
+
function isSessionOwnedBySubagent(session) {
|
|
824
|
+
return session.header.origin === "subagent";
|
|
829
825
|
}
|
|
830
826
|
function assertNever(value) {
|
|
831
827
|
throw new Error(`interconnect: unhandled variant ${JSON.stringify(value)}`);
|
|
@@ -836,6 +832,7 @@ var DEFAULT_INSTANCE_ID = "dsh";
|
|
|
836
832
|
var DEFAULT_REQUEST_TIMEOUT_MS = 1e4;
|
|
837
833
|
var LINK_HANDSHAKE_TIMEOUT_MS = 1e4;
|
|
838
834
|
var LINK_CHANNEL = "/interconnect/link";
|
|
835
|
+
var SELF_PEER_ORIGIN = "self";
|
|
839
836
|
var MAX_INSTANCE_ID_CHARS = 256;
|
|
840
837
|
var MAX_REQUEST_ID_CHARS = 256;
|
|
841
838
|
var MAX_LISTED_SESSIONS = 100;
|
|
@@ -963,11 +960,19 @@ var InterconnectService = class extends Service {
|
|
|
963
960
|
this.requestTimeoutMs = config.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
|
|
964
961
|
this.delivery = config.delivery ?? "followup";
|
|
965
962
|
this.allowResume = config.allowResume ?? true;
|
|
966
|
-
for (const origin of Object.
|
|
963
|
+
for (const [peerInstanceId, origin] of Object.entries(config.peers ?? {})) {
|
|
964
|
+
if (origin === SELF_PEER_ORIGIN) {
|
|
965
|
+
if (peerInstanceId !== this.instanceId) {
|
|
966
|
+
throw new Error(
|
|
967
|
+
`interconnect: peer "${peerInstanceId}" is configured as self, but this instance announces "${this.instanceId}"; key a self peer by the instanceId`
|
|
968
|
+
);
|
|
969
|
+
}
|
|
970
|
+
continue;
|
|
971
|
+
}
|
|
967
972
|
linkUrl(trimBase(origin));
|
|
968
973
|
}
|
|
969
974
|
for (const [peerInstanceId, origin] of Object.entries(config.peers ?? {})) {
|
|
970
|
-
this.link(peerInstanceId, origin);
|
|
975
|
+
this.link(peerInstanceId, origin === SELF_PEER_ORIGIN ? () => this.selfOrigin() : origin);
|
|
971
976
|
}
|
|
972
977
|
const upgrade = {
|
|
973
978
|
path: LINK_CHANNEL,
|
|
@@ -1108,7 +1113,7 @@ var InterconnectService = class extends Service {
|
|
|
1108
1113
|
* Returns the peer identity when reachable, or undefined when the link is
|
|
1109
1114
|
* not up or no answer arrives.
|
|
1110
1115
|
* @param instanceId - the peer's `instanceId` as configured under {@link Config.peers}.
|
|
1111
|
-
* @returns the peer's identity, or undefined when
|
|
1116
|
+
* @returns the peer's identity, or undefined when there is no usable answer.
|
|
1112
1117
|
*/
|
|
1113
1118
|
async ping(instanceId) {
|
|
1114
1119
|
return this.queryRequest(instanceId, { kind: "ping" });
|
|
@@ -1122,16 +1127,33 @@ var InterconnectService = class extends Service {
|
|
|
1122
1127
|
async list(instanceId) {
|
|
1123
1128
|
return this.queryRequest(instanceId, { kind: "list" });
|
|
1124
1129
|
}
|
|
1130
|
+
/**
|
|
1131
|
+
* The peers this instance configures, each with the current state of the link
|
|
1132
|
+
* it dials, in configuration order. This is how a caller tells an id this
|
|
1133
|
+
* instance cannot reach at all (`peer-not-configured`) from one whose link is
|
|
1134
|
+
* merely down (`unreachable`), which the bare `undefined` of `ping`/`list`
|
|
1135
|
+
* cannot express. A configured peer whose link is `down` is still a valid
|
|
1136
|
+
* target: its reconnect runs on its own backoff, so it may answer later.
|
|
1137
|
+
* @returns one row per configured peer.
|
|
1138
|
+
*/
|
|
1139
|
+
peers() {
|
|
1140
|
+
return [...this.linkStates].map(([instanceId, state]) => ({
|
|
1141
|
+
instanceId,
|
|
1142
|
+
origin: state.peer,
|
|
1143
|
+
link: state.writable() ? "up" : "down"
|
|
1144
|
+
}));
|
|
1145
|
+
}
|
|
1125
1146
|
/**
|
|
1126
1147
|
* Send a `msg` frame over the live link to a peer and resolve with its
|
|
1127
|
-
* correlated `msg-result`.
|
|
1128
|
-
*
|
|
1129
|
-
*
|
|
1130
|
-
*
|
|
1148
|
+
* correlated `msg-result`. A `peer-not-configured` result when this instance
|
|
1149
|
+
* configures no route for that id, undefined when the route exists but its
|
|
1150
|
+
* link is down, and a `message-too-large` result when the encoded frame would
|
|
1151
|
+
* not fit the link.
|
|
1131
1152
|
*/
|
|
1132
1153
|
async msgRequest(instanceId, kind, payload) {
|
|
1133
1154
|
const state = this.linkStates.get(instanceId);
|
|
1134
|
-
if (state === void 0
|
|
1155
|
+
if (state === void 0) return { delivered: false, instance: instanceId, reason: "peer-not-configured" };
|
|
1156
|
+
if (!state.writable()) return void 0;
|
|
1135
1157
|
const reqId = `m${++this.reqIdCounter}-${randomUUID()}`;
|
|
1136
1158
|
const frame = {
|
|
1137
1159
|
type: "msg",
|
|
@@ -1193,7 +1215,8 @@ var InterconnectService = class extends Service {
|
|
|
1193
1215
|
* `Config.peers` entry; the resulting state owns reconnection for the life of
|
|
1194
1216
|
* the service fiber.
|
|
1195
1217
|
* @param instanceId - the peer's `instanceId`.
|
|
1196
|
-
* @param origin - receiver origin this instance dials
|
|
1218
|
+
* @param origin - receiver origin this instance dials (e.g.
|
|
1219
|
+
* `http://127.0.0.1:13080`), or a resolver for a `self` route.
|
|
1197
1220
|
*/
|
|
1198
1221
|
link(instanceId, origin) {
|
|
1199
1222
|
const state = new LinkState(
|
|
@@ -1203,11 +1226,28 @@ var InterconnectService = class extends Service {
|
|
|
1203
1226
|
this.ctx.logger,
|
|
1204
1227
|
() => this.resolveToken(),
|
|
1205
1228
|
instanceId,
|
|
1206
|
-
trimBase(origin)
|
|
1229
|
+
typeof origin === "string" ? trimBase(origin) : origin
|
|
1207
1230
|
);
|
|
1208
1231
|
this.linkStates.set(instanceId, state);
|
|
1209
1232
|
state.dial();
|
|
1210
1233
|
}
|
|
1234
|
+
/**
|
|
1235
|
+
* The origin of this instance's own host webserver, or undefined while no
|
|
1236
|
+
* listening port exists. Read on every dial rather than cached: the
|
|
1237
|
+
* webserver may activate after this service, its port is assigned only in
|
|
1238
|
+
* its listen callback (`--port 0` picks a fresh one per start), and a
|
|
1239
|
+
* replacement webserver would move it again. Loopback is used regardless of
|
|
1240
|
+
* the bind host: a server bound to `0.0.0.0` also accepts `127.0.0.1`, while
|
|
1241
|
+
* `0.0.0.0` itself is a bind address, not a dialable origin.
|
|
1242
|
+
* @returns `http://127.0.0.1:<port>`, or undefined without a listening server.
|
|
1243
|
+
*/
|
|
1244
|
+
selfOrigin() {
|
|
1245
|
+
const server = this.ctx.get("webServer");
|
|
1246
|
+
if (server === void 0) return void 0;
|
|
1247
|
+
const port = server.port;
|
|
1248
|
+
if (typeof port !== "number" || port <= 0) return void 0;
|
|
1249
|
+
return `http://127.0.0.1:${String(port)}`;
|
|
1250
|
+
}
|
|
1211
1251
|
/** Push one serialized lifecycle fact out to every linked peer over WS. */
|
|
1212
1252
|
fanout(notification) {
|
|
1213
1253
|
this.broadcast(notification);
|
|
@@ -1342,7 +1382,7 @@ var InterconnectService = class extends Service {
|
|
|
1342
1382
|
* @returns the bounded listing.
|
|
1343
1383
|
*/
|
|
1344
1384
|
listSessions(rowBudgetBytes) {
|
|
1345
|
-
const reachable = this.ctx.agents.list().filter((agent) => !isSessionOwnedBySubagent(
|
|
1385
|
+
const reachable = this.ctx.agents.list().filter((agent) => !isSessionOwnedBySubagent(agent.session));
|
|
1346
1386
|
const sessions = [];
|
|
1347
1387
|
let used = 0;
|
|
1348
1388
|
for (const agent of reachable.slice(0, MAX_LISTED_SESSIONS)) {
|
|
@@ -1369,7 +1409,8 @@ var InterconnectService = class extends Service {
|
|
|
1369
1409
|
let title;
|
|
1370
1410
|
try {
|
|
1371
1411
|
const snapshot = this.ctx.get("sessionProjections")?.snapshot(agent.session);
|
|
1372
|
-
const
|
|
1412
|
+
const values = snapshot?.values;
|
|
1413
|
+
const value = values?.title;
|
|
1373
1414
|
if (typeof value === "string" && value !== "") title = value;
|
|
1374
1415
|
} catch {
|
|
1375
1416
|
title = void 0;
|
|
@@ -1427,7 +1468,7 @@ var InterconnectService = class extends Service {
|
|
|
1427
1468
|
}
|
|
1428
1469
|
agent = woken.agent;
|
|
1429
1470
|
}
|
|
1430
|
-
if (isSessionOwnedBySubagent(
|
|
1471
|
+
if (isSessionOwnedBySubagent(agent.session)) {
|
|
1431
1472
|
return { delivered: false, instance: this.instanceId, reason: "session-owned-by-subagent" };
|
|
1432
1473
|
}
|
|
1433
1474
|
const sender = (() => {
|
|
@@ -1636,24 +1677,40 @@ var LinkState = class {
|
|
|
1636
1677
|
this.logger = logger;
|
|
1637
1678
|
this.resolveToken = resolveToken;
|
|
1638
1679
|
this.instanceId = instanceId;
|
|
1639
|
-
this.
|
|
1680
|
+
this.origin = origin;
|
|
1640
1681
|
}
|
|
1641
1682
|
dialed;
|
|
1642
1683
|
closed = false;
|
|
1643
1684
|
retry = 0;
|
|
1644
1685
|
reconnectTimer;
|
|
1645
1686
|
warnedNoToken = false;
|
|
1646
|
-
|
|
1647
|
-
peer;
|
|
1687
|
+
warnedNoOrigin = false;
|
|
1648
1688
|
instanceId;
|
|
1689
|
+
origin;
|
|
1690
|
+
/** The origin this link dials now; for an unresolved `self` route, the sentinel is shown. */
|
|
1691
|
+
get peer() {
|
|
1692
|
+
return typeof this.origin === "string" ? this.origin : this.origin() ?? SELF_PEER_ORIGIN;
|
|
1693
|
+
}
|
|
1649
1694
|
/** Open the socket; reconnect is scheduled by the close handler. */
|
|
1650
1695
|
dial() {
|
|
1651
1696
|
if (this.closed) return;
|
|
1697
|
+
const origin = typeof this.origin === "string" ? this.origin : this.origin();
|
|
1698
|
+
if (origin === void 0) {
|
|
1699
|
+
if (this.retry >= 1 && !this.warnedNoOrigin) {
|
|
1700
|
+
this.warnedNoOrigin = true;
|
|
1701
|
+
this.logger.warn(
|
|
1702
|
+
`interconnect: peer ${this.instanceId} is configured as self but this instance has no listening webServer port yet; the link stays down and retries`
|
|
1703
|
+
);
|
|
1704
|
+
}
|
|
1705
|
+
this.scheduleReconnect();
|
|
1706
|
+
return;
|
|
1707
|
+
}
|
|
1708
|
+
this.warnedNoOrigin = false;
|
|
1652
1709
|
let url;
|
|
1653
1710
|
try {
|
|
1654
|
-
url = linkUrl(
|
|
1711
|
+
url = linkUrl(origin);
|
|
1655
1712
|
} catch {
|
|
1656
|
-
this.logger.warn(`interconnect: invalid peer origin ${
|
|
1713
|
+
this.logger.warn(`interconnect: invalid peer origin ${origin}; link to ${this.instanceId} stays down`);
|
|
1657
1714
|
return;
|
|
1658
1715
|
}
|
|
1659
1716
|
void this.resolveToken().then((token) => {
|
|
@@ -5,7 +5,7 @@ import { credentialRef } from "@deepseek-ai/dsh-credentials";
|
|
|
5
5
|
import { remoteErrorOf } from "@deepseek-ai/dsh-typert-protocol";
|
|
6
6
|
import { boundContextSummary, createUserMessage } from "@deepseek-ai/dsh-llm";
|
|
7
7
|
|
|
8
|
-
//
|
|
8
|
+
// ../dsh-wt-ic-merge/vendor/cosmokit/src/misc.ts
|
|
9
9
|
function isNullable(value) {
|
|
10
10
|
return value === null || value === void 0;
|
|
11
11
|
}
|
|
@@ -27,7 +27,7 @@ function pick(source, keys, forced) {
|
|
|
27
27
|
return result;
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
//
|
|
30
|
+
// ../dsh-wt-ic-merge/vendor/cosmokit/src/types.ts
|
|
31
31
|
function is(type, value) {
|
|
32
32
|
if (arguments.length === 1) return (value2) => is(type, value2);
|
|
33
33
|
return type in globalThis && value instanceof globalThis[type] || Object.prototype.toString.call(value).slice(8, -1) === type;
|
|
@@ -136,7 +136,7 @@ function deepEqual(a, b, strict) {
|
|
|
136
136
|
}) ?? Object.keys({ ...a, ...b }).every((key) => deepEqual(a[key], b[key], strict));
|
|
137
137
|
}
|
|
138
138
|
|
|
139
|
-
//
|
|
139
|
+
// ../dsh-wt-ic-merge/vendor/cosmokit/src/time.ts
|
|
140
140
|
var Time;
|
|
141
141
|
((Time2) => {
|
|
142
142
|
Time2.millisecond = 1;
|
|
@@ -216,7 +216,7 @@ var Time;
|
|
|
216
216
|
Time2.template = template;
|
|
217
217
|
})(Time || (Time = {}));
|
|
218
218
|
|
|
219
|
-
//
|
|
219
|
+
// ../dsh-wt-ic-merge/vendor/schemastery/lib/index.mjs
|
|
220
220
|
var kSchema = Symbol.for("schemastery");
|
|
221
221
|
var kValidationError = Symbol.for("ValidationError");
|
|
222
222
|
globalThis.__schemastery_index__ ??= 0;
|
|
@@ -820,12 +820,8 @@ defineMethod("transform", [
|
|
|
820
820
|
|
|
821
821
|
// src/interconnect/index.ts
|
|
822
822
|
import WebSocket, { WebSocketServer } from "ws";
|
|
823
|
-
function isSessionOwnedBySubagent(
|
|
824
|
-
|
|
825
|
-
const parentId = session.header.parentSession;
|
|
826
|
-
if (parentId === void 0 || agent === void 0) return false;
|
|
827
|
-
const parent = ctx.agents.get(parentId);
|
|
828
|
-
return parent !== void 0 && ctx.agents.isOwnedBy(agent.id, parent);
|
|
823
|
+
function isSessionOwnedBySubagent(session) {
|
|
824
|
+
return session.header.origin === "subagent";
|
|
829
825
|
}
|
|
830
826
|
function assertNever(value) {
|
|
831
827
|
throw new Error(`interconnect: unhandled variant ${JSON.stringify(value)}`);
|
|
@@ -836,6 +832,7 @@ var DEFAULT_INSTANCE_ID = "dsh";
|
|
|
836
832
|
var DEFAULT_REQUEST_TIMEOUT_MS = 1e4;
|
|
837
833
|
var LINK_HANDSHAKE_TIMEOUT_MS = 1e4;
|
|
838
834
|
var LINK_CHANNEL = "/interconnect/link";
|
|
835
|
+
var SELF_PEER_ORIGIN = "self";
|
|
839
836
|
var MAX_INSTANCE_ID_CHARS = 256;
|
|
840
837
|
var MAX_REQUEST_ID_CHARS = 256;
|
|
841
838
|
var MAX_LISTED_SESSIONS = 100;
|
|
@@ -963,11 +960,19 @@ var InterconnectService = class extends Service {
|
|
|
963
960
|
this.requestTimeoutMs = config.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
|
|
964
961
|
this.delivery = config.delivery ?? "followup";
|
|
965
962
|
this.allowResume = config.allowResume ?? true;
|
|
966
|
-
for (const origin of Object.
|
|
963
|
+
for (const [peerInstanceId, origin] of Object.entries(config.peers ?? {})) {
|
|
964
|
+
if (origin === SELF_PEER_ORIGIN) {
|
|
965
|
+
if (peerInstanceId !== this.instanceId) {
|
|
966
|
+
throw new Error(
|
|
967
|
+
`interconnect: peer "${peerInstanceId}" is configured as self, but this instance announces "${this.instanceId}"; key a self peer by the instanceId`
|
|
968
|
+
);
|
|
969
|
+
}
|
|
970
|
+
continue;
|
|
971
|
+
}
|
|
967
972
|
linkUrl(trimBase(origin));
|
|
968
973
|
}
|
|
969
974
|
for (const [peerInstanceId, origin] of Object.entries(config.peers ?? {})) {
|
|
970
|
-
this.link(peerInstanceId, origin);
|
|
975
|
+
this.link(peerInstanceId, origin === SELF_PEER_ORIGIN ? () => this.selfOrigin() : origin);
|
|
971
976
|
}
|
|
972
977
|
const upgrade = {
|
|
973
978
|
path: LINK_CHANNEL,
|
|
@@ -1108,7 +1113,7 @@ var InterconnectService = class extends Service {
|
|
|
1108
1113
|
* Returns the peer identity when reachable, or undefined when the link is
|
|
1109
1114
|
* not up or no answer arrives.
|
|
1110
1115
|
* @param instanceId - the peer's `instanceId` as configured under {@link Config.peers}.
|
|
1111
|
-
* @returns the peer's identity, or undefined when
|
|
1116
|
+
* @returns the peer's identity, or undefined when there is no usable answer.
|
|
1112
1117
|
*/
|
|
1113
1118
|
async ping(instanceId) {
|
|
1114
1119
|
return this.queryRequest(instanceId, { kind: "ping" });
|
|
@@ -1122,16 +1127,33 @@ var InterconnectService = class extends Service {
|
|
|
1122
1127
|
async list(instanceId) {
|
|
1123
1128
|
return this.queryRequest(instanceId, { kind: "list" });
|
|
1124
1129
|
}
|
|
1130
|
+
/**
|
|
1131
|
+
* The peers this instance configures, each with the current state of the link
|
|
1132
|
+
* it dials, in configuration order. This is how a caller tells an id this
|
|
1133
|
+
* instance cannot reach at all (`peer-not-configured`) from one whose link is
|
|
1134
|
+
* merely down (`unreachable`), which the bare `undefined` of `ping`/`list`
|
|
1135
|
+
* cannot express. A configured peer whose link is `down` is still a valid
|
|
1136
|
+
* target: its reconnect runs on its own backoff, so it may answer later.
|
|
1137
|
+
* @returns one row per configured peer.
|
|
1138
|
+
*/
|
|
1139
|
+
peers() {
|
|
1140
|
+
return [...this.linkStates].map(([instanceId, state]) => ({
|
|
1141
|
+
instanceId,
|
|
1142
|
+
origin: state.peer,
|
|
1143
|
+
link: state.writable() ? "up" : "down"
|
|
1144
|
+
}));
|
|
1145
|
+
}
|
|
1125
1146
|
/**
|
|
1126
1147
|
* Send a `msg` frame over the live link to a peer and resolve with its
|
|
1127
|
-
* correlated `msg-result`.
|
|
1128
|
-
*
|
|
1129
|
-
*
|
|
1130
|
-
*
|
|
1148
|
+
* correlated `msg-result`. A `peer-not-configured` result when this instance
|
|
1149
|
+
* configures no route for that id, undefined when the route exists but its
|
|
1150
|
+
* link is down, and a `message-too-large` result when the encoded frame would
|
|
1151
|
+
* not fit the link.
|
|
1131
1152
|
*/
|
|
1132
1153
|
async msgRequest(instanceId, kind, payload) {
|
|
1133
1154
|
const state = this.linkStates.get(instanceId);
|
|
1134
|
-
if (state === void 0
|
|
1155
|
+
if (state === void 0) return { delivered: false, instance: instanceId, reason: "peer-not-configured" };
|
|
1156
|
+
if (!state.writable()) return void 0;
|
|
1135
1157
|
const reqId = `m${++this.reqIdCounter}-${randomUUID()}`;
|
|
1136
1158
|
const frame = {
|
|
1137
1159
|
type: "msg",
|
|
@@ -1193,7 +1215,8 @@ var InterconnectService = class extends Service {
|
|
|
1193
1215
|
* `Config.peers` entry; the resulting state owns reconnection for the life of
|
|
1194
1216
|
* the service fiber.
|
|
1195
1217
|
* @param instanceId - the peer's `instanceId`.
|
|
1196
|
-
* @param origin - receiver origin this instance dials
|
|
1218
|
+
* @param origin - receiver origin this instance dials (e.g.
|
|
1219
|
+
* `http://127.0.0.1:13080`), or a resolver for a `self` route.
|
|
1197
1220
|
*/
|
|
1198
1221
|
link(instanceId, origin) {
|
|
1199
1222
|
const state = new LinkState(
|
|
@@ -1203,11 +1226,28 @@ var InterconnectService = class extends Service {
|
|
|
1203
1226
|
this.ctx.logger,
|
|
1204
1227
|
() => this.resolveToken(),
|
|
1205
1228
|
instanceId,
|
|
1206
|
-
trimBase(origin)
|
|
1229
|
+
typeof origin === "string" ? trimBase(origin) : origin
|
|
1207
1230
|
);
|
|
1208
1231
|
this.linkStates.set(instanceId, state);
|
|
1209
1232
|
state.dial();
|
|
1210
1233
|
}
|
|
1234
|
+
/**
|
|
1235
|
+
* The origin of this instance's own host webserver, or undefined while no
|
|
1236
|
+
* listening port exists. Read on every dial rather than cached: the
|
|
1237
|
+
* webserver may activate after this service, its port is assigned only in
|
|
1238
|
+
* its listen callback (`--port 0` picks a fresh one per start), and a
|
|
1239
|
+
* replacement webserver would move it again. Loopback is used regardless of
|
|
1240
|
+
* the bind host: a server bound to `0.0.0.0` also accepts `127.0.0.1`, while
|
|
1241
|
+
* `0.0.0.0` itself is a bind address, not a dialable origin.
|
|
1242
|
+
* @returns `http://127.0.0.1:<port>`, or undefined without a listening server.
|
|
1243
|
+
*/
|
|
1244
|
+
selfOrigin() {
|
|
1245
|
+
const server = this.ctx.get("webServer");
|
|
1246
|
+
if (server === void 0) return void 0;
|
|
1247
|
+
const port = server.port;
|
|
1248
|
+
if (typeof port !== "number" || port <= 0) return void 0;
|
|
1249
|
+
return `http://127.0.0.1:${String(port)}`;
|
|
1250
|
+
}
|
|
1211
1251
|
/** Push one serialized lifecycle fact out to every linked peer over WS. */
|
|
1212
1252
|
fanout(notification) {
|
|
1213
1253
|
this.broadcast(notification);
|
|
@@ -1342,7 +1382,7 @@ var InterconnectService = class extends Service {
|
|
|
1342
1382
|
* @returns the bounded listing.
|
|
1343
1383
|
*/
|
|
1344
1384
|
listSessions(rowBudgetBytes) {
|
|
1345
|
-
const reachable = this.ctx.agents.list().filter((agent) => !isSessionOwnedBySubagent(
|
|
1385
|
+
const reachable = this.ctx.agents.list().filter((agent) => !isSessionOwnedBySubagent(agent.session));
|
|
1346
1386
|
const sessions = [];
|
|
1347
1387
|
let used = 0;
|
|
1348
1388
|
for (const agent of reachable.slice(0, MAX_LISTED_SESSIONS)) {
|
|
@@ -1369,7 +1409,8 @@ var InterconnectService = class extends Service {
|
|
|
1369
1409
|
let title;
|
|
1370
1410
|
try {
|
|
1371
1411
|
const snapshot = this.ctx.get("sessionProjections")?.snapshot(agent.session);
|
|
1372
|
-
const
|
|
1412
|
+
const values = snapshot?.values;
|
|
1413
|
+
const value = values?.title;
|
|
1373
1414
|
if (typeof value === "string" && value !== "") title = value;
|
|
1374
1415
|
} catch {
|
|
1375
1416
|
title = void 0;
|
|
@@ -1427,7 +1468,7 @@ var InterconnectService = class extends Service {
|
|
|
1427
1468
|
}
|
|
1428
1469
|
agent = woken.agent;
|
|
1429
1470
|
}
|
|
1430
|
-
if (isSessionOwnedBySubagent(
|
|
1471
|
+
if (isSessionOwnedBySubagent(agent.session)) {
|
|
1431
1472
|
return { delivered: false, instance: this.instanceId, reason: "session-owned-by-subagent" };
|
|
1432
1473
|
}
|
|
1433
1474
|
const sender = (() => {
|
|
@@ -1636,24 +1677,40 @@ var LinkState = class {
|
|
|
1636
1677
|
this.logger = logger;
|
|
1637
1678
|
this.resolveToken = resolveToken;
|
|
1638
1679
|
this.instanceId = instanceId;
|
|
1639
|
-
this.
|
|
1680
|
+
this.origin = origin;
|
|
1640
1681
|
}
|
|
1641
1682
|
dialed;
|
|
1642
1683
|
closed = false;
|
|
1643
1684
|
retry = 0;
|
|
1644
1685
|
reconnectTimer;
|
|
1645
1686
|
warnedNoToken = false;
|
|
1646
|
-
|
|
1647
|
-
peer;
|
|
1687
|
+
warnedNoOrigin = false;
|
|
1648
1688
|
instanceId;
|
|
1689
|
+
origin;
|
|
1690
|
+
/** The origin this link dials now; for an unresolved `self` route, the sentinel is shown. */
|
|
1691
|
+
get peer() {
|
|
1692
|
+
return typeof this.origin === "string" ? this.origin : this.origin() ?? SELF_PEER_ORIGIN;
|
|
1693
|
+
}
|
|
1649
1694
|
/** Open the socket; reconnect is scheduled by the close handler. */
|
|
1650
1695
|
dial() {
|
|
1651
1696
|
if (this.closed) return;
|
|
1697
|
+
const origin = typeof this.origin === "string" ? this.origin : this.origin();
|
|
1698
|
+
if (origin === void 0) {
|
|
1699
|
+
if (this.retry >= 1 && !this.warnedNoOrigin) {
|
|
1700
|
+
this.warnedNoOrigin = true;
|
|
1701
|
+
this.logger.warn(
|
|
1702
|
+
`interconnect: peer ${this.instanceId} is configured as self but this instance has no listening webServer port yet; the link stays down and retries`
|
|
1703
|
+
);
|
|
1704
|
+
}
|
|
1705
|
+
this.scheduleReconnect();
|
|
1706
|
+
return;
|
|
1707
|
+
}
|
|
1708
|
+
this.warnedNoOrigin = false;
|
|
1652
1709
|
let url;
|
|
1653
1710
|
try {
|
|
1654
|
-
url = linkUrl(
|
|
1711
|
+
url = linkUrl(origin);
|
|
1655
1712
|
} catch {
|
|
1656
|
-
this.logger.warn(`interconnect: invalid peer origin ${
|
|
1713
|
+
this.logger.warn(`interconnect: invalid peer origin ${origin}; link to ${this.instanceId} stays down`);
|
|
1657
1714
|
return;
|
|
1658
1715
|
}
|
|
1659
1716
|
void this.resolveToken().then((token) => {
|