@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,201 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from "vitest";
|
|
2
|
+
import type { MatrixClient } from "../sdk.js";
|
|
3
|
+
import { createMatrixRoomInfoResolver } from "./room-info.js";
|
|
4
|
+
|
|
5
|
+
type RoomStateHandler = (
|
|
6
|
+
roomId: string,
|
|
7
|
+
eventType: string,
|
|
8
|
+
stateKey: string,
|
|
9
|
+
) => Promise<Record<string, unknown>>;
|
|
10
|
+
|
|
11
|
+
type RoomInfoClientStub = MatrixClient & {
|
|
12
|
+
getRoomStateEvent: ReturnType<typeof vi.fn>;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
function createRoomStateClient(handler: RoomStateHandler): RoomInfoClientStub {
|
|
16
|
+
return {
|
|
17
|
+
getRoomStateEvent: vi.fn(handler),
|
|
18
|
+
} as unknown as RoomInfoClientStub;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function createClientStub() {
|
|
22
|
+
return createRoomStateClient(async (roomId, eventType, stateKey) => {
|
|
23
|
+
if (eventType === "m.room.name") {
|
|
24
|
+
return { name: `Room ${roomId}` };
|
|
25
|
+
}
|
|
26
|
+
if (eventType === "m.room.canonical_alias") {
|
|
27
|
+
return {
|
|
28
|
+
alias: `#alias-${roomId}:example.org`,
|
|
29
|
+
alt_aliases: [`#alt-${roomId}:example.org`],
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
if (eventType === "m.room.member") {
|
|
33
|
+
return { displayname: `Display ${roomId}:${stateKey}` };
|
|
34
|
+
}
|
|
35
|
+
return {};
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function createMissingMetadataError() {
|
|
40
|
+
const err = new Error("M_NOT_FOUND");
|
|
41
|
+
Object.assign(err, {
|
|
42
|
+
statusCode: 404,
|
|
43
|
+
body: { errcode: "M_NOT_FOUND" },
|
|
44
|
+
});
|
|
45
|
+
return err;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function getRoomStateCallCount(client: RoomInfoClientStub, eventType: string) {
|
|
49
|
+
return client.getRoomStateEvent.mock.calls.filter(([, type]) => type === eventType).length;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
describe("createMatrixRoomInfoResolver", () => {
|
|
53
|
+
it("caches room names and member display names, and loads aliases only on demand", async () => {
|
|
54
|
+
const client = createClientStub();
|
|
55
|
+
const resolver = createMatrixRoomInfoResolver(client);
|
|
56
|
+
|
|
57
|
+
await expect(resolver.getRoomInfo("!room:example.org")).resolves.toEqual({
|
|
58
|
+
name: "Room !room:example.org",
|
|
59
|
+
altAliases: [],
|
|
60
|
+
nameResolved: true,
|
|
61
|
+
aliasesResolved: false,
|
|
62
|
+
});
|
|
63
|
+
await resolver.getRoomInfo("!room:example.org");
|
|
64
|
+
await expect(
|
|
65
|
+
resolver.getRoomInfo("!room:example.org", { includeAliases: true }),
|
|
66
|
+
).resolves.toEqual({
|
|
67
|
+
name: "Room !room:example.org",
|
|
68
|
+
canonicalAlias: "#alias-!room:example.org:example.org",
|
|
69
|
+
altAliases: ["#alt-!room:example.org:example.org"],
|
|
70
|
+
nameResolved: true,
|
|
71
|
+
aliasesResolved: true,
|
|
72
|
+
});
|
|
73
|
+
await resolver.getRoomInfo("!room:example.org", { includeAliases: true });
|
|
74
|
+
await resolver.getMemberDisplayName("!room:example.org", "@alice:example.org");
|
|
75
|
+
await resolver.getMemberDisplayName("!room:example.org", "@alice:example.org");
|
|
76
|
+
|
|
77
|
+
expect(client.getRoomStateEvent).toHaveBeenCalledTimes(3);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it("caches fallback user IDs when member display names are missing", async () => {
|
|
81
|
+
const client = createRoomStateClient(async () => ({}));
|
|
82
|
+
const resolver = createMatrixRoomInfoResolver(client);
|
|
83
|
+
|
|
84
|
+
await expect(
|
|
85
|
+
resolver.getMemberDisplayName("!room:example.org", "@alice:example.org"),
|
|
86
|
+
).resolves.toBe("@alice:example.org");
|
|
87
|
+
await expect(
|
|
88
|
+
resolver.getMemberDisplayName("!room:example.org", "@alice:example.org"),
|
|
89
|
+
).resolves.toBe("@alice:example.org");
|
|
90
|
+
|
|
91
|
+
expect(client.getRoomStateEvent).toHaveBeenCalledTimes(1);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it("marks unresolved room metadata when room info lookups fail", async () => {
|
|
95
|
+
const client = createRoomStateClient(async (_roomId, eventType) => {
|
|
96
|
+
if (eventType === "m.room.member") {
|
|
97
|
+
return {};
|
|
98
|
+
}
|
|
99
|
+
throw new Error("room info unavailable");
|
|
100
|
+
});
|
|
101
|
+
const resolver = createMatrixRoomInfoResolver(client);
|
|
102
|
+
|
|
103
|
+
await expect(
|
|
104
|
+
resolver.getRoomInfo("!room:example.org", { includeAliases: true }),
|
|
105
|
+
).resolves.toEqual({
|
|
106
|
+
altAliases: [],
|
|
107
|
+
aliasesResolved: false,
|
|
108
|
+
nameResolved: false,
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it("treats missing room metadata as resolved-empty state", async () => {
|
|
113
|
+
const client = createRoomStateClient(async (_roomId, eventType) => {
|
|
114
|
+
if (eventType === "m.room.name" || eventType === "m.room.canonical_alias") {
|
|
115
|
+
throw createMissingMetadataError();
|
|
116
|
+
}
|
|
117
|
+
return {};
|
|
118
|
+
});
|
|
119
|
+
const resolver = createMatrixRoomInfoResolver(client);
|
|
120
|
+
|
|
121
|
+
await expect(
|
|
122
|
+
resolver.getRoomInfo("!room:example.org", { includeAliases: true }),
|
|
123
|
+
).resolves.toEqual({
|
|
124
|
+
altAliases: [],
|
|
125
|
+
aliasesResolved: true,
|
|
126
|
+
nameResolved: true,
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it("retries room metadata after a transient lookup failure", async () => {
|
|
131
|
+
const client = createRoomStateClient(async (_roomId, eventType) => {
|
|
132
|
+
if (eventType === "m.room.name") {
|
|
133
|
+
if (getRoomStateCallCount(client, eventType) === 1) {
|
|
134
|
+
throw new Error("name lookup unavailable");
|
|
135
|
+
}
|
|
136
|
+
return { name: "Recovered Room" };
|
|
137
|
+
}
|
|
138
|
+
if (eventType === "m.room.canonical_alias") {
|
|
139
|
+
if (getRoomStateCallCount(client, eventType) === 1) {
|
|
140
|
+
throw new Error("alias lookup unavailable");
|
|
141
|
+
}
|
|
142
|
+
return {
|
|
143
|
+
alias: "#recovered:example.org",
|
|
144
|
+
alt_aliases: ["#alt-recovered:example.org"],
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
return {};
|
|
148
|
+
});
|
|
149
|
+
const resolver = createMatrixRoomInfoResolver(client);
|
|
150
|
+
|
|
151
|
+
await expect(
|
|
152
|
+
resolver.getRoomInfo("!room:example.org", { includeAliases: true }),
|
|
153
|
+
).resolves.toEqual({
|
|
154
|
+
altAliases: [],
|
|
155
|
+
aliasesResolved: false,
|
|
156
|
+
nameResolved: false,
|
|
157
|
+
});
|
|
158
|
+
await expect(
|
|
159
|
+
resolver.getRoomInfo("!room:example.org", { includeAliases: true }),
|
|
160
|
+
).resolves.toEqual({
|
|
161
|
+
name: "Recovered Room",
|
|
162
|
+
canonicalAlias: "#recovered:example.org",
|
|
163
|
+
altAliases: ["#alt-recovered:example.org"],
|
|
164
|
+
nameResolved: true,
|
|
165
|
+
aliasesResolved: true,
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it("caches fallback user IDs when member display-name lookups fail", async () => {
|
|
170
|
+
const client = createRoomStateClient(async () => {
|
|
171
|
+
throw new Error("member lookup failed");
|
|
172
|
+
});
|
|
173
|
+
const resolver = createMatrixRoomInfoResolver(client);
|
|
174
|
+
|
|
175
|
+
await expect(
|
|
176
|
+
resolver.getMemberDisplayName("!room:example.org", "@alice:example.org"),
|
|
177
|
+
).resolves.toBe("@alice:example.org");
|
|
178
|
+
await expect(
|
|
179
|
+
resolver.getMemberDisplayName("!room:example.org", "@alice:example.org"),
|
|
180
|
+
).resolves.toBe("@alice:example.org");
|
|
181
|
+
|
|
182
|
+
expect(client.getRoomStateEvent).toHaveBeenCalledTimes(1);
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it("bounds cached room and member entries", async () => {
|
|
186
|
+
const client = createClientStub();
|
|
187
|
+
const resolver = createMatrixRoomInfoResolver(client);
|
|
188
|
+
|
|
189
|
+
for (let i = 0; i <= 1024; i += 1) {
|
|
190
|
+
await resolver.getRoomInfo(`!room-${i}:example.org`);
|
|
191
|
+
}
|
|
192
|
+
await resolver.getRoomInfo("!room-0:example.org");
|
|
193
|
+
|
|
194
|
+
for (let i = 0; i <= 4096; i += 1) {
|
|
195
|
+
await resolver.getMemberDisplayName("!room:example.org", `@user-${i}:example.org`);
|
|
196
|
+
}
|
|
197
|
+
await resolver.getMemberDisplayName("!room:example.org", "@user-0:example.org");
|
|
198
|
+
|
|
199
|
+
expect(client.getRoomStateEvent).toHaveBeenCalledTimes(5124);
|
|
200
|
+
});
|
|
201
|
+
});
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { isMatrixNotFoundError } from "../errors.js";
|
|
2
|
+
import type { MatrixClient } from "../sdk.js";
|
|
3
|
+
|
|
4
|
+
export type MatrixRoomInfo = {
|
|
5
|
+
name?: string;
|
|
6
|
+
canonicalAlias?: string;
|
|
7
|
+
altAliases: string[];
|
|
8
|
+
nameResolved: boolean;
|
|
9
|
+
aliasesResolved: boolean;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
const MAX_TRACKED_ROOM_INFO = 1024;
|
|
13
|
+
const MAX_TRACKED_MEMBER_DISPLAY_NAMES = 4096;
|
|
14
|
+
|
|
15
|
+
function rememberBounded<T>(map: Map<string, T>, key: string, value: T, maxEntries: number): void {
|
|
16
|
+
map.set(key, value);
|
|
17
|
+
if (map.size > maxEntries) {
|
|
18
|
+
const oldest = map.keys().next().value;
|
|
19
|
+
if (typeof oldest === "string") {
|
|
20
|
+
map.delete(oldest);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function createMatrixRoomInfoResolver(client: MatrixClient) {
|
|
26
|
+
const roomNameCache = new Map<string, Pick<MatrixRoomInfo, "name" | "nameResolved">>();
|
|
27
|
+
const roomAliasCache = new Map<
|
|
28
|
+
string,
|
|
29
|
+
Pick<MatrixRoomInfo, "canonicalAlias" | "altAliases" | "aliasesResolved">
|
|
30
|
+
>();
|
|
31
|
+
const memberDisplayNameCache = new Map<string, string>();
|
|
32
|
+
|
|
33
|
+
const getRoomName = async (
|
|
34
|
+
roomId: string,
|
|
35
|
+
): Promise<Pick<MatrixRoomInfo, "name" | "nameResolved">> => {
|
|
36
|
+
if (roomNameCache.has(roomId)) {
|
|
37
|
+
return roomNameCache.get(roomId) ?? { nameResolved: false };
|
|
38
|
+
}
|
|
39
|
+
let name: string | undefined;
|
|
40
|
+
let nameResolved = false;
|
|
41
|
+
try {
|
|
42
|
+
const nameState = await client.getRoomStateEvent(roomId, "m.room.name", "");
|
|
43
|
+
nameResolved = true;
|
|
44
|
+
if (nameState && typeof nameState.name === "string") {
|
|
45
|
+
name = nameState.name;
|
|
46
|
+
}
|
|
47
|
+
} catch (err) {
|
|
48
|
+
if (isMatrixNotFoundError(err)) {
|
|
49
|
+
nameResolved = true;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
const info = { name, nameResolved };
|
|
53
|
+
if (nameResolved) {
|
|
54
|
+
rememberBounded(roomNameCache, roomId, info, MAX_TRACKED_ROOM_INFO);
|
|
55
|
+
}
|
|
56
|
+
return info;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const getRoomAliases = async (
|
|
60
|
+
roomId: string,
|
|
61
|
+
): Promise<Pick<MatrixRoomInfo, "canonicalAlias" | "altAliases" | "aliasesResolved">> => {
|
|
62
|
+
const cached = roomAliasCache.get(roomId);
|
|
63
|
+
if (cached) {
|
|
64
|
+
return cached;
|
|
65
|
+
}
|
|
66
|
+
let canonicalAlias: string | undefined;
|
|
67
|
+
let altAliases: string[] = [];
|
|
68
|
+
let aliasesResolved = false;
|
|
69
|
+
try {
|
|
70
|
+
const aliasState = await client.getRoomStateEvent(roomId, "m.room.canonical_alias", "");
|
|
71
|
+
aliasesResolved = true;
|
|
72
|
+
if (aliasState && typeof aliasState.alias === "string") {
|
|
73
|
+
canonicalAlias = aliasState.alias;
|
|
74
|
+
}
|
|
75
|
+
const rawAliases = aliasState?.alt_aliases;
|
|
76
|
+
if (Array.isArray(rawAliases)) {
|
|
77
|
+
altAliases = rawAliases.filter((entry): entry is string => typeof entry === "string");
|
|
78
|
+
}
|
|
79
|
+
} catch (err) {
|
|
80
|
+
if (isMatrixNotFoundError(err)) {
|
|
81
|
+
aliasesResolved = true;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
const info = { canonicalAlias, altAliases, aliasesResolved };
|
|
85
|
+
if (aliasesResolved) {
|
|
86
|
+
rememberBounded(roomAliasCache, roomId, info, MAX_TRACKED_ROOM_INFO);
|
|
87
|
+
}
|
|
88
|
+
return info;
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
const getRoomInfo = async (
|
|
92
|
+
roomId: string,
|
|
93
|
+
opts: { includeAliases?: boolean } = {},
|
|
94
|
+
): Promise<MatrixRoomInfo> => {
|
|
95
|
+
const { name, nameResolved } = await getRoomName(roomId);
|
|
96
|
+
if (!opts.includeAliases) {
|
|
97
|
+
return { name, altAliases: [], nameResolved, aliasesResolved: false };
|
|
98
|
+
}
|
|
99
|
+
const aliases = await getRoomAliases(roomId);
|
|
100
|
+
return { name, nameResolved, ...aliases };
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
const getMemberDisplayName = async (roomId: string, userId: string): Promise<string> => {
|
|
104
|
+
const cacheKey = `${roomId}:${userId}`;
|
|
105
|
+
if (memberDisplayNameCache.has(cacheKey)) {
|
|
106
|
+
return memberDisplayNameCache.get(cacheKey) ?? userId;
|
|
107
|
+
}
|
|
108
|
+
const memberState = await client
|
|
109
|
+
.getRoomStateEvent(roomId, "m.room.member", userId)
|
|
110
|
+
.catch(() => null);
|
|
111
|
+
const displayName =
|
|
112
|
+
memberState && typeof memberState.displayname === "string" ? memberState.displayname : userId;
|
|
113
|
+
rememberBounded(
|
|
114
|
+
memberDisplayNameCache,
|
|
115
|
+
cacheKey,
|
|
116
|
+
displayName,
|
|
117
|
+
MAX_TRACKED_MEMBER_DISPLAY_NAMES,
|
|
118
|
+
);
|
|
119
|
+
return displayName;
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
return {
|
|
123
|
+
getRoomInfo,
|
|
124
|
+
getMemberDisplayName,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { resolveMatrixRoomConfig } from "./rooms.js";
|
|
3
|
+
|
|
4
|
+
describe("resolveMatrixRoomConfig", () => {
|
|
5
|
+
it("matches room IDs and aliases, not names", () => {
|
|
6
|
+
const rooms = {
|
|
7
|
+
"!room:example.org": { enabled: true },
|
|
8
|
+
"#alias:example.org": { enabled: true },
|
|
9
|
+
"Project Room": { enabled: true },
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
const byId = resolveMatrixRoomConfig({
|
|
13
|
+
rooms,
|
|
14
|
+
roomId: "!room:example.org",
|
|
15
|
+
aliases: [],
|
|
16
|
+
});
|
|
17
|
+
expect(byId.allowed).toBe(true);
|
|
18
|
+
expect(byId.matchKey).toBe("!room:example.org");
|
|
19
|
+
|
|
20
|
+
const byAlias = resolveMatrixRoomConfig({
|
|
21
|
+
rooms,
|
|
22
|
+
roomId: "!other:example.org",
|
|
23
|
+
aliases: ["#alias:example.org"],
|
|
24
|
+
});
|
|
25
|
+
expect(byAlias.allowed).toBe(true);
|
|
26
|
+
expect(byAlias.matchKey).toBe("#alias:example.org");
|
|
27
|
+
|
|
28
|
+
const byName = resolveMatrixRoomConfig({
|
|
29
|
+
rooms: { "Project Room": { enabled: true } },
|
|
30
|
+
roomId: "!different:example.org",
|
|
31
|
+
aliases: [],
|
|
32
|
+
});
|
|
33
|
+
expect(byName.allowed).toBe(false);
|
|
34
|
+
expect(byName.config).toBeUndefined();
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
describe("matchSource classification", () => {
|
|
38
|
+
it('returns matchSource="direct" for exact room ID match', () => {
|
|
39
|
+
const result = resolveMatrixRoomConfig({
|
|
40
|
+
rooms: { "!room:example.org": { enabled: true } },
|
|
41
|
+
roomId: "!room:example.org",
|
|
42
|
+
aliases: [],
|
|
43
|
+
});
|
|
44
|
+
expect(result.matchSource).toBe("direct");
|
|
45
|
+
expect(result.config).toBeDefined();
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('returns matchSource="direct" for alias match', () => {
|
|
49
|
+
const result = resolveMatrixRoomConfig({
|
|
50
|
+
rooms: { "#alias:example.org": { enabled: true } },
|
|
51
|
+
roomId: "!room:example.org",
|
|
52
|
+
aliases: ["#alias:example.org"],
|
|
53
|
+
});
|
|
54
|
+
expect(result.matchSource).toBe("direct");
|
|
55
|
+
expect(result.config).toBeDefined();
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('returns matchSource="wildcard" for wildcard match', () => {
|
|
59
|
+
const result = resolveMatrixRoomConfig({
|
|
60
|
+
rooms: { "*": { enabled: true } },
|
|
61
|
+
roomId: "!any:example.org",
|
|
62
|
+
aliases: [],
|
|
63
|
+
});
|
|
64
|
+
expect(result.matchSource).toBe("wildcard");
|
|
65
|
+
expect(result.config).toBeDefined();
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it("returns undefined matchSource when no match", () => {
|
|
69
|
+
const result = resolveMatrixRoomConfig({
|
|
70
|
+
rooms: { "!other:example.org": { enabled: true } },
|
|
71
|
+
roomId: "!room:example.org",
|
|
72
|
+
aliases: [],
|
|
73
|
+
});
|
|
74
|
+
expect(result.matchSource).toBeUndefined();
|
|
75
|
+
expect(result.config).toBeUndefined();
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it("direct match takes priority over wildcard", () => {
|
|
79
|
+
const result = resolveMatrixRoomConfig({
|
|
80
|
+
rooms: {
|
|
81
|
+
"!room:example.org": { enabled: true, systemPrompt: "room-specific" },
|
|
82
|
+
"*": { enabled: true, systemPrompt: "generic" },
|
|
83
|
+
},
|
|
84
|
+
roomId: "!room:example.org",
|
|
85
|
+
aliases: [],
|
|
86
|
+
});
|
|
87
|
+
expect(result.matchSource).toBe("direct");
|
|
88
|
+
expect(result.config?.systemPrompt).toBe("room-specific");
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
describe("DM override safety (matchSource distinction)", () => {
|
|
93
|
+
// These tests verify the matchSource property that handler.ts uses
|
|
94
|
+
// to decide whether a configured room should override DM classification.
|
|
95
|
+
// Only "direct" matches should trigger the override -- never "wildcard".
|
|
96
|
+
|
|
97
|
+
it("wildcard config should NOT be usable to override DM classification", () => {
|
|
98
|
+
const result = resolveMatrixRoomConfig({
|
|
99
|
+
rooms: { "*": { enabled: true, skills: ["general"] } },
|
|
100
|
+
roomId: "!dm-room:example.org",
|
|
101
|
+
aliases: [],
|
|
102
|
+
});
|
|
103
|
+
// handler.ts checks: matchSource === "direct" -> this is "wildcard", so no override
|
|
104
|
+
expect(result.matchSource).not.toBe("direct");
|
|
105
|
+
expect(result.matchSource).toBe("wildcard");
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it("explicitly configured room should be usable to override DM classification", () => {
|
|
109
|
+
const result = resolveMatrixRoomConfig({
|
|
110
|
+
rooms: {
|
|
111
|
+
"!configured-room:example.org": { enabled: true },
|
|
112
|
+
"*": { enabled: true },
|
|
113
|
+
},
|
|
114
|
+
roomId: "!configured-room:example.org",
|
|
115
|
+
aliases: [],
|
|
116
|
+
});
|
|
117
|
+
// handler.ts checks: matchSource === "direct" -> this IS "direct", so override is safe
|
|
118
|
+
expect(result.matchSource).toBe("direct");
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
});
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { MatrixRoomConfig } from "../../types.js";
|
|
2
|
+
import { buildChannelKeyCandidates, resolveChannelEntryMatch } from "./runtime-api.js";
|
|
3
|
+
|
|
4
|
+
export type MatrixRoomConfigResolved = {
|
|
5
|
+
allowed: boolean;
|
|
6
|
+
allowlistConfigured: boolean;
|
|
7
|
+
config?: MatrixRoomConfig;
|
|
8
|
+
matchKey?: string;
|
|
9
|
+
matchSource?: "direct" | "wildcard";
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
function readLegacyRoomAllowAlias(room: MatrixRoomConfig | undefined): boolean | undefined {
|
|
13
|
+
const rawRoom = room as Record<string, unknown> | undefined;
|
|
14
|
+
return typeof rawRoom?.allow === "boolean" ? rawRoom.allow : undefined;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function resolveMatrixRoomConfig(params: {
|
|
18
|
+
rooms?: Record<string, MatrixRoomConfig>;
|
|
19
|
+
roomId: string;
|
|
20
|
+
aliases: string[];
|
|
21
|
+
}): MatrixRoomConfigResolved {
|
|
22
|
+
const rooms = params.rooms ?? {};
|
|
23
|
+
const keys = Object.keys(rooms);
|
|
24
|
+
const allowlistConfigured = keys.length > 0;
|
|
25
|
+
const candidates = buildChannelKeyCandidates(
|
|
26
|
+
params.roomId,
|
|
27
|
+
`room:${params.roomId}`,
|
|
28
|
+
...params.aliases,
|
|
29
|
+
);
|
|
30
|
+
const {
|
|
31
|
+
entry: matched,
|
|
32
|
+
key: matchedKey,
|
|
33
|
+
wildcardEntry,
|
|
34
|
+
wildcardKey,
|
|
35
|
+
} = resolveChannelEntryMatch({
|
|
36
|
+
entries: rooms,
|
|
37
|
+
keys: candidates,
|
|
38
|
+
wildcardKey: "*",
|
|
39
|
+
});
|
|
40
|
+
const resolved = matched ?? wildcardEntry;
|
|
41
|
+
const legacyAllow = readLegacyRoomAllowAlias(resolved);
|
|
42
|
+
const allowed = resolved ? resolved.enabled !== false && legacyAllow !== false : false;
|
|
43
|
+
const matchKey = matchedKey ?? wildcardKey;
|
|
44
|
+
const matchSource = matched ? "direct" : wildcardEntry ? "wildcard" : undefined;
|
|
45
|
+
return {
|
|
46
|
+
allowed,
|
|
47
|
+
allowlistConfigured,
|
|
48
|
+
config: resolved,
|
|
49
|
+
matchKey,
|
|
50
|
+
matchSource,
|
|
51
|
+
};
|
|
52
|
+
}
|