fix(runtime): isolate channel messages by target

This commit is contained in:
2026-08-05 19:03:34 +08:00
parent 7cac422a31
commit a68b7cf3bf
3 changed files with 33 additions and 2 deletions
@@ -379,6 +379,27 @@ describe("LineUpRuntime", () => {
]);
});
it("ignores channel messages addressed to another user before scope routing", async () => {
const foreign = agentEnvelope("foreign-user-message", "lineup:2:channel_1:user_2", {
target: { kind: "human", id: "user_2" },
});
const own = agentEnvelope("current-user-message", "lineup:2:channel_1:user_1", {
target: { kind: "human", id: "user_1" },
});
const transport = new FakeTransport([[
{ message_seq: 40, from_uid: "agent_1", payload: foreign },
{ message_seq: 41, from_uid: "agent_1", payload: own },
], []]);
const runtime = new LineUpRuntime({ storage: new MemoryStorage(), createTransport: () => transport });
const chat = runtime.openChatApp();
await runtime.login(login);
await runtime.syncOnce();
expect(chat.listAgentMessages()).toHaveLength(1);
expect(chat.listAgentMessages()[0]?.message_id).toBe("current-user-message");
expect(runtime.snapshot()?.cursor).toBe(41);
});
it("rejects malformed scope and never delivers the same message id twice", async () => {
const conversationID = "user_1:2:channel_1";
const duplicate = agentEnvelope("dedupe_me", conversationID);
@@ -754,6 +754,16 @@ export class LineUpRuntime {
runtimeLog("message.ignored", { stage: "sender" });
return;
}
// The AppServer channel is shared by several users. Agent envelopes carry
// the intended human target; discard another user's message before scope
// validation so it is not reported as a malformed message for this user.
const parsedEnvelope = parseEnvelopeJSON(payload);
if (parsedEnvelope.ok
&& parsedEnvelope.envelope.target?.kind === "human"
&& parsedEnvelope.envelope.target.id !== this.session.uid) {
runtimeLog("message.ignored", { stage: "target" });
return;
}
if (message.from_uid === this.session.uid) {
const own = parseEnvelopeJSON(payload);
if (own.ok && CONTROL_ECHO_TYPES.has(own.envelope.type)) return;