dsh-interconnect 0.6.4 → 0.7.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 +32 -2
- package/lib/index.js +81 -2
- package/lib/interconnect/index.js +81 -2
- package/lib/interconnect/index.js.map +2 -2
- package/lib/tool-interconnect/index.js +85 -1
- package/lib/tool-interconnect/index.js.map +2 -2
- package/lib/types/interconnect/index.d.ts +24 -2
- package/lib/types/interconnect/index.d.ts.map +1 -1
- package/lib/types/interconnect/types.d.ts +71 -1
- package/lib/types/interconnect/types.d.ts.map +1 -1
- package/lib/types/tool-interconnect/index.d.ts.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
- `interconnect_send`:向对端实例的指定 session 投递消息;可选 `delivery` 选投递模式、`resume` 唤醒离线 session
|
|
18
18
|
- `interconnect_list`:列出对端实例的 live session(id + 标题 + 状态),用于在不预先知道 session id 时寻址
|
|
19
19
|
- `interconnect_ping`:探测对端实例活性与身份
|
|
20
|
+
- `interconnect_reply`:向记录过的发送方回传消息,只需本机 session id + 文本,无需再次寻址
|
|
20
21
|
|
|
21
22
|
## 用法
|
|
22
23
|
|
|
@@ -37,6 +38,29 @@ session-b07326da-… [running]
|
|
|
37
38
|
只列 live session 是有意的:`send` 能到达的正好是这些。对端存在但没有运行 agent 的 session
|
|
38
39
|
不会出现在列表里,也收不到消息。
|
|
39
40
|
|
|
41
|
+
### 回复(`reply`)
|
|
42
|
+
|
|
43
|
+
`send` 的线负载带一个可选的 `sender` 身份(`baseUrl` + `instanceId` + `sessionId`),收到
|
|
44
|
+
消息的 instance 会按「本地 session id → 该 sender」记下这份身份。之后那个 session 可以只凭
|
|
45
|
+
**自己的 session id + 文本**把消息回传给发送方,不用再次给出对端 origin 或远程 session id。
|
|
46
|
+
|
|
47
|
+
```text
|
|
48
|
+
# 源实例 A 指定目标 B 的 session,并带上自己的身份
|
|
49
|
+
interconnect_send(baseUrl=B, sessionId=B-sess, text="…", sender={baseUrl:A, instanceId:A, sessionId:A-sess})
|
|
50
|
+
|
|
51
|
+
# B 回传:只给本地 session id + 文本,目标从记录的 sender 解析
|
|
52
|
+
interconnect_reply(sessionId=B-sess, text="reply")
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
- `sender` 是**自报**的,只用于 reply 归因与寻址,**不是**路由或鉴权依据——连接本身仍由
|
|
56
|
+
共享密钥在每个 origin 上独立鉴权。
|
|
57
|
+
- 回复的消息也带 `sender`(当本机配置了 `selfBaseUrl`),所以对话可双向多轮延续。
|
|
58
|
+
- 只有当入站 send **带了 `sender`** 时 reply 才有目标;对端版本没带、或本 session 从未收过
|
|
59
|
+
互联消息时,`reply` 返回 `delivered: false, reason: "no-sender-known"`。
|
|
60
|
+
- `sender` **不进模型上下文**(和 `source` 一样只落到持久化日志与 UI 归因)——这条消息的
|
|
61
|
+
内容字面就是 wire 上传来的 `text`,模型看到的仍是普通 user 文本,只是不带任何结构化的
|
|
62
|
+
发送方标记。
|
|
63
|
+
|
|
40
64
|
### 投递失败的原因
|
|
41
65
|
|
|
42
66
|
`delivered: false` 单独一个布尔值无法据以行动,因为不同失败需要不同应对,所以
|
|
@@ -49,6 +73,7 @@ session-b07326da-… [running]
|
|
|
49
73
|
| `resume-refused` | 请求了唤醒,但对端不允许(`allowResume: false`) | 再带 `resume` 也没用 |
|
|
50
74
|
| `resume-failed` | 允许唤醒且尝试了,但没得到 live agent(无此持久化 session,或被别的 owner 持有) | 换目标 |
|
|
51
75
|
| `session-owned-by-subagent` | 该 session 属于 subagent 路由,投递权在它的父 agent | 通过父 agent 触达,别直接投 |
|
|
76
|
+
| `no-sender-known` | `reply` 指向的本地 session 从未记录过发送方(它没收到过带 `sender` 的消息,或对端版本过旧没带 `sender`) | 先用 `interconnect_send` 主动建立联系,或确认对端配置了 `selfBaseUrl` |
|
|
52
77
|
|
|
53
78
|
`reason` 恰好在 `delivered` 为 false 时出现。
|
|
54
79
|
|
|
@@ -127,6 +152,7 @@ Host 自己的 resume 路径对同一个 session 报 `SessionFormatUnsupportedEr
|
|
|
127
152
|
| 字段 | 默认 | 说明 |
|
|
128
153
|
|---|---|---|
|
|
129
154
|
| `instanceId` | `'dsh'` | 本实例自报的 id,出现在 `ping`/`send`/`list` 的回包里。仅诊断用,**不作为路由依据** |
|
|
155
|
+
| `selfBaseUrl` | 无 | 本实例对外可达的 origin(如 `http://my-box:3080`)。设置后,出站 `send`/`reply` 会带上本实例的 `sender` 身份,使对端能回信;不设置则不归因、对端 `reply` 报 `no-sender-known` |
|
|
130
156
|
| `requestTimeoutMs` | `10000` | 出站请求超时,上限 60000 |
|
|
131
157
|
| `peers` | `[]` | 启动时的事件 fan-out 目标;运行时可用 `subscribe()` 增补 |
|
|
132
158
|
| `delivery` | `'followup'` | 入站消息未带 `delivery` 时的默认投递模式 |
|
|
@@ -136,6 +162,7 @@ Host 自己的 resume 路径对同一个 session 报 `SessionFormatUnsupportedEr
|
|
|
136
162
|
- id: interconnect
|
|
137
163
|
config:
|
|
138
164
|
instanceId: my-box
|
|
165
|
+
selfBaseUrl: http://my-box:3080 # 让对端能 reply 回本机
|
|
139
166
|
peers: ['http://peer-host:3080']
|
|
140
167
|
delivery: followup
|
|
141
168
|
allowResume: false # 拒绝一切唤醒请求
|
|
@@ -187,7 +214,7 @@ pnpm run build # esbuild → lib/
|
|
|
187
214
|
|
|
188
215
|
## 验证
|
|
189
216
|
|
|
190
|
-
-
|
|
217
|
+
- 67/67 单测通过(服务 + 工具);类型检查、构建均干净。
|
|
191
218
|
- 已在两台机器之间实测双向互通:消息投递、WebSocket 事件推流、以及 agent 经
|
|
192
219
|
`interconnect_send` 工具反向回发,均验证通过。
|
|
193
220
|
- CI(GitHub Actions):clone 公开 DSH 仓库作为 sibling,跑 `pnpm run check`。
|
|
@@ -198,9 +225,12 @@ pnpm run build # esbuild → lib/
|
|
|
198
225
|
`{ kind: 'user' }`。负例:把该 source 改回 `kind: 'user'`,对应断言转红。
|
|
199
226
|
**注意 `source` 不进模型上下文**——只有 `role` 和 `content` 会,而 `role` 是 `user`。
|
|
200
227
|
所以这个字段的价值在持久化日志与 UI 归因,接收方的**模型本身分辨不出**消息来自插件。
|
|
228
|
+
同理,reply 的记录与寻址依赖 `sender`,它同样不进模型上下文。
|
|
229
|
+
- reply 双向多轮延续在**同一测试进程内用真实 HTTP 双向回环**验证(A→B→A→B),不是 mockout。
|
|
201
230
|
- 每个行为改动都配负例对照(删掉实现使对应断言转红),而不只是「测试通过」。
|
|
202
231
|
例如:去掉请求处理器的 `await` 会让 25 个测试转红;去掉 `resume` 的发送方 opt-in
|
|
203
|
-
门或接收方否决门,各让 1
|
|
232
|
+
门或接收方否决门,各让 1 个转红;去掉入站 send 的 sender 记录(`senders.set`),
|
|
233
|
+
两条 reply 回环测试转红。
|
|
204
234
|
|
|
205
235
|
## 许可
|
|
206
236
|
|
package/lib/index.js
CHANGED
|
@@ -847,6 +847,17 @@ var linkFrameSchema = Schema.union([
|
|
|
847
847
|
Schema.object({ type: Schema.const("event"), notification: notificationSchema })
|
|
848
848
|
]);
|
|
849
849
|
var sendPayloadSchema = Schema.object({
|
|
850
|
+
sessionId: Schema.string(),
|
|
851
|
+
text: Schema.string(),
|
|
852
|
+
sender: Schema.object({
|
|
853
|
+
baseUrl: Schema.string(),
|
|
854
|
+
instanceId: Schema.string(),
|
|
855
|
+
sessionId: Schema.string()
|
|
856
|
+
}),
|
|
857
|
+
delivery: Schema.union([Schema.const("followup"), Schema.const("steer"), Schema.const("inject")]),
|
|
858
|
+
resume: Schema.boolean()
|
|
859
|
+
});
|
|
860
|
+
var replyPayloadSchema = Schema.object({
|
|
850
861
|
sessionId: Schema.string(),
|
|
851
862
|
text: Schema.string(),
|
|
852
863
|
delivery: Schema.union([Schema.const("followup"), Schema.const("steer"), Schema.const("inject")]),
|
|
@@ -860,12 +871,14 @@ var InterconnectService = class extends Service {
|
|
|
860
871
|
static inject = ["webServer", "agents", "credentials"];
|
|
861
872
|
static Config = Schema.object({
|
|
862
873
|
instanceId: Schema.string().default("dsh"),
|
|
874
|
+
selfBaseUrl: Schema.string(),
|
|
863
875
|
requestTimeoutMs: Schema.natural().max(6e4).default(1e4),
|
|
864
876
|
peers: Schema.array(Schema.string()).default([]),
|
|
865
877
|
delivery: Schema.union([Schema.const("followup"), Schema.const("steer"), Schema.const("inject")]).default("followup"),
|
|
866
878
|
allowResume: Schema.boolean().default(true)
|
|
867
879
|
});
|
|
868
880
|
instanceId;
|
|
881
|
+
selfBaseUrl;
|
|
869
882
|
requestTimeoutMs;
|
|
870
883
|
delivery;
|
|
871
884
|
allowResume;
|
|
@@ -877,9 +890,12 @@ var InterconnectService = class extends Service {
|
|
|
877
890
|
heartbeatTimer;
|
|
878
891
|
/** Peer identity each live socket announced via its `hello` frame, if any. */
|
|
879
892
|
peerOf = /* @__PURE__ */ new WeakMap();
|
|
893
|
+
/** Sender each local session last received a send from, keyed by local session id. */
|
|
894
|
+
senders = /* @__PURE__ */ new Map();
|
|
880
895
|
constructor(ctx, config) {
|
|
881
896
|
super(ctx, "interconnect");
|
|
882
897
|
this.instanceId = config.instanceId;
|
|
898
|
+
this.selfBaseUrl = config.selfBaseUrl;
|
|
883
899
|
this.requestTimeoutMs = config.requestTimeoutMs;
|
|
884
900
|
this.delivery = config.delivery ?? "followup";
|
|
885
901
|
this.allowResume = config.allowResume ?? true;
|
|
@@ -950,16 +966,49 @@ var InterconnectService = class extends Service {
|
|
|
950
966
|
this.server.close();
|
|
951
967
|
}, "interconnect: websocket teardown");
|
|
952
968
|
}
|
|
969
|
+
/**
|
|
970
|
+
* This instance's identity to attach to outbound messages, when configured.
|
|
971
|
+
* Absent (`selfBaseUrl` unset) outbound sends are not attributable and the
|
|
972
|
+
* peer cannot reply to them.
|
|
973
|
+
* @param sessionId - the local session that is sending, used as the reply target.
|
|
974
|
+
*/
|
|
975
|
+
selfSender(sessionId) {
|
|
976
|
+
return this.selfBaseUrl === void 0 ? void 0 : { baseUrl: this.selfBaseUrl, instanceId: this.instanceId, sessionId };
|
|
977
|
+
}
|
|
953
978
|
async send(request) {
|
|
954
979
|
const payload = {
|
|
955
980
|
sessionId: request.sessionId,
|
|
956
981
|
text: request.text,
|
|
982
|
+
...request.sender === void 0 ? {} : { sender: request.sender },
|
|
957
983
|
...request.delivery === void 0 ? {} : { delivery: request.delivery },
|
|
958
984
|
...request.resume === void 0 ? {} : { resume: request.resume }
|
|
959
985
|
};
|
|
960
986
|
const result = await this.post(request.baseUrl, "send", payload);
|
|
961
987
|
return result ?? { delivered: false, instance: this.instanceId, reason: "unreachable" };
|
|
962
988
|
}
|
|
989
|
+
/**
|
|
990
|
+
* Deliver one text message back to the peer that a local session last
|
|
991
|
+
* received a send from. `sessionId` names the LOCAL replying session; the
|
|
992
|
+
* outbound target is the `sender` that session recorded, resolved to a
|
|
993
|
+
* `send` against the recalled baseUrl and remote session id.
|
|
994
|
+
*/
|
|
995
|
+
async reply(request) {
|
|
996
|
+
const sender = this.senders.get(request.sessionId);
|
|
997
|
+
if (sender === void 0) {
|
|
998
|
+
return { delivered: false, instance: this.instanceId, reason: "no-sender-known" };
|
|
999
|
+
}
|
|
1000
|
+
const payload = {
|
|
1001
|
+
sessionId: sender.sessionId,
|
|
1002
|
+
text: request.text,
|
|
1003
|
+
...request.delivery === void 0 ? {} : { delivery: request.delivery },
|
|
1004
|
+
...request.resume === void 0 ? {} : { resume: request.resume },
|
|
1005
|
+
// Attribute the reply to this instance so the peer, in turn, can reply
|
|
1006
|
+
// back — chaining the conversation. Absent when we have no self origin.
|
|
1007
|
+
...this.selfBaseUrl === void 0 ? {} : { sender: this.selfSender(request.sessionId) }
|
|
1008
|
+
};
|
|
1009
|
+
const result = await this.post(sender.baseUrl, "send", payload);
|
|
1010
|
+
return result ?? { delivered: false, instance: this.instanceId, reason: "unreachable" };
|
|
1011
|
+
}
|
|
963
1012
|
async ping(baseUrl) {
|
|
964
1013
|
return this.post(baseUrl, "ping", {});
|
|
965
1014
|
}
|
|
@@ -1134,6 +1183,15 @@ var InterconnectService = class extends Service {
|
|
|
1134
1183
|
}
|
|
1135
1184
|
return this.deliver(parsed);
|
|
1136
1185
|
}
|
|
1186
|
+
if (endpoint === "reply") {
|
|
1187
|
+
let parsed;
|
|
1188
|
+
try {
|
|
1189
|
+
parsed = Schema.resolve(payload, replyPayloadSchema, {})[0];
|
|
1190
|
+
} catch (error) {
|
|
1191
|
+
throw new InvalidPayloadError(`reply payload invalid: ${error instanceof Error ? error.message : String(error)}`);
|
|
1192
|
+
}
|
|
1193
|
+
return this.replyForSession(parsed);
|
|
1194
|
+
}
|
|
1137
1195
|
if (endpoint === "list") {
|
|
1138
1196
|
return this.listSessions();
|
|
1139
1197
|
}
|
|
@@ -1226,12 +1284,21 @@ var InterconnectService = class extends Service {
|
|
|
1226
1284
|
if (hasApiRemoteSubagentOwner(this.ctx, agent.session, agent)) {
|
|
1227
1285
|
return { delivered: false, instance: this.instanceId, reason: "session-owned-by-subagent" };
|
|
1228
1286
|
}
|
|
1287
|
+
const sender = (() => {
|
|
1288
|
+
const candidate = payload.sender;
|
|
1289
|
+
return candidate === void 0 || typeof candidate.baseUrl !== "string" ? void 0 : candidate;
|
|
1290
|
+
})();
|
|
1291
|
+
if (sender !== void 0) {
|
|
1292
|
+
this.senders.set(payload.sessionId, sender);
|
|
1293
|
+
}
|
|
1229
1294
|
const message = createUserMessage({
|
|
1230
1295
|
source: {
|
|
1231
1296
|
kind: "plugin",
|
|
1232
1297
|
plugin: PLUGIN_SOURCE,
|
|
1233
1298
|
form: "notice",
|
|
1234
|
-
summary: boundContextSummary(
|
|
1299
|
+
summary: boundContextSummary(
|
|
1300
|
+
sender === void 0 ? `interconnect handoff delivered on instance ${this.instanceId}` : `interconnect handoff from ${sender.instanceId} (session ${sender.sessionId}) delivered on instance ${this.instanceId}`
|
|
1301
|
+
)
|
|
1235
1302
|
},
|
|
1236
1303
|
content: [{ type: "text", text: payload.text }]
|
|
1237
1304
|
});
|
|
@@ -1249,7 +1316,19 @@ var InterconnectService = class extends Service {
|
|
|
1249
1316
|
}
|
|
1250
1317
|
return { delivered: true, instance: this.instanceId, delivery: mode };
|
|
1251
1318
|
}
|
|
1252
|
-
/**
|
|
1319
|
+
/**
|
|
1320
|
+
* Inbound `reply` dispatch: forward a request to {link reply}, resolving the
|
|
1321
|
+
* outbound sender for the named local session. Kept separate from {link
|
|
1322
|
+
* deliver} so the two wire endpoints stay distinct in the request pipeline.
|
|
1323
|
+
*/
|
|
1324
|
+
async replyForSession(payload) {
|
|
1325
|
+
return this.reply({
|
|
1326
|
+
sessionId: payload.sessionId,
|
|
1327
|
+
text: payload.text,
|
|
1328
|
+
...payload.delivery === void 0 ? {} : { delivery: payload.delivery },
|
|
1329
|
+
...payload.resume === void 0 ? {} : { resume: payload.resume }
|
|
1330
|
+
});
|
|
1331
|
+
}
|
|
1253
1332
|
async post(baseUrl, endpoint, payload) {
|
|
1254
1333
|
const token = await this.resolveToken();
|
|
1255
1334
|
if (token === void 0) return void 0;
|
|
@@ -847,6 +847,17 @@ var linkFrameSchema = Schema.union([
|
|
|
847
847
|
Schema.object({ type: Schema.const("event"), notification: notificationSchema })
|
|
848
848
|
]);
|
|
849
849
|
var sendPayloadSchema = Schema.object({
|
|
850
|
+
sessionId: Schema.string(),
|
|
851
|
+
text: Schema.string(),
|
|
852
|
+
sender: Schema.object({
|
|
853
|
+
baseUrl: Schema.string(),
|
|
854
|
+
instanceId: Schema.string(),
|
|
855
|
+
sessionId: Schema.string()
|
|
856
|
+
}),
|
|
857
|
+
delivery: Schema.union([Schema.const("followup"), Schema.const("steer"), Schema.const("inject")]),
|
|
858
|
+
resume: Schema.boolean()
|
|
859
|
+
});
|
|
860
|
+
var replyPayloadSchema = Schema.object({
|
|
850
861
|
sessionId: Schema.string(),
|
|
851
862
|
text: Schema.string(),
|
|
852
863
|
delivery: Schema.union([Schema.const("followup"), Schema.const("steer"), Schema.const("inject")]),
|
|
@@ -860,12 +871,14 @@ var InterconnectService = class extends Service {
|
|
|
860
871
|
static inject = ["webServer", "agents", "credentials"];
|
|
861
872
|
static Config = Schema.object({
|
|
862
873
|
instanceId: Schema.string().default("dsh"),
|
|
874
|
+
selfBaseUrl: Schema.string(),
|
|
863
875
|
requestTimeoutMs: Schema.natural().max(6e4).default(1e4),
|
|
864
876
|
peers: Schema.array(Schema.string()).default([]),
|
|
865
877
|
delivery: Schema.union([Schema.const("followup"), Schema.const("steer"), Schema.const("inject")]).default("followup"),
|
|
866
878
|
allowResume: Schema.boolean().default(true)
|
|
867
879
|
});
|
|
868
880
|
instanceId;
|
|
881
|
+
selfBaseUrl;
|
|
869
882
|
requestTimeoutMs;
|
|
870
883
|
delivery;
|
|
871
884
|
allowResume;
|
|
@@ -877,9 +890,12 @@ var InterconnectService = class extends Service {
|
|
|
877
890
|
heartbeatTimer;
|
|
878
891
|
/** Peer identity each live socket announced via its `hello` frame, if any. */
|
|
879
892
|
peerOf = /* @__PURE__ */ new WeakMap();
|
|
893
|
+
/** Sender each local session last received a send from, keyed by local session id. */
|
|
894
|
+
senders = /* @__PURE__ */ new Map();
|
|
880
895
|
constructor(ctx, config) {
|
|
881
896
|
super(ctx, "interconnect");
|
|
882
897
|
this.instanceId = config.instanceId;
|
|
898
|
+
this.selfBaseUrl = config.selfBaseUrl;
|
|
883
899
|
this.requestTimeoutMs = config.requestTimeoutMs;
|
|
884
900
|
this.delivery = config.delivery ?? "followup";
|
|
885
901
|
this.allowResume = config.allowResume ?? true;
|
|
@@ -950,16 +966,49 @@ var InterconnectService = class extends Service {
|
|
|
950
966
|
this.server.close();
|
|
951
967
|
}, "interconnect: websocket teardown");
|
|
952
968
|
}
|
|
969
|
+
/**
|
|
970
|
+
* This instance's identity to attach to outbound messages, when configured.
|
|
971
|
+
* Absent (`selfBaseUrl` unset) outbound sends are not attributable and the
|
|
972
|
+
* peer cannot reply to them.
|
|
973
|
+
* @param sessionId - the local session that is sending, used as the reply target.
|
|
974
|
+
*/
|
|
975
|
+
selfSender(sessionId) {
|
|
976
|
+
return this.selfBaseUrl === void 0 ? void 0 : { baseUrl: this.selfBaseUrl, instanceId: this.instanceId, sessionId };
|
|
977
|
+
}
|
|
953
978
|
async send(request) {
|
|
954
979
|
const payload = {
|
|
955
980
|
sessionId: request.sessionId,
|
|
956
981
|
text: request.text,
|
|
982
|
+
...request.sender === void 0 ? {} : { sender: request.sender },
|
|
957
983
|
...request.delivery === void 0 ? {} : { delivery: request.delivery },
|
|
958
984
|
...request.resume === void 0 ? {} : { resume: request.resume }
|
|
959
985
|
};
|
|
960
986
|
const result = await this.post(request.baseUrl, "send", payload);
|
|
961
987
|
return result ?? { delivered: false, instance: this.instanceId, reason: "unreachable" };
|
|
962
988
|
}
|
|
989
|
+
/**
|
|
990
|
+
* Deliver one text message back to the peer that a local session last
|
|
991
|
+
* received a send from. `sessionId` names the LOCAL replying session; the
|
|
992
|
+
* outbound target is the `sender` that session recorded, resolved to a
|
|
993
|
+
* `send` against the recalled baseUrl and remote session id.
|
|
994
|
+
*/
|
|
995
|
+
async reply(request) {
|
|
996
|
+
const sender = this.senders.get(request.sessionId);
|
|
997
|
+
if (sender === void 0) {
|
|
998
|
+
return { delivered: false, instance: this.instanceId, reason: "no-sender-known" };
|
|
999
|
+
}
|
|
1000
|
+
const payload = {
|
|
1001
|
+
sessionId: sender.sessionId,
|
|
1002
|
+
text: request.text,
|
|
1003
|
+
...request.delivery === void 0 ? {} : { delivery: request.delivery },
|
|
1004
|
+
...request.resume === void 0 ? {} : { resume: request.resume },
|
|
1005
|
+
// Attribute the reply to this instance so the peer, in turn, can reply
|
|
1006
|
+
// back — chaining the conversation. Absent when we have no self origin.
|
|
1007
|
+
...this.selfBaseUrl === void 0 ? {} : { sender: this.selfSender(request.sessionId) }
|
|
1008
|
+
};
|
|
1009
|
+
const result = await this.post(sender.baseUrl, "send", payload);
|
|
1010
|
+
return result ?? { delivered: false, instance: this.instanceId, reason: "unreachable" };
|
|
1011
|
+
}
|
|
963
1012
|
async ping(baseUrl) {
|
|
964
1013
|
return this.post(baseUrl, "ping", {});
|
|
965
1014
|
}
|
|
@@ -1134,6 +1183,15 @@ var InterconnectService = class extends Service {
|
|
|
1134
1183
|
}
|
|
1135
1184
|
return this.deliver(parsed);
|
|
1136
1185
|
}
|
|
1186
|
+
if (endpoint === "reply") {
|
|
1187
|
+
let parsed;
|
|
1188
|
+
try {
|
|
1189
|
+
parsed = Schema.resolve(payload, replyPayloadSchema, {})[0];
|
|
1190
|
+
} catch (error) {
|
|
1191
|
+
throw new InvalidPayloadError(`reply payload invalid: ${error instanceof Error ? error.message : String(error)}`);
|
|
1192
|
+
}
|
|
1193
|
+
return this.replyForSession(parsed);
|
|
1194
|
+
}
|
|
1137
1195
|
if (endpoint === "list") {
|
|
1138
1196
|
return this.listSessions();
|
|
1139
1197
|
}
|
|
@@ -1226,12 +1284,21 @@ var InterconnectService = class extends Service {
|
|
|
1226
1284
|
if (hasApiRemoteSubagentOwner(this.ctx, agent.session, agent)) {
|
|
1227
1285
|
return { delivered: false, instance: this.instanceId, reason: "session-owned-by-subagent" };
|
|
1228
1286
|
}
|
|
1287
|
+
const sender = (() => {
|
|
1288
|
+
const candidate = payload.sender;
|
|
1289
|
+
return candidate === void 0 || typeof candidate.baseUrl !== "string" ? void 0 : candidate;
|
|
1290
|
+
})();
|
|
1291
|
+
if (sender !== void 0) {
|
|
1292
|
+
this.senders.set(payload.sessionId, sender);
|
|
1293
|
+
}
|
|
1229
1294
|
const message = createUserMessage({
|
|
1230
1295
|
source: {
|
|
1231
1296
|
kind: "plugin",
|
|
1232
1297
|
plugin: PLUGIN_SOURCE,
|
|
1233
1298
|
form: "notice",
|
|
1234
|
-
summary: boundContextSummary(
|
|
1299
|
+
summary: boundContextSummary(
|
|
1300
|
+
sender === void 0 ? `interconnect handoff delivered on instance ${this.instanceId}` : `interconnect handoff from ${sender.instanceId} (session ${sender.sessionId}) delivered on instance ${this.instanceId}`
|
|
1301
|
+
)
|
|
1235
1302
|
},
|
|
1236
1303
|
content: [{ type: "text", text: payload.text }]
|
|
1237
1304
|
});
|
|
@@ -1249,7 +1316,19 @@ var InterconnectService = class extends Service {
|
|
|
1249
1316
|
}
|
|
1250
1317
|
return { delivered: true, instance: this.instanceId, delivery: mode };
|
|
1251
1318
|
}
|
|
1252
|
-
/**
|
|
1319
|
+
/**
|
|
1320
|
+
* Inbound `reply` dispatch: forward a request to {link reply}, resolving the
|
|
1321
|
+
* outbound sender for the named local session. Kept separate from {link
|
|
1322
|
+
* deliver} so the two wire endpoints stay distinct in the request pipeline.
|
|
1323
|
+
*/
|
|
1324
|
+
async replyForSession(payload) {
|
|
1325
|
+
return this.reply({
|
|
1326
|
+
sessionId: payload.sessionId,
|
|
1327
|
+
text: payload.text,
|
|
1328
|
+
...payload.delivery === void 0 ? {} : { delivery: payload.delivery },
|
|
1329
|
+
...payload.resume === void 0 ? {} : { resume: payload.resume }
|
|
1330
|
+
});
|
|
1331
|
+
}
|
|
1253
1332
|
async post(baseUrl, endpoint, payload) {
|
|
1254
1333
|
const token = await this.resolveToken();
|
|
1255
1334
|
if (token === void 0) return void 0;
|