@brantrusnak/openclaw-omadeus 1.0.3 → 1.0.4

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/README.md CHANGED
@@ -1,61 +1,44 @@
1
1
  # OpenClaw Omadeus Plugin
2
2
 
3
- [Omadeus](https://omadeus.com) plugin for [OpenClaw](https://www.npmjs.com/package/openclaw).
3
+ [![Socket Badge](https://badge.socket.dev/npm/package/@brantrusnak/openclaw-omadeus)](https://badge.socket.dev/npm/package/@brantrusnak/openclaw-omadeus)
4
+ [![CI](https://github.com/brantrusnak/openclaw-omadeus-plugin/actions/workflows/npm-publish.yml/badge.svg)](https://github.com/brantrusnak/openclaw-omadeus-plugin/actions/workflows/npm-publish.yml)
5
+ [![npm version](https://img.shields.io/npm/v/@brantrusnak/openclaw-omadeus)](https://www.npmjs.com/package/@brantrusnak/openclaw-omadeus)
6
+ [![License: ISC](https://img.shields.io/npm/l/@brantrusnak/openclaw-omadeus)](https://www.npmjs.com/package/@brantrusnak/openclaw-omadeus)
4
7
 
5
- This plugin connects OpenClaw to Omadeus over WebSocket so OpenClaw can listen
6
- for Omadeus messages and reply through the selected Omadeus channel.
8
+ [Omadeus](https://omadeus.com) plugin for [OpenClaw](https://www.npmjs.com/package/openclaw).
7
9
 
8
10
  ## Requirements
9
11
 
10
12
  - Node.js 22 or newer
11
13
  - OpenClaw 2026.4.10 or newer
12
- - An Omadeus account with access to the organization and channel you want
13
- OpenClaw to use
14
+ - An Omadeus account
14
15
 
15
16
  ## Install
16
17
 
17
- Install OpenClaw first:
18
-
19
18
  ```bash
20
19
  npm install -g openclaw
20
+ openclaw plugins install @brantrusnak/openclaw-omadeus
21
21
  ```
22
22
 
23
- Set up OpenClaw if you have not already:
24
-
25
- ```bash
26
- openclaw onboard
27
- ```
28
-
29
- Then install this plugin with the OpenClaw plugin command:
23
+ Verify the plugin was installed:
30
24
 
31
25
  ```bash
32
- openclaw plugins install @brantrusnak/openclaw-omadeus
26
+ openclaw plugins list
33
27
  ```
34
28
 
35
- You can confirm OpenClaw discovered the plugin with:
29
+ Then run setup:
36
30
 
37
31
  ```bash
38
- openclaw plugins list
39
- openclaw plugins inspect omadeus
32
+ openclaw onboard
40
33
  ```
41
34
 
42
35
  ## Configure
43
36
 
44
- After installing the plugin, run OpenClaw configuration and choose Omadeus when
45
- prompted:
46
-
47
37
  ```bash
48
38
  openclaw configure
49
39
  ```
50
40
 
51
- The Omadeus setup flow asks for:
52
-
53
- - Omadeus email and password
54
- - Organization ID
55
- - The Omadeus member/account to listen as
56
- - The Omadeus channel to use for messages
57
-
58
- The plugin also supports these environment variables:
41
+ You can also set credentials via environment variables:
59
42
 
60
43
  ```bash
61
44
  export OMADEUS_EMAIL="you@example.com"
@@ -63,48 +46,16 @@ export OMADEUS_PASSWORD="your-password"
63
46
  export OMADEUS_ORGANIZATION_ID="123"
64
47
  ```
65
48
 
66
- The default Omadeus endpoints are:
67
-
68
- - CAS: `https://dev1-cas.rouztech.com`
69
- - Maestro: `https://dev3-maestro.rouztech.com`
70
-
71
- If your Omadeus deployment uses different endpoints, configure them through the
72
- OpenClaw setup flow or in your OpenClaw config under `channels.omadeus`.
73
-
74
- ## Start OpenClaw
75
-
76
- Once OpenClaw and the plugin are configured, start or restart the gateway:
49
+ ## Start
77
50
 
78
51
  ```bash
79
52
  openclaw gateway
80
53
  ```
81
54
 
82
- Check channel health with:
83
-
84
- ```bash
85
- openclaw channels status --deep
86
- openclaw plugins doctor
87
- ```
88
-
89
55
  ## Local Development
90
56
 
91
- Build the runtime files before linking a local checkout into OpenClaw:
92
-
93
57
  ```bash
94
58
  npm install
95
59
  npm run build
96
60
  openclaw plugins install . --link
97
61
  ```
98
-
99
- Before publishing, inspect the npm package contents:
100
-
101
- ```bash
102
- npm run prepack
103
- npm pack --dry-run
104
- ```
105
-
106
- Publish to npm:
107
-
108
- ```bash
109
- npm publish
110
- ```
@@ -2,20 +2,29 @@ import { getCasSession, setCasSession } from "../store.js";
2
2
  //#region src/api/auth.api.ts
3
3
  const CAS_APPLICATION_ID = 1;
4
4
  const CAS_SCOPES = "title,email,avatar,firstName,lastName,birth,phone,countryCode";
5
+ function formatFetchError(label, url, method, err) {
6
+ const base = err instanceof Error ? err.message : String(err);
7
+ const cause = err instanceof Error && err.cause instanceof Error ? err.cause.message : void 0;
8
+ const detail = cause && cause !== base ? `${base} (${cause})` : base;
9
+ return /* @__PURE__ */ new Error(`${label} (${method} ${url}) failed: ${detail}`);
10
+ }
11
+ async function omadeusFetch(label, url, init) {
12
+ const method = init.method ?? "GET";
13
+ try {
14
+ return await fetch(url, init);
15
+ } catch (err) {
16
+ throw formatFetchError(label, url, method, err);
17
+ }
18
+ }
5
19
  async function createCasToken(params) {
6
20
  const { casUrl, email, password } = params;
7
- const url = `${casUrl}/apiv1/tokens`;
8
- const jsonBody = JSON.stringify({
9
- email,
10
- password
11
- });
12
- const res = await fetch(url, {
21
+ const res = await omadeusFetch("CAS token request", `${casUrl}/apiv1/tokens`, {
13
22
  method: "CREATE",
14
- headers: {
15
- "Content-Type": "application/json;charset=UTF-8",
16
- "Content-Length": String(jsonBody.length)
17
- },
18
- body: jsonBody
23
+ headers: { "Content-Type": "application/json;charset=UTF-8" },
24
+ body: JSON.stringify({
25
+ email,
26
+ password
27
+ })
19
28
  });
20
29
  if (!res.ok) {
21
30
  const text = await res.text().catch(() => "");
@@ -42,16 +51,13 @@ async function createAuthorizationCode(params) {
42
51
  redirectUri: redirectUri ?? ""
43
52
  });
44
53
  if (redirectUri) qs.set("redirectUri", redirectUri);
45
- const url = `${casUrl}/apiv1/authorizationcodes?${qs}`;
46
- const body = "";
47
- const headers = {
48
- Authorization: `Bearer ${token}`,
49
- ...casSession?.refreshCookie ? { Cookie: casSession.refreshCookie } : {}
50
- };
51
- const res = await fetch(url, {
54
+ const res = await omadeusFetch("CAS authorization code request", `${casUrl}/apiv1/authorizationcodes?${qs}`, {
52
55
  method: "CREATE",
53
- body,
54
- headers
56
+ body: "",
57
+ headers: {
58
+ Authorization: `Bearer ${token}`,
59
+ ...casSession?.refreshCookie ? { Cookie: casSession.refreshCookie } : {}
60
+ }
55
61
  });
56
62
  if (!res.ok) {
57
63
  const text = await res.text().catch(() => "");
@@ -64,8 +70,7 @@ async function createAuthorizationCode(params) {
64
70
  }
65
71
  async function obtainSessionToken(params) {
66
72
  const { maestroUrl, authorizationCode, organizationId } = params;
67
- const url = `${maestroUrl}/dolphin/apiv1/oauth2/tokens`;
68
- const res = await fetch(url, {
73
+ const res = await omadeusFetch("Omadeus session token request", `${maestroUrl}/dolphin/apiv1/oauth2/tokens`, {
69
74
  method: "OBTAIN",
70
75
  headers: { "Content-Type": "application/json;charset=UTF-8" },
71
76
  body: JSON.stringify({
@@ -83,8 +88,7 @@ async function obtainSessionToken(params) {
83
88
  }
84
89
  async function listOrganizations(params) {
85
90
  const { maestroUrl, email } = params;
86
- const url = `${maestroUrl}/dolphin/apiv1/organizations`;
87
- const res = await fetch(url, {
91
+ const res = await omadeusFetch("Omadeus list organizations", `${maestroUrl}/dolphin/apiv1/organizations`, {
88
92
  method: "LIST",
89
93
  headers: { "Content-Type": "application/json;charset=UTF-8" },
90
94
  body: JSON.stringify({ email })
@@ -97,8 +101,7 @@ async function listOrganizations(params) {
97
101
  }
98
102
  async function listOrganizationMembers(params) {
99
103
  const { maestroUrl, sessionToken, organizationId } = params;
100
- const url = `${maestroUrl}/dolphin/apiv1/organizations/${organizationId}/members`;
101
- const res = await fetch(url, {
104
+ const res = await omadeusFetch("Omadeus list organization members", `${maestroUrl}/dolphin/apiv1/organizations/${organizationId}/members`, {
102
105
  method: "LIST",
103
106
  headers: {
104
107
  Authorization: `Bearer ${sessionToken}`,
@@ -8,7 +8,17 @@ import { authenticate } from "./auth.js";
8
8
  import { DEFAULT_ACCOUNT_ID } from "openclaw/plugin-sdk/setup";
9
9
  //#region src/onboarding.ts
10
10
  const channel = "omadeus";
11
- const DONE = "__done__";
11
+ function formatAuthError(err) {
12
+ if (!(err instanceof Error)) return String(err);
13
+ const parts = [err.message];
14
+ const { cause } = err;
15
+ if (cause instanceof Error) {
16
+ parts.push(cause.message);
17
+ const code = cause.code;
18
+ if (typeof code === "string" && code) parts.push(`(${code})`);
19
+ } else if (typeof cause === "string" && cause.trim()) parts.push(cause);
20
+ return parts.join(" — ");
21
+ }
12
22
  async function noteOmadeusAuthHelp(prompter) {
13
23
  await prompter.note([
14
24
  "Omadeus authenticates via CAS + Maestro (email + password + organization).",
@@ -66,7 +76,14 @@ async function promptChannelSelection(params) {
66
76
  skip: 0,
67
77
  take: 100
68
78
  });
69
- if (channels.length === 0) throw new Error("No channels found for this account.");
79
+ if (channels.length === 0) {
80
+ await prompter.note("No channels found for this account. Channel listening will stay disabled.", "Omadeus channels");
81
+ return [];
82
+ }
83
+ if (!await prompter.confirm({
84
+ message: "Listen for messages in Omadeus channels?",
85
+ initialValue: (existingChannelViewIds?.length ?? 0) > 0
86
+ })) return [];
70
87
  const selected = await promptMultiSelect({
71
88
  prompter,
72
89
  message: "Which channels should OpenClaw listen to?",
@@ -75,11 +92,9 @@ async function promptChannelSelection(params) {
75
92
  label: item.title || `Channel ${item.id}`,
76
93
  hint: [item.privateRoomId ? `private:${item.privateRoomId}` : void 0, item.publicRoomId ? `public:${item.publicRoomId}` : void 0].filter(Boolean).join(" | ")
77
94
  })),
78
- initialValues: existingChannelViewIds && existingChannelViewIds.length > 0 ? existingChannelViewIds.map(String) : [String(channels[0].id)]
95
+ initialValues: existingChannelViewIds && existingChannelViewIds.length > 0 ? existingChannelViewIds.map(String) : void 0
79
96
  });
80
- const chosen = channels.filter((item) => selected.includes(String(item.id)));
81
- if (chosen.length === 0) throw new Error("At least one channel must be selected.");
82
- return chosen;
97
+ return channels.filter((item) => selected.includes(String(item.id)));
83
98
  }
84
99
  function memberHint(member) {
85
100
  const parts = [
@@ -90,32 +105,11 @@ function memberHint(member) {
90
105
  return parts.length > 0 ? parts.join(" | ") : void 0;
91
106
  }
92
107
  async function promptMultiSelect(params) {
93
- const multi = params.prompter;
94
- const runMulti = multi.multiSelect ?? multi.multiselect;
95
- if (runMulti) return runMulti({
108
+ return params.prompter.multiselect({
96
109
  message: params.message,
97
110
  options: params.options,
98
- initialValues: params.initialValues,
99
- initialValue: params.initialValues
111
+ initialValues: params.initialValues
100
112
  });
101
- const selected = new Set(params.initialValues ?? []);
102
- while (true) {
103
- const next = await params.prompter.select({
104
- message: `${params.message} (${selected.size} selected)`,
105
- options: [{
106
- value: DONE,
107
- label: selected.size > 0 ? "Done" : "Done (select none)"
108
- }, ...params.options.map((option) => ({
109
- ...option,
110
- label: selected.has(option.value) ? `[selected] ${option.label}` : option.label
111
- }))],
112
- initialValue: DONE
113
- });
114
- const value = String(next);
115
- if (value === DONE) return [...selected];
116
- if (selected.has(value)) selected.delete(value);
117
- else selected.add(value);
118
- }
119
113
  }
120
114
  async function loadSelectableMembers(params) {
121
115
  const excluded = new Set(params.excludeReferenceIds ?? []);
@@ -244,8 +238,7 @@ const omadeusSetupWizard = {
244
238
  await prompter.note(`Authenticated as ${payload.email}`, "Omadeus authentication");
245
239
  break;
246
240
  } catch (err) {
247
- const msg = err instanceof Error ? err.message : String(err);
248
- await prompter.note(`Authentication failed: ${msg}`, "Omadeus authentication");
241
+ await prompter.note(`Authentication failed: ${formatAuthError(err)}`, "Omadeus authentication");
249
242
  if (!await prompter.confirm({
250
243
  message: "Re-enter email/password and try again?",
251
244
  initialValue: true
@@ -280,12 +273,12 @@ const omadeusSetupWizard = {
280
273
  memberReferenceId: selfReferenceId,
281
274
  existingChannelViewIds: existingInbound?.channels?.allowedChannelViewIds
282
275
  });
283
- const channelSenderIds = await promptSenderAllowlist({
276
+ const channelSenderIds = selectedChannels.length > 0 ? await promptSenderAllowlist({
284
277
  prompter,
285
278
  message: "Which users can trigger OpenClaw from allowed channels?",
286
279
  members,
287
280
  existingReferenceIds: existingInbound?.channels?.allowedSenderReferenceIds
288
- });
281
+ }) : void 0;
289
282
  const entityKinds = await promptEntityKindSelection({
290
283
  prompter,
291
284
  existingKinds: existingInbound?.entities?.allowedKinds
@@ -301,10 +294,11 @@ const omadeusSetupWizard = {
301
294
  const channelTitles = selectedChannels.map((selectedChannel) => selectedChannel.title || `Channel ${selectedChannel.id}`).join(", ");
302
295
  const senderSummary = (ids) => ids && ids.length > 0 ? ids.join(", ") : "all users";
303
296
  const entityKindSummary = entityKinds.length > 0 ? entityKinds.join(", ") : "none (entity rooms disabled)";
297
+ const channelSummary = selectedChannels.length > 0 ? `- Channels "${channelTitles}": rooms ${channelRoomIds.join(", ") || "(no room ids)"} from ${senderSummary(channelSenderIds)}; @mention not required in those rooms.` : "- Channels: disabled (none selected).";
304
298
  await prompter.note([
305
299
  `Inbound policy (Jaguar chat):`,
306
300
  `- Direct messages: enabled for ${senderSummary(directSenderIds)} (no @mention required).`,
307
- `- Channels "${channelTitles}": rooms ${channelRoomIds.join(", ") || "(no room ids)"} from ${senderSummary(channelSenderIds)}; @mention not required in those rooms.`,
301
+ channelSummary,
308
302
  `- Entity rooms (${entityKindSummary}): ${senderSummary(entitySenderIds)}; @mention required.`
309
303
  ].join("\n"), "Omadeus inbound policy");
310
304
  next = {
@@ -327,7 +321,7 @@ const omadeusSetupWizard = {
327
321
  requireMention: "never"
328
322
  },
329
323
  channels: {
330
- enabled: true,
324
+ enabled: selectedChannels.length > 0,
331
325
  allowedRoomIds: channelRoomIds,
332
326
  allowedChannelViewIds: channelViewIds,
333
327
  ...channelSenderIds ? { allowedSenderReferenceIds: channelSenderIds } : {},
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brantrusnak/openclaw-omadeus",
3
- "version": "1.0.3",
3
+ "version": "1.0.4",
4
4
  "private": false,
5
5
  "description": "OpenClaw Omadeus project management channel plugin",
6
6
  "homepage": "https://github.com/brantrusnak/openclaw-omadeus-plugin#readme",
@@ -9,6 +9,27 @@ import type {
9
9
  const CAS_APPLICATION_ID = 1;
10
10
  const CAS_SCOPES = "title,email,avatar,firstName,lastName,birth,phone,countryCode";
11
11
 
12
+ function formatFetchError(label: string, url: string, method: string, err: unknown): Error {
13
+ const base = err instanceof Error ? err.message : String(err);
14
+ const cause =
15
+ err instanceof Error && err.cause instanceof Error ? err.cause.message : undefined;
16
+ const detail = cause && cause !== base ? `${base} (${cause})` : base;
17
+ return new Error(`${label} (${method} ${url}) failed: ${detail}`);
18
+ }
19
+
20
+ async function omadeusFetch(
21
+ label: string,
22
+ url: string,
23
+ init: RequestInit,
24
+ ): Promise<Response> {
25
+ const method = init.method ?? "GET";
26
+ try {
27
+ return await fetch(url, init);
28
+ } catch (err) {
29
+ throw formatFetchError(label, url, method, err);
30
+ }
31
+ }
32
+
12
33
  export async function createCasToken(params: {
13
34
  casUrl: string;
14
35
  email: string;
@@ -17,11 +38,10 @@ export async function createCasToken(params: {
17
38
  const { casUrl, email, password } = params;
18
39
  const url = `${casUrl}/apiv1/tokens`;
19
40
  const jsonBody = JSON.stringify({ email, password });
20
- const res = await fetch(url, {
41
+ const res = await omadeusFetch("CAS token request", url, {
21
42
  method: "CREATE",
22
43
  headers: {
23
44
  "Content-Type": "application/json;charset=UTF-8",
24
- "Content-Length": String(jsonBody.length),
25
45
  },
26
46
  body: jsonBody,
27
47
  });
@@ -44,7 +64,7 @@ export async function getMe(params: {
44
64
  }): Promise<{ email: string }> {
45
65
  const { casUrl, casToken, refreshCookie } = params;
46
66
  const url = `${casUrl}/apiv1/members/me`;
47
- const res = await fetch(url, {
67
+ const res = await omadeusFetch("CAS get member", url, {
48
68
  method: "GET",
49
69
  headers: {
50
70
  Authorization: `Bearer ${casToken}`,
@@ -80,7 +100,7 @@ export async function createAuthorizationCode(params: {
80
100
  Authorization: `Bearer ${token}`,
81
101
  ...(casSession?.refreshCookie ? { Cookie: casSession.refreshCookie } : {}),
82
102
  };
83
- const res = await fetch(url, {
103
+ const res = await omadeusFetch("CAS authorization code request", url, {
84
104
  method: "CREATE",
85
105
  body,
86
106
  headers,
@@ -104,7 +124,7 @@ export async function obtainSessionToken(params: {
104
124
  }): Promise<string> {
105
125
  const { maestroUrl, authorizationCode, organizationId } = params;
106
126
  const url = `${maestroUrl}/dolphin/apiv1/oauth2/tokens`;
107
- const res = await fetch(url, {
127
+ const res = await omadeusFetch("Omadeus session token request", url, {
108
128
  method: "OBTAIN",
109
129
  headers: { "Content-Type": "application/json;charset=UTF-8" },
110
130
  body: JSON.stringify({ authorizationCode, organizationId }),
@@ -126,7 +146,7 @@ export async function listOrganizations(params: {
126
146
  }): Promise<OmadeusOrganization[]> {
127
147
  const { maestroUrl, email } = params;
128
148
  const url = `${maestroUrl}/dolphin/apiv1/organizations`;
129
- const res = await fetch(url, {
149
+ const res = await omadeusFetch("Omadeus list organizations", url, {
130
150
  method: "LIST",
131
151
  headers: { "Content-Type": "application/json;charset=UTF-8" },
132
152
  body: JSON.stringify({ email }),
@@ -145,7 +165,7 @@ export async function listOrganizationMembers(params: {
145
165
  }): Promise<OmadeusOrganizationMember[]> {
146
166
  const { maestroUrl, sessionToken, organizationId } = params;
147
167
  const url = `${maestroUrl}/dolphin/apiv1/organizations/${organizationId}/members`;
148
- const res = await fetch(url, {
168
+ const res = await omadeusFetch("Omadeus list organization members", url, {
149
169
  method: "LIST",
150
170
  headers: {
151
171
  Authorization: `Bearer ${sessionToken}`,
package/src/onboarding.ts CHANGED
@@ -18,7 +18,6 @@ import type {
18
18
  import { OMADEUS_INBOUND_ENTITY_KINDS } from "./types.js";
19
19
 
20
20
  const channel = "omadeus" as const;
21
- const DONE = "__done__";
22
21
 
23
22
  type SelectOption = {
24
23
  value: string;
@@ -26,17 +25,21 @@ type SelectOption = {
26
25
  hint?: string;
27
26
  };
28
27
 
29
- type MultiSelectFn = (args: {
30
- message: string;
31
- options: SelectOption[];
32
- initialValues?: string[];
33
- initialValue?: string[];
34
- }) => Promise<string[]>;
35
-
36
- type MultiSelectPrompter = {
37
- multiSelect?: MultiSelectFn;
38
- multiselect?: MultiSelectFn;
39
- };
28
+ function formatAuthError(err: unknown): string {
29
+ if (!(err instanceof Error)) return String(err);
30
+ const parts = [err.message];
31
+ const { cause } = err;
32
+ if (cause instanceof Error) {
33
+ parts.push(cause.message);
34
+ const code = (cause as Error & { code?: unknown }).code;
35
+ if (typeof code === "string" && code) {
36
+ parts.push(`(${code})`);
37
+ }
38
+ } else if (typeof cause === "string" && cause.trim()) {
39
+ parts.push(cause);
40
+ }
41
+ return parts.join(" — ");
42
+ }
40
43
 
41
44
  async function noteOmadeusAuthHelp(prompter: WizardPrompter): Promise<void> {
42
45
  await prompter.note(
@@ -118,8 +121,21 @@ async function promptChannelSelection(params: {
118
121
  take: 100,
119
122
  });
120
123
  if (channels.length === 0) {
121
- throw new Error("No channels found for this account.");
124
+ await prompter.note(
125
+ "No channels found for this account. Channel listening will stay disabled.",
126
+ "Omadeus channels",
127
+ );
128
+ return [];
122
129
  }
130
+
131
+ const listenToChannels = await prompter.confirm({
132
+ message: "Listen for messages in Omadeus channels?",
133
+ initialValue: (existingChannelViewIds?.length ?? 0) > 0,
134
+ });
135
+ if (!listenToChannels) {
136
+ return [];
137
+ }
138
+
123
139
  const selected = await promptMultiSelect({
124
140
  prompter,
125
141
  message: "Which channels should OpenClaw listen to?",
@@ -133,13 +149,9 @@ async function promptChannelSelection(params: {
133
149
  initialValues:
134
150
  existingChannelViewIds && existingChannelViewIds.length > 0
135
151
  ? existingChannelViewIds.map(String)
136
- : [String(channels[0]!.id)],
152
+ : undefined,
137
153
  });
138
- const chosen = channels.filter((item) => selected.includes(String(item.id)));
139
- if (chosen.length === 0) {
140
- throw new Error("At least one channel must be selected.");
141
- }
142
- return chosen;
154
+ return channels.filter((item) => selected.includes(String(item.id)));
143
155
  }
144
156
 
145
157
  function memberHint(member: OmadeusOrganizationMember): string | undefined {
@@ -154,40 +166,11 @@ async function promptMultiSelect(params: {
154
166
  options: SelectOption[];
155
167
  initialValues?: string[];
156
168
  }): Promise<string[]> {
157
- const multi = params.prompter as unknown as MultiSelectPrompter;
158
- const runMulti = multi.multiSelect ?? multi.multiselect;
159
- if (runMulti) {
160
- return runMulti({
161
- message: params.message,
162
- options: params.options,
163
- initialValues: params.initialValues,
164
- initialValue: params.initialValues,
165
- });
166
- }
167
-
168
- const selected = new Set(params.initialValues ?? []);
169
- while (true) {
170
- const next = await params.prompter.select({
171
- message: `${params.message} (${selected.size} selected)`,
172
- options: [
173
- { value: DONE, label: selected.size > 0 ? "Done" : "Done (select none)" },
174
- ...params.options.map((option) => ({
175
- ...option,
176
- label: selected.has(option.value) ? `[selected] ${option.label}` : option.label,
177
- })),
178
- ],
179
- initialValue: DONE,
180
- });
181
- const value = String(next);
182
- if (value === DONE) {
183
- return [...selected];
184
- }
185
- if (selected.has(value)) {
186
- selected.delete(value);
187
- } else {
188
- selected.add(value);
189
- }
190
- }
169
+ return params.prompter.multiselect({
170
+ message: params.message,
171
+ options: params.options,
172
+ initialValues: params.initialValues,
173
+ });
191
174
  }
192
175
 
193
176
  async function loadSelectableMembers(params: {
@@ -370,8 +353,10 @@ export const omadeusSetupWizard: ChannelSetupWizard = {
370
353
  await prompter.note(`Authenticated as ${payload.email}`, "Omadeus authentication");
371
354
  break;
372
355
  } catch (err) {
373
- const msg = err instanceof Error ? err.message : String(err);
374
- await prompter.note(`Authentication failed: ${msg}`, "Omadeus authentication");
356
+ await prompter.note(
357
+ `Authentication failed: ${formatAuthError(err)}`,
358
+ "Omadeus authentication",
359
+ );
375
360
  const retry = await prompter.confirm({
376
361
  message: "Re-enter email/password and try again?",
377
362
  initialValue: true,
@@ -418,12 +403,15 @@ export const omadeusSetupWizard: ChannelSetupWizard = {
418
403
  existingChannelViewIds: existingInbound?.channels?.allowedChannelViewIds,
419
404
  });
420
405
 
421
- const channelSenderIds = await promptSenderAllowlist({
422
- prompter,
423
- message: "Which users can trigger OpenClaw from allowed channels?",
424
- members,
425
- existingReferenceIds: existingInbound?.channels?.allowedSenderReferenceIds,
426
- });
406
+ const channelSenderIds =
407
+ selectedChannels.length > 0
408
+ ? await promptSenderAllowlist({
409
+ prompter,
410
+ message: "Which users can trigger OpenClaw from allowed channels?",
411
+ members,
412
+ existingReferenceIds: existingInbound?.channels?.allowedSenderReferenceIds,
413
+ })
414
+ : undefined;
427
415
 
428
416
  const entityKinds = await promptEntityKindSelection({
429
417
  prompter,
@@ -456,11 +444,16 @@ export const omadeusSetupWizard: ChannelSetupWizard = {
456
444
  const entityKindSummary =
457
445
  entityKinds.length > 0 ? entityKinds.join(", ") : "none (entity rooms disabled)";
458
446
 
447
+ const channelSummary =
448
+ selectedChannels.length > 0
449
+ ? `- Channels "${channelTitles}": rooms ${channelRoomIds.join(", ") || "(no room ids)"} from ${senderSummary(channelSenderIds)}; @mention not required in those rooms.`
450
+ : "- Channels: disabled (none selected).";
451
+
459
452
  await prompter.note(
460
453
  [
461
454
  `Inbound policy (Jaguar chat):`,
462
455
  `- Direct messages: enabled for ${senderSummary(directSenderIds)} (no @mention required).`,
463
- `- Channels "${channelTitles}": rooms ${channelRoomIds.join(", ") || "(no room ids)"} from ${senderSummary(channelSenderIds)}; @mention not required in those rooms.`,
456
+ channelSummary,
464
457
  `- Entity rooms (${entityKindSummary}): ${senderSummary(entitySenderIds)}; @mention required.`,
465
458
  ].join("\n"),
466
459
  "Omadeus inbound policy",
@@ -486,7 +479,7 @@ export const omadeusSetupWizard: ChannelSetupWizard = {
486
479
  requireMention: "never",
487
480
  },
488
481
  channels: {
489
- enabled: true,
482
+ enabled: selectedChannels.length > 0,
490
483
  allowedRoomIds: channelRoomIds,
491
484
  allowedChannelViewIds: channelViewIds,
492
485
  ...(channelSenderIds ? { allowedSenderReferenceIds: channelSenderIds } : {}),