@brantrusnak/openclaw-omadeus 1.0.2 → 1.0.3
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 +4 -1
- package/dist/_virtual/_rolldown/runtime.js +4 -0
- package/dist/api.js +5 -0
- package/dist/index.js +14 -0
- package/dist/runtime-api.js +15 -0
- package/dist/setup-entry.js +7 -0
- package/dist/src/allowed-reaction-emojis.js +21 -0
- package/dist/src/api/auth.api.js +115 -0
- package/dist/src/api/channel.api.js +23 -0
- package/dist/src/api/message.api.js +76 -0
- package/dist/src/api/nugget.api.js +127 -0
- package/dist/src/auth.js +30 -0
- package/dist/src/channel.js +626 -0
- package/dist/src/config.js +52 -0
- package/dist/src/defaults.js +5 -0
- package/dist/src/inbound-policy.js +205 -0
- package/dist/src/inbound.js +97 -0
- package/dist/src/member-resolve.js +53 -0
- package/dist/src/message-handler.js +262 -0
- package/dist/src/nugget-lookup.js +140 -0
- package/dist/src/onboarding.js +363 -0
- package/dist/src/outbound.js +17 -0
- package/dist/src/reply-dispatcher.js +46 -0
- package/dist/src/runtime.js +5 -0
- package/dist/src/setup-core.js +46 -0
- package/dist/src/setup-surface.js +2 -0
- package/dist/src/socket/dolphin.socket.js +18 -0
- package/dist/src/socket/jaguar.socket.js +22 -0
- package/dist/src/socket/socket.js +153 -0
- package/dist/src/store.js +13 -0
- package/dist/src/token.js +84 -0
- package/dist/src/types.js +15 -0
- package/dist/src/utils/http.util.js +43 -0
- package/dist/src/utils/jwt.util.js +15 -0
- package/package.json +10 -3
- package/src/channel.ts +127 -238
- package/src/member-resolve.ts +1 -1
- package/src/onboarding.ts +71 -110
- package/src/setup-core.ts +10 -1
- package/src/socket/socket.ts +24 -11
package/src/onboarding.ts
CHANGED
|
@@ -8,29 +8,17 @@ import { listMemberChannelViews } from "./api/channel.api.js";
|
|
|
8
8
|
import { authenticate } from "./auth.js";
|
|
9
9
|
import { getOmadeusChannelConfig, resolveOmadeusAccount } from "./config.js";
|
|
10
10
|
import { OMADEUS_CAS_URL, OMADEUS_MAESTRO_URL } from "./defaults.js";
|
|
11
|
+
import { formatMemberLabel } from "./member-resolve.js";
|
|
11
12
|
import type {
|
|
12
13
|
OmadeusChannelConfig,
|
|
13
14
|
OmadeusChannelView,
|
|
14
15
|
OmadeusInboundEntityKind,
|
|
15
16
|
OmadeusOrganizationMember,
|
|
16
17
|
} from "./types.js";
|
|
18
|
+
import { OMADEUS_INBOUND_ENTITY_KINDS } from "./types.js";
|
|
17
19
|
|
|
18
20
|
const channel = "omadeus" as const;
|
|
19
21
|
const DONE = "__done__";
|
|
20
|
-
const ALL_ENTITY_KINDS: OmadeusInboundEntityKind[] = [
|
|
21
|
-
"task",
|
|
22
|
-
"nugget",
|
|
23
|
-
"project",
|
|
24
|
-
"release",
|
|
25
|
-
"sprint",
|
|
26
|
-
"summary",
|
|
27
|
-
"client",
|
|
28
|
-
"folder",
|
|
29
|
-
];
|
|
30
|
-
|
|
31
|
-
type CoreConfig = OpenClawConfig & {
|
|
32
|
-
channels?: { omadeus?: OmadeusChannelConfig };
|
|
33
|
-
};
|
|
34
22
|
|
|
35
23
|
type SelectOption = {
|
|
36
24
|
value: string;
|
|
@@ -38,25 +26,18 @@ type SelectOption = {
|
|
|
38
26
|
hint?: string;
|
|
39
27
|
};
|
|
40
28
|
|
|
29
|
+
type MultiSelectFn = (args: {
|
|
30
|
+
message: string;
|
|
31
|
+
options: SelectOption[];
|
|
32
|
+
initialValues?: string[];
|
|
33
|
+
initialValue?: string[];
|
|
34
|
+
}) => Promise<string[]>;
|
|
35
|
+
|
|
41
36
|
type MultiSelectPrompter = {
|
|
42
|
-
multiSelect?:
|
|
43
|
-
|
|
44
|
-
options: SelectOption[];
|
|
45
|
-
initialValues?: string[];
|
|
46
|
-
initialValue?: string[];
|
|
47
|
-
}) => Promise<string[]>;
|
|
48
|
-
multiselect?: (args: {
|
|
49
|
-
message: string;
|
|
50
|
-
options: SelectOption[];
|
|
51
|
-
initialValues?: string[];
|
|
52
|
-
initialValue?: string[];
|
|
53
|
-
}) => Promise<string[]>;
|
|
37
|
+
multiSelect?: MultiSelectFn;
|
|
38
|
+
multiselect?: MultiSelectFn;
|
|
54
39
|
};
|
|
55
40
|
|
|
56
|
-
function getOmadeusSection(cfg: OpenClawConfig): OmadeusChannelConfig | undefined {
|
|
57
|
-
return getOmadeusChannelConfig(cfg as CoreConfig);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
41
|
async function noteOmadeusAuthHelp(prompter: WizardPrompter): Promise<void> {
|
|
61
42
|
await prompter.note(
|
|
62
43
|
[
|
|
@@ -80,35 +61,32 @@ async function promptOrganizationId(params: {
|
|
|
80
61
|
}): Promise<number> {
|
|
81
62
|
const { prompter, maestroUrl, email, existing } = params;
|
|
82
63
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
);
|
|
93
|
-
return orgs[0]!.id;
|
|
94
|
-
}
|
|
95
|
-
const choice = await prompter.select({
|
|
96
|
-
message: "Select organization",
|
|
97
|
-
options: orgs.map((org) => ({
|
|
98
|
-
value: String(org.id),
|
|
99
|
-
label: `${org.title} (${org.membersCount} members)`,
|
|
100
|
-
hint: `ID: ${org.id}`,
|
|
101
|
-
})),
|
|
102
|
-
initialValue: existing ? String(existing) : String(orgs[0]!.id),
|
|
103
|
-
});
|
|
104
|
-
return Number(choice);
|
|
64
|
+
try {
|
|
65
|
+
const orgs = await listOrganizations({ maestroUrl, email });
|
|
66
|
+
if (orgs.length > 0) {
|
|
67
|
+
if (orgs.length === 1) {
|
|
68
|
+
await prompter.note(
|
|
69
|
+
`Found organization: ${orgs[0]!.title} (${orgs[0]!.id})`,
|
|
70
|
+
"Omadeus organization",
|
|
71
|
+
);
|
|
72
|
+
return orgs[0]!.id;
|
|
105
73
|
}
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
74
|
+
const choice = await prompter.select({
|
|
75
|
+
message: "Select organization",
|
|
76
|
+
options: orgs.map((org) => ({
|
|
77
|
+
value: String(org.id),
|
|
78
|
+
label: `${org.title} (${org.membersCount} members)`,
|
|
79
|
+
hint: `ID: ${org.id}`,
|
|
80
|
+
})),
|
|
81
|
+
initialValue: existing ? String(existing) : String(orgs[0]!.id),
|
|
82
|
+
});
|
|
83
|
+
return Number(choice);
|
|
111
84
|
}
|
|
85
|
+
} catch {
|
|
86
|
+
await prompter.note(
|
|
87
|
+
"Could not fetch organizations from the API. Enter the ID manually.",
|
|
88
|
+
"Omadeus organization",
|
|
89
|
+
);
|
|
112
90
|
}
|
|
113
91
|
|
|
114
92
|
const raw = await prompter.text({
|
|
@@ -164,20 +142,6 @@ async function promptChannelSelection(params: {
|
|
|
164
142
|
return chosen;
|
|
165
143
|
}
|
|
166
144
|
|
|
167
|
-
function memberLabel(member: OmadeusOrganizationMember): string {
|
|
168
|
-
const fullName = `${member.firstName ?? ""} ${member.lastName ?? ""}`.trim();
|
|
169
|
-
if (member.title?.trim()) {
|
|
170
|
-
return member.title.trim();
|
|
171
|
-
}
|
|
172
|
-
if (fullName) {
|
|
173
|
-
return fullName;
|
|
174
|
-
}
|
|
175
|
-
if (member.email?.trim()) {
|
|
176
|
-
return member.email.trim();
|
|
177
|
-
}
|
|
178
|
-
return `Member ${member.referenceId}`;
|
|
179
|
-
}
|
|
180
|
-
|
|
181
145
|
function memberHint(member: OmadeusOrganizationMember): string | undefined {
|
|
182
146
|
const fullName = `${member.firstName ?? ""} ${member.lastName ?? ""}`.trim();
|
|
183
147
|
const parts = [fullName, member.email?.trim(), `ref:${member.referenceId}`].filter(Boolean);
|
|
@@ -241,13 +205,13 @@ async function loadSelectableMembers(params: {
|
|
|
241
205
|
})
|
|
242
206
|
)
|
|
243
207
|
.filter((member) => member.isSystem !== true && !excluded.has(member.referenceId))
|
|
244
|
-
.sort((a, b) =>
|
|
208
|
+
.sort((a, b) => formatMemberLabel(a).localeCompare(formatMemberLabel(b)));
|
|
245
209
|
}
|
|
246
210
|
|
|
247
211
|
function memberOptions(members: OmadeusOrganizationMember[]): SelectOption[] {
|
|
248
212
|
return members.map((member) => ({
|
|
249
213
|
value: String(member.referenceId),
|
|
250
|
-
label:
|
|
214
|
+
label: formatMemberLabel(member),
|
|
251
215
|
hint: memberHint(member),
|
|
252
216
|
}));
|
|
253
217
|
}
|
|
@@ -258,6 +222,27 @@ function readReferenceIds(values: string[]): number[] {
|
|
|
258
222
|
.filter((value) => Number.isInteger(value) && value > 0);
|
|
259
223
|
}
|
|
260
224
|
|
|
225
|
+
async function promptCredentials(
|
|
226
|
+
prompter: WizardPrompter,
|
|
227
|
+
existing: { email?: string; password?: string },
|
|
228
|
+
): Promise<{ email: string; password: string }> {
|
|
229
|
+
const email = String(
|
|
230
|
+
await prompter.text({
|
|
231
|
+
message: "Omadeus username (email)",
|
|
232
|
+
initialValue: existing.email,
|
|
233
|
+
validate: (value) => (String(value ?? "").trim() ? undefined : "Required"),
|
|
234
|
+
}),
|
|
235
|
+
).trim();
|
|
236
|
+
const password = String(
|
|
237
|
+
await prompter.text({
|
|
238
|
+
message: "Omadeus password",
|
|
239
|
+
sensitive: true,
|
|
240
|
+
validate: (value) => (String(value ?? "").trim() ? undefined : "Required"),
|
|
241
|
+
}),
|
|
242
|
+
).trim();
|
|
243
|
+
return { email, password };
|
|
244
|
+
}
|
|
245
|
+
|
|
261
246
|
async function promptSenderAllowlist(params: {
|
|
262
247
|
prompter: WizardPrompter;
|
|
263
248
|
message: string;
|
|
@@ -277,7 +262,7 @@ async function promptSenderAllowlist(params: {
|
|
|
277
262
|
],
|
|
278
263
|
initialValue: existingReferenceIds && existingReferenceIds.length > 0 ? "specific" : "all",
|
|
279
264
|
});
|
|
280
|
-
if (
|
|
265
|
+
if (mode === "all") {
|
|
281
266
|
return undefined;
|
|
282
267
|
}
|
|
283
268
|
|
|
@@ -297,16 +282,17 @@ async function promptEntityKindSelection(params: {
|
|
|
297
282
|
const selected = await promptMultiSelect({
|
|
298
283
|
prompter: params.prompter,
|
|
299
284
|
message: "Which entity room types should OpenClaw listen to?",
|
|
300
|
-
options:
|
|
285
|
+
options: OMADEUS_INBOUND_ENTITY_KINDS.map((kind) => ({
|
|
301
286
|
value: kind,
|
|
302
287
|
label: kind,
|
|
303
288
|
})),
|
|
304
289
|
initialValues:
|
|
305
290
|
params.existingKinds && params.existingKinds.length > 0
|
|
306
291
|
? params.existingKinds
|
|
307
|
-
:
|
|
292
|
+
: [...OMADEUS_INBOUND_ENTITY_KINDS],
|
|
308
293
|
});
|
|
309
|
-
|
|
294
|
+
const selectedSet = new Set(selected);
|
|
295
|
+
return OMADEUS_INBOUND_ENTITY_KINDS.filter((kind) => selectedSet.has(kind));
|
|
310
296
|
}
|
|
311
297
|
|
|
312
298
|
export const omadeusSetupWizard: ChannelSetupWizard = {
|
|
@@ -343,7 +329,7 @@ export const omadeusSetupWizard: ChannelSetupWizard = {
|
|
|
343
329
|
credentials: [],
|
|
344
330
|
finalize: async ({ cfg, prompter }) => {
|
|
345
331
|
const account = resolveOmadeusAccount({ cfg });
|
|
346
|
-
const section =
|
|
332
|
+
const section = getOmadeusChannelConfig(cfg) ?? {};
|
|
347
333
|
let next = cfg;
|
|
348
334
|
|
|
349
335
|
if (account.credentialSource === "none") {
|
|
@@ -356,21 +342,10 @@ export const omadeusSetupWizard: ChannelSetupWizard = {
|
|
|
356
342
|
const casUrl = OMADEUS_CAS_URL;
|
|
357
343
|
const maestroUrl = OMADEUS_MAESTRO_URL;
|
|
358
344
|
|
|
359
|
-
let email =
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
validate: (value) => (String(value ?? "").trim() ? undefined : "Required"),
|
|
364
|
-
}),
|
|
365
|
-
).trim();
|
|
366
|
-
|
|
367
|
-
let password = String(
|
|
368
|
-
await prompter.text({
|
|
369
|
-
message: "Omadeus password",
|
|
370
|
-
initialValue: section.password ?? envPassword,
|
|
371
|
-
validate: (value) => (String(value ?? "").trim() ? undefined : "Required"),
|
|
372
|
-
}),
|
|
373
|
-
).trim();
|
|
345
|
+
let { email, password } = await promptCredentials(prompter, {
|
|
346
|
+
email: section.email ?? envEmail,
|
|
347
|
+
password: section.password ?? envPassword,
|
|
348
|
+
});
|
|
374
349
|
|
|
375
350
|
const organizationId = await promptOrganizationId({
|
|
376
351
|
prompter,
|
|
@@ -379,7 +354,6 @@ export const omadeusSetupWizard: ChannelSetupWizard = {
|
|
|
379
354
|
existing: section.organizationId,
|
|
380
355
|
});
|
|
381
356
|
|
|
382
|
-
// Verify the full auth flow before saving
|
|
383
357
|
let sessionToken: string | undefined;
|
|
384
358
|
let selfReferenceId: number | undefined;
|
|
385
359
|
while (true) {
|
|
@@ -409,20 +383,7 @@ export const omadeusSetupWizard: ChannelSetupWizard = {
|
|
|
409
383
|
);
|
|
410
384
|
break;
|
|
411
385
|
}
|
|
412
|
-
email =
|
|
413
|
-
await prompter.text({
|
|
414
|
-
message: "Omadeus email",
|
|
415
|
-
initialValue: email,
|
|
416
|
-
validate: (value) => (String(value ?? "").trim() ? undefined : "Required"),
|
|
417
|
-
}),
|
|
418
|
-
).trim();
|
|
419
|
-
password = String(
|
|
420
|
-
await prompter.text({
|
|
421
|
-
message: "Omadeus password",
|
|
422
|
-
initialValue: password,
|
|
423
|
-
validate: (value) => (String(value ?? "").trim() ? undefined : "Required"),
|
|
424
|
-
}),
|
|
425
|
-
).trim();
|
|
386
|
+
({ email, password } = await promptCredentials(prompter, { email, password }));
|
|
426
387
|
}
|
|
427
388
|
}
|
|
428
389
|
|
|
@@ -516,7 +477,7 @@ export const omadeusSetupWizard: ChannelSetupWizard = {
|
|
|
516
477
|
email,
|
|
517
478
|
password,
|
|
518
479
|
organizationId,
|
|
519
|
-
|
|
480
|
+
sessionToken,
|
|
520
481
|
inbound: {
|
|
521
482
|
version: 1,
|
|
522
483
|
direct: {
|
|
@@ -548,7 +509,7 @@ export const omadeusSetupWizard: ChannelSetupWizard = {
|
|
|
548
509
|
...cfg,
|
|
549
510
|
channels: {
|
|
550
511
|
...cfg.channels,
|
|
551
|
-
omadeus: { ...
|
|
512
|
+
omadeus: { ...getOmadeusChannelConfig(cfg), enabled: false },
|
|
552
513
|
},
|
|
553
514
|
}),
|
|
554
515
|
};
|
package/src/setup-core.ts
CHANGED
|
@@ -35,12 +35,21 @@ export const omadeusSetupAdapter: ChannelSetupAdapter = {
|
|
|
35
35
|
const password = input.password?.trim() || undefined;
|
|
36
36
|
const organizationId = readSetupNumberField(rawInput, "organizationId");
|
|
37
37
|
|
|
38
|
+
const channelsRecord = cfg.channels as Record<string, unknown> | undefined;
|
|
39
|
+
const omadeusExisting = channelsRecord?.["omadeus"];
|
|
40
|
+
const omadeusPrevious =
|
|
41
|
+
omadeusExisting !== null &&
|
|
42
|
+
typeof omadeusExisting === "object" &&
|
|
43
|
+
!Array.isArray(omadeusExisting)
|
|
44
|
+
? (omadeusExisting as Record<string, unknown>)
|
|
45
|
+
: {};
|
|
46
|
+
|
|
38
47
|
return {
|
|
39
48
|
...cfg,
|
|
40
49
|
channels: {
|
|
41
50
|
...cfg.channels,
|
|
42
51
|
omadeus: {
|
|
43
|
-
...
|
|
52
|
+
...omadeusPrevious,
|
|
44
53
|
enabled: true,
|
|
45
54
|
...(casUrl ? { casUrl } : {}),
|
|
46
55
|
...(maestroUrl ? { maestroUrl } : {}),
|
package/src/socket/socket.ts
CHANGED
|
@@ -31,10 +31,12 @@ const HEARTBEAT_MISSED_MAX = 5;
|
|
|
31
31
|
const KEEP_ALIVE_CONTENT = "keep-alive";
|
|
32
32
|
const KEEP_ALIVE_ACTION = "answer";
|
|
33
33
|
|
|
34
|
-
function
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
34
|
+
function isServerKeepAlive(data: Record<string, unknown>): boolean {
|
|
35
|
+
return (data as { content?: unknown }).content === KEEP_ALIVE_CONTENT;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function isClientKeepAlive(data: Record<string, unknown>): boolean {
|
|
39
|
+
return (data as { data?: unknown }).data === KEEP_ALIVE_CONTENT;
|
|
38
40
|
}
|
|
39
41
|
|
|
40
42
|
export function createOmadeusSocketClient(opts: OmadeusSocketOptions): OmadeusSocketClient {
|
|
@@ -87,7 +89,7 @@ export function createOmadeusSocketClient(opts: OmadeusSocketOptions): OmadeusSo
|
|
|
87
89
|
return;
|
|
88
90
|
}
|
|
89
91
|
heartbeatMissCount += 1;
|
|
90
|
-
|
|
92
|
+
sendKeepAliveFrame();
|
|
91
93
|
|
|
92
94
|
if (heartbeatMissCount >= HEARTBEAT_MISSED_MAX) {
|
|
93
95
|
log?.warn(
|
|
@@ -104,6 +106,12 @@ export function createOmadeusSocketClient(opts: OmadeusSocketOptions): OmadeusSo
|
|
|
104
106
|
}, HEARTBEAT_INTERVAL_MS);
|
|
105
107
|
}
|
|
106
108
|
|
|
109
|
+
function sendKeepAliveFrame() {
|
|
110
|
+
if (ws?.readyState === WebSocket.OPEN) {
|
|
111
|
+
ws.send(JSON.stringify({ data: KEEP_ALIVE_CONTENT, action: KEEP_ALIVE_ACTION }));
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
107
115
|
function connect() {
|
|
108
116
|
if (ws) {
|
|
109
117
|
ws.removeAllListeners();
|
|
@@ -144,19 +152,24 @@ export function createOmadeusSocketClient(opts: OmadeusSocketOptions): OmadeusSo
|
|
|
144
152
|
const data = JSON.parse(String(raw)) as Record<string, unknown>;
|
|
145
153
|
|
|
146
154
|
const action = (data as { action?: unknown }).action;
|
|
147
|
-
if (
|
|
155
|
+
if (isServerKeepAlive(data) && action === KEEP_ALIVE_ACTION) {
|
|
148
156
|
resetHeartbeat();
|
|
149
157
|
return;
|
|
150
158
|
}
|
|
151
159
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
160
|
+
if (isClientKeepAlive(data) && action === KEEP_ALIVE_ACTION) {
|
|
161
|
+
resetHeartbeat();
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// If backend sends a heartbeat ping, answer it immediately.
|
|
166
|
+
if (isServerKeepAlive(data) && action === "heartbeat") {
|
|
167
|
+
resetHeartbeat();
|
|
168
|
+
sendKeepAliveFrame();
|
|
157
169
|
return;
|
|
158
170
|
}
|
|
159
171
|
|
|
172
|
+
resetHeartbeat();
|
|
160
173
|
onEvent?.(data);
|
|
161
174
|
} catch {
|
|
162
175
|
log?.warn(`${logPrefix} unparseable message: ${String(raw).slice(0, 200)}`);
|