aws-runtime-bridge 1.9.108 → 1.9.110
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/dist/adapter/AcodeSdkAdapter.d.ts +4 -0
- package/dist/adapter/AcodeSdkAdapter.js +2 -2
- package/dist/adapter/types.d.ts +2 -0
- package/dist/routes/sessions.js +1 -1
- package/dist/routes/terminal.js +25 -25
- package/package/acode/dist/runtime.d.ts +6 -0
- package/package/acode/dist/runtime.js +21 -21
- package/package/aws-client-agent-mcp/dist/agent-client.d.ts +2 -0
- package/package/aws-client-agent-mcp/dist/agent-client.js +1 -1
- package/package/aws-client-agent-mcp/dist/mcp-server.d.ts +1 -1
- package/package/aws-client-agent-mcp/dist/mcp-server.js +12 -12
- package/package/aws-client-agent-mcp/dist/mcp-server.test.js +102 -58
- package/package/aws-client-agent-mcp/dist/message-buffer.d.ts +2 -0
- package/package/aws-client-agent-mcp/dist/message-buffer.js +1 -1
- package/package/aws-client-agent-mcp/dist/message-buffer.test.js +39 -0
- package/package/aws-client-agent-mcp/dist/types.d.ts +2 -0
- package/package.json +1 -1
|
@@ -56,6 +56,15 @@ async function withScopedMemoryServers(callback) {
|
|
|
56
56
|
function getHandleToolCall(server) {
|
|
57
57
|
return Reflect.get(server, "handleToolCall");
|
|
58
58
|
}
|
|
59
|
+
/**
|
|
60
|
+
* 测试辅助:访问 McpServer 的私有 messageBuffer(WebSocket 本地消息队列),
|
|
61
|
+
* 用于模拟外部消息到达后唤醒阻塞中的 poll_message。
|
|
62
|
+
* 注意:直接依赖私有字段名,若 mcp-server.ts 重命名该字段需同步修改本函数,
|
|
63
|
+
* 避免测试在字段改名后静默失效(Reflect 访问不会报编译错误)。
|
|
64
|
+
*/
|
|
65
|
+
function getMessageBuffer(server) {
|
|
66
|
+
return Reflect.get(server, "messageBuffer");
|
|
67
|
+
}
|
|
59
68
|
function attachMemoryStatsReporter(server, reportedStats) {
|
|
60
69
|
const existingClient = Reflect.get(server, "agentClient");
|
|
61
70
|
Reflect.set(server, "agentClient", {
|
|
@@ -167,7 +176,7 @@ describe("McpServer unread backlog hydration", () => {
|
|
|
167
176
|
});
|
|
168
177
|
// Simulate a WebSocket message arriving after hydration returns empty.
|
|
169
178
|
// poll_message must keep blocking on the local buffer until the message arrives.
|
|
170
|
-
const buffer =
|
|
179
|
+
const buffer = getMessageBuffer(server);
|
|
171
180
|
setTimeout(() => {
|
|
172
181
|
buffer.push(directMessage);
|
|
173
182
|
}, 50);
|
|
@@ -202,7 +211,7 @@ describe("McpServer unread backlog hydration", () => {
|
|
|
202
211
|
},
|
|
203
212
|
});
|
|
204
213
|
// Simulate WebSocket message arriving after hydration fails
|
|
205
|
-
const buffer =
|
|
214
|
+
const buffer = getMessageBuffer(server);
|
|
206
215
|
setTimeout(() => {
|
|
207
216
|
buffer.push(directMessage);
|
|
208
217
|
}, 50);
|
|
@@ -233,7 +242,7 @@ describe("McpServer unread backlog hydration", () => {
|
|
|
233
242
|
directMessages: [],
|
|
234
243
|
groupMessages: [groupMessage],
|
|
235
244
|
});
|
|
236
|
-
const messageBuffer =
|
|
245
|
+
const messageBuffer = getMessageBuffer(server);
|
|
237
246
|
messageBuffer.setProfileMessage({
|
|
238
247
|
id: "agent-1",
|
|
239
248
|
displayName: "Agent One",
|
|
@@ -469,7 +478,7 @@ describe("McpServer unread backlog hydration", () => {
|
|
|
469
478
|
return { directMessages: [taskNotification], groupMessages: [] };
|
|
470
479
|
},
|
|
471
480
|
});
|
|
472
|
-
const messageBuffer =
|
|
481
|
+
const messageBuffer = getMessageBuffer(server);
|
|
473
482
|
messageBuffer.push(cachedMessage);
|
|
474
483
|
const handleToolCall = getHandleToolCall(server);
|
|
475
484
|
const result = (await handleToolCall.call(server, "poll_message", {}));
|
|
@@ -501,7 +510,7 @@ describe("McpServer unread backlog hydration", () => {
|
|
|
501
510
|
},
|
|
502
511
|
});
|
|
503
512
|
// Simulate WebSocket message arriving after backend returns empty
|
|
504
|
-
const buffer =
|
|
513
|
+
const buffer = getMessageBuffer(server);
|
|
505
514
|
setTimeout(() => {
|
|
506
515
|
buffer.push(delayedMessage);
|
|
507
516
|
}, 50);
|
|
@@ -1129,7 +1138,7 @@ describe("McpServer memory tools", () => {
|
|
|
1129
1138
|
});
|
|
1130
1139
|
}
|
|
1131
1140
|
// Override poll to resolve immediately with empty (simulating timeout without waiting 30s)
|
|
1132
|
-
const buffer =
|
|
1141
|
+
const buffer = getMessageBuffer(server);
|
|
1133
1142
|
let pollCallCount = 0;
|
|
1134
1143
|
buffer.poll = ((_timeoutMs) => {
|
|
1135
1144
|
pollCallCount++;
|
|
@@ -1144,7 +1153,7 @@ describe("McpServer memory tools", () => {
|
|
|
1144
1153
|
assert.equal(result.directMessages[0]?.receiverId, "agent-self");
|
|
1145
1154
|
assert.match(result.directMessages[0]?.content ?? "", /本地 memory 已超过建议容量/);
|
|
1146
1155
|
assert.match(result.directMessages[0]?.content ?? "", /get_memory_stats/);
|
|
1147
|
-
assert.
|
|
1156
|
+
assert.equal(result.directMessages[0]?.prompt, undefined);
|
|
1148
1157
|
// 非紧急类在初始路径直接返回,不进入 while 循环
|
|
1149
1158
|
assert.equal(pollCallCount, 0);
|
|
1150
1159
|
});
|
|
@@ -1215,7 +1224,7 @@ describe("McpServer memory tools", () => {
|
|
|
1215
1224
|
assert.equal(result.directMessages[0]?.receiverId, "agent-self");
|
|
1216
1225
|
assert.match(result.directMessages[0]?.content ?? "", /本地 memory 已超过建议容量/);
|
|
1217
1226
|
assert.match(result.directMessages[0]?.content ?? "", /agent_memory\/recent 类型下当前 33 条 memory/);
|
|
1218
|
-
assert.
|
|
1227
|
+
assert.equal(result.directMessages[0]?.prompt, undefined);
|
|
1219
1228
|
});
|
|
1220
1229
|
});
|
|
1221
1230
|
it("rejects incomplete memory update arguments", async () => {
|
|
@@ -1232,6 +1241,48 @@ describe("McpServer memory tools", () => {
|
|
|
1232
1241
|
}), /old_string and new_string must be provided together/);
|
|
1233
1242
|
});
|
|
1234
1243
|
});
|
|
1244
|
+
it("get_message with _aws_force_inject returns only force messages and keeps normal ones", async () => {
|
|
1245
|
+
await withMemoryServer(async (server) => {
|
|
1246
|
+
const handleToolCall = getHandleToolCall(server);
|
|
1247
|
+
Reflect.set(server, "agentClient", {
|
|
1248
|
+
fetchUnreadMessages: async () => ({
|
|
1249
|
+
directMessages: [],
|
|
1250
|
+
groupMessages: [],
|
|
1251
|
+
}),
|
|
1252
|
+
getState: () => ({ agentId: "agent-self" }),
|
|
1253
|
+
getStatusReporter: () => null,
|
|
1254
|
+
});
|
|
1255
|
+
const messageBuffer = getMessageBuffer(server);
|
|
1256
|
+
messageBuffer.push({
|
|
1257
|
+
msgId: "dm-force-1",
|
|
1258
|
+
senderId: "human-1",
|
|
1259
|
+
senderName: "Human One",
|
|
1260
|
+
content: "紧急消息",
|
|
1261
|
+
requireReply: false,
|
|
1262
|
+
force: true,
|
|
1263
|
+
timestamp: new Date().toISOString(),
|
|
1264
|
+
});
|
|
1265
|
+
messageBuffer.push({
|
|
1266
|
+
msgId: "dm-normal-1",
|
|
1267
|
+
senderId: "agent-2",
|
|
1268
|
+
senderName: "Agent Two",
|
|
1269
|
+
content: "普通消息",
|
|
1270
|
+
requireReply: false,
|
|
1271
|
+
timestamp: new Date().toISOString(),
|
|
1272
|
+
});
|
|
1273
|
+
// runtime 迭代边界强制注入调用:只取 force=true 的私信
|
|
1274
|
+
const result = (await handleToolCall.call(server, "get_message", {
|
|
1275
|
+
_aws_force_inject: true,
|
|
1276
|
+
}));
|
|
1277
|
+
assert.equal(result.directMessages.length, 1);
|
|
1278
|
+
assert.equal(result.directMessages[0]?.msgId, "dm-force-1");
|
|
1279
|
+
assert.equal(result.groupMessages.length, 0);
|
|
1280
|
+
// 普通消息留在缓冲区,下一次 get_message 正常返回
|
|
1281
|
+
const rest = (await handleToolCall.call(server, "get_message", {}));
|
|
1282
|
+
assert.equal(rest.directMessages.length, 1);
|
|
1283
|
+
assert.equal(rest.directMessages[0]?.msgId, "dm-normal-1");
|
|
1284
|
+
});
|
|
1285
|
+
});
|
|
1235
1286
|
it("redacts memory content from status reporting", async () => {
|
|
1236
1287
|
await withMemoryServer(async (server) => {
|
|
1237
1288
|
const reported = [];
|
|
@@ -1737,7 +1788,7 @@ describe("McpServer incoming group message filtering", () => {
|
|
|
1737
1788
|
};
|
|
1738
1789
|
const handleIncomingMessage = Reflect.get(server, "handleIncomingMessage");
|
|
1739
1790
|
await handleIncomingMessage.call(server, selfGroupMessage);
|
|
1740
|
-
const messageBuffer =
|
|
1791
|
+
const messageBuffer = getMessageBuffer(server);
|
|
1741
1792
|
const drained = messageBuffer.get();
|
|
1742
1793
|
assert.deepEqual(drained, {
|
|
1743
1794
|
directMessages: [],
|
|
@@ -1767,7 +1818,7 @@ describe("McpServer incoming group message filtering", () => {
|
|
|
1767
1818
|
};
|
|
1768
1819
|
const handleIncomingMessage = Reflect.get(server, "handleIncomingMessage");
|
|
1769
1820
|
await handleIncomingMessage.call(server, taskAssignedMessage);
|
|
1770
|
-
const messageBuffer =
|
|
1821
|
+
const messageBuffer = getMessageBuffer(server);
|
|
1771
1822
|
const drained = messageBuffer.get();
|
|
1772
1823
|
assert.deepEqual(drained, {
|
|
1773
1824
|
directMessages: [],
|
|
@@ -1967,7 +2018,7 @@ describe("AgentClient group room aggregation", () => {
|
|
|
1967
2018
|
assert.equal(result.count, 2);
|
|
1968
2019
|
});
|
|
1969
2020
|
});
|
|
1970
|
-
describe("McpServer todo
|
|
2021
|
+
describe("McpServer todo guard on poll_message", () => {
|
|
1971
2022
|
// 使用唯一 agentId 避免污染开发者真实 todo 文件;每次测试写入并清理
|
|
1972
2023
|
const testAgentId = `test-todo-reminder-${process.pid}-${Date.now()}`;
|
|
1973
2024
|
const todoDir = path.join(os.homedir(), ".acode", "todos");
|
|
@@ -2004,7 +2055,7 @@ describe("McpServer todo reminder on poll_message", () => {
|
|
|
2004
2055
|
timestamp: new Date().toISOString(),
|
|
2005
2056
|
};
|
|
2006
2057
|
}
|
|
2007
|
-
it("returns todo
|
|
2058
|
+
it("returns todo block message when incomplete todos exist", async () => {
|
|
2008
2059
|
await writeTodoFile([
|
|
2009
2060
|
{ content: "任务A", status: "pending", priority: "high" },
|
|
2010
2061
|
{ content: "任务B", status: "completed", priority: "low" },
|
|
@@ -2012,51 +2063,48 @@ describe("McpServer todo reminder on poll_message", () => {
|
|
|
2012
2063
|
try {
|
|
2013
2064
|
const server = createServerForTodo();
|
|
2014
2065
|
const handleToolCall = getHandleToolCall(server);
|
|
2066
|
+
// 有未完成 todo 时 poll_message 正常 JSON 返回提示消息(非异常)
|
|
2015
2067
|
const result = (await handleToolCall.call(server, "poll_message", {}));
|
|
2016
|
-
// 无未读消息时,todo 单独返回(组合2)
|
|
2017
2068
|
assert.equal(result.directMessages.length, 1);
|
|
2018
2069
|
assert.equal(result.directMessages[0].senderId, "MCP-System");
|
|
2019
|
-
assert.equal(result.directMessages[0].
|
|
2020
|
-
assert.match(result.directMessages[0].content, /Todo 列表存在 1 项未完成/);
|
|
2070
|
+
assert.equal(result.directMessages[0].content, "当前有未处理的todo list不能使用poll_message阻塞获取消息");
|
|
2021
2071
|
}
|
|
2022
2072
|
finally {
|
|
2023
2073
|
await cleanupTodoFile();
|
|
2024
2074
|
}
|
|
2025
2075
|
});
|
|
2026
|
-
it("returns
|
|
2076
|
+
it("returns todo block message even with unread messages", async () => {
|
|
2027
2077
|
await writeTodoFile([
|
|
2028
2078
|
{ content: "任务A", status: "pending", priority: "high" },
|
|
2029
2079
|
]);
|
|
2030
2080
|
try {
|
|
2031
2081
|
const server = createServerForTodo();
|
|
2032
2082
|
// 先把消息放入 buffer,模拟已有未读消息
|
|
2033
|
-
const buffer =
|
|
2083
|
+
const buffer = getMessageBuffer(server);
|
|
2034
2084
|
const msg = makeDirectMessage("msg-before-poll");
|
|
2035
2085
|
buffer.push(msg);
|
|
2036
2086
|
const handleToolCall = getHandleToolCall(server);
|
|
2087
|
+
// todo 前置检查在 hydrate 之前,即使有未读消息也先返回提示
|
|
2037
2088
|
const result = (await handleToolCall.call(server, "poll_message", {}));
|
|
2038
|
-
|
|
2039
|
-
assert.equal(result.directMessages.
|
|
2040
|
-
assert.equal(result.directMessages[0].senderId, "MCP-System");
|
|
2041
|
-
assert.match(result.directMessages[0].content, /Todo 列表存在 1 项未完成/);
|
|
2042
|
-
assert.equal(result.directMessages[1].msgId, "msg-before-poll");
|
|
2089
|
+
assert.equal(result.directMessages.length, 1);
|
|
2090
|
+
assert.equal(result.directMessages[0].content, "当前有未处理的todo list不能使用poll_message阻塞获取消息");
|
|
2043
2091
|
}
|
|
2044
2092
|
finally {
|
|
2045
2093
|
await cleanupTodoFile();
|
|
2046
2094
|
}
|
|
2047
2095
|
});
|
|
2048
|
-
it("
|
|
2096
|
+
it("allows poll_message when all todos are completed", async () => {
|
|
2049
2097
|
await writeTodoFile([
|
|
2050
2098
|
{ content: "任务A", status: "completed", priority: "high" },
|
|
2051
2099
|
]);
|
|
2052
2100
|
try {
|
|
2053
2101
|
const server = createServerForTodo();
|
|
2054
|
-
const buffer =
|
|
2102
|
+
const buffer = getMessageBuffer(server);
|
|
2055
2103
|
const wake = makeDirectMessage("wake-completed");
|
|
2056
2104
|
setTimeout(() => buffer.push(wake), 50);
|
|
2057
2105
|
const handleToolCall = getHandleToolCall(server);
|
|
2058
2106
|
const result = (await handleToolCall.call(server, "poll_message", {}));
|
|
2059
|
-
//
|
|
2107
|
+
// 全完成时无未完成 todo,进入 while 循环等待消息后返回
|
|
2060
2108
|
assert.equal(result.directMessages.length, 1);
|
|
2061
2109
|
assert.equal(result.directMessages[0].msgId, "wake-completed");
|
|
2062
2110
|
}
|
|
@@ -2064,62 +2112,58 @@ describe("McpServer todo reminder on poll_message", () => {
|
|
|
2064
2112
|
await cleanupTodoFile();
|
|
2065
2113
|
}
|
|
2066
2114
|
});
|
|
2067
|
-
it("
|
|
2115
|
+
it("allows poll_message when todo file is absent", async () => {
|
|
2116
|
+
// 确保文件不存在
|
|
2117
|
+
await cleanupTodoFile();
|
|
2118
|
+
const server = createServerForTodo();
|
|
2119
|
+
const buffer = getMessageBuffer(server);
|
|
2120
|
+
const wake = makeDirectMessage("wake-no-file");
|
|
2121
|
+
setTimeout(() => buffer.push(wake), 50);
|
|
2122
|
+
const handleToolCall = getHandleToolCall(server);
|
|
2123
|
+
const result = (await handleToolCall.call(server, "poll_message", {}));
|
|
2124
|
+
// 无 todo 文件 -> 进入 while 循环等待消息后返回
|
|
2125
|
+
assert.equal(result.directMessages.length, 1);
|
|
2126
|
+
assert.equal(result.directMessages[0].msgId, "wake-no-file");
|
|
2127
|
+
});
|
|
2128
|
+
it("returns todo block message when todo has in_progress status", async () => {
|
|
2068
2129
|
await writeTodoFile([
|
|
2069
|
-
{ content: "任务A", status: "
|
|
2130
|
+
{ content: "任务A", status: "in_progress", priority: "high" },
|
|
2070
2131
|
]);
|
|
2071
2132
|
try {
|
|
2072
2133
|
const server = createServerForTodo();
|
|
2073
2134
|
const handleToolCall = getHandleToolCall(server);
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
assert.equal(
|
|
2077
|
-
assert.match(result1.directMessages[0].content, /Todo 列表存在 1 项未完成/);
|
|
2078
|
-
// 第二次 poll(待办未变):仍返回 todo
|
|
2079
|
-
const result2 = (await handleToolCall.call(server, "poll_message", {}));
|
|
2080
|
-
assert.equal(result2.directMessages.length, 1);
|
|
2081
|
-
assert.match(result2.directMessages[0].content, /Todo 列表存在 1 项未完成/);
|
|
2135
|
+
const result = (await handleToolCall.call(server, "poll_message", {}));
|
|
2136
|
+
assert.equal(result.directMessages.length, 1);
|
|
2137
|
+
assert.equal(result.directMessages[0].content, "当前有未处理的todo list不能使用poll_message阻塞获取消息");
|
|
2082
2138
|
}
|
|
2083
2139
|
finally {
|
|
2084
2140
|
await cleanupTodoFile();
|
|
2085
2141
|
}
|
|
2086
2142
|
});
|
|
2087
|
-
it("
|
|
2143
|
+
it("allows poll_message again after todos are all completed", async () => {
|
|
2088
2144
|
await writeTodoFile([
|
|
2089
2145
|
{ content: "任务A", status: "pending", priority: "high" },
|
|
2090
2146
|
]);
|
|
2091
2147
|
try {
|
|
2092
2148
|
const server = createServerForTodo();
|
|
2093
2149
|
const handleToolCall = getHandleToolCall(server);
|
|
2094
|
-
// 第一次 poll
|
|
2150
|
+
// 第一次 poll:有未完成项 -> 返回提示消息(正常 JSON)
|
|
2095
2151
|
const result1 = (await handleToolCall.call(server, "poll_message", {}));
|
|
2096
2152
|
assert.equal(result1.directMessages.length, 1);
|
|
2097
|
-
assert.
|
|
2098
|
-
//
|
|
2153
|
+
assert.equal(result1.directMessages[0].content, "当前有未处理的todo list不能使用poll_message阻塞获取消息");
|
|
2154
|
+
// 全部完成后:poll_message 恢复正常阻塞,等消息返回
|
|
2099
2155
|
await writeTodoFile([
|
|
2100
|
-
{ content: "任务A", status: "
|
|
2101
|
-
{ content: "任务B", status: "in_progress", priority: "medium" },
|
|
2156
|
+
{ content: "任务A", status: "completed", priority: "high" },
|
|
2102
2157
|
]);
|
|
2103
|
-
|
|
2104
|
-
const
|
|
2105
|
-
|
|
2106
|
-
|
|
2158
|
+
const buffer = getMessageBuffer(server);
|
|
2159
|
+
const wake = makeDirectMessage("wake-after-complete");
|
|
2160
|
+
setTimeout(() => buffer.push(wake), 50);
|
|
2161
|
+
const result = (await handleToolCall.call(server, "poll_message", {}));
|
|
2162
|
+
assert.equal(result.directMessages.length, 1);
|
|
2163
|
+
assert.equal(result.directMessages[0].msgId, "wake-after-complete");
|
|
2107
2164
|
}
|
|
2108
2165
|
finally {
|
|
2109
2166
|
await cleanupTodoFile();
|
|
2110
2167
|
}
|
|
2111
2168
|
});
|
|
2112
|
-
it("does not inject todo reminder when todo file is absent", async () => {
|
|
2113
|
-
// 确保文件不存在
|
|
2114
|
-
await cleanupTodoFile();
|
|
2115
|
-
const server = createServerForTodo();
|
|
2116
|
-
const buffer = Reflect.get(server, "messageBuffer");
|
|
2117
|
-
const wake = makeDirectMessage("wake-no-file");
|
|
2118
|
-
setTimeout(() => buffer.push(wake), 50);
|
|
2119
|
-
const handleToolCall = getHandleToolCall(server);
|
|
2120
|
-
const result = (await handleToolCall.call(server, "poll_message", {}));
|
|
2121
|
-
// 无 todo 文件 -> 进入 while 循环等待消息后返回
|
|
2122
|
-
assert.equal(result.directMessages.length, 1);
|
|
2123
|
-
assert.equal(result.directMessages[0].msgId, "wake-no-file");
|
|
2124
|
-
});
|
|
2125
2169
|
});
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{logger as r}from"./logger.js";const
|
|
1
|
+
import{logger as r}from"./logger.js";const o=500;function h(n){if(!(n.msgId===void 0||n.msgId===null))return n.msgId}class u{buffer=[];resolvers=[];onMessageArrived=null;isPolling=!1;seenMsgIds=new Map;setOnMessageArrived(e){this.onMessageArrived=e}setIsPolling(e){this.isPolling=e}markSeen(e){const s=h(e);s!==void 0&&(this.seenMsgIds.set(s,Date.now()),this.cleanupSeenMsgIds())}isDuplicate(e){const s=h(e);return s===void 0?!1:this.seenMsgIds.has(s)?!0:(this.seenMsgIds.set(s,Date.now()),this.cleanupSeenMsgIds(),!1)}cleanupSeenMsgIds(){if(this.seenMsgIds.size<=o)return;let e=0;const s=this.seenMsgIds.size-o;for(const t of this.seenMsgIds.keys()){if(e>=s)break;this.seenMsgIds.delete(t),e++}}push(e){if(this.isDuplicate(e)){r.info(`[MessageBuffer] \u5FFD\u7565\u91CD\u590D\u6D88\u606F\uFF0CmsgId: ${e.msgId}`);return}this.buffer.push(e),r.info(`[MessageBuffer] \u6D88\u606F\u5165\u7F13\u51B2\u533A\uFF0C\u5F53\u524D\u6570\u91CF: ${this.buffer.length}`),this.isPolling&&this.onMessageArrived&&(r.info("[MessageBuffer] \u68C0\u6D4B\u5230 poll \u7B49\u5F85\u4E2D\uFF0C\u89E6\u53D1\u6D88\u606F\u5230\u8FBE\u56DE\u8C03"),this.onMessageArrived()),this.tryDispatch()}setProfileMessage(e){r.info(`[MessageBuffer] \u6536\u5230 Profile \u63A8\u9001\uFF0CdisplayName: ${e.displayName}\u3002\u8EAB\u4EFD\u4FE1\u606F\u8BF7\u901A\u8FC7 get_profile \u83B7\u53D6\u3002`)}removeProfileMessage(){r.info("[MessageBuffer] removeProfileMessage \u88AB\u8C03\u7528\uFF0C\u5F53\u524D\u65E0\u9700\u79FB\u9664\u672A\u8BFB\u8EAB\u4EFD\u6D88\u606F")}hasProfileMessage(){return!1}wakeUpWaiters(){if(this.tryDispatch(),this.buffer.length===0&&this.resolvers.length>0){const e=this.resolvers.shift();e&&(this.updatePollingState(),r.info("[MessageBuffer] wakeUpWaiters: \u5524\u9192 poll \u8D70 hydrate \u91CD\u67E5"),e({directMessages:[],groupMessages:[]}))}}getProfileMessage(){return null}updatePollingState(){this.isPolling=this.resolvers.length>0}poll(e){if(r.info(`[MessageBuffer] poll() \u88AB\u8C03\u7528\uFF0Cbuffer: ${this.buffer.length}${e?`\uFF0C\u8D85\u65F6: ${e}ms`:""}`),this.buffer.length>0){const s=this.consumeAll();return r.info(`[MessageBuffer] poll \u7ACB\u5373\u8FD4\u56DE\uFF0C\u79C1\u804A: ${s.directMessages.length}\uFF0C\u7FA4\u804A: ${s.groupMessages.length}`),Promise.resolve(s)}return this.resolvers.length===0&&(this.isPolling=!0),new Promise(s=>{let t=null,l=!1;const f=i=>{if(l)return;l=!0;const g=this.resolvers.indexOf(f);g>-1&&this.resolvers.splice(g,1),t&&(clearTimeout(t),t=null),this.updatePollingState(),r.info(`[MessageBuffer] poll \u6536\u5230\u6D88\u606F\uFF0C\u79C1\u804A: ${i.directMessages.length}\uFF0C\u7FA4\u804A: ${i.groupMessages.length}`),s(i)};this.resolvers.push(f),this.tryDispatch(),e&&e>0&&(t=setTimeout(()=>{if(l)return;l=!0;const i=this.resolvers.indexOf(f);i>-1&&this.resolvers.splice(i,1),this.updatePollingState(),r.info(`[MessageBuffer] poll \u8D85\u65F6\uFF08${e}ms\uFF09\uFF0C\u8FD4\u56DE\u7A7A\u6D88\u606F`),s({directMessages:[],groupMessages:[]})},e))})}get(){if(this.buffer.length>0){const e=this.consumeAll();return r.info(`[MessageBuffer] get \u8FD4\u56DE\u6D88\u606F\uFF0C\u79C1\u804A: ${e.directMessages.length}\uFF0C\u7FA4\u804A: ${e.groupMessages.length}`),e}return{directMessages:[],groupMessages:[]}}takeForceDirectMessages(){const e=[],s=[];for(const t of this.buffer)this.isDirectMessage(t)&&t.force===!0?(this.markSeen(t),e.push(t)):s.push(t);return this.buffer=s,e.length>0&&(r.info(`[MessageBuffer] takeForceDirectMessages \u53D6\u8D70\u5F3A\u5236\u79C1\u4FE1 ${e.length} \u6761\uFF0C\u5269\u4F59 ${s.length} \u6761`),this.tryDispatch()),{directMessages:e,groupMessages:[]}}size(){return this.buffer.length}clear(){this.buffer=[];for(const e of this.resolvers)e({directMessages:[],groupMessages:[]});this.resolvers=[],this.isPolling=!1,this.seenMsgIds.clear()}tryDispatch(){for(;this.resolvers.length>0&&this.buffer.length>0;){const e=this.resolvers.shift();if(e){const s=this.consumeAll();e(s)}}this.updatePollingState()}consumeAll(){const e=[],s=[];for(;this.buffer.length>0;){const t=this.buffer.shift();t&&(this.markSeen(t),this.isDirectMessage(t)?e.push(t):s.push(t))}return{directMessages:e,groupMessages:s}}isDirectMessage(e){return typeof e.msgId=="string"}}export{u as MessageBuffer};
|
|
2
2
|
|
|
@@ -40,4 +40,43 @@ describe('MessageBuffer poll', () => {
|
|
|
40
40
|
assert.equal(messages.groupMessages.length, 1);
|
|
41
41
|
assert.deepEqual(messages.groupMessages[0], groupMessage);
|
|
42
42
|
});
|
|
43
|
+
it('takeForceDirectMessages only takes force=true direct messages and keeps others', () => {
|
|
44
|
+
const buffer = new MessageBuffer();
|
|
45
|
+
const forcedMessage = {
|
|
46
|
+
msgId: 'dm-force-1',
|
|
47
|
+
senderId: 'human-1',
|
|
48
|
+
senderName: 'Human One',
|
|
49
|
+
content: '紧急:先停下处理这个',
|
|
50
|
+
requireReply: false,
|
|
51
|
+
force: true,
|
|
52
|
+
timestamp: new Date().toISOString()
|
|
53
|
+
};
|
|
54
|
+
const normalMessage = {
|
|
55
|
+
msgId: 'dm-normal-1',
|
|
56
|
+
senderId: 'agent-2',
|
|
57
|
+
senderName: 'Agent Two',
|
|
58
|
+
content: '普通消息',
|
|
59
|
+
requireReply: false,
|
|
60
|
+
timestamp: new Date().toISOString()
|
|
61
|
+
};
|
|
62
|
+
const groupMessage = {
|
|
63
|
+
msgId: 2,
|
|
64
|
+
senderId: 'human-1',
|
|
65
|
+
senderName: 'Human One',
|
|
66
|
+
content: '群消息',
|
|
67
|
+
timestamp: new Date().toISOString()
|
|
68
|
+
};
|
|
69
|
+
buffer.push(normalMessage);
|
|
70
|
+
buffer.push(groupMessage);
|
|
71
|
+
buffer.push(forcedMessage);
|
|
72
|
+
const taken = buffer.takeForceDirectMessages();
|
|
73
|
+
assert.equal(taken.directMessages.length, 1);
|
|
74
|
+
assert.deepEqual(taken.directMessages[0], forcedMessage);
|
|
75
|
+
assert.deepEqual(taken.groupMessages, []);
|
|
76
|
+
const rest = buffer.get();
|
|
77
|
+
assert.equal(rest.directMessages.length, 1);
|
|
78
|
+
assert.deepEqual(rest.directMessages[0], normalMessage);
|
|
79
|
+
assert.equal(rest.groupMessages.length, 1);
|
|
80
|
+
assert.deepEqual(rest.groupMessages[0], groupMessage);
|
|
81
|
+
});
|
|
43
82
|
});
|