@brantrusnak/openclaw-omadeus 1.0.3 → 1.0.5

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}`,
@@ -27,10 +27,11 @@ const gatewayState = {
27
27
  };
28
28
  const isUnconfigured = (account) => account.credentialSource === "none";
29
29
  let lastPersistedToken = null;
30
- async function persistSessionToken(token) {
30
+ async function persistSessionToken(token, environment) {
31
31
  if (lastPersistedToken === token) return;
32
32
  const runtime = getOmadeusRuntime();
33
- if ((getOmadeusChannelConfig(runtime.config.current()) ?? {}).sessionToken === token) {
33
+ const section = getOmadeusChannelConfig(runtime.config.current()) ?? {};
34
+ if (section.sessionToken === token && section.sessionTokenEnvironment === environment) {
34
35
  lastPersistedToken = token;
35
36
  return;
36
37
  }
@@ -41,7 +42,8 @@ async function persistSessionToken(token) {
41
42
  ...draft.channels ?? {},
42
43
  omadeus: {
43
44
  ...getOmadeusChannelConfig(draft) ?? {},
44
- sessionToken: token
45
+ sessionToken: token,
46
+ sessionTokenEnvironment: environment
45
47
  }
46
48
  };
47
49
  }
@@ -82,12 +84,12 @@ const omadeusConfigAdapter = createTopLevelChannelConfigAdapter({
82
84
  defaultAccountId: resolveDefaultOmadeusAccountId,
83
85
  deleteMode: "clear-fields",
84
86
  clearBaseFields: [
85
- "casUrl",
86
- "maestroUrl",
87
+ "environment",
87
88
  "email",
88
89
  "password",
89
90
  "organizationId",
90
91
  "sessionToken",
92
+ "sessionTokenEnvironment",
91
93
  "inbound"
92
94
  ],
93
95
  resolveAllowFrom: () => [],
@@ -473,7 +475,7 @@ const omadeusPlugin = {
473
475
  initialToken: account.sessionToken,
474
476
  onRefresh: (token) => {
475
477
  log.info("[omadeus] token refreshed");
476
- persistSessionToken(token).catch((err) => log.warn(`[omadeus] failed to persist session token: ${String(err)}`));
478
+ persistSessionToken(token, account.environment).catch((err) => log.warn(`[omadeus] failed to persist session token: ${String(err)}`));
477
479
  },
478
480
  onError: (err) => {
479
481
  log.error(`[omadeus] token refresh failed: ${err.message}`);
@@ -1,5 +1,5 @@
1
1
  import { DEFAULT_ACCOUNT_ID } from "../runtime-api.js";
2
- import "./defaults.js";
2
+ import { getOmadeusEnvironmentUrls, resolveOmadeusEnvironment } from "./defaults.js";
3
3
  //#region src/config.ts
4
4
  function getOmadeusChannelConfig(cfg) {
5
5
  return cfg.channels?.["omadeus"];
@@ -14,11 +14,15 @@ function resolveDefaultOmadeusAccountId(_cfg) {
14
14
  function resolveOmadeusAccount(params) {
15
15
  const { cfg } = params;
16
16
  const section = getOmadeusChannelConfig(cfg) ?? {};
17
+ const environment = resolveOmadeusEnvironment(section.environment);
18
+ const { casUrl, maestroUrl } = getOmadeusEnvironmentUrls(environment);
17
19
  const envCredentials = resolveOmadeusEnvCredentials();
18
20
  const email = section.email?.trim() || envCredentials?.email || "";
19
21
  const password = section.password?.trim() || envCredentials?.password || "";
20
22
  const orgId = section.organizationId ?? envCredentials?.organizationId;
21
- const sessionToken = section.sessionToken?.trim() ?? "";
23
+ const rawSessionToken = section.sessionToken?.trim() ?? "";
24
+ const sessionTokenEnvironment = resolveOmadeusEnvironment(section.sessionTokenEnvironment);
25
+ const sessionToken = Boolean(rawSessionToken) && sessionTokenEnvironment === environment ? rawSessionToken : "";
22
26
  const hasCredentials = Boolean(email && password && orgId);
23
27
  const hasSessionToken = Boolean(sessionToken);
24
28
  const credentialSource = Boolean(section.email?.trim() && section.password?.trim() && section.organizationId) ? "config" : hasCredentials ? "env" : hasSessionToken ? "session" : "none";
@@ -27,8 +31,9 @@ function resolveOmadeusAccount(params) {
27
31
  name: "Omadeus",
28
32
  enabled: section.enabled !== false,
29
33
  config: section,
30
- casUrl: section.casUrl?.trim() || "https://dev1-cas.rouztech.com",
31
- maestroUrl: section.maestroUrl?.trim() || "https://dev3-maestro.rouztech.com",
34
+ environment,
35
+ casUrl,
36
+ maestroUrl,
32
37
  email,
33
38
  password,
34
39
  organizationId: orgId ?? 0,
@@ -1,5 +1,31 @@
1
- //#region src/defaults.ts
2
- const OMADEUS_CAS_URL = "https://dev1-cas.rouztech.com";
3
- const OMADEUS_MAESTRO_URL = "https://dev3-maestro.rouztech.com";
1
+ const OMADEUS_ENVIRONMENTS = {
2
+ production: {
3
+ label: "Production",
4
+ casUrl: "https://xas.xeba.tech",
5
+ maestroUrl: "https://maestro.xeba.tech"
6
+ },
7
+ staging: {
8
+ label: "Staging",
9
+ casUrl: "https://staging-xas.xeba.tech",
10
+ maestroUrl: "https://staging.xeba.tech"
11
+ },
12
+ dev: {
13
+ label: "Dev",
14
+ casUrl: "https://dev1-cas.rouztech.com",
15
+ maestroUrl: "https://dev1-maestro.rouztech.com"
16
+ }
17
+ };
18
+ const OMADEUS_ENVIRONMENT_SET = new Set(Object.keys(OMADEUS_ENVIRONMENTS));
19
+ function resolveOmadeusEnvironment(value) {
20
+ if (typeof value === "string" && OMADEUS_ENVIRONMENT_SET.has(value)) return value;
21
+ return "dev";
22
+ }
23
+ function getOmadeusEnvironmentUrls(env) {
24
+ const config = OMADEUS_ENVIRONMENTS[env];
25
+ return {
26
+ casUrl: config.casUrl,
27
+ maestroUrl: config.maestroUrl
28
+ };
29
+ }
4
30
  //#endregion
5
- export { OMADEUS_CAS_URL, OMADEUS_MAESTRO_URL };
31
+ export { OMADEUS_ENVIRONMENTS, getOmadeusEnvironmentUrls, resolveOmadeusEnvironment };
@@ -1,4 +1,4 @@
1
- import { OMADEUS_CAS_URL, OMADEUS_MAESTRO_URL } from "./defaults.js";
1
+ import { OMADEUS_ENVIRONMENTS, getOmadeusEnvironmentUrls, resolveOmadeusEnvironment } from "./defaults.js";
2
2
  import { getOmadeusChannelConfig, resolveOmadeusAccount } from "./config.js";
3
3
  import { listOrganizationMembers, listOrganizations } from "./api/auth.api.js";
4
4
  import { formatMemberLabel } from "./member-resolve.js";
@@ -8,18 +8,37 @@ 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__";
12
- async function noteOmadeusAuthHelp(prompter) {
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
+ }
22
+ async function noteOmadeusAuthHelp(prompter, environment) {
23
+ const envLabel = OMADEUS_ENVIRONMENTS[environment].label;
13
24
  await prompter.note([
14
- "Omadeus authenticates via CAS + Maestro (email + password + organization).",
15
- "You need:",
16
- " - Email + password",
17
- " - Organization ID (we can look it up for you)",
18
- `CAS URL: ${OMADEUS_CAS_URL}`,
19
- `Maestro URL: ${OMADEUS_MAESTRO_URL}`,
20
- "Env vars supported: OMADEUS_EMAIL, OMADEUS_PASSWORD, OMADEUS_ORGANIZATION_ID."
25
+ `Connect OpenClaw to Omadeus (${envLabel}).`,
26
+ "",
27
+ "We'll ask for your email and password, then show the organizations on your account so you can pick one."
21
28
  ].join("\n"), "Omadeus setup");
22
29
  }
30
+ async function promptEnvironment(prompter, existing) {
31
+ const initial = existing ?? "dev";
32
+ return resolveOmadeusEnvironment(await prompter.select({
33
+ message: "Select Omadeus environment",
34
+ options: Object.keys(OMADEUS_ENVIRONMENTS).map((env) => ({
35
+ value: env,
36
+ label: OMADEUS_ENVIRONMENTS[env].label,
37
+ hint: getOmadeusEnvironmentUrls(env).maestroUrl
38
+ })),
39
+ initialValue: initial
40
+ }));
41
+ }
23
42
  async function promptOrganizationId(params) {
24
43
  const { prompter, maestroUrl, email, existing } = params;
25
44
  try {
@@ -66,7 +85,14 @@ async function promptChannelSelection(params) {
66
85
  skip: 0,
67
86
  take: 100
68
87
  });
69
- if (channels.length === 0) throw new Error("No channels found for this account.");
88
+ if (channels.length === 0) {
89
+ await prompter.note("No channels found for this account. Channel listening will stay disabled.", "Omadeus channels");
90
+ return [];
91
+ }
92
+ if (!await prompter.confirm({
93
+ message: "Listen for messages in Omadeus channels?",
94
+ initialValue: (existingChannelViewIds?.length ?? 0) > 0
95
+ })) return [];
70
96
  const selected = await promptMultiSelect({
71
97
  prompter,
72
98
  message: "Which channels should OpenClaw listen to?",
@@ -75,11 +101,9 @@ async function promptChannelSelection(params) {
75
101
  label: item.title || `Channel ${item.id}`,
76
102
  hint: [item.privateRoomId ? `private:${item.privateRoomId}` : void 0, item.publicRoomId ? `public:${item.publicRoomId}` : void 0].filter(Boolean).join(" | ")
77
103
  })),
78
- initialValues: existingChannelViewIds && existingChannelViewIds.length > 0 ? existingChannelViewIds.map(String) : [String(channels[0].id)]
104
+ initialValues: existingChannelViewIds && existingChannelViewIds.length > 0 ? existingChannelViewIds.map(String) : void 0
79
105
  });
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;
106
+ return channels.filter((item) => selected.includes(String(item.id)));
83
107
  }
84
108
  function memberHint(member) {
85
109
  const parts = [
@@ -90,32 +114,11 @@ function memberHint(member) {
90
114
  return parts.length > 0 ? parts.join(" | ") : void 0;
91
115
  }
92
116
  async function promptMultiSelect(params) {
93
- const multi = params.prompter;
94
- const runMulti = multi.multiSelect ?? multi.multiselect;
95
- if (runMulti) return runMulti({
117
+ return params.prompter.multiselect({
96
118
  message: params.message,
97
119
  options: params.options,
98
- initialValues: params.initialValues,
99
- initialValue: params.initialValues
120
+ initialValues: params.initialValues
100
121
  });
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
122
  }
120
123
  async function loadSelectableMembers(params) {
121
124
  const excluded = new Set(params.excludeReferenceIds ?? []);
@@ -214,11 +217,11 @@ const omadeusSetupWizard = {
214
217
  const account = resolveOmadeusAccount({ cfg });
215
218
  const section = getOmadeusChannelConfig(cfg) ?? {};
216
219
  let next = cfg;
217
- if (account.credentialSource === "none") await noteOmadeusAuthHelp(prompter);
220
+ const environment = await promptEnvironment(prompter, section.environment ? resolveOmadeusEnvironment(section.environment) : void 0);
221
+ const { casUrl, maestroUrl } = getOmadeusEnvironmentUrls(environment);
222
+ if (account.credentialSource === "none") await noteOmadeusAuthHelp(prompter, environment);
218
223
  const envEmail = process.env.OMADEUS_EMAIL?.trim();
219
224
  const envPassword = process.env.OMADEUS_PASSWORD?.trim();
220
- const casUrl = OMADEUS_CAS_URL;
221
- const maestroUrl = OMADEUS_MAESTRO_URL;
222
225
  let { email, password } = await promptCredentials(prompter, {
223
226
  email: section.email ?? envEmail,
224
227
  password: section.password ?? envPassword
@@ -244,8 +247,7 @@ const omadeusSetupWizard = {
244
247
  await prompter.note(`Authenticated as ${payload.email}`, "Omadeus authentication");
245
248
  break;
246
249
  } catch (err) {
247
- const msg = err instanceof Error ? err.message : String(err);
248
- await prompter.note(`Authentication failed: ${msg}`, "Omadeus authentication");
250
+ await prompter.note(`Authentication failed: ${formatAuthError(err)}`, "Omadeus authentication");
249
251
  if (!await prompter.confirm({
250
252
  message: "Re-enter email/password and try again?",
251
253
  initialValue: true
@@ -280,12 +282,12 @@ const omadeusSetupWizard = {
280
282
  memberReferenceId: selfReferenceId,
281
283
  existingChannelViewIds: existingInbound?.channels?.allowedChannelViewIds
282
284
  });
283
- const channelSenderIds = await promptSenderAllowlist({
285
+ const channelSenderIds = selectedChannels.length > 0 ? await promptSenderAllowlist({
284
286
  prompter,
285
287
  message: "Which users can trigger OpenClaw from allowed channels?",
286
288
  members,
287
289
  existingReferenceIds: existingInbound?.channels?.allowedSenderReferenceIds
288
- });
290
+ }) : void 0;
289
291
  const entityKinds = await promptEntityKindSelection({
290
292
  prompter,
291
293
  existingKinds: existingInbound?.entities?.allowedKinds
@@ -301,10 +303,11 @@ const omadeusSetupWizard = {
301
303
  const channelTitles = selectedChannels.map((selectedChannel) => selectedChannel.title || `Channel ${selectedChannel.id}`).join(", ");
302
304
  const senderSummary = (ids) => ids && ids.length > 0 ? ids.join(", ") : "all users";
303
305
  const entityKindSummary = entityKinds.length > 0 ? entityKinds.join(", ") : "none (entity rooms disabled)";
306
+ 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
307
  await prompter.note([
305
308
  `Inbound policy (Jaguar chat):`,
306
309
  `- 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.`,
310
+ channelSummary,
308
311
  `- Entity rooms (${entityKindSummary}): ${senderSummary(entitySenderIds)}; @mention required.`
309
312
  ].join("\n"), "Omadeus inbound policy");
310
313
  next = {
@@ -313,12 +316,12 @@ const omadeusSetupWizard = {
313
316
  ...next.channels,
314
317
  omadeus: {
315
318
  enabled: true,
316
- casUrl,
317
- maestroUrl,
319
+ environment,
318
320
  email,
319
321
  password,
320
322
  organizationId,
321
323
  sessionToken,
324
+ sessionTokenEnvironment: environment,
322
325
  inbound: {
323
326
  version: 1,
324
327
  direct: {
@@ -327,7 +330,7 @@ const omadeusSetupWizard = {
327
330
  requireMention: "never"
328
331
  },
329
332
  channels: {
330
- enabled: true,
333
+ enabled: selectedChannels.length > 0,
331
334
  allowedRoomIds: channelRoomIds,
332
335
  allowedChannelViewIds: channelViewIds,
333
336
  ...channelSenderIds ? { allowedSenderReferenceIds: channelSenderIds } : {},
@@ -1,3 +1,4 @@
1
+ import { resolveOmadeusEnvironment } from "./defaults.js";
1
2
  //#region src/setup-core.ts
2
3
  function readSetupStringField(input, key) {
3
4
  const value = input[key];
@@ -18,8 +19,8 @@ const omadeusSetupAdapter = {
18
19
  },
19
20
  applyAccountConfig: ({ cfg, input }) => {
20
21
  const rawInput = input;
21
- const casUrl = input.httpUrl?.trim() || void 0;
22
- const maestroUrl = input.url?.trim() || void 0;
22
+ const environmentRaw = readSetupStringField(rawInput, "environment");
23
+ const environment = environmentRaw ? resolveOmadeusEnvironment(environmentRaw) : void 0;
23
24
  const email = readSetupStringField(rawInput, "email");
24
25
  const password = input.password?.trim() || void 0;
25
26
  const organizationId = readSetupNumberField(rawInput, "organizationId");
@@ -32,8 +33,7 @@ const omadeusSetupAdapter = {
32
33
  omadeus: {
33
34
  ...omadeusPrevious,
34
35
  enabled: true,
35
- ...casUrl ? { casUrl } : {},
36
- ...maestroUrl ? { maestroUrl } : {},
36
+ ...environment ? { environment } : {},
37
37
  ...email ? { email } : {},
38
38
  ...password ? { password } : {},
39
39
  ...organizationId ? { organizationId } : {}
@@ -6,12 +6,18 @@
6
6
  "additionalProperties": false,
7
7
  "properties": {
8
8
  "enabled": { "type": "boolean" },
9
- "casUrl": { "type": "string" },
10
- "maestroUrl": { "type": "string" },
9
+ "environment": {
10
+ "type": "string",
11
+ "enum": ["production", "staging", "dev"]
12
+ },
11
13
  "email": { "type": "string" },
12
14
  "password": { "type": "string" },
13
15
  "organizationId": { "type": "number" },
14
16
  "sessionToken": { "type": "string" },
17
+ "sessionTokenEnvironment": {
18
+ "type": "string",
19
+ "enum": ["production", "staging", "dev"]
20
+ },
15
21
  "inbound": {
16
22
  "type": "object",
17
23
  "additionalProperties": false,
@@ -92,12 +98,18 @@
92
98
  "additionalProperties": false,
93
99
  "properties": {
94
100
  "enabled": { "type": "boolean" },
95
- "casUrl": { "type": "string" },
96
- "maestroUrl": { "type": "string" },
101
+ "environment": {
102
+ "type": "string",
103
+ "enum": ["production", "staging", "dev"]
104
+ },
97
105
  "email": { "type": "string" },
98
106
  "password": { "type": "string" },
99
107
  "organizationId": { "type": "number" },
100
108
  "sessionToken": { "type": "string" },
109
+ "sessionTokenEnvironment": {
110
+ "type": "string",
111
+ "enum": ["production", "staging", "dev"]
112
+ },
101
113
  "inbound": {
102
114
  "type": "object",
103
115
  "additionalProperties": false,
@@ -172,13 +184,8 @@
172
184
  }
173
185
  },
174
186
  "uiHints": {
175
- "casUrl": {
176
- "label": "CAS URL",
177
- "placeholder": "https://dev1-cas.rouztech.com"
178
- },
179
- "maestroUrl": {
180
- "label": "Maestro URL",
181
- "placeholder": "https://dev3-maestro.rouztech.com"
187
+ "environment": {
188
+ "label": "Environment"
182
189
  },
183
190
  "email": {
184
191
  "label": "Email"
@@ -195,6 +202,10 @@
195
202
  "sensitive": true,
196
203
  "advanced": true
197
204
  },
205
+ "sessionTokenEnvironment": {
206
+ "label": "Session token environment",
207
+ "advanced": true
208
+ },
198
209
  "inbound": {
199
210
  "label": "Inbound policy (Jaguar chat)",
200
211
  "advanced": true
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.5",
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/channel.ts CHANGED
@@ -57,12 +57,15 @@ const isUnconfigured = (account: Account) => account.credentialSource === "none"
57
57
 
58
58
  let lastPersistedToken: string | null = null;
59
59
 
60
- async function persistSessionToken(token: string): Promise<void> {
60
+ async function persistSessionToken(
61
+ token: string,
62
+ environment: Account["environment"],
63
+ ): Promise<void> {
61
64
  if (lastPersistedToken === token) return;
62
65
  const runtime = getOmadeusRuntime();
63
66
  const cfg = runtime.config.current() as OpenClawConfig;
64
67
  const section = getOmadeusChannelConfig(cfg) ?? {};
65
- if (section.sessionToken === token) {
68
+ if (section.sessionToken === token && section.sessionTokenEnvironment === environment) {
66
69
  lastPersistedToken = token;
67
70
  return;
68
71
  }
@@ -74,6 +77,7 @@ async function persistSessionToken(token: string): Promise<void> {
74
77
  omadeus: {
75
78
  ...(getOmadeusChannelConfig(draft) ?? {}),
76
79
  sessionToken: token,
80
+ sessionTokenEnvironment: environment,
77
81
  },
78
82
  };
79
83
  },
@@ -103,12 +107,12 @@ const omadeusConfigAdapter = createTopLevelChannelConfigAdapter<Account>({
103
107
  defaultAccountId: resolveDefaultOmadeusAccountId,
104
108
  deleteMode: "clear-fields",
105
109
  clearBaseFields: [
106
- "casUrl",
107
- "maestroUrl",
110
+ "environment",
108
111
  "email",
109
112
  "password",
110
113
  "organizationId",
111
114
  "sessionToken",
115
+ "sessionTokenEnvironment",
112
116
  "inbound",
113
117
  ],
114
118
  // Keep adapter contract satisfied even though Omadeus no longer uses DM allowlists.
@@ -561,7 +565,7 @@ export const omadeusPlugin: ChannelPlugin<Account> = {
561
565
  initialToken: account.sessionToken,
562
566
  onRefresh: (token) => {
563
567
  log.info("[omadeus] token refreshed");
564
- void persistSessionToken(token).catch((err) =>
568
+ void persistSessionToken(token, account.environment).catch((err) =>
565
569
  log.warn(`[omadeus] failed to persist session token: ${String(err)}`),
566
570
  );
567
571
  },
package/src/config.ts CHANGED
@@ -1,5 +1,8 @@
1
1
  import { DEFAULT_ACCOUNT_ID, type OpenClawConfig } from "../runtime-api.js";
2
- import { OMADEUS_CAS_URL, OMADEUS_MAESTRO_URL } from "./defaults.js";
2
+ import {
3
+ getOmadeusEnvironmentUrls,
4
+ resolveOmadeusEnvironment,
5
+ } from "./defaults.js";
3
6
  import type { OmadeusChannelConfig, ResolvedOmadeusAccount } from "./types.js";
4
7
 
5
8
  export function getOmadeusChannelConfig(cfg: OpenClawConfig): OmadeusChannelConfig | undefined {
@@ -24,11 +27,17 @@ export function resolveOmadeusAccount(params: {
24
27
  }): ResolvedOmadeusAccount {
25
28
  const { cfg } = params;
26
29
  const section = getOmadeusChannelConfig(cfg) ?? {};
30
+ const environment = resolveOmadeusEnvironment(section.environment);
31
+ const { casUrl, maestroUrl } = getOmadeusEnvironmentUrls(environment);
27
32
  const envCredentials = resolveOmadeusEnvCredentials();
28
33
  const email = section.email?.trim() || envCredentials?.email || "";
29
34
  const password = section.password?.trim() || envCredentials?.password || "";
30
35
  const orgId = section.organizationId ?? envCredentials?.organizationId;
31
- const sessionToken = section.sessionToken?.trim() ?? "";
36
+ const rawSessionToken = section.sessionToken?.trim() ?? "";
37
+ const sessionTokenEnvironment = resolveOmadeusEnvironment(section.sessionTokenEnvironment);
38
+ const sessionTokenValid =
39
+ Boolean(rawSessionToken) && sessionTokenEnvironment === environment;
40
+ const sessionToken = sessionTokenValid ? rawSessionToken : "";
32
41
  const hasCredentials = Boolean(email && password && orgId);
33
42
  const hasSessionToken = Boolean(sessionToken);
34
43
  const hasConfigCredentials = Boolean(
@@ -47,8 +56,9 @@ export function resolveOmadeusAccount(params: {
47
56
  name: "Omadeus",
48
57
  enabled: section.enabled !== false,
49
58
  config: section,
50
- casUrl: section.casUrl?.trim() || OMADEUS_CAS_URL,
51
- maestroUrl: section.maestroUrl?.trim() || OMADEUS_MAESTRO_URL,
59
+ environment,
60
+ casUrl,
61
+ maestroUrl,
52
62
  email,
53
63
  password,
54
64
  organizationId: orgId ?? 0,
package/src/defaults.ts CHANGED
@@ -1,2 +1,44 @@
1
- export const OMADEUS_CAS_URL = "https://dev1-cas.rouztech.com";
2
- export const OMADEUS_MAESTRO_URL = "https://dev3-maestro.rouztech.com";
1
+ export type OmadeusEnvironment = "production" | "staging" | "dev";
2
+
3
+ export const OMADEUS_DEFAULT_ENVIRONMENT: OmadeusEnvironment = "dev";
4
+
5
+ export type OmadeusEnvironmentConfig = {
6
+ label: string;
7
+ casUrl: string;
8
+ maestroUrl: string;
9
+ };
10
+
11
+ export const OMADEUS_ENVIRONMENTS: Record<OmadeusEnvironment, OmadeusEnvironmentConfig> = {
12
+ production: {
13
+ label: "Production",
14
+ casUrl: "https://xas.xeba.tech",
15
+ maestroUrl: "https://maestro.xeba.tech",
16
+ },
17
+ staging: {
18
+ label: "Staging",
19
+ casUrl: "https://staging-xas.xeba.tech",
20
+ maestroUrl: "https://staging.xeba.tech",
21
+ },
22
+ dev: {
23
+ label: "Dev",
24
+ casUrl: "https://dev1-cas.rouztech.com",
25
+ maestroUrl: "https://dev1-maestro.rouztech.com",
26
+ },
27
+ };
28
+
29
+ const OMADEUS_ENVIRONMENT_SET = new Set<string>(Object.keys(OMADEUS_ENVIRONMENTS));
30
+
31
+ export function resolveOmadeusEnvironment(value: unknown): OmadeusEnvironment {
32
+ if (typeof value === "string" && OMADEUS_ENVIRONMENT_SET.has(value)) {
33
+ return value as OmadeusEnvironment;
34
+ }
35
+ return OMADEUS_DEFAULT_ENVIRONMENT;
36
+ }
37
+
38
+ export function getOmadeusEnvironmentUrls(env: OmadeusEnvironment): {
39
+ casUrl: string;
40
+ maestroUrl: string;
41
+ } {
42
+ const config = OMADEUS_ENVIRONMENTS[env];
43
+ return { casUrl: config.casUrl, maestroUrl: config.maestroUrl };
44
+ }
package/src/onboarding.ts CHANGED
@@ -7,7 +7,13 @@ import {
7
7
  import { listMemberChannelViews } from "./api/channel.api.js";
8
8
  import { authenticate } from "./auth.js";
9
9
  import { getOmadeusChannelConfig, resolveOmadeusAccount } from "./config.js";
10
- import { OMADEUS_CAS_URL, OMADEUS_MAESTRO_URL } from "./defaults.js";
10
+ import {
11
+ getOmadeusEnvironmentUrls,
12
+ OMADEUS_DEFAULT_ENVIRONMENT,
13
+ OMADEUS_ENVIRONMENTS,
14
+ resolveOmadeusEnvironment,
15
+ type OmadeusEnvironment,
16
+ } from "./defaults.js";
11
17
  import { formatMemberLabel } from "./member-resolve.js";
12
18
  import type {
13
19
  OmadeusChannelConfig,
@@ -18,7 +24,6 @@ import type {
18
24
  import { OMADEUS_INBOUND_ENTITY_KINDS } from "./types.js";
19
25
 
20
26
  const channel = "omadeus" as const;
21
- const DONE = "__done__";
22
27
 
23
28
  type SelectOption = {
24
29
  value: string;
@@ -26,33 +31,54 @@ type SelectOption = {
26
31
  hint?: string;
27
32
  };
28
33
 
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
- };
34
+ function formatAuthError(err: unknown): string {
35
+ if (!(err instanceof Error)) return String(err);
36
+ const parts = [err.message];
37
+ const { cause } = err;
38
+ if (cause instanceof Error) {
39
+ parts.push(cause.message);
40
+ const code = (cause as Error & { code?: unknown }).code;
41
+ if (typeof code === "string" && code) {
42
+ parts.push(`(${code})`);
43
+ }
44
+ } else if (typeof cause === "string" && cause.trim()) {
45
+ parts.push(cause);
46
+ }
47
+ return parts.join(" — ");
48
+ }
40
49
 
41
- async function noteOmadeusAuthHelp(prompter: WizardPrompter): Promise<void> {
50
+ async function noteOmadeusAuthHelp(
51
+ prompter: WizardPrompter,
52
+ environment: OmadeusEnvironment,
53
+ ): Promise<void> {
54
+ const envLabel = OMADEUS_ENVIRONMENTS[environment].label;
42
55
  await prompter.note(
43
56
  [
44
- "Omadeus authenticates via CAS + Maestro (email + password + organization).",
45
- "You need:",
46
- " - Email + password",
47
- " - Organization ID (we can look it up for you)",
48
- `CAS URL: ${OMADEUS_CAS_URL}`,
49
- `Maestro URL: ${OMADEUS_MAESTRO_URL}`,
50
- "Env vars supported: OMADEUS_EMAIL, OMADEUS_PASSWORD, OMADEUS_ORGANIZATION_ID.",
57
+ `Connect OpenClaw to Omadeus (${envLabel}).`,
58
+ "",
59
+ "We'll ask for your email and password, then show the organizations on your account so you can pick one.",
51
60
  ].join("\n"),
52
61
  "Omadeus setup",
53
62
  );
54
63
  }
55
64
 
65
+ async function promptEnvironment(
66
+ prompter: WizardPrompter,
67
+ existing?: OmadeusEnvironment,
68
+ ): Promise<OmadeusEnvironment> {
69
+ const initial = existing ?? OMADEUS_DEFAULT_ENVIRONMENT;
70
+ const choice = await prompter.select({
71
+ message: "Select Omadeus environment",
72
+ options: (Object.keys(OMADEUS_ENVIRONMENTS) as OmadeusEnvironment[]).map((env) => ({
73
+ value: env,
74
+ label: OMADEUS_ENVIRONMENTS[env].label,
75
+ hint: getOmadeusEnvironmentUrls(env).maestroUrl,
76
+ })),
77
+ initialValue: initial,
78
+ });
79
+ return resolveOmadeusEnvironment(choice);
80
+ }
81
+
56
82
  async function promptOrganizationId(params: {
57
83
  prompter: WizardPrompter;
58
84
  maestroUrl: string;
@@ -118,8 +144,21 @@ async function promptChannelSelection(params: {
118
144
  take: 100,
119
145
  });
120
146
  if (channels.length === 0) {
121
- throw new Error("No channels found for this account.");
147
+ await prompter.note(
148
+ "No channels found for this account. Channel listening will stay disabled.",
149
+ "Omadeus channels",
150
+ );
151
+ return [];
152
+ }
153
+
154
+ const listenToChannels = await prompter.confirm({
155
+ message: "Listen for messages in Omadeus channels?",
156
+ initialValue: (existingChannelViewIds?.length ?? 0) > 0,
157
+ });
158
+ if (!listenToChannels) {
159
+ return [];
122
160
  }
161
+
123
162
  const selected = await promptMultiSelect({
124
163
  prompter,
125
164
  message: "Which channels should OpenClaw listen to?",
@@ -133,13 +172,9 @@ async function promptChannelSelection(params: {
133
172
  initialValues:
134
173
  existingChannelViewIds && existingChannelViewIds.length > 0
135
174
  ? existingChannelViewIds.map(String)
136
- : [String(channels[0]!.id)],
175
+ : undefined,
137
176
  });
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;
177
+ return channels.filter((item) => selected.includes(String(item.id)));
143
178
  }
144
179
 
145
180
  function memberHint(member: OmadeusOrganizationMember): string | undefined {
@@ -154,40 +189,11 @@ async function promptMultiSelect(params: {
154
189
  options: SelectOption[];
155
190
  initialValues?: string[];
156
191
  }): 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
- }
192
+ return params.prompter.multiselect({
193
+ message: params.message,
194
+ options: params.options,
195
+ initialValues: params.initialValues,
196
+ });
191
197
  }
192
198
 
193
199
  async function loadSelectableMembers(params: {
@@ -332,16 +338,19 @@ export const omadeusSetupWizard: ChannelSetupWizard = {
332
338
  const section = getOmadeusChannelConfig(cfg) ?? {};
333
339
  let next = cfg;
334
340
 
341
+ const environment = await promptEnvironment(
342
+ prompter,
343
+ section.environment ? resolveOmadeusEnvironment(section.environment) : undefined,
344
+ );
345
+ const { casUrl, maestroUrl } = getOmadeusEnvironmentUrls(environment);
346
+
335
347
  if (account.credentialSource === "none") {
336
- await noteOmadeusAuthHelp(prompter);
348
+ await noteOmadeusAuthHelp(prompter, environment);
337
349
  }
338
350
 
339
351
  const envEmail = process.env.OMADEUS_EMAIL?.trim();
340
352
  const envPassword = process.env.OMADEUS_PASSWORD?.trim();
341
353
 
342
- const casUrl = OMADEUS_CAS_URL;
343
- const maestroUrl = OMADEUS_MAESTRO_URL;
344
-
345
354
  let { email, password } = await promptCredentials(prompter, {
346
355
  email: section.email ?? envEmail,
347
356
  password: section.password ?? envPassword,
@@ -370,8 +379,10 @@ export const omadeusSetupWizard: ChannelSetupWizard = {
370
379
  await prompter.note(`Authenticated as ${payload.email}`, "Omadeus authentication");
371
380
  break;
372
381
  } catch (err) {
373
- const msg = err instanceof Error ? err.message : String(err);
374
- await prompter.note(`Authentication failed: ${msg}`, "Omadeus authentication");
382
+ await prompter.note(
383
+ `Authentication failed: ${formatAuthError(err)}`,
384
+ "Omadeus authentication",
385
+ );
375
386
  const retry = await prompter.confirm({
376
387
  message: "Re-enter email/password and try again?",
377
388
  initialValue: true,
@@ -418,12 +429,15 @@ export const omadeusSetupWizard: ChannelSetupWizard = {
418
429
  existingChannelViewIds: existingInbound?.channels?.allowedChannelViewIds,
419
430
  });
420
431
 
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
- });
432
+ const channelSenderIds =
433
+ selectedChannels.length > 0
434
+ ? await promptSenderAllowlist({
435
+ prompter,
436
+ message: "Which users can trigger OpenClaw from allowed channels?",
437
+ members,
438
+ existingReferenceIds: existingInbound?.channels?.allowedSenderReferenceIds,
439
+ })
440
+ : undefined;
427
441
 
428
442
  const entityKinds = await promptEntityKindSelection({
429
443
  prompter,
@@ -456,11 +470,16 @@ export const omadeusSetupWizard: ChannelSetupWizard = {
456
470
  const entityKindSummary =
457
471
  entityKinds.length > 0 ? entityKinds.join(", ") : "none (entity rooms disabled)";
458
472
 
473
+ const channelSummary =
474
+ selectedChannels.length > 0
475
+ ? `- Channels "${channelTitles}": rooms ${channelRoomIds.join(", ") || "(no room ids)"} from ${senderSummary(channelSenderIds)}; @mention not required in those rooms.`
476
+ : "- Channels: disabled (none selected).";
477
+
459
478
  await prompter.note(
460
479
  [
461
480
  `Inbound policy (Jaguar chat):`,
462
481
  `- 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.`,
482
+ channelSummary,
464
483
  `- Entity rooms (${entityKindSummary}): ${senderSummary(entitySenderIds)}; @mention required.`,
465
484
  ].join("\n"),
466
485
  "Omadeus inbound policy",
@@ -472,12 +491,12 @@ export const omadeusSetupWizard: ChannelSetupWizard = {
472
491
  ...next.channels,
473
492
  omadeus: {
474
493
  enabled: true,
475
- casUrl,
476
- maestroUrl,
494
+ environment,
477
495
  email,
478
496
  password,
479
497
  organizationId,
480
498
  sessionToken,
499
+ sessionTokenEnvironment: environment,
481
500
  inbound: {
482
501
  version: 1,
483
502
  direct: {
@@ -486,7 +505,7 @@ export const omadeusSetupWizard: ChannelSetupWizard = {
486
505
  requireMention: "never",
487
506
  },
488
507
  channels: {
489
- enabled: true,
508
+ enabled: selectedChannels.length > 0,
490
509
  allowedRoomIds: channelRoomIds,
491
510
  allowedChannelViewIds: channelViewIds,
492
511
  ...(channelSenderIds ? { allowedSenderReferenceIds: channelSenderIds } : {}),
package/src/setup-core.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import type { ChannelSetupAdapter } from "openclaw/plugin-sdk/setup";
2
2
  import type { OpenClawConfig } from "../runtime-api.js";
3
+ import { resolveOmadeusEnvironment } from "./defaults.js";
3
4
 
4
5
  function readSetupStringField(input: Record<string, unknown>, key: string): string | undefined {
5
6
  const value = input[key];
@@ -29,8 +30,8 @@ export const omadeusSetupAdapter: ChannelSetupAdapter = {
29
30
  },
30
31
  applyAccountConfig: ({ cfg, input }) => {
31
32
  const rawInput = input as Record<string, unknown>;
32
- const casUrl = input.httpUrl?.trim() || undefined;
33
- const maestroUrl = input.url?.trim() || undefined;
33
+ const environmentRaw = readSetupStringField(rawInput, "environment");
34
+ const environment = environmentRaw ? resolveOmadeusEnvironment(environmentRaw) : undefined;
34
35
  const email = readSetupStringField(rawInput, "email");
35
36
  const password = input.password?.trim() || undefined;
36
37
  const organizationId = readSetupNumberField(rawInput, "organizationId");
@@ -51,8 +52,7 @@ export const omadeusSetupAdapter: ChannelSetupAdapter = {
51
52
  omadeus: {
52
53
  ...omadeusPrevious,
53
54
  enabled: true,
54
- ...(casUrl ? { casUrl } : {}),
55
- ...(maestroUrl ? { maestroUrl } : {}),
55
+ ...(environment ? { environment } : {}),
56
56
  ...(email ? { email } : {}),
57
57
  ...(password ? { password } : {}),
58
58
  ...(organizationId ? { organizationId } : {}),
package/src/types.ts CHANGED
@@ -1,3 +1,5 @@
1
+ import type { OmadeusEnvironment } from "./defaults.js";
2
+
1
3
  // ---------------------------------------------------------------------------
2
4
  // Omadeus config shape (stored under channels.omadeus in OpenClaw config)
3
5
  // ---------------------------------------------------------------------------
@@ -61,13 +63,14 @@ export type OmadeusInboundPolicy = {
61
63
 
62
64
  export type OmadeusChannelConfig = {
63
65
  enabled?: boolean;
64
- casUrl?: string;
65
- maestroUrl?: string;
66
+ environment?: OmadeusEnvironment;
66
67
  email?: string;
67
68
  password?: string;
68
69
  organizationId?: number;
69
70
  /** Cached Omadeus session JWT obtained during onboarding/startup. */
70
71
  sessionToken?: string;
72
+ /** Environment the cached sessionToken was minted under (must match `environment`). */
73
+ sessionTokenEnvironment?: OmadeusEnvironment;
71
74
  /** Jaguar chat ingress allowlists and mention rules. */
72
75
  inbound?: OmadeusInboundPolicy;
73
76
  };
@@ -77,6 +80,7 @@ export type ResolvedOmadeusAccount = {
77
80
  name?: string;
78
81
  enabled: boolean;
79
82
  config: OmadeusChannelConfig;
83
+ environment: OmadeusEnvironment;
80
84
  casUrl: string;
81
85
  maestroUrl: string;
82
86
  email: string;