@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,225 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import { createRequire } from "node:module";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
|
|
7
|
+
import type { RuntimeEnv } from "../runtime-api.js";
|
|
8
|
+
|
|
9
|
+
const REQUIRED_MATRIX_PACKAGES = [
|
|
10
|
+
"@archipelagolab/lobi-js-sdk",
|
|
11
|
+
"@matrix-org/matrix-sdk-crypto-nodejs",
|
|
12
|
+
"@matrix-org/matrix-sdk-crypto-wasm",
|
|
13
|
+
];
|
|
14
|
+
|
|
15
|
+
type MatrixCryptoRuntimeDeps = {
|
|
16
|
+
requireFn?: (id: string) => unknown;
|
|
17
|
+
runCommand?: (params: {
|
|
18
|
+
argv: string[];
|
|
19
|
+
cwd: string;
|
|
20
|
+
timeoutMs: number;
|
|
21
|
+
env?: NodeJS.ProcessEnv;
|
|
22
|
+
}) => Promise<CommandResult>;
|
|
23
|
+
resolveFn?: (id: string) => string;
|
|
24
|
+
nodeExecutable?: string;
|
|
25
|
+
log?: (message: string) => void;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
function resolveMissingMatrixPackages(): string[] {
|
|
29
|
+
try {
|
|
30
|
+
const req = createRequire(import.meta.url);
|
|
31
|
+
return REQUIRED_MATRIX_PACKAGES.filter((pkg) => {
|
|
32
|
+
try {
|
|
33
|
+
req.resolve(pkg);
|
|
34
|
+
return false;
|
|
35
|
+
} catch {
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
} catch {
|
|
40
|
+
return [...REQUIRED_MATRIX_PACKAGES];
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function isMatrixSdkAvailable(): boolean {
|
|
45
|
+
return resolveMissingMatrixPackages().length === 0;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function resolvePluginRoot(): string {
|
|
49
|
+
const currentDir = path.dirname(fileURLToPath(import.meta.url));
|
|
50
|
+
return path.resolve(currentDir, "..", "..");
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
type CommandResult = {
|
|
54
|
+
code: number;
|
|
55
|
+
stdout: string;
|
|
56
|
+
stderr: string;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
async function runFixedCommandWithTimeout(params: {
|
|
60
|
+
argv: string[];
|
|
61
|
+
cwd: string;
|
|
62
|
+
timeoutMs: number;
|
|
63
|
+
env?: NodeJS.ProcessEnv;
|
|
64
|
+
}): Promise<CommandResult> {
|
|
65
|
+
return await new Promise((resolve) => {
|
|
66
|
+
const [command, ...args] = params.argv;
|
|
67
|
+
if (!command) {
|
|
68
|
+
resolve({
|
|
69
|
+
code: 1,
|
|
70
|
+
stdout: "",
|
|
71
|
+
stderr: "command is required",
|
|
72
|
+
});
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const proc = spawn(command, args, {
|
|
77
|
+
cwd: params.cwd,
|
|
78
|
+
env: { ...process.env, ...params.env },
|
|
79
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
let stdout = "";
|
|
83
|
+
let stderr = "";
|
|
84
|
+
let settled = false;
|
|
85
|
+
let timer: NodeJS.Timeout | null = null;
|
|
86
|
+
|
|
87
|
+
const finalize = (result: CommandResult) => {
|
|
88
|
+
if (settled) {
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
settled = true;
|
|
92
|
+
if (timer) {
|
|
93
|
+
clearTimeout(timer);
|
|
94
|
+
}
|
|
95
|
+
resolve(result);
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
proc.stdout?.on("data", (chunk: Buffer | string) => {
|
|
99
|
+
stdout += chunk.toString();
|
|
100
|
+
});
|
|
101
|
+
proc.stderr?.on("data", (chunk: Buffer | string) => {
|
|
102
|
+
stderr += chunk.toString();
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
timer = setTimeout(() => {
|
|
106
|
+
proc.kill("SIGKILL");
|
|
107
|
+
finalize({
|
|
108
|
+
code: 124,
|
|
109
|
+
stdout,
|
|
110
|
+
stderr: stderr || `command timed out after ${params.timeoutMs}ms`,
|
|
111
|
+
});
|
|
112
|
+
}, params.timeoutMs);
|
|
113
|
+
|
|
114
|
+
proc.on("error", (err) => {
|
|
115
|
+
finalize({
|
|
116
|
+
code: 1,
|
|
117
|
+
stdout,
|
|
118
|
+
stderr: err.message,
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
proc.on("close", (code) => {
|
|
123
|
+
finalize({
|
|
124
|
+
code: code ?? 1,
|
|
125
|
+
stdout,
|
|
126
|
+
stderr,
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function defaultRequireFn(id: string): unknown {
|
|
133
|
+
return createRequire(import.meta.url)(id);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function defaultResolveFn(id: string): string {
|
|
137
|
+
return createRequire(import.meta.url).resolve(id);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function isMissingMatrixCryptoRuntimeError(error: unknown): boolean {
|
|
141
|
+
const message = formatErrorMessage(error);
|
|
142
|
+
return (
|
|
143
|
+
message.includes("@matrix-org/matrix-sdk-crypto-nodejs-") ||
|
|
144
|
+
message.includes("matrix-sdk-crypto-nodejs") ||
|
|
145
|
+
message.includes("download-lib.js")
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export async function ensureMatrixCryptoRuntime(
|
|
150
|
+
params: MatrixCryptoRuntimeDeps = {},
|
|
151
|
+
): Promise<void> {
|
|
152
|
+
const requireFn = params.requireFn ?? defaultRequireFn;
|
|
153
|
+
try {
|
|
154
|
+
requireFn("@matrix-org/matrix-sdk-crypto-nodejs");
|
|
155
|
+
return;
|
|
156
|
+
} catch (err) {
|
|
157
|
+
if (!isMissingMatrixCryptoRuntimeError(err)) {
|
|
158
|
+
throw err;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const resolveFn = params.resolveFn ?? defaultResolveFn;
|
|
163
|
+
const scriptPath = resolveFn("@matrix-org/matrix-sdk-crypto-nodejs/download-lib.js");
|
|
164
|
+
params.log?.("matrix: bootstrapping native crypto runtime");
|
|
165
|
+
const runCommand = params.runCommand ?? runFixedCommandWithTimeout;
|
|
166
|
+
const nodeExecutable = params.nodeExecutable ?? process.execPath;
|
|
167
|
+
const result = await runCommand({
|
|
168
|
+
argv: [nodeExecutable, scriptPath],
|
|
169
|
+
cwd: path.dirname(scriptPath),
|
|
170
|
+
timeoutMs: 300_000,
|
|
171
|
+
env: { COREPACK_ENABLE_DOWNLOAD_PROMPT: "0" },
|
|
172
|
+
});
|
|
173
|
+
if (result.code !== 0) {
|
|
174
|
+
throw new Error(
|
|
175
|
+
result.stderr.trim() || result.stdout.trim() || "Matrix crypto runtime bootstrap failed.",
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
requireFn("@matrix-org/matrix-sdk-crypto-nodejs");
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export async function ensureMatrixSdkInstalled(params: {
|
|
183
|
+
runtime: RuntimeEnv;
|
|
184
|
+
confirm?: (message: string) => Promise<boolean>;
|
|
185
|
+
}): Promise<void> {
|
|
186
|
+
if (isMatrixSdkAvailable()) {
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
const confirm = params.confirm;
|
|
190
|
+
if (confirm) {
|
|
191
|
+
const ok = await confirm(
|
|
192
|
+
"Matrix requires @archipelagolab/lobi-js-sdk, @matrix-org/matrix-sdk-crypto-nodejs, and @matrix-org/matrix-sdk-crypto-wasm. Install now?",
|
|
193
|
+
);
|
|
194
|
+
if (!ok) {
|
|
195
|
+
throw new Error(
|
|
196
|
+
"Matrix requires @archipelagolab/lobi-js-sdk, @matrix-org/matrix-sdk-crypto-nodejs, and @matrix-org/matrix-sdk-crypto-wasm (install dependencies first).",
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const root = resolvePluginRoot();
|
|
202
|
+
const command = fs.existsSync(path.join(root, "pnpm-lock.yaml"))
|
|
203
|
+
? ["pnpm", "install"]
|
|
204
|
+
: ["npm", "install", "--omit=dev", "--silent"];
|
|
205
|
+
params.runtime.log?.(`matrix: installing dependencies via ${command[0]} (${root})…`);
|
|
206
|
+
const result = await runFixedCommandWithTimeout({
|
|
207
|
+
argv: command,
|
|
208
|
+
cwd: root,
|
|
209
|
+
timeoutMs: 300_000,
|
|
210
|
+
env: { COREPACK_ENABLE_DOWNLOAD_PROMPT: "0" },
|
|
211
|
+
});
|
|
212
|
+
if (result.code !== 0) {
|
|
213
|
+
throw new Error(
|
|
214
|
+
result.stderr.trim() || result.stdout.trim() || "Matrix dependency install failed.",
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
if (!isMatrixSdkAvailable()) {
|
|
218
|
+
const missing = resolveMissingMatrixPackages();
|
|
219
|
+
throw new Error(
|
|
220
|
+
missing.length > 0
|
|
221
|
+
? `Matrix dependency install completed but required packages are still missing: ${missing.join(", ")}`
|
|
222
|
+
: "Matrix dependency install completed but Matrix dependencies are still missing.",
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { isOpenClawManagedMatrixDevice, summarizeMatrixDeviceHealth } from "./device-health.js";
|
|
3
|
+
|
|
4
|
+
describe("matrix device health", () => {
|
|
5
|
+
it("detects OpenClaw-managed device names", () => {
|
|
6
|
+
expect(isOpenClawManagedMatrixDevice("OpenClaw Gateway")).toBe(true);
|
|
7
|
+
expect(isOpenClawManagedMatrixDevice("OpenClaw Debug")).toBe(true);
|
|
8
|
+
expect(isOpenClawManagedMatrixDevice("Element iPhone")).toBe(false);
|
|
9
|
+
expect(isOpenClawManagedMatrixDevice(null)).toBe(false);
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it("summarizes stale OpenClaw-managed devices separately from the current device", () => {
|
|
13
|
+
const summary = summarizeMatrixDeviceHealth([
|
|
14
|
+
{
|
|
15
|
+
deviceId: "du314Zpw3A",
|
|
16
|
+
displayName: "OpenClaw Gateway",
|
|
17
|
+
current: true,
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
deviceId: "BritdXC6iL",
|
|
21
|
+
displayName: "OpenClaw Gateway",
|
|
22
|
+
current: false,
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
deviceId: "G6NJU9cTgs",
|
|
26
|
+
displayName: "OpenClaw Debug",
|
|
27
|
+
current: false,
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
deviceId: "phone123",
|
|
31
|
+
displayName: "Element iPhone",
|
|
32
|
+
current: false,
|
|
33
|
+
},
|
|
34
|
+
]);
|
|
35
|
+
|
|
36
|
+
expect(summary.currentDeviceId).toBe("du314Zpw3A");
|
|
37
|
+
expect(summary.currentOpenClawDevices).toEqual([
|
|
38
|
+
expect.objectContaining({ deviceId: "du314Zpw3A" }),
|
|
39
|
+
]);
|
|
40
|
+
expect(summary.staleOpenClawDevices).toEqual([
|
|
41
|
+
expect.objectContaining({ deviceId: "BritdXC6iL" }),
|
|
42
|
+
expect.objectContaining({ deviceId: "G6NJU9cTgs" }),
|
|
43
|
+
]);
|
|
44
|
+
});
|
|
45
|
+
});
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export type MatrixManagedDeviceInfo = {
|
|
2
|
+
deviceId: string;
|
|
3
|
+
displayName: string | null;
|
|
4
|
+
current: boolean;
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
export type MatrixDeviceHealthSummary = {
|
|
8
|
+
currentDeviceId: string | null;
|
|
9
|
+
staleOpenClawDevices: MatrixManagedDeviceInfo[];
|
|
10
|
+
currentOpenClawDevices: MatrixManagedDeviceInfo[];
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const OPENCLAW_DEVICE_NAME_PREFIX = "OpenClaw ";
|
|
14
|
+
|
|
15
|
+
export function isOpenClawManagedMatrixDevice(displayName: string | null | undefined): boolean {
|
|
16
|
+
return displayName?.startsWith(OPENCLAW_DEVICE_NAME_PREFIX) === true;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function summarizeMatrixDeviceHealth(
|
|
20
|
+
devices: MatrixManagedDeviceInfo[],
|
|
21
|
+
): MatrixDeviceHealthSummary {
|
|
22
|
+
const currentDeviceId = devices.find((device) => device.current)?.deviceId ?? null;
|
|
23
|
+
const openClawDevices = devices.filter((device) =>
|
|
24
|
+
isOpenClawManagedMatrixDevice(device.displayName),
|
|
25
|
+
);
|
|
26
|
+
return {
|
|
27
|
+
currentDeviceId,
|
|
28
|
+
staleOpenClawDevices: openClawDevices.filter((device) => !device.current),
|
|
29
|
+
currentOpenClawDevices: openClawDevices.filter((device) => device.current),
|
|
30
|
+
};
|
|
31
|
+
}
|
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from "vitest";
|
|
2
|
+
import {
|
|
3
|
+
inspectMatrixDirectRooms,
|
|
4
|
+
persistMatrixDirectRoomMapping,
|
|
5
|
+
promoteMatrixDirectRoomCandidate,
|
|
6
|
+
repairMatrixDirectRooms,
|
|
7
|
+
} from "./direct-management.js";
|
|
8
|
+
import type { MatrixClient } from "./sdk.js";
|
|
9
|
+
import { EventType } from "./send/types.js";
|
|
10
|
+
|
|
11
|
+
function createClient(overrides: Partial<MatrixClient> = {}): MatrixClient {
|
|
12
|
+
return {
|
|
13
|
+
getUserId: vi.fn(async () => "@bot:example.org"),
|
|
14
|
+
getAccountData: vi.fn(async () => undefined),
|
|
15
|
+
getJoinedRooms: vi.fn(async () => [] as string[]),
|
|
16
|
+
getJoinedRoomMembers: vi.fn(async () => [] as string[]),
|
|
17
|
+
getRoomStateEvent: vi.fn(async () => ({})),
|
|
18
|
+
setAccountData: vi.fn(async () => undefined),
|
|
19
|
+
createDirectRoom: vi.fn(async () => "!created:example.org"),
|
|
20
|
+
...overrides,
|
|
21
|
+
} as unknown as MatrixClient;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
describe("inspectMatrixDirectRooms", () => {
|
|
25
|
+
it("prefers strict mapped rooms over discovered rooms", async () => {
|
|
26
|
+
const client = createClient({
|
|
27
|
+
getAccountData: vi.fn(async () => ({
|
|
28
|
+
"@alice:example.org": ["!dm:example.org", "!shared:example.org"],
|
|
29
|
+
})),
|
|
30
|
+
getJoinedRooms: vi.fn(async () => ["!dm:example.org", "!shared:example.org"]),
|
|
31
|
+
getJoinedRoomMembers: vi.fn(async (roomId: string) =>
|
|
32
|
+
roomId === "!dm:example.org"
|
|
33
|
+
? ["@bot:example.org", "@alice:example.org"]
|
|
34
|
+
: ["@bot:example.org", "@alice:example.org", "@mallory:example.org"],
|
|
35
|
+
),
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
const result = await inspectMatrixDirectRooms({
|
|
39
|
+
client,
|
|
40
|
+
remoteUserId: "@alice:example.org",
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
expect(result.activeRoomId).toBe("!dm:example.org");
|
|
44
|
+
expect(result.mappedRooms).toEqual([
|
|
45
|
+
expect.objectContaining({ roomId: "!dm:example.org", strict: true }),
|
|
46
|
+
expect.objectContaining({ roomId: "!shared:example.org", strict: false }),
|
|
47
|
+
]);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it("falls back to discovered strict joined rooms when m.direct is stale", async () => {
|
|
51
|
+
const client = createClient({
|
|
52
|
+
getAccountData: vi.fn(async () => ({
|
|
53
|
+
"@alice:example.org": ["!stale:example.org"],
|
|
54
|
+
})),
|
|
55
|
+
getJoinedRooms: vi.fn(async () => ["!stale:example.org", "!fresh:example.org"]),
|
|
56
|
+
getJoinedRoomMembers: vi.fn(async (roomId: string) =>
|
|
57
|
+
roomId === "!fresh:example.org"
|
|
58
|
+
? ["@bot:example.org", "@alice:example.org"]
|
|
59
|
+
: ["@bot:example.org", "@alice:example.org", "@mallory:example.org"],
|
|
60
|
+
),
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
const result = await inspectMatrixDirectRooms({
|
|
64
|
+
client,
|
|
65
|
+
remoteUserId: "@alice:example.org",
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
expect(result.activeRoomId).toBe("!fresh:example.org");
|
|
69
|
+
expect(result.discoveredStrictRoomIds).toEqual(["!fresh:example.org"]);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it("prefers discovered rooms marked direct in local member state over plain strict rooms", async () => {
|
|
73
|
+
const client = createClient({
|
|
74
|
+
getJoinedRooms: vi.fn(async () => ["!fallback:example.org", "!explicit:example.org"]),
|
|
75
|
+
getJoinedRoomMembers: vi.fn(async () => ["@bot:example.org", "@alice:example.org"]),
|
|
76
|
+
getRoomStateEvent: vi.fn(async (roomId: string, _eventType: string, userId: string) =>
|
|
77
|
+
roomId === "!explicit:example.org" && userId === "@bot:example.org"
|
|
78
|
+
? { is_direct: true }
|
|
79
|
+
: {},
|
|
80
|
+
),
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
const result = await inspectMatrixDirectRooms({
|
|
84
|
+
client,
|
|
85
|
+
remoteUserId: "@alice:example.org",
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
expect(result.activeRoomId).toBe("!explicit:example.org");
|
|
89
|
+
expect(result.discoveredStrictRoomIds).toEqual([
|
|
90
|
+
"!fallback:example.org",
|
|
91
|
+
"!explicit:example.org",
|
|
92
|
+
]);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it("ignores remote member-state direct flags when ranking discovered rooms", async () => {
|
|
96
|
+
const client = createClient({
|
|
97
|
+
getJoinedRooms: vi.fn(async () => ["!fallback:example.org", "!remote-marked:example.org"]),
|
|
98
|
+
getJoinedRoomMembers: vi.fn(async () => ["@bot:example.org", "@alice:example.org"]),
|
|
99
|
+
getRoomStateEvent: vi.fn(async (roomId: string, _eventType: string, userId: string) =>
|
|
100
|
+
roomId === "!remote-marked:example.org" && userId === "@alice:example.org"
|
|
101
|
+
? { is_direct: true }
|
|
102
|
+
: {},
|
|
103
|
+
),
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
const result = await inspectMatrixDirectRooms({
|
|
107
|
+
client,
|
|
108
|
+
remoteUserId: "@alice:example.org",
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
expect(result.activeRoomId).toBe("!fallback:example.org");
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it("does not treat discovered rooms with local is_direct false as active DMs", async () => {
|
|
115
|
+
const client = createClient({
|
|
116
|
+
getJoinedRooms: vi.fn(async () => ["!blocked:example.org"]),
|
|
117
|
+
getJoinedRoomMembers: vi.fn(async () => ["@bot:example.org", "@alice:example.org"]),
|
|
118
|
+
getRoomStateEvent: vi.fn(async (_roomId: string, _eventType: string, userId: string) => ({
|
|
119
|
+
is_direct: userId === "@bot:example.org" ? false : undefined,
|
|
120
|
+
})),
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
const result = await inspectMatrixDirectRooms({
|
|
124
|
+
client,
|
|
125
|
+
remoteUserId: "@alice:example.org",
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
expect(result.activeRoomId).toBeNull();
|
|
129
|
+
expect(result.discoveredStrictRoomIds).toEqual([]);
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
describe("repairMatrixDirectRooms", () => {
|
|
134
|
+
it("repoints m.direct to an existing strict joined room", async () => {
|
|
135
|
+
const setAccountData = vi.fn(async () => undefined);
|
|
136
|
+
const client = createClient({
|
|
137
|
+
getAccountData: vi.fn(async () => ({
|
|
138
|
+
"@alice:example.org": ["!stale:example.org"],
|
|
139
|
+
})),
|
|
140
|
+
getJoinedRooms: vi.fn(async () => ["!stale:example.org", "!fresh:example.org"]),
|
|
141
|
+
getJoinedRoomMembers: vi.fn(async (roomId: string) =>
|
|
142
|
+
roomId === "!fresh:example.org"
|
|
143
|
+
? ["@bot:example.org", "@alice:example.org"]
|
|
144
|
+
: ["@bot:example.org", "@alice:example.org", "@mallory:example.org"],
|
|
145
|
+
),
|
|
146
|
+
setAccountData,
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
const result = await repairMatrixDirectRooms({
|
|
150
|
+
client,
|
|
151
|
+
remoteUserId: "@alice:example.org",
|
|
152
|
+
encrypted: true,
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
expect(result.activeRoomId).toBe("!fresh:example.org");
|
|
156
|
+
expect(result.createdRoomId).toBeNull();
|
|
157
|
+
expect(setAccountData).toHaveBeenCalledWith(
|
|
158
|
+
EventType.Direct,
|
|
159
|
+
expect.objectContaining({
|
|
160
|
+
"@alice:example.org": ["!fresh:example.org", "!stale:example.org"],
|
|
161
|
+
}),
|
|
162
|
+
);
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it("creates a fresh direct room when no healthy DM exists", async () => {
|
|
166
|
+
const createDirectRoom = vi.fn(async () => "!created:example.org");
|
|
167
|
+
const setAccountData = vi.fn(async () => undefined);
|
|
168
|
+
const client = createClient({
|
|
169
|
+
getJoinedRooms: vi.fn(async () => ["!shared:example.org"]),
|
|
170
|
+
getJoinedRoomMembers: vi.fn(async () => [
|
|
171
|
+
"@bot:example.org",
|
|
172
|
+
"@alice:example.org",
|
|
173
|
+
"@mallory:example.org",
|
|
174
|
+
]),
|
|
175
|
+
createDirectRoom,
|
|
176
|
+
setAccountData,
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
const result = await repairMatrixDirectRooms({
|
|
180
|
+
client,
|
|
181
|
+
remoteUserId: "@alice:example.org",
|
|
182
|
+
encrypted: true,
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
expect(createDirectRoom).toHaveBeenCalledWith("@alice:example.org", { encrypted: true });
|
|
186
|
+
expect(result.createdRoomId).toBe("!created:example.org");
|
|
187
|
+
expect(setAccountData).toHaveBeenCalledWith(
|
|
188
|
+
EventType.Direct,
|
|
189
|
+
expect.objectContaining({
|
|
190
|
+
"@alice:example.org": ["!created:example.org"],
|
|
191
|
+
}),
|
|
192
|
+
);
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
it("rejects unqualified Matrix user ids", async () => {
|
|
196
|
+
const client = createClient();
|
|
197
|
+
|
|
198
|
+
await expect(
|
|
199
|
+
repairMatrixDirectRooms({
|
|
200
|
+
client,
|
|
201
|
+
remoteUserId: "alice",
|
|
202
|
+
}),
|
|
203
|
+
).rejects.toThrow('Matrix user IDs must be fully qualified (got "alice")');
|
|
204
|
+
});
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
describe("promoteMatrixDirectRoomCandidate", () => {
|
|
208
|
+
it("classifies a strict room as direct and repairs m.direct", async () => {
|
|
209
|
+
const setAccountData = vi.fn(async () => undefined);
|
|
210
|
+
const client = createClient({
|
|
211
|
+
getJoinedRoomMembers: vi.fn(async () => ["@bot:example.org", "@alice:example.org"]),
|
|
212
|
+
setAccountData,
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
const result = await promoteMatrixDirectRoomCandidate({
|
|
216
|
+
client,
|
|
217
|
+
remoteUserId: "@alice:example.org",
|
|
218
|
+
roomId: "!fresh:example.org",
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
expect(result).toEqual({
|
|
222
|
+
classifyAsDirect: true,
|
|
223
|
+
repaired: true,
|
|
224
|
+
roomId: "!fresh:example.org",
|
|
225
|
+
reason: "promoted",
|
|
226
|
+
});
|
|
227
|
+
expect(setAccountData).toHaveBeenCalledWith(
|
|
228
|
+
EventType.Direct,
|
|
229
|
+
expect.objectContaining({
|
|
230
|
+
"@alice:example.org": ["!fresh:example.org"],
|
|
231
|
+
}),
|
|
232
|
+
);
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
it("does not classify rooms with local is_direct false as direct", async () => {
|
|
236
|
+
const setAccountData = vi.fn(async () => undefined);
|
|
237
|
+
const client = createClient({
|
|
238
|
+
getJoinedRoomMembers: vi.fn(async () => ["@bot:example.org", "@alice:example.org"]),
|
|
239
|
+
getRoomStateEvent: vi.fn(async (_roomId: string, _eventType: string, stateKey: string) =>
|
|
240
|
+
stateKey === "@bot:example.org" ? { is_direct: false } : {},
|
|
241
|
+
),
|
|
242
|
+
setAccountData,
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
const result = await promoteMatrixDirectRoomCandidate({
|
|
246
|
+
client,
|
|
247
|
+
remoteUserId: "@alice:example.org",
|
|
248
|
+
roomId: "!blocked:example.org",
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
expect(result).toEqual({
|
|
252
|
+
classifyAsDirect: false,
|
|
253
|
+
repaired: false,
|
|
254
|
+
reason: "local-explicit-false",
|
|
255
|
+
});
|
|
256
|
+
expect(setAccountData).not.toHaveBeenCalled();
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
it("returns already-mapped without rewriting account data", async () => {
|
|
260
|
+
const setAccountData = vi.fn(async () => undefined);
|
|
261
|
+
const client = createClient({
|
|
262
|
+
getAccountData: vi.fn(async () => ({
|
|
263
|
+
"@alice:example.org": ["!mapped:example.org", "!older:example.org"],
|
|
264
|
+
})),
|
|
265
|
+
getJoinedRoomMembers: vi.fn(async () => ["@bot:example.org", "@alice:example.org"]),
|
|
266
|
+
setAccountData,
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
const result = await promoteMatrixDirectRoomCandidate({
|
|
270
|
+
client,
|
|
271
|
+
remoteUserId: "@alice:example.org",
|
|
272
|
+
roomId: "!mapped:example.org",
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
expect(result).toEqual({
|
|
276
|
+
classifyAsDirect: true,
|
|
277
|
+
repaired: false,
|
|
278
|
+
roomId: "!mapped:example.org",
|
|
279
|
+
reason: "already-mapped",
|
|
280
|
+
});
|
|
281
|
+
expect(setAccountData).not.toHaveBeenCalled();
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
it("still classifies the room as direct when repair fails", async () => {
|
|
285
|
+
const client = createClient({
|
|
286
|
+
getJoinedRoomMembers: vi.fn(async () => ["@bot:example.org", "@alice:example.org"]),
|
|
287
|
+
setAccountData: vi.fn(async () => {
|
|
288
|
+
throw new Error("account data unavailable");
|
|
289
|
+
}),
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
const result = await promoteMatrixDirectRoomCandidate({
|
|
293
|
+
client,
|
|
294
|
+
remoteUserId: "@alice:example.org",
|
|
295
|
+
roomId: "!fresh:example.org",
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
expect(result).toEqual({
|
|
299
|
+
classifyAsDirect: true,
|
|
300
|
+
repaired: false,
|
|
301
|
+
roomId: "!fresh:example.org",
|
|
302
|
+
reason: "repair-failed",
|
|
303
|
+
});
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
it("serializes concurrent m.direct writes so distinct mappings are not lost", async () => {
|
|
307
|
+
let directContent: Record<string, string[]> = {};
|
|
308
|
+
let releaseFirstWrite!: () => void;
|
|
309
|
+
const firstWriteStarted = new Promise<void>((resolve) => {
|
|
310
|
+
releaseFirstWrite = () => {
|
|
311
|
+
resolve();
|
|
312
|
+
};
|
|
313
|
+
});
|
|
314
|
+
let writeCount = 0;
|
|
315
|
+
const setAccountData = vi.fn(async (_eventType: string, content: Record<string, string[]>) => {
|
|
316
|
+
writeCount += 1;
|
|
317
|
+
if (writeCount === 1) {
|
|
318
|
+
await firstWriteStarted;
|
|
319
|
+
}
|
|
320
|
+
directContent = { ...content };
|
|
321
|
+
});
|
|
322
|
+
const client = createClient({
|
|
323
|
+
getAccountData: vi.fn(async () => ({ ...directContent })),
|
|
324
|
+
setAccountData,
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
const firstWrite = persistMatrixDirectRoomMapping({
|
|
328
|
+
client,
|
|
329
|
+
remoteUserId: "@alice:example.org",
|
|
330
|
+
roomId: "!alice:example.org",
|
|
331
|
+
});
|
|
332
|
+
await vi.waitFor(() => {
|
|
333
|
+
expect(setAccountData).toHaveBeenCalledTimes(1);
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
const secondWrite = persistMatrixDirectRoomMapping({
|
|
337
|
+
client,
|
|
338
|
+
remoteUserId: "@bob:example.org",
|
|
339
|
+
roomId: "!bob:example.org",
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
releaseFirstWrite();
|
|
343
|
+
await expect(Promise.all([firstWrite, secondWrite])).resolves.toEqual([true, true]);
|
|
344
|
+
|
|
345
|
+
expect(directContent).toEqual({
|
|
346
|
+
"@alice:example.org": ["!alice:example.org"],
|
|
347
|
+
"@bob:example.org": ["!bob:example.org"],
|
|
348
|
+
});
|
|
349
|
+
});
|
|
350
|
+
});
|