@archipelagolab/lobi 1.0.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/CHANGELOG.md +164 -0
- package/ENDOFFILE +0 -0
- package/EOF +0 -0
- package/LICENSE +21 -0
- package/SPEC-SUPPORT.md +116 -0
- package/YAMLEND +0 -0
- package/api.ts +18 -0
- package/archipelagolab-lobi-1.0.0.tgz +0 -0
- package/auth-presence.ts +56 -0
- package/channel-plugin-api.ts +3 -0
- package/cli-metadata.ts +11 -0
- package/contract-api.ts +17 -0
- package/docs/CHECKLIST.md +83 -0
- package/docs/FORK_SDK_GUIDE.md +279 -0
- package/helper-api.ts +3 -0
- package/index.test.ts +61 -0
- package/index.ts +65 -0
- package/openclaw.plugin.json +23 -0
- package/package.json +52 -0
- package/plugin-entry.handlers.runtime.ts +1 -0
- package/runtime-api.ts +54 -0
- package/runtime-heavy-api.ts +1 -0
- package/scripts/migrate-to-lobi.sh +72 -0
- package/secret-contract-api.ts +5 -0
- package/setup-entry.ts +13 -0
- package/src/account-selection.test.ts +124 -0
- package/src/account-selection.ts +226 -0
- package/src/actions.account-propagation.test.ts +251 -0
- package/src/actions.test.ts +251 -0
- package/src/actions.ts +336 -0
- package/src/approval-auth.test.ts +23 -0
- package/src/approval-auth.ts +25 -0
- package/src/approval-handler.runtime.test.ts +46 -0
- package/src/approval-handler.runtime.ts +400 -0
- package/src/approval-ids.ts +6 -0
- package/src/approval-native.test.ts +329 -0
- package/src/approval-native.ts +336 -0
- package/src/approval-reactions.test.ts +107 -0
- package/src/approval-reactions.ts +158 -0
- package/src/auth-precedence.ts +61 -0
- package/src/channel-account-paths.ts +92 -0
- package/src/channel.account-paths.test.ts +102 -0
- package/src/channel.directory.test.ts +601 -0
- package/src/channel.resolve.test.ts +38 -0
- package/src/channel.runtime.ts +16 -0
- package/src/channel.setup.test.ts +269 -0
- package/src/channel.ts +570 -0
- package/src/cli-metadata.ts +19 -0
- package/src/cli.test.ts +1015 -0
- package/src/cli.ts +1198 -0
- package/src/config-adapter.ts +41 -0
- package/src/config-schema.test.ts +90 -0
- package/src/config-schema.ts +114 -0
- package/src/directory-live.test.ts +200 -0
- package/src/directory-live.ts +238 -0
- package/src/doctor-contract.ts +287 -0
- package/src/doctor.test.ts +440 -0
- package/src/doctor.ts +262 -0
- package/src/env-vars.ts +92 -0
- package/src/exec-approval-resolver.test.ts +68 -0
- package/src/exec-approval-resolver.ts +23 -0
- package/src/exec-approvals.test.ts +483 -0
- package/src/exec-approvals.ts +290 -0
- package/src/group-mentions.ts +41 -0
- package/src/legacy-crypto-inspector-availability.test.ts +81 -0
- package/src/legacy-crypto-inspector-availability.ts +60 -0
- package/src/legacy-crypto.test.ts +234 -0
- package/src/legacy-crypto.ts +549 -0
- package/src/legacy-state.test.ts +86 -0
- package/src/legacy-state.ts +156 -0
- package/src/matrix/account-config.ts +150 -0
- package/src/matrix/accounts.readiness.test.ts +27 -0
- package/src/matrix/accounts.test.ts +757 -0
- package/src/matrix/accounts.ts +194 -0
- package/src/matrix/actions/client.test.ts +215 -0
- package/src/matrix/actions/client.ts +31 -0
- package/src/matrix/actions/devices.test.ts +114 -0
- package/src/matrix/actions/devices.ts +34 -0
- package/src/matrix/actions/limits.test.ts +15 -0
- package/src/matrix/actions/limits.ts +6 -0
- package/src/matrix/actions/messages.test.ts +289 -0
- package/src/matrix/actions/messages.ts +123 -0
- package/src/matrix/actions/pins.test.ts +74 -0
- package/src/matrix/actions/pins.ts +64 -0
- package/src/matrix/actions/polls.test.ts +71 -0
- package/src/matrix/actions/polls.ts +109 -0
- package/src/matrix/actions/profile.test.ts +109 -0
- package/src/matrix/actions/profile.ts +37 -0
- package/src/matrix/actions/reactions.test.ts +135 -0
- package/src/matrix/actions/reactions.ts +59 -0
- package/src/matrix/actions/room.test.ts +79 -0
- package/src/matrix/actions/room.ts +71 -0
- package/src/matrix/actions/summary.test.ts +87 -0
- package/src/matrix/actions/summary.ts +88 -0
- package/src/matrix/actions/types.ts +82 -0
- package/src/matrix/actions/verification.test.ts +105 -0
- package/src/matrix/actions/verification.ts +237 -0
- package/src/matrix/actions.ts +37 -0
- package/src/matrix/active-client.ts +26 -0
- package/src/matrix/async-lock.ts +18 -0
- package/src/matrix/backup-health.ts +115 -0
- package/src/matrix/client/config-runtime-api.ts +14 -0
- package/src/matrix/client/config-secret-input.runtime.ts +1 -0
- package/src/matrix/client/config.ts +982 -0
- package/src/matrix/client/create-client.test.ts +115 -0
- package/src/matrix/client/create-client.ts +101 -0
- package/src/matrix/client/env-auth.ts +6 -0
- package/src/matrix/client/file-sync-store.test.ts +265 -0
- package/src/matrix/client/file-sync-store.ts +289 -0
- package/src/matrix/client/logging.ts +123 -0
- package/src/matrix/client/migration-snapshot.runtime.ts +1 -0
- package/src/matrix/client/private-network-host.ts +56 -0
- package/src/matrix/client/runtime.ts +4 -0
- package/src/matrix/client/shared.test.ts +344 -0
- package/src/matrix/client/shared.ts +306 -0
- package/src/matrix/client/storage.test.ts +634 -0
- package/src/matrix/client/storage.ts +544 -0
- package/src/matrix/client/types.ts +50 -0
- package/src/matrix/client-bootstrap.test.ts +84 -0
- package/src/matrix/client-bootstrap.ts +164 -0
- package/src/matrix/client-resolver.test-helpers.ts +147 -0
- package/src/matrix/client.test.ts +1521 -0
- package/src/matrix/client.ts +23 -0
- package/src/matrix/config-paths.ts +31 -0
- package/src/matrix/config-update.test.ts +237 -0
- package/src/matrix/config-update.ts +291 -0
- package/src/matrix/credentials-read.ts +206 -0
- package/src/matrix/credentials-write.runtime.ts +26 -0
- package/src/matrix/credentials.test.ts +501 -0
- package/src/matrix/credentials.ts +95 -0
- package/src/matrix/deps.test.ts +74 -0
- package/src/matrix/deps.ts +225 -0
- package/src/matrix/device-health.test.ts +45 -0
- package/src/matrix/device-health.ts +31 -0
- package/src/matrix/direct-management.test.ts +350 -0
- package/src/matrix/direct-management.ts +347 -0
- package/src/matrix/direct-room.test.ts +61 -0
- package/src/matrix/direct-room.ts +128 -0
- package/src/matrix/draft-stream.test.ts +406 -0
- package/src/matrix/draft-stream.ts +216 -0
- package/src/matrix/encryption-guidance.ts +27 -0
- package/src/matrix/errors.ts +21 -0
- package/src/matrix/format.test.ts +340 -0
- package/src/matrix/format.ts +428 -0
- package/src/matrix/legacy-crypto-inspector.ts +95 -0
- package/src/matrix/media-errors.ts +20 -0
- package/src/matrix/media-text.ts +169 -0
- package/src/matrix/monitor/access-state.test.ts +45 -0
- package/src/matrix/monitor/access-state.ts +77 -0
- package/src/matrix/monitor/ack-config.test.ts +57 -0
- package/src/matrix/monitor/ack-config.ts +26 -0
- package/src/matrix/monitor/allowlist.test.ts +45 -0
- package/src/matrix/monitor/allowlist.ts +94 -0
- package/src/matrix/monitor/auto-join.test.ts +203 -0
- package/src/matrix/monitor/auto-join.ts +86 -0
- package/src/matrix/monitor/config.test.ts +197 -0
- package/src/matrix/monitor/config.ts +303 -0
- package/src/matrix/monitor/context-summary.ts +43 -0
- package/src/matrix/monitor/direct.test.ts +529 -0
- package/src/matrix/monitor/direct.ts +270 -0
- package/src/matrix/monitor/events.test.ts +1524 -0
- package/src/matrix/monitor/events.ts +213 -0
- package/src/matrix/monitor/handler.body-for-agent.test.ts +396 -0
- package/src/matrix/monitor/handler.group-history.test.ts +648 -0
- package/src/matrix/monitor/handler.media-failure.test.ts +267 -0
- package/src/matrix/monitor/handler.test-helpers.ts +308 -0
- package/src/matrix/monitor/handler.test.ts +2952 -0
- package/src/matrix/monitor/handler.thread-root-media.test.ts +82 -0
- package/src/matrix/monitor/handler.ts +1679 -0
- package/src/matrix/monitor/inbound-dedupe.test.ts +146 -0
- package/src/matrix/monitor/inbound-dedupe.ts +267 -0
- package/src/matrix/monitor/index.test.ts +920 -0
- package/src/matrix/monitor/index.ts +434 -0
- package/src/matrix/monitor/legacy-crypto-restore.test.ts +206 -0
- package/src/matrix/monitor/legacy-crypto-restore.ts +139 -0
- package/src/matrix/monitor/location.ts +100 -0
- package/src/matrix/monitor/media.test.ts +159 -0
- package/src/matrix/monitor/media.ts +119 -0
- package/src/matrix/monitor/mentions.test.ts +289 -0
- package/src/matrix/monitor/mentions.ts +177 -0
- package/src/matrix/monitor/reaction-events.test.ts +326 -0
- package/src/matrix/monitor/reaction-events.ts +187 -0
- package/src/matrix/monitor/recent-invite.test.ts +92 -0
- package/src/matrix/monitor/recent-invite.ts +30 -0
- package/src/matrix/monitor/replies.test.ts +265 -0
- package/src/matrix/monitor/replies.ts +136 -0
- package/src/matrix/monitor/reply-context.test.ts +276 -0
- package/src/matrix/monitor/reply-context.ts +92 -0
- package/src/matrix/monitor/room-history.test.ts +258 -0
- package/src/matrix/monitor/room-history.ts +301 -0
- package/src/matrix/monitor/room-info.test.ts +201 -0
- package/src/matrix/monitor/room-info.ts +126 -0
- package/src/matrix/monitor/rooms.test.ts +121 -0
- package/src/matrix/monitor/rooms.ts +52 -0
- package/src/matrix/monitor/route.test.ts +255 -0
- package/src/matrix/monitor/route.ts +178 -0
- package/src/matrix/monitor/runtime-api.ts +31 -0
- package/src/matrix/monitor/startup-verification.test.ts +294 -0
- package/src/matrix/monitor/startup-verification.ts +237 -0
- package/src/matrix/monitor/startup.test.ts +257 -0
- package/src/matrix/monitor/startup.ts +218 -0
- package/src/matrix/monitor/status.ts +111 -0
- package/src/matrix/monitor/sync-lifecycle.test.ts +224 -0
- package/src/matrix/monitor/sync-lifecycle.ts +91 -0
- package/src/matrix/monitor/task-runner.ts +38 -0
- package/src/matrix/monitor/thread-context.test.ts +149 -0
- package/src/matrix/monitor/thread-context.ts +108 -0
- package/src/matrix/monitor/threads.test.ts +68 -0
- package/src/matrix/monitor/threads.ts +85 -0
- package/src/matrix/monitor/types.ts +30 -0
- package/src/matrix/monitor/verification-events.ts +627 -0
- package/src/matrix/monitor/verification-utils.test.ts +47 -0
- package/src/matrix/monitor/verification-utils.ts +46 -0
- package/src/matrix/outbound-media-runtime.ts +1 -0
- package/src/matrix/poll-summary.ts +110 -0
- package/src/matrix/poll-types.test.ts +205 -0
- package/src/matrix/poll-types.ts +433 -0
- package/src/matrix/probe.runtime.ts +4 -0
- package/src/matrix/probe.test.ts +154 -0
- package/src/matrix/probe.ts +96 -0
- package/src/matrix/profile.test.ts +154 -0
- package/src/matrix/profile.ts +184 -0
- package/src/matrix/reaction-common.test.ts +96 -0
- package/src/matrix/reaction-common.ts +147 -0
- package/src/matrix/sdk/crypto-bootstrap.test.ts +505 -0
- package/src/matrix/sdk/crypto-bootstrap.ts +341 -0
- package/src/matrix/sdk/crypto-facade.test.ts +197 -0
- package/src/matrix/sdk/crypto-facade.ts +207 -0
- package/src/matrix/sdk/crypto-node.runtime.test.ts +27 -0
- package/src/matrix/sdk/crypto-node.runtime.ts +9 -0
- package/src/matrix/sdk/crypto-runtime.ts +11 -0
- package/src/matrix/sdk/decrypt-bridge.ts +356 -0
- package/src/matrix/sdk/event-helpers.test.ts +60 -0
- package/src/matrix/sdk/event-helpers.ts +71 -0
- package/src/matrix/sdk/http-client.test.ts +134 -0
- package/src/matrix/sdk/http-client.ts +87 -0
- package/src/matrix/sdk/idb-persistence-lock.ts +51 -0
- package/src/matrix/sdk/idb-persistence.lock-order.test.ts +108 -0
- package/src/matrix/sdk/idb-persistence.test-helpers.ts +88 -0
- package/src/matrix/sdk/idb-persistence.test.ts +149 -0
- package/src/matrix/sdk/idb-persistence.ts +283 -0
- package/src/matrix/sdk/logger.test.ts +25 -0
- package/src/matrix/sdk/logger.ts +108 -0
- package/src/matrix/sdk/read-response-with-limit.ts +19 -0
- package/src/matrix/sdk/recovery-key-store.test.ts +385 -0
- package/src/matrix/sdk/recovery-key-store.ts +430 -0
- package/src/matrix/sdk/transport.test.ts +161 -0
- package/src/matrix/sdk/transport.ts +344 -0
- package/src/matrix/sdk/types.ts +236 -0
- package/src/matrix/sdk/verification-manager.test.ts +509 -0
- package/src/matrix/sdk/verification-manager.ts +694 -0
- package/src/matrix/sdk/verification-status.ts +23 -0
- package/src/matrix/sdk.test.ts +2568 -0
- package/src/matrix/sdk.ts +1789 -0
- package/src/matrix/send/client.test.ts +174 -0
- package/src/matrix/send/client.ts +90 -0
- package/src/matrix/send/formatting.ts +189 -0
- package/src/matrix/send/media.ts +244 -0
- package/src/matrix/send/targets.test.ts +254 -0
- package/src/matrix/send/targets.ts +104 -0
- package/src/matrix/send/types.ts +134 -0
- package/src/matrix/send.test.ts +958 -0
- package/src/matrix/send.ts +609 -0
- package/src/matrix/session-store-metadata.ts +108 -0
- package/src/matrix/startup-abort.ts +44 -0
- package/src/matrix/sync-state.ts +27 -0
- package/src/matrix/target-ids.ts +102 -0
- package/src/matrix/thread-bindings-shared.ts +201 -0
- package/src/matrix/thread-bindings.test.ts +673 -0
- package/src/matrix/thread-bindings.ts +577 -0
- package/src/matrix-migration.runtime.ts +9 -0
- package/src/migration-config.test.ts +228 -0
- package/src/migration-config.ts +243 -0
- package/src/migration-snapshot-backup.ts +117 -0
- package/src/migration-snapshot.test.ts +184 -0
- package/src/migration-snapshot.ts +55 -0
- package/src/onboarding.resolve.test.ts +55 -0
- package/src/onboarding.test-harness.ts +158 -0
- package/src/onboarding.test.ts +665 -0
- package/src/onboarding.ts +773 -0
- package/src/outbound.test.ts +173 -0
- package/src/outbound.ts +78 -0
- package/src/plugin-entry.runtime.js +159 -0
- package/src/plugin-entry.runtime.test.ts +108 -0
- package/src/plugin-entry.runtime.ts +68 -0
- package/src/profile-update.ts +68 -0
- package/src/record-shared.ts +3 -0
- package/src/resolve-targets.test.ts +178 -0
- package/src/resolve-targets.ts +175 -0
- package/src/resolver.ts +21 -0
- package/src/runtime-api.ts +144 -0
- package/src/runtime.ts +7 -0
- package/src/secret-contract.ts +174 -0
- package/src/session-route.test.ts +315 -0
- package/src/session-route.ts +113 -0
- package/src/setup-bootstrap.ts +94 -0
- package/src/setup-config.ts +222 -0
- package/src/setup-contract.ts +89 -0
- package/src/setup-core.test.ts +326 -0
- package/src/setup-core.ts +50 -0
- package/src/setup-surface.ts +4 -0
- package/src/startup-maintenance.test.ts +227 -0
- package/src/startup-maintenance.ts +114 -0
- package/src/storage-paths.ts +92 -0
- package/src/test-helpers.ts +42 -0
- package/src/test-mocks.ts +55 -0
- package/src/test-runtime.ts +72 -0
- package/src/test-support/monitor-route-test-support.ts +8 -0
- package/src/tool-actions.runtime.ts +1 -0
- package/src/tool-actions.test.ts +422 -0
- package/src/tool-actions.ts +498 -0
- package/src/types.ts +230 -0
- package/test-api.ts +2 -0
- package/thread-bindings-runtime.ts +4 -0
- package/tsconfig.json +16 -0
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
import type { MatrixClient } from "../sdk.js";
|
|
3
|
+
import { EventType } from "./types.js";
|
|
4
|
+
|
|
5
|
+
const { resolveMatrixRoomId, normalizeThreadId } = await import("./targets.js");
|
|
6
|
+
|
|
7
|
+
beforeEach(() => {
|
|
8
|
+
vi.clearAllMocks();
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
describe("resolveMatrixRoomId", () => {
|
|
12
|
+
it("uses m.direct when available", async () => {
|
|
13
|
+
const userId = "@user:example.org";
|
|
14
|
+
const client = {
|
|
15
|
+
getAccountData: vi.fn().mockResolvedValue({
|
|
16
|
+
[userId]: ["!room:example.org"],
|
|
17
|
+
}),
|
|
18
|
+
getUserId: vi.fn().mockResolvedValue("@bot:example.org"),
|
|
19
|
+
getJoinedRooms: vi.fn(),
|
|
20
|
+
getJoinedRoomMembers: vi.fn().mockResolvedValue(["@bot:example.org", userId]),
|
|
21
|
+
setAccountData: vi.fn(),
|
|
22
|
+
} as unknown as MatrixClient;
|
|
23
|
+
|
|
24
|
+
const roomId = await resolveMatrixRoomId(client, userId);
|
|
25
|
+
|
|
26
|
+
expect(roomId).toBe("!room:example.org");
|
|
27
|
+
expect(client.getJoinedRooms).not.toHaveBeenCalled();
|
|
28
|
+
expect(client.setAccountData).not.toHaveBeenCalled();
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("falls back to joined rooms and persists m.direct", async () => {
|
|
32
|
+
const userId = "@fallback:example.org";
|
|
33
|
+
const roomId = "!room:example.org";
|
|
34
|
+
const setAccountData = vi.fn().mockResolvedValue(undefined);
|
|
35
|
+
const client = {
|
|
36
|
+
getAccountData: vi.fn().mockRejectedValue(new Error("nope")),
|
|
37
|
+
getUserId: vi.fn().mockResolvedValue("@bot:example.org"),
|
|
38
|
+
getJoinedRooms: vi.fn().mockResolvedValue([roomId]),
|
|
39
|
+
getJoinedRoomMembers: vi.fn().mockResolvedValue(["@bot:example.org", userId]),
|
|
40
|
+
setAccountData,
|
|
41
|
+
} as unknown as MatrixClient;
|
|
42
|
+
|
|
43
|
+
const resolved = await resolveMatrixRoomId(client, userId);
|
|
44
|
+
|
|
45
|
+
expect(resolved).toBe(roomId);
|
|
46
|
+
expect(setAccountData).toHaveBeenCalledWith(
|
|
47
|
+
EventType.Direct,
|
|
48
|
+
expect.objectContaining({ [userId]: [roomId] }),
|
|
49
|
+
);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it("prefers joined rooms marked direct in local member state over plain strict rooms", async () => {
|
|
53
|
+
const userId = "@fallback:example.org";
|
|
54
|
+
const client = {
|
|
55
|
+
getAccountData: vi.fn().mockRejectedValue(new Error("nope")),
|
|
56
|
+
getUserId: vi.fn().mockResolvedValue("@bot:example.org"),
|
|
57
|
+
getJoinedRooms: vi.fn().mockResolvedValue(["!fallback:example.org", "!explicit:example.org"]),
|
|
58
|
+
getJoinedRoomMembers: vi.fn().mockResolvedValue(["@bot:example.org", userId]),
|
|
59
|
+
getRoomStateEvent: vi
|
|
60
|
+
.fn()
|
|
61
|
+
.mockImplementation(async (roomId: string, _eventType: string, stateKey: string) =>
|
|
62
|
+
roomId === "!explicit:example.org" && stateKey === "@bot:example.org"
|
|
63
|
+
? { is_direct: true }
|
|
64
|
+
: {},
|
|
65
|
+
),
|
|
66
|
+
setAccountData: vi.fn().mockResolvedValue(undefined),
|
|
67
|
+
} as unknown as MatrixClient;
|
|
68
|
+
|
|
69
|
+
const resolved = await resolveMatrixRoomId(client, userId);
|
|
70
|
+
|
|
71
|
+
expect(resolved).toBe("!explicit:example.org");
|
|
72
|
+
expect(client.setAccountData).toHaveBeenCalledWith(
|
|
73
|
+
EventType.Direct,
|
|
74
|
+
expect.objectContaining({ [userId]: ["!explicit:example.org"] }),
|
|
75
|
+
);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it("ignores remote member-state direct flags when resolving a direct room", async () => {
|
|
79
|
+
const userId = "@fallback:example.org";
|
|
80
|
+
const client = {
|
|
81
|
+
getAccountData: vi.fn().mockRejectedValue(new Error("nope")),
|
|
82
|
+
getUserId: vi.fn().mockResolvedValue("@bot:example.org"),
|
|
83
|
+
getJoinedRooms: vi
|
|
84
|
+
.fn()
|
|
85
|
+
.mockResolvedValue(["!fallback:example.org", "!remote-marked:example.org"]),
|
|
86
|
+
getJoinedRoomMembers: vi.fn().mockResolvedValue(["@bot:example.org", userId]),
|
|
87
|
+
getRoomStateEvent: vi
|
|
88
|
+
.fn()
|
|
89
|
+
.mockImplementation(async (roomId: string, _eventType: string, stateKey: string) =>
|
|
90
|
+
roomId === "!remote-marked:example.org" && stateKey === userId ? { is_direct: true } : {},
|
|
91
|
+
),
|
|
92
|
+
setAccountData: vi.fn().mockResolvedValue(undefined),
|
|
93
|
+
} as unknown as MatrixClient;
|
|
94
|
+
|
|
95
|
+
const resolved = await resolveMatrixRoomId(client, userId);
|
|
96
|
+
|
|
97
|
+
expect(resolved).toBe("!fallback:example.org");
|
|
98
|
+
expect(client.setAccountData).toHaveBeenCalledWith(
|
|
99
|
+
EventType.Direct,
|
|
100
|
+
expect.objectContaining({ [userId]: ["!fallback:example.org"] }),
|
|
101
|
+
);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it("continues when a room member lookup fails", async () => {
|
|
105
|
+
const userId = "@continue:example.org";
|
|
106
|
+
const roomId = "!good:example.org";
|
|
107
|
+
const setAccountData = vi.fn().mockResolvedValue(undefined);
|
|
108
|
+
const getJoinedRoomMembers = vi
|
|
109
|
+
.fn()
|
|
110
|
+
.mockRejectedValueOnce(new Error("boom"))
|
|
111
|
+
.mockResolvedValueOnce(["@bot:example.org", userId]);
|
|
112
|
+
const client = {
|
|
113
|
+
getAccountData: vi.fn().mockRejectedValue(new Error("nope")),
|
|
114
|
+
getUserId: vi.fn().mockResolvedValue("@bot:example.org"),
|
|
115
|
+
getJoinedRooms: vi.fn().mockResolvedValue(["!bad:example.org", roomId]),
|
|
116
|
+
getJoinedRoomMembers,
|
|
117
|
+
setAccountData,
|
|
118
|
+
} as unknown as MatrixClient;
|
|
119
|
+
|
|
120
|
+
const resolved = await resolveMatrixRoomId(client, userId);
|
|
121
|
+
|
|
122
|
+
expect(resolved).toBe(roomId);
|
|
123
|
+
expect(setAccountData).toHaveBeenCalled();
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it("does not fall back to larger shared rooms for direct-user sends", async () => {
|
|
127
|
+
const userId = "@group:example.org";
|
|
128
|
+
const roomId = "!group:example.org";
|
|
129
|
+
const client = {
|
|
130
|
+
getAccountData: vi.fn().mockRejectedValue(new Error("nope")),
|
|
131
|
+
getUserId: vi.fn().mockResolvedValue("@bot:example.org"),
|
|
132
|
+
getJoinedRooms: vi.fn().mockResolvedValue([roomId]),
|
|
133
|
+
getJoinedRoomMembers: vi
|
|
134
|
+
.fn()
|
|
135
|
+
.mockResolvedValue(["@bot:example.org", userId, "@extra:example.org"]),
|
|
136
|
+
setAccountData: vi.fn().mockResolvedValue(undefined),
|
|
137
|
+
} as unknown as MatrixClient;
|
|
138
|
+
|
|
139
|
+
await expect(resolveMatrixRoomId(client, userId)).rejects.toThrow(
|
|
140
|
+
`No direct room found for ${userId} (m.direct missing)`,
|
|
141
|
+
);
|
|
142
|
+
expect(client.setAccountData).not.toHaveBeenCalled();
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it("accepts nested Matrix user target prefixes", async () => {
|
|
146
|
+
const userId = "@prefixed:example.org";
|
|
147
|
+
const roomId = "!prefixed-room:example.org";
|
|
148
|
+
const client = {
|
|
149
|
+
getAccountData: vi.fn().mockResolvedValue({
|
|
150
|
+
[userId]: [roomId],
|
|
151
|
+
}),
|
|
152
|
+
getUserId: vi.fn().mockResolvedValue("@bot:example.org"),
|
|
153
|
+
getJoinedRooms: vi.fn(),
|
|
154
|
+
getJoinedRoomMembers: vi.fn().mockResolvedValue(["@bot:example.org", userId]),
|
|
155
|
+
setAccountData: vi.fn(),
|
|
156
|
+
resolveRoom: vi.fn(),
|
|
157
|
+
} as unknown as MatrixClient;
|
|
158
|
+
|
|
159
|
+
const resolved = await resolveMatrixRoomId(client, `matrix:user:${userId}`);
|
|
160
|
+
|
|
161
|
+
expect(resolved).toBe(roomId);
|
|
162
|
+
expect(client.resolveRoom).not.toHaveBeenCalled();
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it("scopes direct-room cache per Matrix client", async () => {
|
|
166
|
+
const userId = "@shared:example.org";
|
|
167
|
+
const clientA = {
|
|
168
|
+
getAccountData: vi.fn().mockResolvedValue({
|
|
169
|
+
[userId]: ["!room-a:example.org"],
|
|
170
|
+
}),
|
|
171
|
+
getUserId: vi.fn().mockResolvedValue("@bot-a:example.org"),
|
|
172
|
+
getJoinedRooms: vi.fn(),
|
|
173
|
+
getJoinedRoomMembers: vi.fn().mockResolvedValue(["@bot-a:example.org", userId]),
|
|
174
|
+
setAccountData: vi.fn(),
|
|
175
|
+
resolveRoom: vi.fn(),
|
|
176
|
+
} as unknown as MatrixClient;
|
|
177
|
+
const clientB = {
|
|
178
|
+
getAccountData: vi.fn().mockResolvedValue({
|
|
179
|
+
[userId]: ["!room-b:example.org"],
|
|
180
|
+
}),
|
|
181
|
+
getUserId: vi.fn().mockResolvedValue("@bot-b:example.org"),
|
|
182
|
+
getJoinedRooms: vi.fn(),
|
|
183
|
+
getJoinedRoomMembers: vi.fn().mockResolvedValue(["@bot-b:example.org", userId]),
|
|
184
|
+
setAccountData: vi.fn(),
|
|
185
|
+
resolveRoom: vi.fn(),
|
|
186
|
+
} as unknown as MatrixClient;
|
|
187
|
+
|
|
188
|
+
await expect(resolveMatrixRoomId(clientA, userId)).resolves.toBe("!room-a:example.org");
|
|
189
|
+
await expect(resolveMatrixRoomId(clientB, userId)).resolves.toBe("!room-b:example.org");
|
|
190
|
+
|
|
191
|
+
expect(clientA.getAccountData).toHaveBeenCalledTimes(1);
|
|
192
|
+
expect(clientB.getAccountData).toHaveBeenCalledTimes(1);
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
it("ignores m.direct entries that point at shared rooms", async () => {
|
|
196
|
+
const userId = "@shared:example.org";
|
|
197
|
+
const client = {
|
|
198
|
+
getAccountData: vi.fn().mockResolvedValue({
|
|
199
|
+
[userId]: ["!shared-room:example.org", "!dm-room:example.org"],
|
|
200
|
+
}),
|
|
201
|
+
getUserId: vi.fn().mockResolvedValue("@bot:example.org"),
|
|
202
|
+
getJoinedRooms: vi.fn(),
|
|
203
|
+
getJoinedRoomMembers: vi
|
|
204
|
+
.fn()
|
|
205
|
+
.mockResolvedValueOnce(["@bot:example.org", userId, "@extra:example.org"])
|
|
206
|
+
.mockResolvedValueOnce(["@bot:example.org", userId]),
|
|
207
|
+
setAccountData: vi.fn(),
|
|
208
|
+
resolveRoom: vi.fn(),
|
|
209
|
+
} as unknown as MatrixClient;
|
|
210
|
+
|
|
211
|
+
await expect(resolveMatrixRoomId(client, userId)).resolves.toBe("!dm-room:example.org");
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
it("revalidates cached direct rooms before reuse when membership changes", async () => {
|
|
215
|
+
const userId = "@shared:example.org";
|
|
216
|
+
const directRooms = ["!dm-room-1:example.org"];
|
|
217
|
+
const membersByRoom = new Map<string, string[]>([
|
|
218
|
+
["!dm-room-1:example.org", ["@bot:example.org", userId]],
|
|
219
|
+
["!dm-room-2:example.org", ["@bot:example.org", userId]],
|
|
220
|
+
]);
|
|
221
|
+
const client = {
|
|
222
|
+
getAccountData: vi.fn().mockImplementation(async () => ({
|
|
223
|
+
[userId]: [...directRooms],
|
|
224
|
+
})),
|
|
225
|
+
getUserId: vi.fn().mockResolvedValue("@bot:example.org"),
|
|
226
|
+
getJoinedRooms: vi
|
|
227
|
+
.fn()
|
|
228
|
+
.mockResolvedValue(["!dm-room-1:example.org", "!dm-room-2:example.org"]),
|
|
229
|
+
getJoinedRoomMembers: vi
|
|
230
|
+
.fn()
|
|
231
|
+
.mockImplementation(async (roomId: string) => membersByRoom.get(roomId) ?? []),
|
|
232
|
+
setAccountData: vi.fn(),
|
|
233
|
+
resolveRoom: vi.fn(),
|
|
234
|
+
} as unknown as MatrixClient;
|
|
235
|
+
|
|
236
|
+
await expect(resolveMatrixRoomId(client, userId)).resolves.toBe("!dm-room-1:example.org");
|
|
237
|
+
|
|
238
|
+
directRooms.splice(0, directRooms.length, "!dm-room-1:example.org", "!dm-room-2:example.org");
|
|
239
|
+
membersByRoom.set("!dm-room-1:example.org", [
|
|
240
|
+
"@bot:example.org",
|
|
241
|
+
userId,
|
|
242
|
+
"@mallory:example.org",
|
|
243
|
+
]);
|
|
244
|
+
|
|
245
|
+
await expect(resolveMatrixRoomId(client, userId)).resolves.toBe("!dm-room-2:example.org");
|
|
246
|
+
});
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
describe("normalizeThreadId", () => {
|
|
250
|
+
it("returns null for empty thread ids", () => {
|
|
251
|
+
expect(normalizeThreadId(" ")).toBeNull();
|
|
252
|
+
expect(normalizeThreadId("$thread")).toBe("$thread");
|
|
253
|
+
});
|
|
254
|
+
});
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import {
|
|
2
|
+
normalizeLowercaseStringOrEmpty,
|
|
3
|
+
normalizeOptionalStringifiedId,
|
|
4
|
+
} from "openclaw/plugin-sdk/text-runtime";
|
|
5
|
+
import { inspectMatrixDirectRooms, persistMatrixDirectRoomMapping } from "../direct-management.js";
|
|
6
|
+
import { isStrictDirectRoom } from "../direct-room.js";
|
|
7
|
+
import type { MatrixClient } from "../sdk.js";
|
|
8
|
+
import { isMatrixQualifiedUserId, normalizeMatrixResolvableTarget } from "../target-ids.js";
|
|
9
|
+
|
|
10
|
+
function normalizeTarget(raw: string): string {
|
|
11
|
+
const trimmed = raw.trim();
|
|
12
|
+
if (!trimmed) {
|
|
13
|
+
throw new Error("Matrix target is required (room:<id> or #alias)");
|
|
14
|
+
}
|
|
15
|
+
return trimmed;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function normalizeThreadId(raw?: string | number | null): string | null {
|
|
19
|
+
return normalizeOptionalStringifiedId(raw) ?? null;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Size-capped to prevent unbounded growth (#4948)
|
|
23
|
+
const MAX_DIRECT_ROOM_CACHE_SIZE = 1024;
|
|
24
|
+
const directRoomCacheByClient = new WeakMap<MatrixClient, Map<string, string>>();
|
|
25
|
+
|
|
26
|
+
function resolveDirectRoomCache(client: MatrixClient): Map<string, string> {
|
|
27
|
+
const existing = directRoomCacheByClient.get(client);
|
|
28
|
+
if (existing) {
|
|
29
|
+
return existing;
|
|
30
|
+
}
|
|
31
|
+
const created = new Map<string, string>();
|
|
32
|
+
directRoomCacheByClient.set(client, created);
|
|
33
|
+
return created;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function setDirectRoomCached(client: MatrixClient, key: string, value: string): void {
|
|
37
|
+
const directRoomCache = resolveDirectRoomCache(client);
|
|
38
|
+
directRoomCache.set(key, value);
|
|
39
|
+
if (directRoomCache.size > MAX_DIRECT_ROOM_CACHE_SIZE) {
|
|
40
|
+
const oldest = directRoomCache.keys().next().value;
|
|
41
|
+
if (oldest !== undefined) {
|
|
42
|
+
directRoomCache.delete(oldest);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async function resolveDirectRoomId(client: MatrixClient, userId: string): Promise<string> {
|
|
48
|
+
const trimmed = userId.trim();
|
|
49
|
+
if (!isMatrixQualifiedUserId(trimmed)) {
|
|
50
|
+
throw new Error(`Matrix user IDs must be fully qualified (got "${trimmed}")`);
|
|
51
|
+
}
|
|
52
|
+
const selfUserId = (await client.getUserId().catch(() => null))?.trim() || null;
|
|
53
|
+
|
|
54
|
+
const directRoomCache = resolveDirectRoomCache(client);
|
|
55
|
+
const cached = directRoomCache.get(trimmed);
|
|
56
|
+
if (
|
|
57
|
+
cached &&
|
|
58
|
+
(await isStrictDirectRoom({ client, roomId: cached, remoteUserId: trimmed, selfUserId }))
|
|
59
|
+
) {
|
|
60
|
+
return cached;
|
|
61
|
+
}
|
|
62
|
+
if (cached) {
|
|
63
|
+
directRoomCache.delete(trimmed);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const inspection = await inspectMatrixDirectRooms({
|
|
67
|
+
client,
|
|
68
|
+
remoteUserId: trimmed,
|
|
69
|
+
});
|
|
70
|
+
if (inspection.activeRoomId) {
|
|
71
|
+
setDirectRoomCached(client, trimmed, inspection.activeRoomId);
|
|
72
|
+
if (inspection.mappedRoomIds[0] !== inspection.activeRoomId) {
|
|
73
|
+
await persistMatrixDirectRoomMapping({
|
|
74
|
+
client,
|
|
75
|
+
remoteUserId: trimmed,
|
|
76
|
+
roomId: inspection.activeRoomId,
|
|
77
|
+
}).catch(() => {
|
|
78
|
+
// Ignore persistence errors when send resolution has already found a usable room.
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
return inspection.activeRoomId;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
throw new Error(`No direct room found for ${trimmed} (m.direct missing)`);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export async function resolveMatrixRoomId(client: MatrixClient, raw: string): Promise<string> {
|
|
88
|
+
const target = normalizeMatrixResolvableTarget(normalizeTarget(raw));
|
|
89
|
+
const lowered = normalizeLowercaseStringOrEmpty(target);
|
|
90
|
+
if (lowered.startsWith("user:")) {
|
|
91
|
+
return await resolveDirectRoomId(client, target.slice("user:".length));
|
|
92
|
+
}
|
|
93
|
+
if (isMatrixQualifiedUserId(target)) {
|
|
94
|
+
return await resolveDirectRoomId(client, target);
|
|
95
|
+
}
|
|
96
|
+
if (target.startsWith("#")) {
|
|
97
|
+
const resolved = await client.resolveRoom(target);
|
|
98
|
+
if (!resolved) {
|
|
99
|
+
throw new Error(`Matrix alias ${target} could not be resolved`);
|
|
100
|
+
}
|
|
101
|
+
return resolved;
|
|
102
|
+
}
|
|
103
|
+
return target;
|
|
104
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import type { CoreConfig } from "../../types.js";
|
|
2
|
+
import {
|
|
3
|
+
MATRIX_ANNOTATION_RELATION_TYPE,
|
|
4
|
+
MATRIX_REACTION_EVENT_TYPE,
|
|
5
|
+
type MatrixReactionEventContent,
|
|
6
|
+
} from "../reaction-common.js";
|
|
7
|
+
import type {
|
|
8
|
+
DimensionalFileInfo,
|
|
9
|
+
EncryptedFile,
|
|
10
|
+
FileWithThumbnailInfo,
|
|
11
|
+
MessageEventContent,
|
|
12
|
+
TextualMessageEventContent,
|
|
13
|
+
TimedFileInfo,
|
|
14
|
+
VideoFileInfo,
|
|
15
|
+
} from "../sdk.js";
|
|
16
|
+
|
|
17
|
+
// Message types
|
|
18
|
+
export const MsgType = {
|
|
19
|
+
Text: "m.text",
|
|
20
|
+
Image: "m.image",
|
|
21
|
+
Audio: "m.audio",
|
|
22
|
+
Video: "m.video",
|
|
23
|
+
File: "m.file",
|
|
24
|
+
Notice: "m.notice",
|
|
25
|
+
} as const;
|
|
26
|
+
|
|
27
|
+
// Relation types
|
|
28
|
+
export const RelationType = {
|
|
29
|
+
Annotation: MATRIX_ANNOTATION_RELATION_TYPE,
|
|
30
|
+
Replace: "m.replace",
|
|
31
|
+
Thread: "m.thread",
|
|
32
|
+
} as const;
|
|
33
|
+
|
|
34
|
+
// Event types
|
|
35
|
+
export const EventType = {
|
|
36
|
+
Direct: "m.direct",
|
|
37
|
+
Reaction: MATRIX_REACTION_EVENT_TYPE,
|
|
38
|
+
RoomMessage: "m.room.message",
|
|
39
|
+
} as const;
|
|
40
|
+
|
|
41
|
+
export const MATRIX_OPENCLAW_FINALIZED_PREVIEW_KEY = "com.openclaw.finalized_preview" as const;
|
|
42
|
+
|
|
43
|
+
export type MatrixDirectAccountData = Record<string, string[]>;
|
|
44
|
+
|
|
45
|
+
export type MatrixReplyRelation = {
|
|
46
|
+
"m.in_reply_to": { event_id: string };
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export type MatrixThreadRelation = {
|
|
50
|
+
rel_type: typeof RelationType.Thread;
|
|
51
|
+
event_id: string;
|
|
52
|
+
is_falling_back?: boolean;
|
|
53
|
+
"m.in_reply_to"?: { event_id: string };
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export type MatrixRelation = MatrixReplyRelation | MatrixThreadRelation;
|
|
57
|
+
|
|
58
|
+
export type MatrixReplyMeta = {
|
|
59
|
+
"m.relates_to"?: MatrixRelation;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
export type MatrixMediaInfo =
|
|
63
|
+
| FileWithThumbnailInfo
|
|
64
|
+
| DimensionalFileInfo
|
|
65
|
+
| TimedFileInfo
|
|
66
|
+
| VideoFileInfo;
|
|
67
|
+
|
|
68
|
+
export type MatrixTextContent = TextualMessageEventContent & MatrixReplyMeta;
|
|
69
|
+
|
|
70
|
+
export type MatrixMediaContent = MessageEventContent &
|
|
71
|
+
MatrixReplyMeta & {
|
|
72
|
+
info?: MatrixMediaInfo;
|
|
73
|
+
url?: string;
|
|
74
|
+
file?: EncryptedFile;
|
|
75
|
+
filename?: string;
|
|
76
|
+
"org.matrix.msc3245.voice"?: Record<string, never>;
|
|
77
|
+
"org.matrix.msc1767.audio"?: { duration: number };
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
export type MatrixOutboundContent = MatrixTextContent | MatrixMediaContent;
|
|
81
|
+
|
|
82
|
+
export type ReactionEventContent = MatrixReactionEventContent;
|
|
83
|
+
|
|
84
|
+
export type MatrixSendResult = {
|
|
85
|
+
messageId: string;
|
|
86
|
+
roomId: string;
|
|
87
|
+
primaryMessageId?: string;
|
|
88
|
+
messageIds?: string[];
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
export type MatrixSendOpts = {
|
|
92
|
+
client?: import("../sdk.js").MatrixClient;
|
|
93
|
+
cfg?: CoreConfig;
|
|
94
|
+
mediaUrl?: string;
|
|
95
|
+
mediaAccess?: {
|
|
96
|
+
localRoots?: readonly string[];
|
|
97
|
+
readFile?: (filePath: string) => Promise<Buffer>;
|
|
98
|
+
};
|
|
99
|
+
mediaLocalRoots?: readonly string[];
|
|
100
|
+
mediaReadFile?: (filePath: string) => Promise<Buffer>;
|
|
101
|
+
accountId?: string;
|
|
102
|
+
replyToId?: string;
|
|
103
|
+
threadId?: string | number | null;
|
|
104
|
+
timeoutMs?: number;
|
|
105
|
+
/** Send audio as voice message instead of audio file. Defaults to false. */
|
|
106
|
+
audioAsVoice?: boolean;
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
export type MatrixMediaMsgType =
|
|
110
|
+
| typeof MsgType.Image
|
|
111
|
+
| typeof MsgType.Audio
|
|
112
|
+
| typeof MsgType.Video
|
|
113
|
+
| typeof MsgType.File;
|
|
114
|
+
|
|
115
|
+
export type MatrixTextMsgType = typeof MsgType.Text | typeof MsgType.Notice;
|
|
116
|
+
|
|
117
|
+
export type MediaKind = "image" | "audio" | "video" | "document" | "unknown";
|
|
118
|
+
|
|
119
|
+
export type MatrixFormattedContent = MessageEventContent & {
|
|
120
|
+
format?: string;
|
|
121
|
+
formatted_body?: string;
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
export type MatrixExtraContentFields = Record<string, unknown>;
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* MSC4357 live marker key.
|
|
128
|
+
* When present on event content, signals that the message is still being
|
|
129
|
+
* streamed (e.g. an LLM generating a response). Supporting clients render
|
|
130
|
+
* the message with a streaming animation until an edit without this marker
|
|
131
|
+
* arrives, indicating the stream is complete.
|
|
132
|
+
* @see https://github.com/matrix-org/matrix-spec-proposals/pull/4357
|
|
133
|
+
*/
|
|
134
|
+
export const MSC4357_LIVE_KEY = "org.matrix.msc4357.live" as const;
|