@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
package/src/cli.test.ts
ADDED
|
@@ -0,0 +1,1015 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import { formatZonedTimestamp } from "openclaw/plugin-sdk/matrix-runtime-shared";
|
|
3
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
4
|
+
import { registerMatrixCli, resetMatrixCliStateForTests } from "./cli.js";
|
|
5
|
+
|
|
6
|
+
const bootstrapMatrixVerificationMock = vi.fn();
|
|
7
|
+
const getMatrixRoomKeyBackupStatusMock = vi.fn();
|
|
8
|
+
const getMatrixVerificationStatusMock = vi.fn();
|
|
9
|
+
const listMatrixOwnDevicesMock = vi.fn();
|
|
10
|
+
const pruneMatrixStaleGatewayDevicesMock = vi.fn();
|
|
11
|
+
const resolveMatrixAccountConfigMock = vi.fn();
|
|
12
|
+
const resolveMatrixAccountMock = vi.fn();
|
|
13
|
+
const resolveMatrixAuthContextMock = vi.fn();
|
|
14
|
+
const matrixSetupApplyAccountConfigMock = vi.fn();
|
|
15
|
+
const matrixSetupValidateInputMock = vi.fn();
|
|
16
|
+
const matrixRuntimeLoadConfigMock = vi.fn();
|
|
17
|
+
const matrixRuntimeWriteConfigFileMock = vi.fn();
|
|
18
|
+
const resetMatrixRoomKeyBackupMock = vi.fn();
|
|
19
|
+
const restoreMatrixRoomKeyBackupMock = vi.fn();
|
|
20
|
+
const setMatrixSdkConsoleLoggingMock = vi.fn();
|
|
21
|
+
const setMatrixSdkLogModeMock = vi.fn();
|
|
22
|
+
const updateMatrixOwnProfileMock = vi.fn();
|
|
23
|
+
const verifyMatrixRecoveryKeyMock = vi.fn();
|
|
24
|
+
const consoleLogMock = vi.fn();
|
|
25
|
+
const consoleErrorMock = vi.fn();
|
|
26
|
+
|
|
27
|
+
vi.mock("./matrix/actions/verification.js", () => ({
|
|
28
|
+
bootstrapMatrixVerification: (...args: unknown[]) => bootstrapMatrixVerificationMock(...args),
|
|
29
|
+
getMatrixRoomKeyBackupStatus: (...args: unknown[]) => getMatrixRoomKeyBackupStatusMock(...args),
|
|
30
|
+
getMatrixVerificationStatus: (...args: unknown[]) => getMatrixVerificationStatusMock(...args),
|
|
31
|
+
resetMatrixRoomKeyBackup: (...args: unknown[]) => resetMatrixRoomKeyBackupMock(...args),
|
|
32
|
+
restoreMatrixRoomKeyBackup: (...args: unknown[]) => restoreMatrixRoomKeyBackupMock(...args),
|
|
33
|
+
verifyMatrixRecoveryKey: (...args: unknown[]) => verifyMatrixRecoveryKeyMock(...args),
|
|
34
|
+
}));
|
|
35
|
+
|
|
36
|
+
vi.mock("./matrix/actions/devices.js", () => ({
|
|
37
|
+
listMatrixOwnDevices: (...args: unknown[]) => listMatrixOwnDevicesMock(...args),
|
|
38
|
+
pruneMatrixStaleGatewayDevices: (...args: unknown[]) =>
|
|
39
|
+
pruneMatrixStaleGatewayDevicesMock(...args),
|
|
40
|
+
}));
|
|
41
|
+
|
|
42
|
+
vi.mock("./matrix/client/logging.js", () => ({
|
|
43
|
+
setMatrixSdkConsoleLogging: (...args: unknown[]) => setMatrixSdkConsoleLoggingMock(...args),
|
|
44
|
+
setMatrixSdkLogMode: (...args: unknown[]) => setMatrixSdkLogModeMock(...args),
|
|
45
|
+
}));
|
|
46
|
+
|
|
47
|
+
vi.mock("./matrix/actions/profile.js", () => ({
|
|
48
|
+
updateMatrixOwnProfile: (...args: unknown[]) => updateMatrixOwnProfileMock(...args),
|
|
49
|
+
}));
|
|
50
|
+
|
|
51
|
+
vi.mock("./matrix/accounts.js", () => ({
|
|
52
|
+
resolveMatrixAccount: (...args: unknown[]) => resolveMatrixAccountMock(...args),
|
|
53
|
+
resolveMatrixAccountConfig: (...args: unknown[]) => resolveMatrixAccountConfigMock(...args),
|
|
54
|
+
}));
|
|
55
|
+
|
|
56
|
+
vi.mock("./matrix/client.js", () => ({
|
|
57
|
+
resolveMatrixAuthContext: (...args: unknown[]) => resolveMatrixAuthContextMock(...args),
|
|
58
|
+
}));
|
|
59
|
+
|
|
60
|
+
vi.mock("./setup-core.js", () => ({
|
|
61
|
+
matrixSetupAdapter: {
|
|
62
|
+
applyAccountConfig: (...args: unknown[]) => matrixSetupApplyAccountConfigMock(...args),
|
|
63
|
+
validateInput: (...args: unknown[]) => matrixSetupValidateInputMock(...args),
|
|
64
|
+
},
|
|
65
|
+
}));
|
|
66
|
+
|
|
67
|
+
vi.mock("./runtime.js", () => ({
|
|
68
|
+
getMatrixRuntime: () => ({
|
|
69
|
+
config: {
|
|
70
|
+
loadConfig: (...args: unknown[]) => matrixRuntimeLoadConfigMock(...args),
|
|
71
|
+
writeConfigFile: (...args: unknown[]) => matrixRuntimeWriteConfigFileMock(...args),
|
|
72
|
+
},
|
|
73
|
+
}),
|
|
74
|
+
}));
|
|
75
|
+
|
|
76
|
+
function buildProgram(): Command {
|
|
77
|
+
const program = new Command();
|
|
78
|
+
registerMatrixCli({ program });
|
|
79
|
+
return program;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function formatExpectedLocalTimestamp(value: string): string {
|
|
83
|
+
return formatZonedTimestamp(new Date(value), { displaySeconds: true }) ?? value;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function mockMatrixVerificationStatus(params: {
|
|
87
|
+
recoveryKeyCreatedAt: string | null;
|
|
88
|
+
verifiedAt?: string;
|
|
89
|
+
}) {
|
|
90
|
+
getMatrixVerificationStatusMock.mockResolvedValue({
|
|
91
|
+
encryptionEnabled: true,
|
|
92
|
+
verified: true,
|
|
93
|
+
localVerified: true,
|
|
94
|
+
crossSigningVerified: true,
|
|
95
|
+
signedByOwner: true,
|
|
96
|
+
userId: "@bot:example.org",
|
|
97
|
+
deviceId: "DEVICE123",
|
|
98
|
+
backupVersion: "1",
|
|
99
|
+
backup: {
|
|
100
|
+
serverVersion: "1",
|
|
101
|
+
activeVersion: "1",
|
|
102
|
+
trusted: true,
|
|
103
|
+
matchesDecryptionKey: true,
|
|
104
|
+
decryptionKeyCached: true,
|
|
105
|
+
},
|
|
106
|
+
recoveryKeyStored: true,
|
|
107
|
+
recoveryKeyCreatedAt: params.recoveryKeyCreatedAt,
|
|
108
|
+
pendingVerifications: 0,
|
|
109
|
+
verifiedAt: params.verifiedAt,
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
describe("matrix CLI verification commands", () => {
|
|
114
|
+
beforeEach(() => {
|
|
115
|
+
resetMatrixCliStateForTests();
|
|
116
|
+
vi.clearAllMocks();
|
|
117
|
+
process.exitCode = undefined;
|
|
118
|
+
vi.spyOn(console, "log").mockImplementation((...args: unknown[]) => consoleLogMock(...args));
|
|
119
|
+
vi.spyOn(console, "error").mockImplementation((...args: unknown[]) =>
|
|
120
|
+
consoleErrorMock(...args),
|
|
121
|
+
);
|
|
122
|
+
consoleLogMock.mockReset();
|
|
123
|
+
consoleErrorMock.mockReset();
|
|
124
|
+
matrixSetupValidateInputMock.mockReturnValue(null);
|
|
125
|
+
matrixSetupApplyAccountConfigMock.mockImplementation(({ cfg }: { cfg: unknown }) => cfg);
|
|
126
|
+
matrixRuntimeLoadConfigMock.mockReturnValue({});
|
|
127
|
+
matrixRuntimeWriteConfigFileMock.mockResolvedValue(undefined);
|
|
128
|
+
resolveMatrixAuthContextMock.mockImplementation(
|
|
129
|
+
({ cfg, accountId }: { cfg: unknown; accountId?: string | null }) => ({
|
|
130
|
+
cfg,
|
|
131
|
+
env: process.env,
|
|
132
|
+
accountId: accountId ?? "default",
|
|
133
|
+
resolved: {},
|
|
134
|
+
}),
|
|
135
|
+
);
|
|
136
|
+
resolveMatrixAccountMock.mockReturnValue({
|
|
137
|
+
configured: false,
|
|
138
|
+
});
|
|
139
|
+
resolveMatrixAccountConfigMock.mockReturnValue({
|
|
140
|
+
encryption: false,
|
|
141
|
+
});
|
|
142
|
+
bootstrapMatrixVerificationMock.mockResolvedValue({
|
|
143
|
+
success: true,
|
|
144
|
+
verification: {
|
|
145
|
+
recoveryKeyCreatedAt: null,
|
|
146
|
+
backupVersion: null,
|
|
147
|
+
},
|
|
148
|
+
crossSigning: {},
|
|
149
|
+
pendingVerifications: 0,
|
|
150
|
+
cryptoBootstrap: {},
|
|
151
|
+
});
|
|
152
|
+
resetMatrixRoomKeyBackupMock.mockResolvedValue({
|
|
153
|
+
success: true,
|
|
154
|
+
previousVersion: "1",
|
|
155
|
+
deletedVersion: "1",
|
|
156
|
+
createdVersion: "2",
|
|
157
|
+
backup: {
|
|
158
|
+
serverVersion: "2",
|
|
159
|
+
activeVersion: "2",
|
|
160
|
+
trusted: true,
|
|
161
|
+
matchesDecryptionKey: true,
|
|
162
|
+
decryptionKeyCached: true,
|
|
163
|
+
keyLoadAttempted: false,
|
|
164
|
+
keyLoadError: null,
|
|
165
|
+
},
|
|
166
|
+
});
|
|
167
|
+
updateMatrixOwnProfileMock.mockResolvedValue({
|
|
168
|
+
skipped: false,
|
|
169
|
+
displayNameUpdated: true,
|
|
170
|
+
avatarUpdated: false,
|
|
171
|
+
resolvedAvatarUrl: null,
|
|
172
|
+
convertedAvatarFromHttp: false,
|
|
173
|
+
});
|
|
174
|
+
listMatrixOwnDevicesMock.mockResolvedValue([]);
|
|
175
|
+
pruneMatrixStaleGatewayDevicesMock.mockResolvedValue({
|
|
176
|
+
before: [],
|
|
177
|
+
staleGatewayDeviceIds: [],
|
|
178
|
+
currentDeviceId: null,
|
|
179
|
+
deletedDeviceIds: [],
|
|
180
|
+
remainingDevices: [],
|
|
181
|
+
});
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
afterEach(() => {
|
|
185
|
+
vi.restoreAllMocks();
|
|
186
|
+
process.exitCode = undefined;
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
it("sets non-zero exit code for device verification failures in JSON mode", async () => {
|
|
190
|
+
verifyMatrixRecoveryKeyMock.mockResolvedValue({
|
|
191
|
+
success: false,
|
|
192
|
+
error: "invalid key",
|
|
193
|
+
});
|
|
194
|
+
const program = buildProgram();
|
|
195
|
+
|
|
196
|
+
await program.parseAsync(["matrix", "verify", "device", "bad-key", "--json"], {
|
|
197
|
+
from: "user",
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
expect(process.exitCode).toBe(1);
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
it("sets non-zero exit code for bootstrap failures in JSON mode", async () => {
|
|
204
|
+
bootstrapMatrixVerificationMock.mockResolvedValue({
|
|
205
|
+
success: false,
|
|
206
|
+
error: "bootstrap failed",
|
|
207
|
+
verification: {},
|
|
208
|
+
crossSigning: {},
|
|
209
|
+
pendingVerifications: 0,
|
|
210
|
+
cryptoBootstrap: null,
|
|
211
|
+
});
|
|
212
|
+
const program = buildProgram();
|
|
213
|
+
|
|
214
|
+
await program.parseAsync(["matrix", "verify", "bootstrap", "--json"], { from: "user" });
|
|
215
|
+
|
|
216
|
+
expect(process.exitCode).toBe(1);
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
it("sets non-zero exit code for backup restore failures in JSON mode", async () => {
|
|
220
|
+
restoreMatrixRoomKeyBackupMock.mockResolvedValue({
|
|
221
|
+
success: false,
|
|
222
|
+
error: "missing backup key",
|
|
223
|
+
backupVersion: null,
|
|
224
|
+
imported: 0,
|
|
225
|
+
total: 0,
|
|
226
|
+
loadedFromSecretStorage: false,
|
|
227
|
+
backup: {
|
|
228
|
+
serverVersion: "1",
|
|
229
|
+
activeVersion: null,
|
|
230
|
+
trusted: true,
|
|
231
|
+
matchesDecryptionKey: false,
|
|
232
|
+
decryptionKeyCached: false,
|
|
233
|
+
},
|
|
234
|
+
});
|
|
235
|
+
const program = buildProgram();
|
|
236
|
+
|
|
237
|
+
await program.parseAsync(["matrix", "verify", "backup", "restore", "--json"], {
|
|
238
|
+
from: "user",
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
expect(process.exitCode).toBe(1);
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
it("sets non-zero exit code for backup reset failures in JSON mode", async () => {
|
|
245
|
+
resetMatrixRoomKeyBackupMock.mockResolvedValue({
|
|
246
|
+
success: false,
|
|
247
|
+
error: "reset failed",
|
|
248
|
+
previousVersion: "1",
|
|
249
|
+
deletedVersion: "1",
|
|
250
|
+
createdVersion: null,
|
|
251
|
+
backup: {
|
|
252
|
+
serverVersion: null,
|
|
253
|
+
activeVersion: null,
|
|
254
|
+
trusted: null,
|
|
255
|
+
matchesDecryptionKey: null,
|
|
256
|
+
decryptionKeyCached: null,
|
|
257
|
+
keyLoadAttempted: false,
|
|
258
|
+
keyLoadError: null,
|
|
259
|
+
},
|
|
260
|
+
});
|
|
261
|
+
const program = buildProgram();
|
|
262
|
+
|
|
263
|
+
await program.parseAsync(["matrix", "verify", "backup", "reset", "--yes", "--json"], {
|
|
264
|
+
from: "user",
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
expect(process.exitCode).toBe(1);
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
it("lists matrix devices", async () => {
|
|
271
|
+
listMatrixOwnDevicesMock.mockResolvedValue([
|
|
272
|
+
{
|
|
273
|
+
deviceId: "A7hWrQ70ea",
|
|
274
|
+
displayName: "OpenClaw Gateway",
|
|
275
|
+
lastSeenIp: "127.0.0.1",
|
|
276
|
+
lastSeenTs: 1_741_507_200_000,
|
|
277
|
+
current: true,
|
|
278
|
+
},
|
|
279
|
+
{
|
|
280
|
+
deviceId: "BritdXC6iL",
|
|
281
|
+
displayName: "OpenClaw Gateway",
|
|
282
|
+
lastSeenIp: null,
|
|
283
|
+
lastSeenTs: null,
|
|
284
|
+
current: false,
|
|
285
|
+
},
|
|
286
|
+
]);
|
|
287
|
+
const program = buildProgram();
|
|
288
|
+
|
|
289
|
+
await program.parseAsync(["matrix", "devices", "list", "--account", "poe"], { from: "user" });
|
|
290
|
+
|
|
291
|
+
expect(listMatrixOwnDevicesMock).toHaveBeenCalledWith({ accountId: "poe" });
|
|
292
|
+
expect(console.log).toHaveBeenCalledWith("Account: poe");
|
|
293
|
+
expect(console.log).toHaveBeenCalledWith("- A7hWrQ70ea (current, OpenClaw Gateway)");
|
|
294
|
+
expect(console.log).toHaveBeenCalledWith(" Last IP: 127.0.0.1");
|
|
295
|
+
expect(console.log).toHaveBeenCalledWith("- BritdXC6iL (OpenClaw Gateway)");
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
it("prunes stale matrix gateway devices", async () => {
|
|
299
|
+
pruneMatrixStaleGatewayDevicesMock.mockResolvedValue({
|
|
300
|
+
before: [
|
|
301
|
+
{
|
|
302
|
+
deviceId: "A7hWrQ70ea",
|
|
303
|
+
displayName: "OpenClaw Gateway",
|
|
304
|
+
lastSeenIp: "127.0.0.1",
|
|
305
|
+
lastSeenTs: 1_741_507_200_000,
|
|
306
|
+
current: true,
|
|
307
|
+
},
|
|
308
|
+
{
|
|
309
|
+
deviceId: "BritdXC6iL",
|
|
310
|
+
displayName: "OpenClaw Gateway",
|
|
311
|
+
lastSeenIp: null,
|
|
312
|
+
lastSeenTs: null,
|
|
313
|
+
current: false,
|
|
314
|
+
},
|
|
315
|
+
],
|
|
316
|
+
staleGatewayDeviceIds: ["BritdXC6iL"],
|
|
317
|
+
currentDeviceId: "A7hWrQ70ea",
|
|
318
|
+
deletedDeviceIds: ["BritdXC6iL"],
|
|
319
|
+
remainingDevices: [
|
|
320
|
+
{
|
|
321
|
+
deviceId: "A7hWrQ70ea",
|
|
322
|
+
displayName: "OpenClaw Gateway",
|
|
323
|
+
lastSeenIp: "127.0.0.1",
|
|
324
|
+
lastSeenTs: 1_741_507_200_000,
|
|
325
|
+
current: true,
|
|
326
|
+
},
|
|
327
|
+
],
|
|
328
|
+
});
|
|
329
|
+
const program = buildProgram();
|
|
330
|
+
|
|
331
|
+
await program.parseAsync(["matrix", "devices", "prune-stale", "--account", "poe"], {
|
|
332
|
+
from: "user",
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
expect(pruneMatrixStaleGatewayDevicesMock).toHaveBeenCalledWith({ accountId: "poe" });
|
|
336
|
+
expect(console.log).toHaveBeenCalledWith("Deleted stale OpenClaw devices: BritdXC6iL");
|
|
337
|
+
expect(console.log).toHaveBeenCalledWith("Current device: A7hWrQ70ea");
|
|
338
|
+
expect(console.log).toHaveBeenCalledWith("Remaining devices: 1");
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
it("adds a matrix account and prints a binding hint", async () => {
|
|
342
|
+
matrixRuntimeLoadConfigMock.mockReturnValue({ channels: {} });
|
|
343
|
+
matrixSetupApplyAccountConfigMock.mockImplementation(
|
|
344
|
+
({ cfg, accountId }: { cfg: Record<string, unknown>; accountId: string }) => ({
|
|
345
|
+
...cfg,
|
|
346
|
+
channels: {
|
|
347
|
+
...(cfg.channels as Record<string, unknown> | undefined),
|
|
348
|
+
matrix: {
|
|
349
|
+
accounts: {
|
|
350
|
+
[accountId]: {
|
|
351
|
+
homeserver: "https://matrix.example.org",
|
|
352
|
+
},
|
|
353
|
+
},
|
|
354
|
+
},
|
|
355
|
+
},
|
|
356
|
+
}),
|
|
357
|
+
);
|
|
358
|
+
const program = buildProgram();
|
|
359
|
+
|
|
360
|
+
await program.parseAsync(
|
|
361
|
+
[
|
|
362
|
+
"matrix",
|
|
363
|
+
"account",
|
|
364
|
+
"add",
|
|
365
|
+
"--account",
|
|
366
|
+
"Ops",
|
|
367
|
+
"--homeserver",
|
|
368
|
+
"https://matrix.example.org",
|
|
369
|
+
"--user-id",
|
|
370
|
+
"@ops:example.org",
|
|
371
|
+
"--password",
|
|
372
|
+
"secret",
|
|
373
|
+
],
|
|
374
|
+
{ from: "user" },
|
|
375
|
+
);
|
|
376
|
+
|
|
377
|
+
expect(matrixSetupValidateInputMock).toHaveBeenCalledWith(
|
|
378
|
+
expect.objectContaining({
|
|
379
|
+
accountId: "ops",
|
|
380
|
+
input: expect.objectContaining({
|
|
381
|
+
homeserver: "https://matrix.example.org",
|
|
382
|
+
userId: "@ops:example.org",
|
|
383
|
+
password: "secret", // pragma: allowlist secret
|
|
384
|
+
}),
|
|
385
|
+
}),
|
|
386
|
+
);
|
|
387
|
+
expect(matrixRuntimeWriteConfigFileMock).toHaveBeenCalledWith(
|
|
388
|
+
expect.objectContaining({
|
|
389
|
+
channels: {
|
|
390
|
+
matrix: {
|
|
391
|
+
accounts: {
|
|
392
|
+
ops: expect.objectContaining({
|
|
393
|
+
homeserver: "https://matrix.example.org",
|
|
394
|
+
}),
|
|
395
|
+
},
|
|
396
|
+
},
|
|
397
|
+
},
|
|
398
|
+
}),
|
|
399
|
+
);
|
|
400
|
+
expect(console.log).toHaveBeenCalledWith("Saved matrix account: ops");
|
|
401
|
+
expect(console.log).toHaveBeenCalledWith(
|
|
402
|
+
"Bind this account to an agent: openclaw agents bind --agent <id> --bind matrix:ops",
|
|
403
|
+
);
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
it("bootstraps verification for newly added encrypted accounts", async () => {
|
|
407
|
+
resolveMatrixAccountConfigMock.mockReturnValue({
|
|
408
|
+
encryption: true,
|
|
409
|
+
});
|
|
410
|
+
listMatrixOwnDevicesMock.mockResolvedValue([
|
|
411
|
+
{
|
|
412
|
+
deviceId: "BritdXC6iL",
|
|
413
|
+
displayName: "OpenClaw Gateway",
|
|
414
|
+
lastSeenIp: null,
|
|
415
|
+
lastSeenTs: null,
|
|
416
|
+
current: false,
|
|
417
|
+
},
|
|
418
|
+
{
|
|
419
|
+
deviceId: "du314Zpw3A",
|
|
420
|
+
displayName: "OpenClaw Gateway",
|
|
421
|
+
lastSeenIp: null,
|
|
422
|
+
lastSeenTs: null,
|
|
423
|
+
current: true,
|
|
424
|
+
},
|
|
425
|
+
]);
|
|
426
|
+
bootstrapMatrixVerificationMock.mockResolvedValue({
|
|
427
|
+
success: true,
|
|
428
|
+
verification: {
|
|
429
|
+
recoveryKeyCreatedAt: "2026-03-09T06:00:00.000Z",
|
|
430
|
+
backupVersion: "7",
|
|
431
|
+
},
|
|
432
|
+
crossSigning: {},
|
|
433
|
+
pendingVerifications: 0,
|
|
434
|
+
cryptoBootstrap: {},
|
|
435
|
+
});
|
|
436
|
+
const program = buildProgram();
|
|
437
|
+
|
|
438
|
+
await program.parseAsync(
|
|
439
|
+
[
|
|
440
|
+
"matrix",
|
|
441
|
+
"account",
|
|
442
|
+
"add",
|
|
443
|
+
"--account",
|
|
444
|
+
"ops",
|
|
445
|
+
"--homeserver",
|
|
446
|
+
"https://matrix.example.org",
|
|
447
|
+
"--user-id",
|
|
448
|
+
"@ops:example.org",
|
|
449
|
+
"--password",
|
|
450
|
+
"secret",
|
|
451
|
+
],
|
|
452
|
+
{ from: "user" },
|
|
453
|
+
);
|
|
454
|
+
|
|
455
|
+
expect(bootstrapMatrixVerificationMock).toHaveBeenCalledWith({ accountId: "ops" });
|
|
456
|
+
expect(console.log).toHaveBeenCalledWith("Matrix verification bootstrap: complete");
|
|
457
|
+
expect(console.log).toHaveBeenCalledWith(
|
|
458
|
+
`Recovery key created at: ${formatExpectedLocalTimestamp("2026-03-09T06:00:00.000Z")}`,
|
|
459
|
+
);
|
|
460
|
+
expect(console.log).toHaveBeenCalledWith("Backup version: 7");
|
|
461
|
+
expect(console.log).toHaveBeenCalledWith(
|
|
462
|
+
"Matrix device hygiene warning: stale OpenClaw devices detected (BritdXC6iL). Run 'openclaw matrix devices prune-stale --account ops'.",
|
|
463
|
+
);
|
|
464
|
+
});
|
|
465
|
+
|
|
466
|
+
it("does not bootstrap verification when updating an already configured account", async () => {
|
|
467
|
+
matrixRuntimeLoadConfigMock.mockReturnValue({
|
|
468
|
+
channels: {
|
|
469
|
+
matrix: {
|
|
470
|
+
accounts: {
|
|
471
|
+
ops: {
|
|
472
|
+
enabled: true,
|
|
473
|
+
homeserver: "https://matrix.example.org",
|
|
474
|
+
},
|
|
475
|
+
},
|
|
476
|
+
},
|
|
477
|
+
},
|
|
478
|
+
});
|
|
479
|
+
resolveMatrixAccountConfigMock.mockReturnValue({
|
|
480
|
+
encryption: true,
|
|
481
|
+
});
|
|
482
|
+
const program = buildProgram();
|
|
483
|
+
|
|
484
|
+
await program.parseAsync(
|
|
485
|
+
[
|
|
486
|
+
"matrix",
|
|
487
|
+
"account",
|
|
488
|
+
"add",
|
|
489
|
+
"--account",
|
|
490
|
+
"ops",
|
|
491
|
+
"--homeserver",
|
|
492
|
+
"https://matrix.example.org",
|
|
493
|
+
"--user-id",
|
|
494
|
+
"@ops:example.org",
|
|
495
|
+
"--password",
|
|
496
|
+
"secret",
|
|
497
|
+
],
|
|
498
|
+
{ from: "user" },
|
|
499
|
+
);
|
|
500
|
+
|
|
501
|
+
expect(bootstrapMatrixVerificationMock).not.toHaveBeenCalled();
|
|
502
|
+
});
|
|
503
|
+
|
|
504
|
+
it("warns instead of failing when device-health probing fails after saving the account", async () => {
|
|
505
|
+
listMatrixOwnDevicesMock.mockRejectedValue(new Error("homeserver unavailable"));
|
|
506
|
+
const program = buildProgram();
|
|
507
|
+
|
|
508
|
+
await program.parseAsync(
|
|
509
|
+
[
|
|
510
|
+
"matrix",
|
|
511
|
+
"account",
|
|
512
|
+
"add",
|
|
513
|
+
"--account",
|
|
514
|
+
"ops",
|
|
515
|
+
"--homeserver",
|
|
516
|
+
"https://matrix.example.org",
|
|
517
|
+
"--user-id",
|
|
518
|
+
"@ops:example.org",
|
|
519
|
+
"--password",
|
|
520
|
+
"secret",
|
|
521
|
+
],
|
|
522
|
+
{ from: "user" },
|
|
523
|
+
);
|
|
524
|
+
|
|
525
|
+
expect(matrixRuntimeWriteConfigFileMock).toHaveBeenCalled();
|
|
526
|
+
expect(process.exitCode).toBeUndefined();
|
|
527
|
+
expect(console.log).toHaveBeenCalledWith("Saved matrix account: ops");
|
|
528
|
+
expect(console.error).toHaveBeenCalledWith(
|
|
529
|
+
"Matrix device health warning: homeserver unavailable",
|
|
530
|
+
);
|
|
531
|
+
});
|
|
532
|
+
|
|
533
|
+
it("returns device-health warnings in JSON mode without failing the account add command", async () => {
|
|
534
|
+
listMatrixOwnDevicesMock.mockRejectedValue(new Error("homeserver unavailable"));
|
|
535
|
+
const program = buildProgram();
|
|
536
|
+
|
|
537
|
+
await program.parseAsync(
|
|
538
|
+
[
|
|
539
|
+
"matrix",
|
|
540
|
+
"account",
|
|
541
|
+
"add",
|
|
542
|
+
"--account",
|
|
543
|
+
"ops",
|
|
544
|
+
"--homeserver",
|
|
545
|
+
"https://matrix.example.org",
|
|
546
|
+
"--user-id",
|
|
547
|
+
"@ops:example.org",
|
|
548
|
+
"--password",
|
|
549
|
+
"secret",
|
|
550
|
+
"--json",
|
|
551
|
+
],
|
|
552
|
+
{ from: "user" },
|
|
553
|
+
);
|
|
554
|
+
|
|
555
|
+
expect(matrixRuntimeWriteConfigFileMock).toHaveBeenCalled();
|
|
556
|
+
expect(process.exitCode).toBeUndefined();
|
|
557
|
+
const jsonOutput = consoleLogMock.mock.calls.at(-1)?.[0];
|
|
558
|
+
expect(typeof jsonOutput).toBe("string");
|
|
559
|
+
expect(JSON.parse(String(jsonOutput))).toEqual(
|
|
560
|
+
expect.objectContaining({
|
|
561
|
+
accountId: "ops",
|
|
562
|
+
deviceHealth: expect.objectContaining({
|
|
563
|
+
currentDeviceId: null,
|
|
564
|
+
staleOpenClawDeviceIds: [],
|
|
565
|
+
error: "homeserver unavailable",
|
|
566
|
+
}),
|
|
567
|
+
}),
|
|
568
|
+
);
|
|
569
|
+
});
|
|
570
|
+
|
|
571
|
+
it("uses --name as fallback account id and prints account-scoped config path", async () => {
|
|
572
|
+
matrixRuntimeLoadConfigMock.mockReturnValue({ channels: {} });
|
|
573
|
+
const program = buildProgram();
|
|
574
|
+
|
|
575
|
+
await program.parseAsync(
|
|
576
|
+
[
|
|
577
|
+
"matrix",
|
|
578
|
+
"account",
|
|
579
|
+
"add",
|
|
580
|
+
"--name",
|
|
581
|
+
"Main Bot",
|
|
582
|
+
"--homeserver",
|
|
583
|
+
"https://matrix.example.org",
|
|
584
|
+
"--user-id",
|
|
585
|
+
"@main:example.org",
|
|
586
|
+
"--password",
|
|
587
|
+
"secret",
|
|
588
|
+
],
|
|
589
|
+
{ from: "user" },
|
|
590
|
+
);
|
|
591
|
+
|
|
592
|
+
expect(matrixSetupValidateInputMock).toHaveBeenCalledWith(
|
|
593
|
+
expect.objectContaining({
|
|
594
|
+
accountId: "main-bot",
|
|
595
|
+
}),
|
|
596
|
+
);
|
|
597
|
+
expect(console.log).toHaveBeenCalledWith("Saved matrix account: main-bot");
|
|
598
|
+
expect(console.log).toHaveBeenCalledWith("Config path: channels.lobi.accounts.main-bot");
|
|
599
|
+
expect(updateMatrixOwnProfileMock).toHaveBeenCalledWith(
|
|
600
|
+
expect.objectContaining({
|
|
601
|
+
accountId: "main-bot",
|
|
602
|
+
displayName: "Main Bot",
|
|
603
|
+
}),
|
|
604
|
+
);
|
|
605
|
+
expect(console.log).toHaveBeenCalledWith(
|
|
606
|
+
"Bind this account to an agent: openclaw agents bind --agent <id> --bind matrix:main-bot",
|
|
607
|
+
);
|
|
608
|
+
});
|
|
609
|
+
|
|
610
|
+
it("forwards --avatar-url through account add setup and profile sync", async () => {
|
|
611
|
+
matrixRuntimeLoadConfigMock.mockReturnValue({ channels: {} });
|
|
612
|
+
const program = buildProgram();
|
|
613
|
+
|
|
614
|
+
await program.parseAsync(
|
|
615
|
+
[
|
|
616
|
+
"matrix",
|
|
617
|
+
"account",
|
|
618
|
+
"add",
|
|
619
|
+
"--name",
|
|
620
|
+
"Ops Bot",
|
|
621
|
+
"--homeserver",
|
|
622
|
+
"https://matrix.example.org",
|
|
623
|
+
"--access-token",
|
|
624
|
+
"ops-token",
|
|
625
|
+
"--avatar-url",
|
|
626
|
+
"mxc://example/ops-avatar",
|
|
627
|
+
],
|
|
628
|
+
{ from: "user" },
|
|
629
|
+
);
|
|
630
|
+
|
|
631
|
+
expect(matrixSetupApplyAccountConfigMock).toHaveBeenCalledWith(
|
|
632
|
+
expect.objectContaining({
|
|
633
|
+
accountId: "ops-bot",
|
|
634
|
+
input: expect.objectContaining({
|
|
635
|
+
name: "Ops Bot",
|
|
636
|
+
homeserver: "https://matrix.example.org",
|
|
637
|
+
accessToken: "ops-token",
|
|
638
|
+
avatarUrl: "mxc://example/ops-avatar",
|
|
639
|
+
}),
|
|
640
|
+
}),
|
|
641
|
+
);
|
|
642
|
+
expect(updateMatrixOwnProfileMock).toHaveBeenCalledWith(
|
|
643
|
+
expect.objectContaining({
|
|
644
|
+
accountId: "ops-bot",
|
|
645
|
+
displayName: "Ops Bot",
|
|
646
|
+
avatarUrl: "mxc://example/ops-avatar",
|
|
647
|
+
}),
|
|
648
|
+
);
|
|
649
|
+
expect(console.log).toHaveBeenCalledWith("Saved matrix account: ops-bot");
|
|
650
|
+
expect(console.log).toHaveBeenCalledWith("Config path: channels.lobi.accounts.ops-bot");
|
|
651
|
+
});
|
|
652
|
+
|
|
653
|
+
it("sets profile name and avatar via profile set command", async () => {
|
|
654
|
+
const program = buildProgram();
|
|
655
|
+
|
|
656
|
+
await program.parseAsync(
|
|
657
|
+
[
|
|
658
|
+
"matrix",
|
|
659
|
+
"profile",
|
|
660
|
+
"set",
|
|
661
|
+
"--account",
|
|
662
|
+
"alerts",
|
|
663
|
+
"--name",
|
|
664
|
+
"Alerts Bot",
|
|
665
|
+
"--avatar-url",
|
|
666
|
+
"mxc://example/avatar",
|
|
667
|
+
],
|
|
668
|
+
{ from: "user" },
|
|
669
|
+
);
|
|
670
|
+
|
|
671
|
+
expect(updateMatrixOwnProfileMock).toHaveBeenCalledWith(
|
|
672
|
+
expect.objectContaining({
|
|
673
|
+
accountId: "alerts",
|
|
674
|
+
displayName: "Alerts Bot",
|
|
675
|
+
avatarUrl: "mxc://example/avatar",
|
|
676
|
+
}),
|
|
677
|
+
);
|
|
678
|
+
expect(matrixRuntimeWriteConfigFileMock).toHaveBeenCalled();
|
|
679
|
+
expect(console.log).toHaveBeenCalledWith("Account: alerts");
|
|
680
|
+
expect(console.log).toHaveBeenCalledWith("Config path: channels.lobi.accounts.alerts");
|
|
681
|
+
});
|
|
682
|
+
|
|
683
|
+
it("returns JSON errors for invalid account setup input", async () => {
|
|
684
|
+
matrixSetupValidateInputMock.mockReturnValue("Matrix requires --homeserver");
|
|
685
|
+
const program = buildProgram();
|
|
686
|
+
|
|
687
|
+
await program.parseAsync(["matrix", "account", "add", "--json"], {
|
|
688
|
+
from: "user",
|
|
689
|
+
});
|
|
690
|
+
|
|
691
|
+
expect(process.exitCode).toBe(1);
|
|
692
|
+
expect(console.log).toHaveBeenCalledWith(
|
|
693
|
+
expect.stringContaining('"error": "Matrix requires --homeserver"'),
|
|
694
|
+
);
|
|
695
|
+
});
|
|
696
|
+
|
|
697
|
+
it("keeps zero exit code for successful bootstrap in JSON mode", async () => {
|
|
698
|
+
process.exitCode = 0;
|
|
699
|
+
bootstrapMatrixVerificationMock.mockResolvedValue({
|
|
700
|
+
success: true,
|
|
701
|
+
verification: {},
|
|
702
|
+
crossSigning: {},
|
|
703
|
+
pendingVerifications: 0,
|
|
704
|
+
cryptoBootstrap: {},
|
|
705
|
+
});
|
|
706
|
+
const program = buildProgram();
|
|
707
|
+
|
|
708
|
+
await program.parseAsync(["matrix", "verify", "bootstrap", "--json"], { from: "user" });
|
|
709
|
+
|
|
710
|
+
expect(process.exitCode).toBe(0);
|
|
711
|
+
});
|
|
712
|
+
|
|
713
|
+
it("prints local timezone timestamps for verify status output in verbose mode", async () => {
|
|
714
|
+
const recoveryCreatedAt = "2026-02-25T20:10:11.000Z";
|
|
715
|
+
mockMatrixVerificationStatus({ recoveryKeyCreatedAt: recoveryCreatedAt });
|
|
716
|
+
const program = buildProgram();
|
|
717
|
+
|
|
718
|
+
await program.parseAsync(["matrix", "verify", "status", "--verbose"], { from: "user" });
|
|
719
|
+
|
|
720
|
+
expect(console.log).toHaveBeenCalledWith(
|
|
721
|
+
`Recovery key created at: ${formatExpectedLocalTimestamp(recoveryCreatedAt)}`,
|
|
722
|
+
);
|
|
723
|
+
expect(console.log).toHaveBeenCalledWith("Diagnostics:");
|
|
724
|
+
expect(console.log).toHaveBeenCalledWith("Locally trusted: yes");
|
|
725
|
+
expect(console.log).toHaveBeenCalledWith("Signed by owner: yes");
|
|
726
|
+
expect(setMatrixSdkLogModeMock).toHaveBeenCalledWith("default");
|
|
727
|
+
});
|
|
728
|
+
|
|
729
|
+
it("prints local timezone timestamps for verify bootstrap and device output in verbose mode", async () => {
|
|
730
|
+
const recoveryCreatedAt = "2026-02-25T20:10:11.000Z";
|
|
731
|
+
const verifiedAt = "2026-02-25T20:14:00.000Z";
|
|
732
|
+
bootstrapMatrixVerificationMock.mockResolvedValue({
|
|
733
|
+
success: true,
|
|
734
|
+
verification: {
|
|
735
|
+
encryptionEnabled: true,
|
|
736
|
+
verified: true,
|
|
737
|
+
userId: "@bot:example.org",
|
|
738
|
+
deviceId: "DEVICE123",
|
|
739
|
+
backupVersion: "1",
|
|
740
|
+
backup: {
|
|
741
|
+
serverVersion: "1",
|
|
742
|
+
activeVersion: "1",
|
|
743
|
+
trusted: true,
|
|
744
|
+
matchesDecryptionKey: true,
|
|
745
|
+
decryptionKeyCached: true,
|
|
746
|
+
},
|
|
747
|
+
recoveryKeyStored: true,
|
|
748
|
+
recoveryKeyId: "SSSS",
|
|
749
|
+
recoveryKeyCreatedAt: recoveryCreatedAt,
|
|
750
|
+
localVerified: true,
|
|
751
|
+
crossSigningVerified: true,
|
|
752
|
+
signedByOwner: true,
|
|
753
|
+
},
|
|
754
|
+
crossSigning: {
|
|
755
|
+
published: true,
|
|
756
|
+
masterKeyPublished: true,
|
|
757
|
+
selfSigningKeyPublished: true,
|
|
758
|
+
userSigningKeyPublished: true,
|
|
759
|
+
},
|
|
760
|
+
pendingVerifications: 0,
|
|
761
|
+
cryptoBootstrap: {},
|
|
762
|
+
});
|
|
763
|
+
verifyMatrixRecoveryKeyMock.mockResolvedValue({
|
|
764
|
+
success: true,
|
|
765
|
+
encryptionEnabled: true,
|
|
766
|
+
userId: "@bot:example.org",
|
|
767
|
+
deviceId: "DEVICE123",
|
|
768
|
+
backupVersion: "1",
|
|
769
|
+
backup: {
|
|
770
|
+
serverVersion: "1",
|
|
771
|
+
activeVersion: "1",
|
|
772
|
+
trusted: true,
|
|
773
|
+
matchesDecryptionKey: true,
|
|
774
|
+
decryptionKeyCached: true,
|
|
775
|
+
},
|
|
776
|
+
verified: true,
|
|
777
|
+
localVerified: true,
|
|
778
|
+
crossSigningVerified: true,
|
|
779
|
+
signedByOwner: true,
|
|
780
|
+
recoveryKeyStored: true,
|
|
781
|
+
recoveryKeyId: "SSSS",
|
|
782
|
+
recoveryKeyCreatedAt: recoveryCreatedAt,
|
|
783
|
+
verifiedAt,
|
|
784
|
+
});
|
|
785
|
+
const program = buildProgram();
|
|
786
|
+
|
|
787
|
+
await program.parseAsync(["matrix", "verify", "bootstrap", "--verbose"], {
|
|
788
|
+
from: "user",
|
|
789
|
+
});
|
|
790
|
+
await program.parseAsync(["matrix", "verify", "device", "valid-key", "--verbose"], {
|
|
791
|
+
from: "user",
|
|
792
|
+
});
|
|
793
|
+
|
|
794
|
+
expect(console.log).toHaveBeenCalledWith(
|
|
795
|
+
`Recovery key created at: ${formatExpectedLocalTimestamp(recoveryCreatedAt)}`,
|
|
796
|
+
);
|
|
797
|
+
expect(console.log).toHaveBeenCalledWith(
|
|
798
|
+
`Verified at: ${formatExpectedLocalTimestamp(verifiedAt)}`,
|
|
799
|
+
);
|
|
800
|
+
});
|
|
801
|
+
|
|
802
|
+
it("keeps default output concise when verbose is not provided", async () => {
|
|
803
|
+
const recoveryCreatedAt = "2026-02-25T20:10:11.000Z";
|
|
804
|
+
mockMatrixVerificationStatus({ recoveryKeyCreatedAt: recoveryCreatedAt });
|
|
805
|
+
const program = buildProgram();
|
|
806
|
+
|
|
807
|
+
await program.parseAsync(["matrix", "verify", "status"], { from: "user" });
|
|
808
|
+
|
|
809
|
+
expect(console.log).not.toHaveBeenCalledWith(
|
|
810
|
+
`Recovery key created at: ${formatExpectedLocalTimestamp(recoveryCreatedAt)}`,
|
|
811
|
+
);
|
|
812
|
+
expect(console.log).not.toHaveBeenCalledWith("Pending verifications: 0");
|
|
813
|
+
expect(console.log).not.toHaveBeenCalledWith("Diagnostics:");
|
|
814
|
+
expect(console.log).toHaveBeenCalledWith("Backup: active and trusted on this device");
|
|
815
|
+
expect(setMatrixSdkLogModeMock).toHaveBeenCalledWith("quiet");
|
|
816
|
+
});
|
|
817
|
+
|
|
818
|
+
it("shows explicit backup issue in default status output", async () => {
|
|
819
|
+
getMatrixVerificationStatusMock.mockResolvedValue({
|
|
820
|
+
encryptionEnabled: true,
|
|
821
|
+
verified: true,
|
|
822
|
+
localVerified: true,
|
|
823
|
+
crossSigningVerified: true,
|
|
824
|
+
signedByOwner: true,
|
|
825
|
+
userId: "@bot:example.org",
|
|
826
|
+
deviceId: "DEVICE123",
|
|
827
|
+
backupVersion: "5256",
|
|
828
|
+
backup: {
|
|
829
|
+
serverVersion: "5256",
|
|
830
|
+
activeVersion: null,
|
|
831
|
+
trusted: true,
|
|
832
|
+
matchesDecryptionKey: false,
|
|
833
|
+
decryptionKeyCached: false,
|
|
834
|
+
keyLoadAttempted: true,
|
|
835
|
+
keyLoadError: null,
|
|
836
|
+
},
|
|
837
|
+
recoveryKeyStored: true,
|
|
838
|
+
recoveryKeyCreatedAt: "2026-02-25T20:10:11.000Z",
|
|
839
|
+
pendingVerifications: 0,
|
|
840
|
+
});
|
|
841
|
+
const program = buildProgram();
|
|
842
|
+
|
|
843
|
+
await program.parseAsync(["matrix", "verify", "status"], { from: "user" });
|
|
844
|
+
|
|
845
|
+
expect(console.log).toHaveBeenCalledWith(
|
|
846
|
+
"Backup issue: backup decryption key is not loaded on this device (secret storage did not return a key)",
|
|
847
|
+
);
|
|
848
|
+
expect(console.log).toHaveBeenCalledWith(
|
|
849
|
+
"- Backup key is not loaded on this device. Run 'openclaw matrix verify backup restore' to load it and restore old room keys.",
|
|
850
|
+
);
|
|
851
|
+
expect(console.log).not.toHaveBeenCalledWith(
|
|
852
|
+
"- Backup is present but not trusted for this device. Re-run 'openclaw matrix verify device <key>'.",
|
|
853
|
+
);
|
|
854
|
+
});
|
|
855
|
+
|
|
856
|
+
it("includes key load failure details in status output", async () => {
|
|
857
|
+
getMatrixVerificationStatusMock.mockResolvedValue({
|
|
858
|
+
encryptionEnabled: true,
|
|
859
|
+
verified: true,
|
|
860
|
+
localVerified: true,
|
|
861
|
+
crossSigningVerified: true,
|
|
862
|
+
signedByOwner: true,
|
|
863
|
+
userId: "@bot:example.org",
|
|
864
|
+
deviceId: "DEVICE123",
|
|
865
|
+
backupVersion: "5256",
|
|
866
|
+
backup: {
|
|
867
|
+
serverVersion: "5256",
|
|
868
|
+
activeVersion: null,
|
|
869
|
+
trusted: true,
|
|
870
|
+
matchesDecryptionKey: false,
|
|
871
|
+
decryptionKeyCached: false,
|
|
872
|
+
keyLoadAttempted: true,
|
|
873
|
+
keyLoadError: "secret storage key is not available",
|
|
874
|
+
},
|
|
875
|
+
recoveryKeyStored: true,
|
|
876
|
+
recoveryKeyCreatedAt: "2026-02-25T20:10:11.000Z",
|
|
877
|
+
pendingVerifications: 0,
|
|
878
|
+
});
|
|
879
|
+
const program = buildProgram();
|
|
880
|
+
|
|
881
|
+
await program.parseAsync(["matrix", "verify", "status"], { from: "user" });
|
|
882
|
+
|
|
883
|
+
expect(console.log).toHaveBeenCalledWith(
|
|
884
|
+
"Backup issue: backup decryption key could not be loaded from secret storage (secret storage key is not available)",
|
|
885
|
+
);
|
|
886
|
+
});
|
|
887
|
+
|
|
888
|
+
it("includes backup reset guidance when the backup key does not match this device", async () => {
|
|
889
|
+
getMatrixVerificationStatusMock.mockResolvedValue({
|
|
890
|
+
encryptionEnabled: true,
|
|
891
|
+
verified: true,
|
|
892
|
+
localVerified: true,
|
|
893
|
+
crossSigningVerified: true,
|
|
894
|
+
signedByOwner: true,
|
|
895
|
+
userId: "@bot:example.org",
|
|
896
|
+
deviceId: "DEVICE123",
|
|
897
|
+
backupVersion: "21868",
|
|
898
|
+
backup: {
|
|
899
|
+
serverVersion: "21868",
|
|
900
|
+
activeVersion: "21868",
|
|
901
|
+
trusted: true,
|
|
902
|
+
matchesDecryptionKey: false,
|
|
903
|
+
decryptionKeyCached: true,
|
|
904
|
+
keyLoadAttempted: false,
|
|
905
|
+
keyLoadError: null,
|
|
906
|
+
},
|
|
907
|
+
recoveryKeyStored: true,
|
|
908
|
+
recoveryKeyCreatedAt: "2026-03-09T14:40:00.000Z",
|
|
909
|
+
pendingVerifications: 0,
|
|
910
|
+
});
|
|
911
|
+
const program = buildProgram();
|
|
912
|
+
|
|
913
|
+
await program.parseAsync(["matrix", "verify", "status"], { from: "user" });
|
|
914
|
+
|
|
915
|
+
expect(console.log).toHaveBeenCalledWith(
|
|
916
|
+
"- If you want a fresh backup baseline and accept losing unrecoverable history, run 'openclaw matrix verify backup reset --yes'. This may also repair secret storage so the new backup key can be loaded after restart.",
|
|
917
|
+
);
|
|
918
|
+
});
|
|
919
|
+
|
|
920
|
+
it("requires --yes before resetting the Matrix room-key backup", async () => {
|
|
921
|
+
const program = buildProgram();
|
|
922
|
+
|
|
923
|
+
await program.parseAsync(["matrix", "verify", "backup", "reset"], { from: "user" });
|
|
924
|
+
|
|
925
|
+
expect(process.exitCode).toBe(1);
|
|
926
|
+
expect(resetMatrixRoomKeyBackupMock).not.toHaveBeenCalled();
|
|
927
|
+
expect(console.error).toHaveBeenCalledWith(
|
|
928
|
+
"Backup reset failed: Refusing to reset Matrix room-key backup without --yes",
|
|
929
|
+
);
|
|
930
|
+
});
|
|
931
|
+
|
|
932
|
+
it("resets the Matrix room-key backup when confirmed", async () => {
|
|
933
|
+
const program = buildProgram();
|
|
934
|
+
|
|
935
|
+
await program.parseAsync(["matrix", "verify", "backup", "reset", "--yes"], {
|
|
936
|
+
from: "user",
|
|
937
|
+
});
|
|
938
|
+
|
|
939
|
+
expect(resetMatrixRoomKeyBackupMock).toHaveBeenCalledWith({ accountId: "default" });
|
|
940
|
+
expect(console.log).toHaveBeenCalledWith("Reset success: yes");
|
|
941
|
+
expect(console.log).toHaveBeenCalledWith("Previous backup version: 1");
|
|
942
|
+
expect(console.log).toHaveBeenCalledWith("Deleted backup version: 1");
|
|
943
|
+
expect(console.log).toHaveBeenCalledWith("Current backup version: 2");
|
|
944
|
+
expect(console.log).toHaveBeenCalledWith("Backup: active and trusted on this device");
|
|
945
|
+
});
|
|
946
|
+
|
|
947
|
+
it("prints resolved account-aware guidance when a named Matrix account is selected implicitly", async () => {
|
|
948
|
+
resolveMatrixAuthContextMock.mockImplementation(
|
|
949
|
+
({ cfg, accountId }: { cfg: unknown; accountId?: string | null }) => ({
|
|
950
|
+
cfg,
|
|
951
|
+
env: process.env,
|
|
952
|
+
accountId: accountId ?? "assistant",
|
|
953
|
+
resolved: {},
|
|
954
|
+
}),
|
|
955
|
+
);
|
|
956
|
+
getMatrixVerificationStatusMock.mockResolvedValue({
|
|
957
|
+
encryptionEnabled: true,
|
|
958
|
+
verified: false,
|
|
959
|
+
localVerified: false,
|
|
960
|
+
crossSigningVerified: false,
|
|
961
|
+
signedByOwner: false,
|
|
962
|
+
userId: "@bot:example.org",
|
|
963
|
+
deviceId: "DEVICE123",
|
|
964
|
+
backupVersion: null,
|
|
965
|
+
backup: {
|
|
966
|
+
serverVersion: null,
|
|
967
|
+
activeVersion: null,
|
|
968
|
+
trusted: null,
|
|
969
|
+
matchesDecryptionKey: null,
|
|
970
|
+
decryptionKeyCached: null,
|
|
971
|
+
keyLoadAttempted: false,
|
|
972
|
+
keyLoadError: null,
|
|
973
|
+
},
|
|
974
|
+
recoveryKeyStored: false,
|
|
975
|
+
recoveryKeyCreatedAt: null,
|
|
976
|
+
pendingVerifications: 0,
|
|
977
|
+
});
|
|
978
|
+
const program = buildProgram();
|
|
979
|
+
|
|
980
|
+
await program.parseAsync(["matrix", "verify", "status"], { from: "user" });
|
|
981
|
+
|
|
982
|
+
expect(getMatrixVerificationStatusMock).toHaveBeenCalledWith({
|
|
983
|
+
accountId: "assistant",
|
|
984
|
+
includeRecoveryKey: false,
|
|
985
|
+
});
|
|
986
|
+
expect(console.log).toHaveBeenCalledWith("Account: assistant");
|
|
987
|
+
expect(console.log).toHaveBeenCalledWith(
|
|
988
|
+
"- Run 'openclaw matrix verify device <key> --account assistant' to verify this device.",
|
|
989
|
+
);
|
|
990
|
+
expect(console.log).toHaveBeenCalledWith(
|
|
991
|
+
"- Run 'openclaw matrix verify bootstrap --account assistant' to create a room key backup.",
|
|
992
|
+
);
|
|
993
|
+
});
|
|
994
|
+
|
|
995
|
+
it("prints backup health lines for verify backup status in verbose mode", async () => {
|
|
996
|
+
getMatrixRoomKeyBackupStatusMock.mockResolvedValue({
|
|
997
|
+
serverVersion: "2",
|
|
998
|
+
activeVersion: null,
|
|
999
|
+
trusted: true,
|
|
1000
|
+
matchesDecryptionKey: false,
|
|
1001
|
+
decryptionKeyCached: false,
|
|
1002
|
+
keyLoadAttempted: true,
|
|
1003
|
+
keyLoadError: null,
|
|
1004
|
+
});
|
|
1005
|
+
const program = buildProgram();
|
|
1006
|
+
|
|
1007
|
+
await program.parseAsync(["matrix", "verify", "backup", "status", "--verbose"], {
|
|
1008
|
+
from: "user",
|
|
1009
|
+
});
|
|
1010
|
+
|
|
1011
|
+
expect(console.log).toHaveBeenCalledWith("Backup server version: 2");
|
|
1012
|
+
expect(console.log).toHaveBeenCalledWith("Backup active on this device: no");
|
|
1013
|
+
expect(console.log).toHaveBeenCalledWith("Backup trusted by this device: yes");
|
|
1014
|
+
});
|
|
1015
|
+
});
|