mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-12 01:31:08 +00:00
Matrix: prompt invite auto-join during onboarding (#62168)
Merged via squash.
Prepared head SHA: aec7a2249a
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Reviewed-by: @gumadeiras
This commit is contained in:
committed by
GitHub
parent
8d2ccd851c
commit
9fd47a5aed
@@ -52,6 +52,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Hooks/wake: queue direct and mapped wake-hook payloads as untrusted system events so external wake content no longer enters the main session as trusted input. (#62003)
|
||||
- Slack/thread mentions: add `channels.slack.thread.requireExplicitMention` so Slack channels that already require mentions can also require explicit `@bot` mentions inside bot-participated threads. (#58276) Thanks @praktika-engineer.
|
||||
- UI/light mode: target both root and nested WebKit scrollbar thumbs in the light theme so page-level and container scrollbars stay visible on light backgrounds. (#61753) Thanks @chziyue.
|
||||
- Matrix/onboarding: add an invite auto-join setup step with explicit off warnings and strict stable-target validation so new Matrix accounts stop silently ignoring invited rooms and fresh DM-style invites unless operators opt in. (#62168) Thanks @gumadeiras.
|
||||
|
||||
## 2026.4.5
|
||||
|
||||
|
||||
@@ -61,13 +61,17 @@ What the Matrix wizard actually asks for:
|
||||
- optional device name
|
||||
- whether to enable E2EE
|
||||
- whether to configure Matrix room access now
|
||||
- whether to configure Matrix invite auto-join now
|
||||
- when invite auto-join is enabled, whether it should be `allowlist`, `always`, or `off`
|
||||
|
||||
Wizard behavior that matters:
|
||||
|
||||
- If Matrix auth env vars already exist for the selected account, and that account does not already have auth saved in config, the wizard offers an env shortcut and only writes `enabled: true` for that account.
|
||||
- If Matrix auth env vars already exist for the selected account, and that account does not already have auth saved in config, the wizard offers an env shortcut so setup can keep auth in env vars instead of copying secrets into config.
|
||||
- When you add another Matrix account interactively, the entered account name is normalized into the account ID used in config and env vars. For example, `Ops Bot` becomes `ops-bot`.
|
||||
- DM allowlist prompts accept full `@user:server` values immediately. Display names only work when live directory lookup finds one exact match; otherwise the wizard asks you to retry with a full Matrix ID.
|
||||
- Room allowlist prompts accept room IDs and aliases directly. They can also resolve joined-room names live, but unresolved names are only kept as typed during setup and are ignored later by runtime allowlist resolution. Prefer `!room:server` or `#alias:server`.
|
||||
- The wizard now shows an explicit warning before the invite auto-join step because `channels.matrix.autoJoin` defaults to `off`; agents will not join invited rooms or fresh DM-style invites unless you set it.
|
||||
- In invite auto-join allowlist mode, use only stable invite targets: `!roomId:server`, `#alias:server`, or `*`. Plain room names are rejected.
|
||||
- Runtime room/session identity uses the stable Matrix room ID. Room-declared aliases are only used as lookup inputs, not as the long-term session key or stable group identity.
|
||||
- To resolve room names before saving them, use `openclaw channels resolve --channel matrix "Project Room"`.
|
||||
|
||||
@@ -77,6 +81,8 @@ Wizard behavior that matters:
|
||||
If you leave it unset, the bot will not join invited rooms or fresh DM-style invites, so it will not appear in new groups or invited DMs unless you join manually first.
|
||||
|
||||
Set `autoJoin: "allowlist"` together with `autoJoinAllowlist` to restrict which invites it accepts, or set `autoJoin: "always"` if you want it to join every invite.
|
||||
|
||||
In `allowlist` mode, `autoJoinAllowlist` only accepts `!roomId:server`, `#alias:server`, or `*`.
|
||||
</Warning>
|
||||
|
||||
Allowlist example:
|
||||
|
||||
@@ -103,6 +103,44 @@ describe("updateMatrixAccountConfig", () => {
|
||||
expect(updated.channels?.["matrix"]?.accounts?.default?.proxy).toBeUndefined();
|
||||
});
|
||||
|
||||
it("stores and clears Matrix invite auto-join settings", () => {
|
||||
const cfg = {
|
||||
channels: {
|
||||
matrix: {
|
||||
accounts: {
|
||||
default: {
|
||||
autoJoin: "allowlist",
|
||||
autoJoinAllowlist: ["#ops:example.org"],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
} as CoreConfig;
|
||||
|
||||
const allowlistUpdated = updateMatrixAccountConfig(cfg, "default", {
|
||||
autoJoin: "allowlist",
|
||||
autoJoinAllowlist: ["!ops-room:example.org", "#ops:example.org"],
|
||||
});
|
||||
expect(allowlistUpdated.channels?.matrix?.accounts?.default).toMatchObject({
|
||||
autoJoin: "allowlist",
|
||||
autoJoinAllowlist: ["!ops-room:example.org", "#ops:example.org"],
|
||||
});
|
||||
|
||||
const offUpdated = updateMatrixAccountConfig(cfg, "default", {
|
||||
autoJoin: "off",
|
||||
autoJoinAllowlist: null,
|
||||
});
|
||||
expect(offUpdated.channels?.matrix?.accounts?.default?.autoJoin).toBe("off");
|
||||
expect(offUpdated.channels?.matrix?.accounts?.default?.autoJoinAllowlist).toBeUndefined();
|
||||
|
||||
const alwaysUpdated = updateMatrixAccountConfig(cfg, "default", {
|
||||
autoJoin: "always",
|
||||
autoJoinAllowlist: null,
|
||||
});
|
||||
expect(alwaysUpdated.channels?.matrix?.accounts?.default?.autoJoin).toBe("always");
|
||||
expect(alwaysUpdated.channels?.matrix?.accounts?.default?.autoJoinAllowlist).toBeUndefined();
|
||||
});
|
||||
|
||||
it("normalizes account id and defaults account enabled=true", () => {
|
||||
const updated = updateMatrixAccountConfig({} as CoreConfig, "Main Bot", {
|
||||
name: "Main Bot",
|
||||
|
||||
@@ -30,6 +30,8 @@ export type MatrixAccountPatch = {
|
||||
encryption?: boolean | null;
|
||||
initialSyncLimit?: number | null;
|
||||
allowBots?: MatrixConfig["allowBots"] | null;
|
||||
autoJoin?: MatrixConfig["autoJoin"] | null;
|
||||
autoJoinAllowlist?: MatrixConfig["autoJoinAllowlist"] | null;
|
||||
dm?: MatrixConfig["dm"] | null;
|
||||
groupPolicy?: MatrixConfig["groupPolicy"] | null;
|
||||
groupAllowFrom?: MatrixConfig["groupAllowFrom"] | null;
|
||||
@@ -203,6 +205,14 @@ export function updateMatrixAccountConfig(
|
||||
nextAccount.allowBots = patch.allowBots;
|
||||
}
|
||||
}
|
||||
if (patch.autoJoin !== undefined) {
|
||||
if (patch.autoJoin === null) {
|
||||
delete nextAccount.autoJoin;
|
||||
} else {
|
||||
nextAccount.autoJoin = patch.autoJoin;
|
||||
}
|
||||
}
|
||||
applyNullableArrayField(nextAccount, "autoJoinAllowlist", patch.autoJoinAllowlist);
|
||||
if (patch.dm !== undefined) {
|
||||
if (patch.dm === null) {
|
||||
delete nextAccount.dm;
|
||||
@@ -245,16 +255,20 @@ export function updateMatrixAccountConfig(
|
||||
);
|
||||
|
||||
if (shouldStoreMatrixAccountAtTopLevel(cfg, normalizedAccountId)) {
|
||||
const { accounts: _ignoredAccounts, defaultAccount, ...baseMatrix } = matrix;
|
||||
const { accounts: _ignoredAccounts, defaultAccount } = matrix;
|
||||
const {
|
||||
accounts: _ignoredNextAccounts,
|
||||
defaultAccount: _ignoredNextDefaultAccount,
|
||||
...topLevelAccount
|
||||
} = nextAccount;
|
||||
return {
|
||||
...cfg,
|
||||
channels: {
|
||||
...cfg.channels,
|
||||
matrix: {
|
||||
...baseMatrix,
|
||||
...(defaultAccount ? { defaultAccount } : {}),
|
||||
enabled: true,
|
||||
...nextAccount,
|
||||
...topLevelAccount,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -117,13 +117,18 @@ export async function runMatrixAddAccountAllowlistConfigure(params: {
|
||||
cfg: CoreConfig;
|
||||
allowFromInput: string;
|
||||
roomsAllowlistInput: string;
|
||||
autoJoinPolicy?: "always" | "allowlist" | "off";
|
||||
autoJoinAllowlistInput?: string;
|
||||
deviceName?: string;
|
||||
notes?: string[];
|
||||
}) {
|
||||
const prompter = createMatrixWizardPrompter({
|
||||
notes: params.notes,
|
||||
select: {
|
||||
"Matrix already configured. What do you want to do?": "add-account",
|
||||
"Matrix auth method": "token",
|
||||
"Matrix rooms access": "allowlist",
|
||||
"Matrix invite auto-join": params.autoJoinPolicy ?? "allowlist",
|
||||
},
|
||||
text: {
|
||||
"Matrix account name": "ops",
|
||||
@@ -132,10 +137,13 @@ export async function runMatrixAddAccountAllowlistConfigure(params: {
|
||||
"Matrix device name (optional)": params.deviceName ?? "",
|
||||
"Matrix allowFrom (full @user:server; display name only if unique)": params.allowFromInput,
|
||||
"Matrix rooms allowlist (comma-separated)": params.roomsAllowlistInput,
|
||||
"Matrix invite auto-join allowlist (comma-separated)":
|
||||
params.autoJoinAllowlistInput ?? "#ops-invites:example.org",
|
||||
},
|
||||
confirm: {
|
||||
"Enable end-to-end encryption (E2EE)?": false,
|
||||
"Configure Matrix rooms access?": true,
|
||||
"Configure Matrix invite auto-join?": true,
|
||||
},
|
||||
onConfirm: async () => false,
|
||||
});
|
||||
|
||||
@@ -85,6 +85,72 @@ describe("matrix onboarding", () => {
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it("routes env-shortcut add-account flow through Matrix invite auto-join setup", async () => {
|
||||
installMatrixTestRuntime();
|
||||
|
||||
process.env.MATRIX_HOMESERVER = "https://matrix.env.example.org";
|
||||
process.env.MATRIX_USER_ID = "@env:example.org";
|
||||
process.env.MATRIX_PASSWORD = "env-password"; // pragma: allowlist secret
|
||||
process.env.MATRIX_ACCESS_TOKEN = "";
|
||||
process.env.MATRIX_OPS_HOMESERVER = "https://matrix.ops.env.example.org";
|
||||
process.env.MATRIX_OPS_ACCESS_TOKEN = "ops-env-token";
|
||||
|
||||
const notes: string[] = [];
|
||||
const prompter = createMatrixWizardPrompter({
|
||||
notes,
|
||||
select: {
|
||||
"Matrix already configured. What do you want to do?": "add-account",
|
||||
"Matrix rooms access": "allowlist",
|
||||
"Matrix invite auto-join": "allowlist",
|
||||
},
|
||||
text: {
|
||||
"Matrix account name": "ops",
|
||||
"Matrix rooms allowlist (comma-separated)": "!ops-room:example.org",
|
||||
"Matrix invite auto-join allowlist (comma-separated)": "#ops-invites:example.org",
|
||||
},
|
||||
confirm: {
|
||||
"Configure Matrix rooms access?": true,
|
||||
"Configure Matrix invite auto-join?": true,
|
||||
},
|
||||
onConfirm: (message) => message.startsWith("Matrix env vars detected"),
|
||||
});
|
||||
|
||||
const result = await runMatrixInteractiveConfigure({
|
||||
cfg: {
|
||||
channels: {
|
||||
matrix: {
|
||||
accounts: {
|
||||
default: {
|
||||
homeserver: "https://matrix.main.example.org",
|
||||
accessToken: "main-token",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
} as CoreConfig,
|
||||
prompter,
|
||||
shouldPromptAccountIds: true,
|
||||
configured: true,
|
||||
});
|
||||
|
||||
expect(result).not.toBe("skip");
|
||||
if (result === "skip") {
|
||||
return;
|
||||
}
|
||||
|
||||
expect(result.accountId).toBe("ops");
|
||||
expect(result.cfg.channels?.matrix?.accounts?.ops).toMatchObject({
|
||||
enabled: true,
|
||||
groupPolicy: "allowlist",
|
||||
groups: {
|
||||
"!ops-room:example.org": { enabled: true },
|
||||
},
|
||||
autoJoin: "allowlist",
|
||||
autoJoinAllowlist: ["#ops-invites:example.org"],
|
||||
});
|
||||
expect(notes.join("\n")).toContain("WARNING: Matrix invite auto-join defaults to off.");
|
||||
});
|
||||
|
||||
it("promotes legacy top-level Matrix config before adding a named account", async () => {
|
||||
installMatrixTestRuntime();
|
||||
|
||||
@@ -289,6 +355,7 @@ describe("matrix onboarding", () => {
|
||||
"Matrix credentials already configured. Keep them?": true,
|
||||
"Enable end-to-end encryption (E2EE)?": false,
|
||||
"Configure Matrix rooms access?": false,
|
||||
"Configure Matrix invite auto-join?": false,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -353,6 +420,7 @@ describe("matrix onboarding", () => {
|
||||
|
||||
it("writes allowlists and room access to the selected Matrix account", async () => {
|
||||
installMatrixTestRuntime();
|
||||
const notes: string[] = [];
|
||||
|
||||
const result = await runMatrixAddAccountAllowlistConfigure({
|
||||
cfg: {
|
||||
@@ -369,7 +437,9 @@ describe("matrix onboarding", () => {
|
||||
} as CoreConfig,
|
||||
allowFromInput: "@alice:example.org",
|
||||
roomsAllowlistInput: "!ops-room:example.org",
|
||||
autoJoinAllowlistInput: "#ops-invites:example.org",
|
||||
deviceName: "Ops Gateway",
|
||||
notes,
|
||||
});
|
||||
|
||||
expect(result).not.toBe("skip");
|
||||
@@ -387,12 +457,124 @@ describe("matrix onboarding", () => {
|
||||
allowFrom: ["@alice:example.org"],
|
||||
},
|
||||
groupPolicy: "allowlist",
|
||||
autoJoin: "allowlist",
|
||||
autoJoinAllowlist: ["#ops-invites:example.org"],
|
||||
groups: {
|
||||
"!ops-room:example.org": { enabled: true },
|
||||
},
|
||||
});
|
||||
expect(result.cfg.channels?.["matrix"]?.dm).toBeUndefined();
|
||||
expect(result.cfg.channels?.["matrix"]?.groups).toBeUndefined();
|
||||
expect(notes.join("\n")).toContain("WARNING: Matrix invite auto-join defaults to off.");
|
||||
});
|
||||
|
||||
it("clears Matrix invite auto-join allowlists when switching auto-join off", async () => {
|
||||
installMatrixTestRuntime();
|
||||
const notes: string[] = [];
|
||||
|
||||
const prompter = createMatrixWizardPrompter({
|
||||
notes,
|
||||
select: {
|
||||
"Matrix already configured. What do you want to do?": "update",
|
||||
"Matrix invite auto-join": "off",
|
||||
},
|
||||
text: {
|
||||
"Matrix homeserver URL": "https://matrix.example.org",
|
||||
"Matrix device name (optional)": "OpenClaw Gateway",
|
||||
},
|
||||
confirm: {
|
||||
"Matrix credentials already configured. Keep them?": true,
|
||||
"Enable end-to-end encryption (E2EE)?": false,
|
||||
"Configure Matrix rooms access?": false,
|
||||
"Configure Matrix invite auto-join?": true,
|
||||
"Update Matrix invite auto-join?": true,
|
||||
},
|
||||
});
|
||||
|
||||
const result = await runMatrixInteractiveConfigure({
|
||||
cfg: {
|
||||
channels: {
|
||||
matrix: {
|
||||
homeserver: "https://matrix.example.org",
|
||||
accessToken: "matrix-token",
|
||||
autoJoin: "allowlist",
|
||||
autoJoinAllowlist: ["#ops:example.org"],
|
||||
},
|
||||
},
|
||||
} as CoreConfig,
|
||||
prompter,
|
||||
configured: true,
|
||||
});
|
||||
|
||||
expect(result).not.toBe("skip");
|
||||
if (result === "skip") {
|
||||
return;
|
||||
}
|
||||
|
||||
expect(result.cfg.channels?.matrix?.autoJoin).toBe("off");
|
||||
expect(result.cfg.channels?.matrix?.autoJoinAllowlist).toBeUndefined();
|
||||
expect(notes.join("\n")).toContain("Matrix invite auto-join remains off.");
|
||||
expect(notes.join("\n")).toContain(
|
||||
"Agents will not join invited rooms or fresh DM-style invites until you change autoJoin.",
|
||||
);
|
||||
});
|
||||
|
||||
it("re-prompts Matrix invite auto-join allowlists until entries are stable invite targets", async () => {
|
||||
installMatrixTestRuntime();
|
||||
const notes: string[] = [];
|
||||
let inviteAllowlistPrompts = 0;
|
||||
|
||||
const prompter = createMatrixWizardPrompter({
|
||||
notes,
|
||||
select: {
|
||||
"Matrix already configured. What do you want to do?": "update",
|
||||
"Matrix invite auto-join": "allowlist",
|
||||
},
|
||||
text: {
|
||||
"Matrix homeserver URL": "https://matrix.example.org",
|
||||
"Matrix device name (optional)": "OpenClaw Gateway",
|
||||
},
|
||||
confirm: {
|
||||
"Matrix credentials already configured. Keep them?": true,
|
||||
"Enable end-to-end encryption (E2EE)?": false,
|
||||
"Configure Matrix rooms access?": false,
|
||||
"Configure Matrix invite auto-join?": true,
|
||||
"Update Matrix invite auto-join?": true,
|
||||
},
|
||||
onText: async (message) => {
|
||||
if (message === "Matrix invite auto-join allowlist (comma-separated)") {
|
||||
inviteAllowlistPrompts += 1;
|
||||
return inviteAllowlistPrompts === 1 ? "Project Room" : "#ops:example.org";
|
||||
}
|
||||
throw new Error(`unexpected text prompt: ${message}`);
|
||||
},
|
||||
});
|
||||
|
||||
const result = await runMatrixInteractiveConfigure({
|
||||
cfg: {
|
||||
channels: {
|
||||
matrix: {
|
||||
homeserver: "https://matrix.example.org",
|
||||
accessToken: "matrix-token",
|
||||
},
|
||||
},
|
||||
} as CoreConfig,
|
||||
prompter,
|
||||
configured: true,
|
||||
});
|
||||
|
||||
expect(result).not.toBe("skip");
|
||||
if (result === "skip") {
|
||||
return;
|
||||
}
|
||||
|
||||
expect(inviteAllowlistPrompts).toBe(2);
|
||||
expect(result.cfg.channels?.matrix?.autoJoin).toBe("allowlist");
|
||||
expect(result.cfg.channels?.matrix?.autoJoinAllowlist).toEqual(["#ops:example.org"]);
|
||||
expect(notes.join("\n")).toContain(
|
||||
"Use only stable Matrix invite targets for auto-join: !roomId:server, #alias:server, or *.",
|
||||
);
|
||||
expect(notes.join("\n")).toContain("Invalid: Project Room");
|
||||
});
|
||||
|
||||
it("reports account-scoped DM config keys for named accounts", () => {
|
||||
|
||||
@@ -30,13 +30,40 @@ import {
|
||||
normalizeAccountId,
|
||||
promptAccountId,
|
||||
promptChannelAccessConfig,
|
||||
splitSetupEntries,
|
||||
type RuntimeEnv,
|
||||
type WizardPrompter,
|
||||
} from "./runtime-api.js";
|
||||
import { moveSingleMatrixAccountConfigToNamedAccount } from "./setup-config.js";
|
||||
import type { CoreConfig } from "./types.js";
|
||||
import type { CoreConfig, MatrixConfig } from "./types.js";
|
||||
|
||||
const channel = "matrix" as const;
|
||||
type MatrixInviteAutoJoinPolicy = NonNullable<MatrixConfig["autoJoin"]>;
|
||||
|
||||
const matrixInviteAutoJoinOptions: Array<{
|
||||
value: MatrixInviteAutoJoinPolicy;
|
||||
label: string;
|
||||
}> = [
|
||||
{ value: "allowlist", label: "Allowlist (recommended)" },
|
||||
{ value: "always", label: "Always (join every invite)" },
|
||||
{ value: "off", label: "Off (do not auto-join invites)" },
|
||||
];
|
||||
|
||||
function isMatrixInviteAutoJoinPolicy(value: string): value is MatrixInviteAutoJoinPolicy {
|
||||
return value === "allowlist" || value === "always" || value === "off";
|
||||
}
|
||||
|
||||
function isMatrixInviteAutoJoinTarget(entry: string): boolean {
|
||||
return (
|
||||
entry === "*" ||
|
||||
(entry.startsWith("!") && entry.includes(":")) ||
|
||||
(entry.startsWith("#") && entry.includes(":"))
|
||||
);
|
||||
}
|
||||
|
||||
function normalizeMatrixInviteAutoJoinTargets(entries: string[]): string[] {
|
||||
return [...new Set(entries.map((entry) => entry.trim()).filter(Boolean))];
|
||||
}
|
||||
|
||||
function resolveMatrixOnboardingAccountId(cfg: CoreConfig, accountId?: string): string {
|
||||
return normalizeAccountId(
|
||||
@@ -95,12 +122,6 @@ async function promptMatrixAllowFrom(params: {
|
||||
const account = resolveMatrixAccount({ cfg, accountId });
|
||||
const canResolve = Boolean(account.configured);
|
||||
|
||||
const parseInput = (raw: string) =>
|
||||
raw
|
||||
.split(/[\n,;]+/g)
|
||||
.map((entry) => entry.trim())
|
||||
.filter(Boolean);
|
||||
|
||||
const isFullUserId = (value: string) => value.startsWith("@") && value.includes(":");
|
||||
|
||||
while (true) {
|
||||
@@ -110,7 +131,7 @@ async function promptMatrixAllowFrom(params: {
|
||||
initialValue: existingAllowFrom[0] ? String(existingAllowFrom[0]) : undefined,
|
||||
validate: (value) => (String(value ?? "").trim() ? undefined : "Required"),
|
||||
});
|
||||
const parts = parseInput(String(entry));
|
||||
const parts = splitSetupEntries(String(entry));
|
||||
const resolvedIds: string[] = [];
|
||||
const pending: string[] = [];
|
||||
const unresolved: string[] = [];
|
||||
@@ -187,6 +208,200 @@ function setMatrixGroupRooms(cfg: CoreConfig, roomKeys: string[], accountId?: st
|
||||
});
|
||||
}
|
||||
|
||||
function setMatrixAutoJoin(
|
||||
cfg: CoreConfig,
|
||||
autoJoin: MatrixInviteAutoJoinPolicy,
|
||||
autoJoinAllowlist: string[],
|
||||
accountId?: string,
|
||||
) {
|
||||
return updateMatrixAccountConfig(cfg, resolveMatrixOnboardingAccountId(cfg, accountId), {
|
||||
autoJoin,
|
||||
autoJoinAllowlist: autoJoin === "allowlist" ? autoJoinAllowlist : null,
|
||||
});
|
||||
}
|
||||
|
||||
async function configureMatrixInviteAutoJoin(params: {
|
||||
cfg: CoreConfig;
|
||||
prompter: WizardPrompter;
|
||||
accountId?: string;
|
||||
}): Promise<CoreConfig> {
|
||||
const accountId = resolveMatrixOnboardingAccountId(params.cfg, params.accountId);
|
||||
const existingConfig = resolveMatrixAccountConfig({ cfg: params.cfg, accountId });
|
||||
const currentPolicy = existingConfig.autoJoin ?? "off";
|
||||
const currentAllowlist = (existingConfig.autoJoinAllowlist ?? []).map((entry) => String(entry));
|
||||
const hasExistingConfig = existingConfig.autoJoin !== undefined || currentAllowlist.length > 0;
|
||||
|
||||
await params.prompter.note(
|
||||
[
|
||||
"WARNING: Matrix invite auto-join defaults to off.",
|
||||
"OpenClaw agents will not join invited rooms or fresh DM-style invites unless you set autoJoin.",
|
||||
'Choose "allowlist" to restrict joins or "always" to join every invite.',
|
||||
].join("\n"),
|
||||
"Matrix invite auto-join",
|
||||
);
|
||||
|
||||
const wants = await params.prompter.confirm({
|
||||
message: hasExistingConfig
|
||||
? "Update Matrix invite auto-join?"
|
||||
: "Configure Matrix invite auto-join?",
|
||||
initialValue: hasExistingConfig ? currentPolicy !== "off" : true,
|
||||
});
|
||||
if (!wants) {
|
||||
return params.cfg;
|
||||
}
|
||||
|
||||
const selectedPolicy = await params.prompter.select({
|
||||
message: "Matrix invite auto-join",
|
||||
options: matrixInviteAutoJoinOptions,
|
||||
initialValue: currentPolicy,
|
||||
});
|
||||
if (!isMatrixInviteAutoJoinPolicy(selectedPolicy)) {
|
||||
throw new Error(`Unsupported Matrix invite auto-join policy: ${String(selectedPolicy)}`);
|
||||
}
|
||||
const policy = selectedPolicy;
|
||||
|
||||
if (policy === "off") {
|
||||
await params.prompter.note(
|
||||
[
|
||||
"Matrix invite auto-join remains off.",
|
||||
"Agents will not join invited rooms or fresh DM-style invites until you change autoJoin.",
|
||||
].join("\n"),
|
||||
"Matrix invite auto-join",
|
||||
);
|
||||
return setMatrixAutoJoin(params.cfg, policy, [], accountId);
|
||||
}
|
||||
|
||||
if (policy === "always") {
|
||||
return setMatrixAutoJoin(params.cfg, policy, [], accountId);
|
||||
}
|
||||
|
||||
while (true) {
|
||||
const rawAllowlist = String(
|
||||
await params.prompter.text({
|
||||
message: "Matrix invite auto-join allowlist (comma-separated)",
|
||||
placeholder: "!roomId:server, #alias:server, *",
|
||||
initialValue: currentAllowlist[0] ? currentAllowlist.join(", ") : undefined,
|
||||
validate: (value) => {
|
||||
const entries = splitSetupEntries(String(value ?? ""));
|
||||
return entries.length > 0 ? undefined : "Required";
|
||||
},
|
||||
}),
|
||||
);
|
||||
const allowlist = normalizeMatrixInviteAutoJoinTargets(splitSetupEntries(rawAllowlist));
|
||||
const invalidEntries = allowlist.filter((entry) => !isMatrixInviteAutoJoinTarget(entry));
|
||||
if (allowlist.length === 0 || invalidEntries.length > 0) {
|
||||
await params.prompter.note(
|
||||
[
|
||||
"Use only stable Matrix invite targets for auto-join: !roomId:server, #alias:server, or *.",
|
||||
invalidEntries.length > 0 ? `Invalid: ${invalidEntries.join(", ")}` : undefined,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join("\n"),
|
||||
"Matrix invite auto-join",
|
||||
);
|
||||
continue;
|
||||
}
|
||||
return setMatrixAutoJoin(params.cfg, "allowlist", allowlist, accountId);
|
||||
}
|
||||
}
|
||||
|
||||
async function configureMatrixAccessPrompts(params: {
|
||||
cfg: CoreConfig;
|
||||
prompter: WizardPrompter;
|
||||
forceAllowFrom: boolean;
|
||||
accountId: string;
|
||||
}): Promise<CoreConfig> {
|
||||
let next = params.cfg;
|
||||
|
||||
if (params.forceAllowFrom) {
|
||||
next = await promptMatrixAllowFrom({
|
||||
cfg: next,
|
||||
prompter: params.prompter,
|
||||
accountId: params.accountId,
|
||||
});
|
||||
}
|
||||
|
||||
const existingAccountConfig = resolveMatrixAccountConfig({
|
||||
cfg: next,
|
||||
accountId: params.accountId,
|
||||
});
|
||||
const existingGroups = existingAccountConfig.groups ?? existingAccountConfig.rooms;
|
||||
const accessConfig = await promptChannelAccessConfig({
|
||||
prompter: params.prompter,
|
||||
label: "Matrix rooms",
|
||||
currentPolicy: existingAccountConfig.groupPolicy ?? "allowlist",
|
||||
currentEntries: Object.keys(existingGroups ?? {}),
|
||||
placeholder: "!roomId:server, #alias:server, Project Room",
|
||||
updatePrompt: Boolean(existingGroups),
|
||||
});
|
||||
if (accessConfig) {
|
||||
if (accessConfig.policy !== "allowlist") {
|
||||
next = setMatrixGroupPolicy(next, accessConfig.policy, params.accountId);
|
||||
} else {
|
||||
let roomKeys = accessConfig.entries;
|
||||
if (accessConfig.entries.length > 0) {
|
||||
try {
|
||||
const resolvedIds: string[] = [];
|
||||
const unresolved: string[] = [];
|
||||
for (const entry of accessConfig.entries) {
|
||||
const trimmed = entry.trim();
|
||||
if (!trimmed) {
|
||||
continue;
|
||||
}
|
||||
const cleaned = trimmed.replace(/^(room|channel):/i, "").trim();
|
||||
if (cleaned.startsWith("!") && cleaned.includes(":")) {
|
||||
resolvedIds.push(cleaned);
|
||||
continue;
|
||||
}
|
||||
const matches = await listMatrixDirectoryGroupsLive({
|
||||
cfg: next,
|
||||
accountId: params.accountId,
|
||||
query: trimmed,
|
||||
limit: 10,
|
||||
});
|
||||
const exact = matches.find(
|
||||
(match) => (match.name ?? "").toLowerCase() === trimmed.toLowerCase(),
|
||||
);
|
||||
const best = exact ?? matches[0];
|
||||
if (best?.id) {
|
||||
resolvedIds.push(best.id);
|
||||
} else {
|
||||
unresolved.push(entry);
|
||||
}
|
||||
}
|
||||
roomKeys = [...resolvedIds, ...unresolved.map((entry) => entry.trim()).filter(Boolean)];
|
||||
if (resolvedIds.length > 0 || unresolved.length > 0) {
|
||||
await params.prompter.note(
|
||||
[
|
||||
resolvedIds.length > 0 ? `Resolved: ${resolvedIds.join(", ")}` : undefined,
|
||||
unresolved.length > 0
|
||||
? `Unresolved (kept as typed): ${unresolved.join(", ")}`
|
||||
: undefined,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join("\n"),
|
||||
"Matrix rooms",
|
||||
);
|
||||
}
|
||||
} catch (err) {
|
||||
await params.prompter.note(
|
||||
`Room lookup failed; keeping entries as typed. ${String(err)}`,
|
||||
"Matrix rooms",
|
||||
);
|
||||
}
|
||||
}
|
||||
next = setMatrixGroupPolicy(next, "allowlist", params.accountId);
|
||||
next = setMatrixGroupRooms(next, roomKeys, params.accountId);
|
||||
}
|
||||
}
|
||||
|
||||
return await configureMatrixInviteAutoJoin({
|
||||
cfg: next,
|
||||
prompter: params.prompter,
|
||||
accountId: params.accountId,
|
||||
});
|
||||
}
|
||||
|
||||
const dmPolicy: ChannelSetupDmPolicy = {
|
||||
label: "Matrix",
|
||||
channel,
|
||||
@@ -289,13 +504,12 @@ async function runMatrixConfigure(params: {
|
||||
});
|
||||
if (useEnv) {
|
||||
next = updateMatrixAccountConfig(next, accountId, { enabled: true });
|
||||
if (params.forceAllowFrom) {
|
||||
next = await promptMatrixAllowFrom({
|
||||
cfg: next,
|
||||
prompter: params.prompter,
|
||||
accountId,
|
||||
});
|
||||
}
|
||||
next = await configureMatrixAccessPrompts({
|
||||
cfg: next,
|
||||
prompter: params.prompter,
|
||||
forceAllowFrom: params.forceAllowFrom,
|
||||
accountId,
|
||||
});
|
||||
return { cfg: next, accountId };
|
||||
}
|
||||
}
|
||||
@@ -421,84 +635,12 @@ async function runMatrixConfigure(params: {
|
||||
encryption: enableEncryption,
|
||||
});
|
||||
|
||||
if (params.forceAllowFrom) {
|
||||
next = await promptMatrixAllowFrom({
|
||||
cfg: next,
|
||||
prompter: params.prompter,
|
||||
accountId,
|
||||
});
|
||||
}
|
||||
|
||||
const existingAccountConfig = resolveMatrixAccountConfig({ cfg: next, accountId });
|
||||
const existingGroups = existingAccountConfig.groups ?? existingAccountConfig.rooms;
|
||||
const accessConfig = await promptChannelAccessConfig({
|
||||
next = await configureMatrixAccessPrompts({
|
||||
cfg: next,
|
||||
prompter: params.prompter,
|
||||
label: "Matrix rooms",
|
||||
currentPolicy: existingAccountConfig.groupPolicy ?? "allowlist",
|
||||
currentEntries: Object.keys(existingGroups ?? {}),
|
||||
placeholder: "!roomId:server, #alias:server, Project Room",
|
||||
updatePrompt: Boolean(existingGroups),
|
||||
forceAllowFrom: params.forceAllowFrom,
|
||||
accountId,
|
||||
});
|
||||
if (accessConfig) {
|
||||
if (accessConfig.policy !== "allowlist") {
|
||||
next = setMatrixGroupPolicy(next, accessConfig.policy, accountId);
|
||||
} else {
|
||||
let roomKeys = accessConfig.entries;
|
||||
if (accessConfig.entries.length > 0) {
|
||||
try {
|
||||
const resolvedIds: string[] = [];
|
||||
const unresolved: string[] = [];
|
||||
for (const entry of accessConfig.entries) {
|
||||
const trimmed = entry.trim();
|
||||
if (!trimmed) {
|
||||
continue;
|
||||
}
|
||||
const cleaned = trimmed.replace(/^(room|channel):/i, "").trim();
|
||||
if (cleaned.startsWith("!") && cleaned.includes(":")) {
|
||||
resolvedIds.push(cleaned);
|
||||
continue;
|
||||
}
|
||||
const matches = await listMatrixDirectoryGroupsLive({
|
||||
cfg: next,
|
||||
accountId,
|
||||
query: trimmed,
|
||||
limit: 10,
|
||||
});
|
||||
const exact = matches.find(
|
||||
(match) => (match.name ?? "").toLowerCase() === trimmed.toLowerCase(),
|
||||
);
|
||||
const best = exact ?? matches[0];
|
||||
if (best?.id) {
|
||||
resolvedIds.push(best.id);
|
||||
} else {
|
||||
unresolved.push(entry);
|
||||
}
|
||||
}
|
||||
roomKeys = [...resolvedIds, ...unresolved.map((entry) => entry.trim()).filter(Boolean)];
|
||||
if (resolvedIds.length > 0 || unresolved.length > 0) {
|
||||
await params.prompter.note(
|
||||
[
|
||||
resolvedIds.length > 0 ? `Resolved: ${resolvedIds.join(", ")}` : undefined,
|
||||
unresolved.length > 0
|
||||
? `Unresolved (kept as typed): ${unresolved.join(", ")}`
|
||||
: undefined,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join("\n"),
|
||||
"Matrix rooms",
|
||||
);
|
||||
}
|
||||
} catch (err) {
|
||||
await params.prompter.note(
|
||||
`Room lookup failed; keeping entries as typed. ${String(err)}`,
|
||||
"Matrix rooms",
|
||||
);
|
||||
}
|
||||
}
|
||||
next = setMatrixGroupPolicy(next, "allowlist", accountId);
|
||||
next = setMatrixGroupRooms(next, roomKeys, accountId);
|
||||
}
|
||||
}
|
||||
|
||||
return { cfg: next, accountId };
|
||||
}
|
||||
|
||||
@@ -57,6 +57,7 @@ export {
|
||||
moveSingleAccountChannelSectionToDefaultAccount,
|
||||
promptAccountId,
|
||||
promptChannelAccessConfig,
|
||||
splitSetupEntries,
|
||||
} from "openclaw/plugin-sdk/setup";
|
||||
export type { RuntimeEnv } from "openclaw/plugin-sdk/runtime";
|
||||
export {
|
||||
|
||||
Reference in New Issue
Block a user