@brantrusnak/openclaw-omadeus 1.0.4 → 1.0.6

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/src/onboarding.ts CHANGED
@@ -7,12 +7,19 @@ 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,
14
20
  OmadeusChannelView,
15
21
  OmadeusInboundEntityKind,
22
+ OmadeusInboundMentionPolicy,
16
23
  OmadeusOrganizationMember,
17
24
  } from "./types.js";
18
25
  import { OMADEUS_INBOUND_ENTITY_KINDS } from "./types.js";
@@ -41,21 +48,38 @@ function formatAuthError(err: unknown): string {
41
48
  return parts.join(" — ");
42
49
  }
43
50
 
44
- async function noteOmadeusAuthHelp(prompter: WizardPrompter): Promise<void> {
51
+ async function noteOmadeusAuthHelp(
52
+ prompter: WizardPrompter,
53
+ environment: OmadeusEnvironment,
54
+ ): Promise<void> {
55
+ const envLabel = OMADEUS_ENVIRONMENTS[environment].label;
45
56
  await prompter.note(
46
57
  [
47
- "Omadeus authenticates via CAS + Maestro (email + password + organization).",
48
- "You need:",
49
- " - Email + password",
50
- " - Organization ID (we can look it up for you)",
51
- `CAS URL: ${OMADEUS_CAS_URL}`,
52
- `Maestro URL: ${OMADEUS_MAESTRO_URL}`,
53
- "Env vars supported: OMADEUS_EMAIL, OMADEUS_PASSWORD, OMADEUS_ORGANIZATION_ID.",
58
+ `Connect OpenClaw to Omadeus (${envLabel}).`,
59
+ "",
60
+ "We'll ask for your email and password, then show the organizations on your account so you can pick one.",
54
61
  ].join("\n"),
55
62
  "Omadeus setup",
56
63
  );
57
64
  }
58
65
 
66
+ async function promptEnvironment(
67
+ prompter: WizardPrompter,
68
+ existing?: OmadeusEnvironment,
69
+ ): Promise<OmadeusEnvironment> {
70
+ const initial = existing ?? OMADEUS_DEFAULT_ENVIRONMENT;
71
+ const choice = await prompter.select({
72
+ message: "Select Omadeus environment",
73
+ options: (Object.keys(OMADEUS_ENVIRONMENTS) as OmadeusEnvironment[]).map((env) => ({
74
+ value: env,
75
+ label: OMADEUS_ENVIRONMENTS[env].label,
76
+ hint: getOmadeusEnvironmentUrls(env).maestroUrl,
77
+ })),
78
+ initialValue: initial,
79
+ });
80
+ return resolveOmadeusEnvironment(choice);
81
+ }
82
+
59
83
  async function promptOrganizationId(params: {
60
84
  prompter: WizardPrompter;
61
85
  maestroUrl: string;
@@ -226,36 +250,68 @@ async function promptCredentials(
226
250
  return { email, password };
227
251
  }
228
252
 
229
- async function promptSenderAllowlist(params: {
253
+ /**
254
+ * Prompt for the set of users allowed to message this OpenClaw instance.
255
+ *
256
+ * This single allowlist governs direct messages, channels, and entity rooms.
257
+ * There is no "all users" option: only whitelisted members may message
258
+ * OpenClaw. The logged-in user is always added
259
+ * to the allowlist (and is excluded from the selectable list by the caller) so
260
+ * they can interact with their own OpenClaw. Self-authored echoes (the reply
261
+ * loop) are filtered earlier, at socket ingestion, by the SentMessageTracker —
262
+ * not by the inbound policy — so allowing yourself here cannot cause a loop.
263
+ */
264
+ async function promptMessagingAllowlist(params: {
230
265
  prompter: WizardPrompter;
231
- message: string;
232
266
  members: OmadeusOrganizationMember[];
267
+ selfReferenceId: number;
233
268
  existingReferenceIds?: number[];
234
- }): Promise<number[] | undefined> {
235
- const { prompter, message, members, existingReferenceIds } = params;
269
+ }): Promise<number[]> {
270
+ const { prompter, members, selfReferenceId, existingReferenceIds } = params;
271
+
272
+ let selected: number[] = [];
236
273
  if (members.length === 0) {
237
- throw new Error("No organization members found.");
274
+ await prompter.note(
275
+ "No other organization members found. Only you will be able to message OpenClaw.",
276
+ "Omadeus messaging allowlist",
277
+ );
278
+ } else {
279
+ const memberReferenceIds = new Set(members.map((member) => member.referenceId));
280
+ const initialValues = (existingReferenceIds ?? [])
281
+ .filter((id) => id !== selfReferenceId && memberReferenceIds.has(id))
282
+ .map(String);
283
+ const chosen = await promptMultiSelect({
284
+ prompter,
285
+ message: "Which users do you want to be able to message this OpenClaw instance? (You are always allowed.)",
286
+ options: memberOptions(members),
287
+ initialValues,
288
+ });
289
+ selected = readReferenceIds(chosen);
238
290
  }
239
291
 
240
- const mode = await prompter.select({
241
- message,
242
- options: [
243
- { value: "all", label: "All users", hint: "No sender allowlist" },
244
- { value: "specific", label: "Specific users", hint: "Select one or more users" },
245
- ],
246
- initialValue: existingReferenceIds && existingReferenceIds.length > 0 ? "specific" : "all",
247
- });
248
- if (mode === "all") {
249
- return undefined;
250
- }
292
+ // Always allow the logged-in user so they can message their own OpenClaw.
293
+ return Array.from(new Set([selfReferenceId, ...selected]));
294
+ }
251
295
 
252
- const selected = await promptMultiSelect({
253
- prompter,
254
- message: `${message} (specific users)`,
255
- options: memberOptions(members),
256
- initialValues: existingReferenceIds?.map(String),
296
+ /**
297
+ * Ask whether an @mention is required to trigger OpenClaw in a given surface
298
+ * (channels or entity rooms). DMs never use this — you can't @mention in a DM.
299
+ */
300
+ async function promptRequireMention(params: {
301
+ prompter: WizardPrompter;
302
+ surfaceLabel: string;
303
+ existing?: OmadeusInboundMentionPolicy;
304
+ }): Promise<OmadeusInboundMentionPolicy> {
305
+ // Preserve an existing "outsideAllowlist" policy so rerunning onboarding does
306
+ // not force previously allowlisted users to start @mentioning OpenClaw.
307
+ if (params.existing === "outsideAllowlist") {
308
+ return "outsideAllowlist";
309
+ }
310
+ const required = await params.prompter.confirm({
311
+ message: `Require an @mention to trigger OpenClaw in ${params.surfaceLabel}?`,
312
+ initialValue: params.existing ? params.existing !== "never" : true,
257
313
  });
258
- return readReferenceIds(selected);
314
+ return required ? "always" : "never";
259
315
  }
260
316
 
261
317
  async function promptEntityKindSelection(params: {
@@ -315,16 +371,19 @@ export const omadeusSetupWizard: ChannelSetupWizard = {
315
371
  const section = getOmadeusChannelConfig(cfg) ?? {};
316
372
  let next = cfg;
317
373
 
374
+ const environment = await promptEnvironment(
375
+ prompter,
376
+ section.environment ? resolveOmadeusEnvironment(section.environment) : undefined,
377
+ );
378
+ const { casUrl, maestroUrl } = getOmadeusEnvironmentUrls(environment);
379
+
318
380
  if (account.credentialSource === "none") {
319
- await noteOmadeusAuthHelp(prompter);
381
+ await noteOmadeusAuthHelp(prompter, environment);
320
382
  }
321
383
 
322
384
  const envEmail = process.env.OMADEUS_EMAIL?.trim();
323
385
  const envPassword = process.env.OMADEUS_PASSWORD?.trim();
324
386
 
325
- const casUrl = OMADEUS_CAS_URL;
326
- const maestroUrl = OMADEUS_MAESTRO_URL;
327
-
328
387
  let { email, password } = await promptCredentials(prompter, {
329
388
  email: section.email ?? envEmail,
330
389
  password: section.password ?? envPassword,
@@ -388,11 +447,17 @@ export const omadeusSetupWizard: ChannelSetupWizard = {
388
447
  });
389
448
  const existingInbound = section.inbound;
390
449
 
391
- const directSenderIds = await promptSenderAllowlist({
450
+ const allowedUserReferenceIds = await promptMessagingAllowlist({
392
451
  prompter,
393
- message: "Which users can DM OpenClaw directly?",
394
452
  members,
395
- existingReferenceIds: existingInbound?.direct?.allowedSenderReferenceIds,
453
+ selfReferenceId,
454
+ existingReferenceIds: Array.from(
455
+ new Set([
456
+ ...(existingInbound?.direct?.allowedSenderReferenceIds ?? []),
457
+ ...(existingInbound?.channels?.allowedSenderReferenceIds ?? []),
458
+ ...(existingInbound?.entities?.allowedSenderReferenceIds ?? []),
459
+ ]),
460
+ ),
396
461
  });
397
462
 
398
463
  const selectedChannels = await promptChannelSelection({
@@ -403,30 +468,32 @@ export const omadeusSetupWizard: ChannelSetupWizard = {
403
468
  existingChannelViewIds: existingInbound?.channels?.allowedChannelViewIds,
404
469
  });
405
470
 
406
- const channelSenderIds =
471
+ // Channels reuse the same messaging allowlist as direct messages.
472
+ const channelSenderIds = selectedChannels.length > 0 ? allowedUserReferenceIds : undefined;
473
+ const channelRequireMention =
407
474
  selectedChannels.length > 0
408
- ? await promptSenderAllowlist({
475
+ ? await promptRequireMention({
409
476
  prompter,
410
- message: "Which users can trigger OpenClaw from allowed channels?",
411
- members,
412
- existingReferenceIds: existingInbound?.channels?.allowedSenderReferenceIds,
477
+ surfaceLabel: "allowed channels",
478
+ existing: existingInbound?.channels?.requireMention,
413
479
  })
414
- : undefined;
480
+ : "never";
415
481
 
416
482
  const entityKinds = await promptEntityKindSelection({
417
483
  prompter,
418
484
  existingKinds: existingInbound?.entities?.allowedKinds,
419
485
  });
420
486
 
421
- const entitySenderIds =
487
+ // Entity rooms reuse the same messaging allowlist as direct messages.
488
+ const entitySenderIds = entityKinds.length > 0 ? allowedUserReferenceIds : undefined;
489
+ const entityRequireMention =
422
490
  entityKinds.length > 0
423
- ? await promptSenderAllowlist({
491
+ ? await promptRequireMention({
424
492
  prompter,
425
- message: "Which users can trigger OpenClaw from entity rooms?",
426
- members,
427
- existingReferenceIds: existingInbound?.entities?.allowedSenderReferenceIds,
493
+ surfaceLabel: "entity rooms",
494
+ existing: existingInbound?.entities?.requireMention,
428
495
  })
429
- : undefined;
496
+ : "never";
430
497
 
431
498
  const channelRoomIds = selectedChannels
432
499
  .flatMap((selectedChannel) => [
@@ -441,20 +508,29 @@ export const omadeusSetupWizard: ChannelSetupWizard = {
441
508
 
442
509
  const senderSummary = (ids: number[] | undefined) =>
443
510
  ids && ids.length > 0 ? ids.join(", ") : "all users";
444
- const entityKindSummary =
445
- entityKinds.length > 0 ? entityKinds.join(", ") : "none (entity rooms disabled)";
511
+ const mentionSummary = (require: OmadeusInboundMentionPolicy) =>
512
+ require === "never"
513
+ ? "no @mention required"
514
+ : require === "outsideAllowlist"
515
+ ? "@mention required outside the allowlist"
516
+ : "@mention required";
446
517
 
447
518
  const channelSummary =
448
519
  selectedChannels.length > 0
449
- ? `- Channels "${channelTitles}": rooms ${channelRoomIds.join(", ") || "(no room ids)"} from ${senderSummary(channelSenderIds)}; @mention not required in those rooms.`
520
+ ? `- Channels "${channelTitles}": rooms ${channelRoomIds.join(", ") || "(no room ids)"} from ${senderSummary(channelSenderIds)}; ${mentionSummary(channelRequireMention)}.`
450
521
  : "- Channels: disabled (none selected).";
451
522
 
523
+ const entitySummary =
524
+ entityKinds.length > 0
525
+ ? `- Entity rooms (${entityKinds.join(", ")}): ${senderSummary(entitySenderIds)}; ${mentionSummary(entityRequireMention)}.`
526
+ : "- Entity rooms: disabled (no room types selected).";
527
+
452
528
  await prompter.note(
453
529
  [
454
530
  `Inbound policy (Jaguar chat):`,
455
- `- Direct messages: enabled for ${senderSummary(directSenderIds)} (no @mention required).`,
531
+ `- Direct messages: enabled for ${senderSummary(allowedUserReferenceIds)}.`,
456
532
  channelSummary,
457
- `- Entity rooms (${entityKindSummary}): ${senderSummary(entitySenderIds)}; @mention required.`,
533
+ entitySummary,
458
534
  ].join("\n"),
459
535
  "Omadeus inbound policy",
460
536
  );
@@ -465,17 +541,17 @@ export const omadeusSetupWizard: ChannelSetupWizard = {
465
541
  ...next.channels,
466
542
  omadeus: {
467
543
  enabled: true,
468
- casUrl,
469
- maestroUrl,
544
+ environment,
470
545
  email,
471
546
  password,
472
547
  organizationId,
473
548
  sessionToken,
549
+ sessionTokenEnvironment: environment,
474
550
  inbound: {
475
551
  version: 1,
476
552
  direct: {
477
553
  enabled: true,
478
- ...(directSenderIds ? { allowedSenderReferenceIds: directSenderIds } : {}),
554
+ allowedSenderReferenceIds: allowedUserReferenceIds,
479
555
  requireMention: "never",
480
556
  },
481
557
  channels: {
@@ -483,13 +559,13 @@ export const omadeusSetupWizard: ChannelSetupWizard = {
483
559
  allowedRoomIds: channelRoomIds,
484
560
  allowedChannelViewIds: channelViewIds,
485
561
  ...(channelSenderIds ? { allowedSenderReferenceIds: channelSenderIds } : {}),
486
- requireMention: "outsideAllowlist",
562
+ requireMention: channelRequireMention,
487
563
  },
488
564
  entities: {
489
565
  enabled: entityKinds.length > 0,
490
566
  allowedKinds: entityKinds,
491
567
  ...(entitySenderIds ? { allowedSenderReferenceIds: entitySenderIds } : {}),
492
- requireMention: "always",
568
+ requireMention: entityRequireMention,
493
569
  },
494
570
  },
495
571
  },
package/src/outbound.ts CHANGED
@@ -1,10 +1,13 @@
1
1
  import { sendRoomMessage } from "./api/message.api.js";
2
+ import type { SentMessageTracker } from "./sent-message-tracker.js";
2
3
  import type { JaguarSocketClient } from "./socket/jaguar.socket.js";
3
- import type { OmadeusApiOptions } from "./utils/http.util.js";
4
+ import { generateTemporaryId, type OmadeusApiOptions } from "./utils/http.util.js";
4
5
 
5
6
  export type OutboundDeps = {
6
7
  apiOpts: OmadeusApiOptions;
7
8
  jaguarSocket: JaguarSocketClient;
9
+ /** Records messages we send so their socket echoes can be suppressed. */
10
+ sentTracker?: SentMessageTracker;
8
11
  };
9
12
 
10
13
  export async function sendOmadeusMessage(
@@ -13,11 +16,20 @@ export async function sendOmadeusMessage(
13
16
  ): Promise<{ channel: string; messageId: string; chatId: string }> {
14
17
  const { to, text } = params;
15
18
 
16
- const result = await sendRoomMessage(deps.apiOpts, { roomId: to, body: text });
19
+ const temporaryId = generateTemporaryId();
20
+ // Register before sending: the socket echo can arrive before this HTTP call
21
+ // returns, so the temporaryId (and body fallback) must already be tracked.
22
+ deps.sentTracker?.trackOutbound({ temporaryId, body: text, roomId: to });
23
+
24
+ const result = await sendRoomMessage(deps.apiOpts, { roomId: to, body: text, temporaryId });
17
25
  if (!result.ok) {
18
26
  throw new Error(`Omadeus send failed: ${result.error}`);
19
27
  }
20
28
 
29
+ if (typeof result.message?.id === "number") {
30
+ deps.sentTracker?.trackId(result.message.id);
31
+ }
32
+
21
33
  return {
22
34
  channel: "omadeus",
23
35
  messageId: String(result.message?.id ?? ""),
@@ -0,0 +1,155 @@
1
+ /**
2
+ * Tracks messages this plugin sent so their Jaguar socket echoes can be
3
+ * suppressed, instead of dropping every message authored by the logged-in
4
+ * account.
5
+ *
6
+ * OpenClaw sends as the same Omadeus account it listens on, so each outbound
7
+ * message is broadcast back to us over the socket. We register up to three keys
8
+ * per send:
9
+ *
10
+ * - the client-generated `temporaryId` — known *before* the HTTP round-trip, so
11
+ * it matches even when the socket echo beats the send response (the common
12
+ * race);
13
+ * - the backend message `id` — known once the send response returns;
14
+ * - a normalized copy of the body scoped to its room — a last-resort fallback
15
+ * used only for self-authored echoes that somehow arrive without a
16
+ * recognizable id. Scoping by room prevents the same text sent in one chat
17
+ * from suppressing an identical message in a different chat.
18
+ *
19
+ * `id` and `temporaryId` are kept in separate maps. Entries expire after a
20
+ * short TTL and each map is size-capped, so the tracker cannot grow unbounded.
21
+ */
22
+
23
+ const DEFAULT_TTL_MS = 2 * 60 * 1000; // 2 minutes — comfortably covers echo latency.
24
+ const DEFAULT_MAX_ENTRIES = 500;
25
+
26
+ export type SentMessageTrackerOptions = {
27
+ ttlMs?: number;
28
+ maxEntries?: number;
29
+ now?: () => number;
30
+ };
31
+
32
+ function normalizeContent(body: string): string {
33
+ return body.trim();
34
+ }
35
+
36
+ /** Normalize a room identity so outbound (`"room:123"`/`"123"`) and the socket
37
+ * echo (numeric `123`) map to the same key. */
38
+ function roomKey(roomId: string | number): string {
39
+ return String(roomId).replace(/^room:/, "").trim();
40
+ }
41
+
42
+ /** Build the room-scoped content key, or undefined when the body is empty. */
43
+ function contentKey(roomId: string | number, body: string): string | undefined {
44
+ const normalized = normalizeContent(body);
45
+ if (!normalized) return undefined;
46
+ return `${roomKey(roomId)}\n${normalized}`;
47
+ }
48
+
49
+ export class SentMessageTracker {
50
+ private readonly ttlMs: number;
51
+ private readonly maxEntries: number;
52
+ private readonly now: () => number;
53
+ private readonly ids = new Map<number, number>();
54
+ private readonly temporaryIds = new Map<string, number>();
55
+ private readonly contents = new Map<string, number>();
56
+
57
+ constructor(options: SentMessageTrackerOptions = {}) {
58
+ this.ttlMs = options.ttlMs ?? DEFAULT_TTL_MS;
59
+ this.maxEntries = options.maxEntries ?? DEFAULT_MAX_ENTRIES;
60
+ this.now = options.now ?? Date.now;
61
+ }
62
+
63
+ /** Register a client-generated temporaryId. Call before sending. */
64
+ trackTemporaryId(temporaryId: string): void {
65
+ if (!temporaryId) return;
66
+ this.remember(this.temporaryIds, temporaryId);
67
+ }
68
+
69
+ /** Register the backend message id once the send response returns. */
70
+ trackId(id: number): void {
71
+ if (!Number.isFinite(id)) return;
72
+ this.remember(this.ids, id);
73
+ }
74
+
75
+ /** Register a message body, scoped to its room, as a fallback match key. */
76
+ trackContent(roomId: string | number, body: string): void {
77
+ const key = contentKey(roomId, body);
78
+ if (!key) return;
79
+ this.remember(this.contents, key);
80
+ }
81
+
82
+ /** Convenience: register whichever keys are available for one outbound message. */
83
+ trackOutbound(params: {
84
+ temporaryId?: string;
85
+ id?: number;
86
+ body?: string;
87
+ roomId?: string | number;
88
+ }): void {
89
+ if (params.temporaryId) this.trackTemporaryId(params.temporaryId);
90
+ if (typeof params.id === "number") this.trackId(params.id);
91
+ if (typeof params.body === "string" && params.roomId !== undefined) {
92
+ this.trackContent(params.roomId, params.body);
93
+ }
94
+ }
95
+
96
+ /**
97
+ * Returns true when an inbound socket message is an echo of something we sent.
98
+ *
99
+ * `id`/`temporaryId` matches are authoritative. The content fallback only
100
+ * applies to self-authored messages — the only ones that can form a reply
101
+ * loop — and is scoped to the message's room, so a different user repeating
102
+ * our text (or the same text in another room) is never suppressed.
103
+ */
104
+ isEcho(msg: {
105
+ id?: number;
106
+ temporaryId?: string;
107
+ body?: string;
108
+ roomId?: string | number;
109
+ fromSelf: boolean;
110
+ }): boolean {
111
+ if (typeof msg.id === "number" && this.has(this.ids, msg.id)) return true;
112
+ if (msg.temporaryId && this.has(this.temporaryIds, msg.temporaryId)) return true;
113
+ if (msg.fromSelf && typeof msg.body === "string" && msg.roomId !== undefined) {
114
+ const key = contentKey(msg.roomId, msg.body);
115
+ if (key && this.has(this.contents, key)) return true;
116
+ }
117
+ return false;
118
+ }
119
+
120
+ private remember<K>(map: Map<K, number>, key: K): void {
121
+ // Delete-then-set so re-registered keys move to the end, keeping insertion
122
+ // order aligned with expiry order (the TTL is constant).
123
+ map.delete(key);
124
+ map.set(key, this.now() + this.ttlMs);
125
+ this.prune(map);
126
+ }
127
+
128
+ private has<K>(map: Map<K, number>, key: K): boolean {
129
+ const expiry = map.get(key);
130
+ if (expiry === undefined) return false;
131
+ if (expiry <= this.now()) {
132
+ map.delete(key);
133
+ return false;
134
+ }
135
+ return true;
136
+ }
137
+
138
+ private prune<K>(map: Map<K, number>): void {
139
+ const now = this.now();
140
+ for (const [key, expiry] of map) {
141
+ if (expiry <= now) {
142
+ map.delete(key);
143
+ } else {
144
+ // Insertion order matches expiry order, so the first live entry means
145
+ // everything after it is also live.
146
+ break;
147
+ }
148
+ }
149
+ while (map.size > this.maxEntries) {
150
+ const oldest = map.keys().next().value as K | undefined;
151
+ if (oldest === undefined) break;
152
+ map.delete(oldest);
153
+ }
154
+ }
155
+ }
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;