badgerclaw 0.1.7 → 1.4.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.

Potentially problematic release.


This version of badgerclaw might be problematic. Click here for more details.

Files changed (105) hide show
  1. package/CHANGELOG.md +104 -0
  2. package/SETUP.md +291 -0
  3. package/index.ts +47 -0
  4. package/openclaw.plugin.json +1 -0
  5. package/package.json +32 -34
  6. package/scripts/postinstall.js +34 -0
  7. package/src/actions.ts +195 -0
  8. package/src/channel.ts +461 -0
  9. package/src/config-schema.ts +62 -0
  10. package/src/connect.ts +17 -0
  11. package/src/directory-live.ts +209 -0
  12. package/src/group-mentions.ts +103 -0
  13. package/src/matrix/accounts.ts +114 -0
  14. package/src/matrix/actions/client.ts +47 -0
  15. package/src/matrix/actions/limits.ts +6 -0
  16. package/src/matrix/actions/messages.ts +126 -0
  17. package/src/matrix/actions/pins.ts +84 -0
  18. package/src/matrix/actions/reactions.ts +102 -0
  19. package/src/matrix/actions/room.ts +85 -0
  20. package/src/matrix/actions/summary.ts +75 -0
  21. package/src/matrix/actions/types.ts +85 -0
  22. package/src/matrix/actions.ts +15 -0
  23. package/src/matrix/active-client.ts +32 -0
  24. package/src/matrix/client/backup.ts +91 -0
  25. package/src/matrix/client/config.ts +274 -0
  26. package/src/matrix/client/create-client.ts +125 -0
  27. package/src/matrix/client/logging.ts +46 -0
  28. package/src/matrix/client/runtime.ts +4 -0
  29. package/src/matrix/client/shared.ts +223 -0
  30. package/src/matrix/client/startup.ts +29 -0
  31. package/src/matrix/client/storage.ts +131 -0
  32. package/src/matrix/client/types.ts +34 -0
  33. package/src/matrix/client-bootstrap.ts +47 -0
  34. package/src/matrix/client.ts +14 -0
  35. package/src/matrix/credentials.ts +125 -0
  36. package/src/matrix/deps.ts +126 -0
  37. package/src/matrix/format.ts +22 -0
  38. package/src/matrix/index.ts +11 -0
  39. package/src/matrix/monitor/access-policy.ts +126 -0
  40. package/src/matrix/monitor/allowlist.ts +94 -0
  41. package/src/matrix/monitor/auto-join.ts +126 -0
  42. package/src/matrix/monitor/bot-commands.ts +431 -0
  43. package/src/matrix/monitor/chat-history.ts +75 -0
  44. package/src/matrix/monitor/direct.ts +152 -0
  45. package/src/matrix/monitor/events.ts +250 -0
  46. package/src/matrix/monitor/handler.ts +847 -0
  47. package/src/matrix/monitor/inbound-body.ts +28 -0
  48. package/src/matrix/monitor/index.ts +414 -0
  49. package/src/matrix/monitor/location.ts +100 -0
  50. package/src/matrix/monitor/media.ts +118 -0
  51. package/src/matrix/monitor/mentions.ts +62 -0
  52. package/src/matrix/monitor/replies.ts +124 -0
  53. package/src/matrix/monitor/room-info.ts +55 -0
  54. package/src/matrix/monitor/rooms.ts +47 -0
  55. package/src/matrix/monitor/threads.ts +68 -0
  56. package/src/matrix/monitor/types.ts +39 -0
  57. package/src/matrix/poll-types.ts +167 -0
  58. package/src/matrix/probe.ts +69 -0
  59. package/src/matrix/sdk-runtime.ts +18 -0
  60. package/src/matrix/send/client.ts +99 -0
  61. package/src/matrix/send/formatting.ts +93 -0
  62. package/src/matrix/send/media.ts +230 -0
  63. package/src/matrix/send/targets.ts +150 -0
  64. package/src/matrix/send/types.ts +110 -0
  65. package/src/matrix/send-queue.ts +28 -0
  66. package/src/matrix/send.ts +267 -0
  67. package/src/onboarding.ts +350 -0
  68. package/src/outbound.ts +58 -0
  69. package/src/resolve-targets.ts +125 -0
  70. package/src/runtime.ts +6 -0
  71. package/src/secret-input.ts +13 -0
  72. package/src/test-mocks.ts +53 -0
  73. package/src/tool-actions.ts +164 -0
  74. package/src/types.ts +121 -0
  75. package/README.md +0 -32
  76. package/dist/commands/autopair.d.ts +0 -3
  77. package/dist/commands/autopair.js +0 -102
  78. package/dist/commands/autopair.js.map +0 -1
  79. package/dist/commands/bot.d.ts +0 -2
  80. package/dist/commands/bot.js +0 -94
  81. package/dist/commands/bot.js.map +0 -1
  82. package/dist/commands/login.d.ts +0 -2
  83. package/dist/commands/login.js +0 -88
  84. package/dist/commands/login.js.map +0 -1
  85. package/dist/commands/logout.d.ts +0 -2
  86. package/dist/commands/logout.js +0 -36
  87. package/dist/commands/logout.js.map +0 -1
  88. package/dist/commands/status.d.ts +0 -2
  89. package/dist/commands/status.js +0 -23
  90. package/dist/commands/status.js.map +0 -1
  91. package/dist/commands/watch.d.ts +0 -2
  92. package/dist/commands/watch.js +0 -29
  93. package/dist/commands/watch.js.map +0 -1
  94. package/dist/index.d.ts +0 -2
  95. package/dist/index.js +0 -23
  96. package/dist/index.js.map +0 -1
  97. package/dist/lib/api.d.ts +0 -4
  98. package/dist/lib/api.js +0 -37
  99. package/dist/lib/api.js.map +0 -1
  100. package/dist/lib/auth.d.ts +0 -11
  101. package/dist/lib/auth.js +0 -48
  102. package/dist/lib/auth.js.map +0 -1
  103. package/dist/lib/pkce.d.ts +0 -2
  104. package/dist/lib/pkce.js +0 -15
  105. package/dist/lib/pkce.js.map +0 -1
@@ -0,0 +1,131 @@
1
+ import crypto from "node:crypto";
2
+ import fs from "node:fs";
3
+ import os from "node:os";
4
+ import path from "node:path";
5
+ import { getMatrixRuntime } from "../../runtime.js";
6
+ import type { MatrixStoragePaths } from "./types.js";
7
+
8
+ export const DEFAULT_ACCOUNT_KEY = "default";
9
+ const STORAGE_META_FILENAME = "storage-meta.json";
10
+
11
+ function sanitizePathSegment(value: string): string {
12
+ const cleaned = value
13
+ .trim()
14
+ .toLowerCase()
15
+ .replace(/[^a-z0-9._-]+/g, "_")
16
+ .replace(/^_+|_+$/g, "");
17
+ return cleaned || "unknown";
18
+ }
19
+
20
+ function resolveHomeserverKey(homeserver: string): string {
21
+ try {
22
+ const url = new URL(homeserver);
23
+ if (url.host) {
24
+ return sanitizePathSegment(url.host);
25
+ }
26
+ } catch {
27
+ // fall through
28
+ }
29
+ return sanitizePathSegment(homeserver);
30
+ }
31
+
32
+ function hashAccessToken(accessToken: string): string {
33
+ return crypto.createHash("sha256").update(accessToken).digest("hex").slice(0, 16);
34
+ }
35
+
36
+ function resolveLegacyStoragePaths(env: NodeJS.ProcessEnv = process.env): {
37
+ storagePath: string;
38
+ cryptoPath: string;
39
+ } {
40
+ const stateDir = getMatrixRuntime().state.resolveStateDir(env, os.homedir);
41
+ return {
42
+ storagePath: path.join(stateDir, "badgerclaw", "bot-storage.json"),
43
+ cryptoPath: path.join(stateDir, "badgerclaw", "crypto"),
44
+ };
45
+ }
46
+
47
+ export function resolveMatrixStoragePaths(params: {
48
+ homeserver: string;
49
+ userId: string;
50
+ accessToken: string;
51
+ accountId?: string | null;
52
+ env?: NodeJS.ProcessEnv;
53
+ }): MatrixStoragePaths {
54
+ const env = params.env ?? process.env;
55
+ const stateDir = getMatrixRuntime().state.resolveStateDir(env, os.homedir);
56
+ const accountKey = sanitizePathSegment(params.accountId ?? DEFAULT_ACCOUNT_KEY);
57
+ const userKey = sanitizePathSegment(params.userId);
58
+ const serverKey = resolveHomeserverKey(params.homeserver);
59
+ const tokenHash = hashAccessToken(params.accessToken);
60
+ const rootDir = path.join(
61
+ stateDir,
62
+ "badgerclaw",
63
+ "accounts",
64
+ accountKey,
65
+ `${serverKey}__${userKey}`,
66
+ tokenHash,
67
+ );
68
+ return {
69
+ rootDir,
70
+ storagePath: path.join(rootDir, "bot-storage.json"),
71
+ cryptoPath: path.join(rootDir, "crypto"),
72
+ metaPath: path.join(rootDir, STORAGE_META_FILENAME),
73
+ accountKey,
74
+ tokenHash,
75
+ };
76
+ }
77
+
78
+ export function maybeMigrateLegacyStorage(params: {
79
+ storagePaths: MatrixStoragePaths;
80
+ env?: NodeJS.ProcessEnv;
81
+ }): void {
82
+ const legacy = resolveLegacyStoragePaths(params.env);
83
+ const hasLegacyStorage = fs.existsSync(legacy.storagePath);
84
+ const hasLegacyCrypto = fs.existsSync(legacy.cryptoPath);
85
+ const hasNewStorage =
86
+ fs.existsSync(params.storagePaths.storagePath) || fs.existsSync(params.storagePaths.cryptoPath);
87
+
88
+ if (!hasLegacyStorage && !hasLegacyCrypto) {
89
+ return;
90
+ }
91
+ if (hasNewStorage) {
92
+ return;
93
+ }
94
+
95
+ fs.mkdirSync(params.storagePaths.rootDir, { recursive: true });
96
+ if (hasLegacyStorage) {
97
+ try {
98
+ fs.renameSync(legacy.storagePath, params.storagePaths.storagePath);
99
+ } catch {
100
+ // Ignore migration failures; new store will be created.
101
+ }
102
+ }
103
+ if (hasLegacyCrypto) {
104
+ try {
105
+ fs.renameSync(legacy.cryptoPath, params.storagePaths.cryptoPath);
106
+ } catch {
107
+ // Ignore migration failures; new store will be created.
108
+ }
109
+ }
110
+ }
111
+
112
+ export function writeStorageMeta(params: {
113
+ storagePaths: MatrixStoragePaths;
114
+ homeserver: string;
115
+ userId: string;
116
+ accountId?: string | null;
117
+ }): void {
118
+ try {
119
+ const payload = {
120
+ homeserver: params.homeserver,
121
+ userId: params.userId,
122
+ accountId: params.accountId ?? DEFAULT_ACCOUNT_KEY,
123
+ accessTokenHash: params.storagePaths.tokenHash,
124
+ createdAt: new Date().toISOString(),
125
+ };
126
+ fs.mkdirSync(params.storagePaths.rootDir, { recursive: true });
127
+ fs.writeFileSync(params.storagePaths.metaPath, JSON.stringify(payload, null, 2), "utf-8");
128
+ } catch {
129
+ // ignore meta write failures
130
+ }
131
+ }
@@ -0,0 +1,34 @@
1
+ export type MatrixResolvedConfig = {
2
+ homeserver: string;
3
+ userId: string;
4
+ accessToken?: string;
5
+ password?: string;
6
+ deviceName?: string;
7
+ initialSyncLimit?: number;
8
+ encryption?: boolean;
9
+ };
10
+
11
+ /**
12
+ * Authenticated Matrix configuration.
13
+ * Note: deviceId is NOT included here because it's implicit in the accessToken.
14
+ * The crypto storage assumes the device ID (and thus access token) does not change
15
+ * between restarts. If the access token becomes invalid or crypto storage is lost,
16
+ * both will need to be recreated together.
17
+ */
18
+ export type MatrixAuth = {
19
+ homeserver: string;
20
+ userId: string;
21
+ accessToken: string;
22
+ deviceName?: string;
23
+ initialSyncLimit?: number;
24
+ encryption?: boolean;
25
+ };
26
+
27
+ export type MatrixStoragePaths = {
28
+ rootDir: string;
29
+ storagePath: string;
30
+ cryptoPath: string;
31
+ metaPath: string;
32
+ accountKey: string;
33
+ tokenHash: string;
34
+ };
@@ -0,0 +1,47 @@
1
+ import { createMatrixClient } from "./client/create-client.js";
2
+ import { startMatrixClientWithGrace } from "./client/startup.js";
3
+ import { getMatrixLogService } from "./sdk-runtime.js";
4
+
5
+ type MatrixClientBootstrapAuth = {
6
+ homeserver: string;
7
+ userId: string;
8
+ accessToken: string;
9
+ encryption?: boolean;
10
+ };
11
+
12
+ type MatrixCryptoPrepare = {
13
+ prepare: (rooms?: string[]) => Promise<void>;
14
+ };
15
+
16
+ type MatrixBootstrapClient = Awaited<ReturnType<typeof createMatrixClient>>;
17
+
18
+ export async function createPreparedMatrixClient(opts: {
19
+ auth: MatrixClientBootstrapAuth;
20
+ timeoutMs?: number;
21
+ accountId?: string;
22
+ }): Promise<MatrixBootstrapClient> {
23
+ const client = await createMatrixClient({
24
+ homeserver: opts.auth.homeserver,
25
+ userId: opts.auth.userId,
26
+ accessToken: opts.auth.accessToken,
27
+ encryption: opts.auth.encryption,
28
+ localTimeoutMs: opts.timeoutMs,
29
+ accountId: opts.accountId,
30
+ });
31
+ if (opts.auth.encryption && client.crypto) {
32
+ try {
33
+ const joinedRooms = await client.getJoinedRooms();
34
+ await (client.crypto as MatrixCryptoPrepare).prepare(joinedRooms);
35
+ } catch {
36
+ // Ignore crypto prep failures for one-off requests.
37
+ }
38
+ }
39
+ await startMatrixClientWithGrace({
40
+ client,
41
+ onError: (err: unknown) => {
42
+ const LogService = getMatrixLogService();
43
+ LogService.error("MatrixClientBootstrap", "client.start() error:", err);
44
+ },
45
+ });
46
+ return client;
47
+ }
@@ -0,0 +1,14 @@
1
+ export type { MatrixAuth, MatrixResolvedConfig } from "./client/types.js";
2
+ export { isBunRuntime } from "./client/runtime.js";
3
+ export {
4
+ resolveMatrixConfig,
5
+ resolveMatrixConfigForAccount,
6
+ resolveMatrixAuth,
7
+ } from "./client/config.js";
8
+ export { createMatrixClient } from "./client/create-client.js";
9
+ export {
10
+ resolveSharedMatrixClient,
11
+ waitForMatrixSync,
12
+ stopSharedClient,
13
+ stopSharedClientForAccount,
14
+ } from "./client/shared.js";
@@ -0,0 +1,125 @@
1
+ import fs from "node:fs";
2
+ import os from "node:os";
3
+ import path from "node:path";
4
+ import { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "openclaw/plugin-sdk/account-id";
5
+ import { getMatrixRuntime } from "../runtime.js";
6
+
7
+ export type MatrixStoredCredentials = {
8
+ homeserver: string;
9
+ userId: string;
10
+ accessToken: string;
11
+ deviceId?: string;
12
+ createdAt: string;
13
+ lastUsedAt?: string;
14
+ };
15
+
16
+ function credentialsFilename(accountId?: string | null): string {
17
+ const normalized = normalizeAccountId(accountId);
18
+ if (normalized === DEFAULT_ACCOUNT_ID) {
19
+ return "credentials.json";
20
+ }
21
+ // normalizeAccountId produces lowercase [a-z0-9-] strings, already filesystem-safe.
22
+ // Different raw IDs that normalize to the same value are the same logical account.
23
+ return `credentials-${normalized}.json`;
24
+ }
25
+
26
+ export function resolveMatrixCredentialsDir(
27
+ env: NodeJS.ProcessEnv = process.env,
28
+ stateDir?: string,
29
+ ): string {
30
+ const resolvedStateDir = stateDir ?? getMatrixRuntime().state.resolveStateDir(env, os.homedir);
31
+ return path.join(resolvedStateDir, "credentials", "badgerclaw");
32
+ }
33
+
34
+ export function resolveMatrixCredentialsPath(
35
+ env: NodeJS.ProcessEnv = process.env,
36
+ accountId?: string | null,
37
+ ): string {
38
+ const dir = resolveMatrixCredentialsDir(env);
39
+ return path.join(dir, credentialsFilename(accountId));
40
+ }
41
+
42
+ export function loadMatrixCredentials(
43
+ env: NodeJS.ProcessEnv = process.env,
44
+ accountId?: string | null,
45
+ ): MatrixStoredCredentials | null {
46
+ const credPath = resolveMatrixCredentialsPath(env, accountId);
47
+ try {
48
+ if (!fs.existsSync(credPath)) {
49
+ return null;
50
+ }
51
+ const raw = fs.readFileSync(credPath, "utf-8");
52
+ const parsed = JSON.parse(raw) as Partial<MatrixStoredCredentials>;
53
+ if (
54
+ typeof parsed.homeserver !== "string" ||
55
+ typeof parsed.userId !== "string" ||
56
+ typeof parsed.accessToken !== "string"
57
+ ) {
58
+ return null;
59
+ }
60
+ return parsed as MatrixStoredCredentials;
61
+ } catch {
62
+ return null;
63
+ }
64
+ }
65
+
66
+ export function saveMatrixCredentials(
67
+ credentials: Omit<MatrixStoredCredentials, "createdAt" | "lastUsedAt">,
68
+ env: NodeJS.ProcessEnv = process.env,
69
+ accountId?: string | null,
70
+ ): void {
71
+ const dir = resolveMatrixCredentialsDir(env);
72
+ fs.mkdirSync(dir, { recursive: true });
73
+
74
+ const credPath = resolveMatrixCredentialsPath(env, accountId);
75
+
76
+ const existing = loadMatrixCredentials(env, accountId);
77
+ const now = new Date().toISOString();
78
+
79
+ const toSave: MatrixStoredCredentials = {
80
+ ...credentials,
81
+ createdAt: existing?.createdAt ?? now,
82
+ lastUsedAt: now,
83
+ };
84
+
85
+ fs.writeFileSync(credPath, JSON.stringify(toSave, null, 2), "utf-8");
86
+ }
87
+
88
+ export function touchMatrixCredentials(
89
+ env: NodeJS.ProcessEnv = process.env,
90
+ accountId?: string | null,
91
+ ): void {
92
+ const existing = loadMatrixCredentials(env, accountId);
93
+ if (!existing) {
94
+ return;
95
+ }
96
+
97
+ existing.lastUsedAt = new Date().toISOString();
98
+ const credPath = resolveMatrixCredentialsPath(env, accountId);
99
+ fs.writeFileSync(credPath, JSON.stringify(existing, null, 2), "utf-8");
100
+ }
101
+
102
+ export function clearMatrixCredentials(
103
+ env: NodeJS.ProcessEnv = process.env,
104
+ accountId?: string | null,
105
+ ): void {
106
+ const credPath = resolveMatrixCredentialsPath(env, accountId);
107
+ try {
108
+ if (fs.existsSync(credPath)) {
109
+ fs.unlinkSync(credPath);
110
+ }
111
+ } catch {
112
+ // ignore
113
+ }
114
+ }
115
+
116
+ export function credentialsMatchConfig(
117
+ stored: MatrixStoredCredentials,
118
+ config: { homeserver: string; userId: string },
119
+ ): boolean {
120
+ // If userId is empty (token-based auth), only match homeserver
121
+ if (!config.userId) {
122
+ return stored.homeserver === config.homeserver;
123
+ }
124
+ return stored.homeserver === config.homeserver && stored.userId === config.userId;
125
+ }
@@ -0,0 +1,126 @@
1
+ import fs from "node:fs";
2
+ import { createRequire } from "node:module";
3
+ import path from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ import { runPluginCommandWithTimeout, type RuntimeEnv } from "openclaw/plugin-sdk/matrix";
6
+
7
+ const MATRIX_SDK_PACKAGE = "@vector-im/matrix-bot-sdk";
8
+ const MATRIX_CRYPTO_DOWNLOAD_HELPER = "@matrix-org/matrix-sdk-crypto-nodejs/download-lib.js";
9
+
10
+ function formatCommandError(result: { stderr: string; stdout: string }): string {
11
+ const stderr = result.stderr.trim();
12
+ if (stderr) {
13
+ return stderr;
14
+ }
15
+ const stdout = result.stdout.trim();
16
+ if (stdout) {
17
+ return stdout;
18
+ }
19
+ return "unknown error";
20
+ }
21
+
22
+ function isMissingMatrixCryptoRuntimeError(err: unknown): boolean {
23
+ const message = err instanceof Error ? err.message : String(err ?? "");
24
+ return (
25
+ message.includes("Cannot find module") &&
26
+ message.includes("@matrix-org/matrix-sdk-crypto-nodejs-")
27
+ );
28
+ }
29
+
30
+ export function isMatrixSdkAvailable(): boolean {
31
+ try {
32
+ const req = createRequire(import.meta.url);
33
+ req.resolve(MATRIX_SDK_PACKAGE);
34
+ return true;
35
+ } catch {
36
+ return false;
37
+ }
38
+ }
39
+
40
+ function resolvePluginRoot(): string {
41
+ const currentDir = path.dirname(fileURLToPath(import.meta.url));
42
+ return path.resolve(currentDir, "..", "..");
43
+ }
44
+
45
+ export async function ensureMatrixCryptoRuntime(
46
+ params: {
47
+ log?: (message: string) => void;
48
+ requireFn?: (id: string) => unknown;
49
+ resolveFn?: (id: string) => string;
50
+ runCommand?: typeof runPluginCommandWithTimeout;
51
+ nodeExecutable?: string;
52
+ } = {},
53
+ ): Promise<void> {
54
+ const req = createRequire(import.meta.url);
55
+ const requireFn = params.requireFn ?? ((id: string) => req(id));
56
+ const resolveFn = params.resolveFn ?? ((id: string) => req.resolve(id));
57
+ const runCommand = params.runCommand ?? runPluginCommandWithTimeout;
58
+ const nodeExecutable = params.nodeExecutable ?? process.execPath;
59
+
60
+ try {
61
+ requireFn(MATRIX_SDK_PACKAGE);
62
+ return;
63
+ } catch (err) {
64
+ if (!isMissingMatrixCryptoRuntimeError(err)) {
65
+ throw err;
66
+ }
67
+ }
68
+
69
+ const scriptPath = resolveFn(MATRIX_CRYPTO_DOWNLOAD_HELPER);
70
+ params.log?.("badgerclaw: crypto runtime missing; downloading platform library…");
71
+ const result = await runCommand({
72
+ argv: [nodeExecutable, scriptPath],
73
+ cwd: path.dirname(scriptPath),
74
+ timeoutMs: 300_000,
75
+ env: { COREPACK_ENABLE_DOWNLOAD_PROMPT: "0" },
76
+ });
77
+ if (result.code !== 0) {
78
+ throw new Error(`BadgerClaw crypto runtime bootstrap failed: ${formatCommandError(result)}`);
79
+ }
80
+
81
+ try {
82
+ requireFn(MATRIX_SDK_PACKAGE);
83
+ } catch (err) {
84
+ throw new Error(
85
+ `BadgerClaw crypto runtime remains unavailable after bootstrap: ${err instanceof Error ? err.message : String(err)}`,
86
+ );
87
+ }
88
+ }
89
+
90
+ export async function ensureMatrixSdkInstalled(params: {
91
+ runtime: RuntimeEnv;
92
+ confirm?: (message: string) => Promise<boolean>;
93
+ }): Promise<void> {
94
+ if (isMatrixSdkAvailable()) {
95
+ return;
96
+ }
97
+ const confirm = params.confirm;
98
+ if (confirm) {
99
+ const ok = await confirm("BadgerClaw requires @vector-im/matrix-bot-sdk. Install now?");
100
+ if (!ok) {
101
+ throw new Error("BadgerClaw requires @vector-im/matrix-bot-sdk (install dependencies first).");
102
+ }
103
+ }
104
+
105
+ const root = resolvePluginRoot();
106
+ const command = fs.existsSync(path.join(root, "pnpm-lock.yaml"))
107
+ ? ["pnpm", "install"]
108
+ : ["npm", "install", "--omit=dev", "--silent"];
109
+ params.runtime.log?.(`badgerclaw: installing dependencies via ${command[0]} (${root})…`);
110
+ const result = await runPluginCommandWithTimeout({
111
+ argv: command,
112
+ cwd: root,
113
+ timeoutMs: 300_000,
114
+ env: { COREPACK_ENABLE_DOWNLOAD_PROMPT: "0" },
115
+ });
116
+ if (result.code !== 0) {
117
+ throw new Error(
118
+ result.stderr.trim() || result.stdout.trim() || "BadgerClaw dependency install failed.",
119
+ );
120
+ }
121
+ if (!isMatrixSdkAvailable()) {
122
+ throw new Error(
123
+ "BadgerClaw dependency install completed but @vector-im/matrix-bot-sdk is still missing.",
124
+ );
125
+ }
126
+ }
@@ -0,0 +1,22 @@
1
+ import MarkdownIt from "markdown-it";
2
+
3
+ const md = new MarkdownIt({
4
+ html: false,
5
+ linkify: true,
6
+ breaks: true,
7
+ typographer: false,
8
+ });
9
+
10
+ md.enable("strikethrough");
11
+
12
+ const { escapeHtml } = md.utils;
13
+
14
+ md.renderer.rules.image = (tokens, idx) => escapeHtml(tokens[idx]?.content ?? "");
15
+
16
+ md.renderer.rules.html_block = (tokens, idx) => escapeHtml(tokens[idx]?.content ?? "");
17
+ md.renderer.rules.html_inline = (tokens, idx) => escapeHtml(tokens[idx]?.content ?? "");
18
+
19
+ export function markdownToMatrixHtml(markdown: string): string {
20
+ const rendered = md.render(markdown ?? "");
21
+ return rendered.trimEnd();
22
+ }
@@ -0,0 +1,11 @@
1
+ export { monitorMatrixProvider } from "./monitor/index.js";
2
+ export { probeMatrix } from "./probe.js";
3
+ export {
4
+ reactMatrixMessage,
5
+ resolveMatrixRoomId,
6
+ sendReadReceiptMatrix,
7
+ sendMessageMatrix,
8
+ sendPollMatrix,
9
+ sendTypingMatrix,
10
+ } from "./send.js";
11
+ export { resolveMatrixAuth, resolveSharedMatrixClient } from "./client.js";
@@ -0,0 +1,126 @@
1
+ import {
2
+ formatAllowlistMatchMeta,
3
+ issuePairingChallenge,
4
+ readStoreAllowFromForDmPolicy,
5
+ resolveDmGroupAccessWithLists,
6
+ resolveSenderScopedGroupPolicy,
7
+ } from "openclaw/plugin-sdk/matrix";
8
+ import {
9
+ normalizeMatrixAllowList,
10
+ resolveMatrixAllowListMatch,
11
+ resolveMatrixAllowListMatches,
12
+ } from "./allowlist.js";
13
+
14
+ type MatrixDmPolicy = "open" | "pairing" | "allowlist" | "disabled";
15
+ type MatrixGroupPolicy = "open" | "allowlist" | "disabled";
16
+
17
+ export async function resolveMatrixAccessState(params: {
18
+ isDirectMessage: boolean;
19
+ resolvedAccountId: string;
20
+ dmPolicy: MatrixDmPolicy;
21
+ groupPolicy: MatrixGroupPolicy;
22
+ allowFrom: string[];
23
+ groupAllowFrom: Array<string | number>;
24
+ senderId: string;
25
+ readStoreForDmPolicy: (provider: string, accountId: string) => Promise<string[]>;
26
+ }) {
27
+ const storeAllowFrom = params.isDirectMessage
28
+ ? await readStoreAllowFromForDmPolicy({
29
+ provider: "badgerclaw",
30
+ accountId: params.resolvedAccountId,
31
+ dmPolicy: params.dmPolicy,
32
+ readStore: params.readStoreForDmPolicy,
33
+ })
34
+ : [];
35
+ const normalizedGroupAllowFrom = normalizeMatrixAllowList(params.groupAllowFrom);
36
+ const senderGroupPolicy = resolveSenderScopedGroupPolicy({
37
+ groupPolicy: params.groupPolicy,
38
+ groupAllowFrom: normalizedGroupAllowFrom,
39
+ });
40
+ const access = resolveDmGroupAccessWithLists({
41
+ isGroup: !params.isDirectMessage,
42
+ dmPolicy: params.dmPolicy,
43
+ groupPolicy: senderGroupPolicy,
44
+ allowFrom: params.allowFrom,
45
+ groupAllowFrom: normalizedGroupAllowFrom,
46
+ storeAllowFrom,
47
+ groupAllowFromFallbackToAllowFrom: false,
48
+ isSenderAllowed: (allowFrom) =>
49
+ resolveMatrixAllowListMatches({
50
+ allowList: normalizeMatrixAllowList(allowFrom),
51
+ userId: params.senderId,
52
+ }),
53
+ });
54
+ const effectiveAllowFrom = normalizeMatrixAllowList(access.effectiveAllowFrom);
55
+ const effectiveGroupAllowFrom = normalizeMatrixAllowList(access.effectiveGroupAllowFrom);
56
+ return {
57
+ access,
58
+ effectiveAllowFrom,
59
+ effectiveGroupAllowFrom,
60
+ groupAllowConfigured: effectiveGroupAllowFrom.length > 0,
61
+ };
62
+ }
63
+
64
+ export async function enforceMatrixDirectMessageAccess(params: {
65
+ dmEnabled: boolean;
66
+ dmPolicy: MatrixDmPolicy;
67
+ accessDecision: "allow" | "block" | "pairing";
68
+ senderId: string;
69
+ senderName: string;
70
+ effectiveAllowFrom: string[];
71
+ upsertPairingRequest: (input: {
72
+ id: string;
73
+ meta?: Record<string, string | undefined>;
74
+ }) => Promise<{
75
+ code: string;
76
+ created: boolean;
77
+ }>;
78
+ sendPairingReply: (text: string) => Promise<void>;
79
+ logVerboseMessage: (message: string) => void;
80
+ }): Promise<boolean> {
81
+ if (!params.dmEnabled) {
82
+ return false;
83
+ }
84
+ if (params.accessDecision === "allow") {
85
+ return true;
86
+ }
87
+ const allowMatch = resolveMatrixAllowListMatch({
88
+ allowList: params.effectiveAllowFrom,
89
+ userId: params.senderId,
90
+ });
91
+ const allowMatchMeta = formatAllowlistMatchMeta(allowMatch);
92
+ if (params.accessDecision === "pairing") {
93
+ await issuePairingChallenge({
94
+ channel: "badgerclaw",
95
+ senderId: params.senderId,
96
+ senderIdLine: `BadgerClaw user id: ${params.senderId}`,
97
+ meta: { name: params.senderName },
98
+ upsertPairingRequest: params.upsertPairingRequest,
99
+ buildReplyText: ({ code }) =>
100
+ [
101
+ "OpenClaw: access not configured.",
102
+ "",
103
+ `Pairing code: ${code}`,
104
+ "",
105
+ "Ask the bot owner to approve with:",
106
+ "openclaw pairing approve badgerclaw <code>",
107
+ ].join("\n"),
108
+ sendPairingReply: params.sendPairingReply,
109
+ onCreated: () => {
110
+ params.logVerboseMessage(
111
+ `badgerclaw pairing request sender=${params.senderId} name=${params.senderName ?? "unknown"} (${allowMatchMeta})`,
112
+ );
113
+ },
114
+ onReplyError: (err) => {
115
+ params.logVerboseMessage(
116
+ `badgerclaw pairing reply failed for ${params.senderId}: ${String(err)}`,
117
+ );
118
+ },
119
+ });
120
+ return false;
121
+ }
122
+ params.logVerboseMessage(
123
+ `badgerclaw: blocked dm sender ${params.senderId} (dmPolicy=${params.dmPolicy}, ${allowMatchMeta})`,
124
+ );
125
+ return false;
126
+ }