@brantrusnak/openclaw-omadeus 1.0.0 → 1.0.2
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 +1 -1
- package/openclaw.plugin.json +144 -28
- package/package.json +3 -5
- package/src/api/auth.api.ts +0 -29
- package/src/api/channel.api.ts +29 -0
- package/src/api/nugget.api.ts +81 -10
- package/src/channel.ts +9 -9
- package/src/inbound-policy.ts +250 -0
- package/src/inbound.ts +20 -0
- package/src/member-resolve.ts +84 -0
- package/src/message-handler.ts +99 -53
- package/src/nugget-lookup.ts +67 -4
- package/src/onboarding.ts +242 -120
- package/src/types.ts +77 -7
package/src/nugget-lookup.ts
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import { readNuggetNumber } from "./api/nugget.api.js";
|
|
2
|
+
import { mergePeopleIntoNuggetAgentPayload } from "./member-resolve.js";
|
|
3
|
+
import type { OmadeusApiOptions } from "./utils/http.util.js";
|
|
4
|
+
|
|
1
5
|
export type NuggetLookupIntent = {
|
|
2
6
|
/** Display nugget number from `N###` (maps to API `number`, not internal `id`). */
|
|
3
7
|
nuggetNumber: number;
|
|
@@ -150,6 +154,13 @@ const NUGGET_FIELDS_FOR_AGENT = [
|
|
|
150
154
|
"releaseNumber",
|
|
151
155
|
"publicRoomId",
|
|
152
156
|
"privateRoomId",
|
|
157
|
+
"memberReferenceId",
|
|
158
|
+
"assigneeReferenceId",
|
|
159
|
+
"ownerReferenceId",
|
|
160
|
+
"memberFirstName",
|
|
161
|
+
"memberLastName",
|
|
162
|
+
"assigneeFirstName",
|
|
163
|
+
"assigneeLastName",
|
|
153
164
|
] as const;
|
|
154
165
|
|
|
155
166
|
export function pickNuggetFieldsForAgent(record: Record<string, unknown>): Record<string, unknown> {
|
|
@@ -162,16 +173,31 @@ export function pickNuggetFieldsForAgent(record: Record<string, unknown>): Recor
|
|
|
162
173
|
return out;
|
|
163
174
|
}
|
|
164
175
|
|
|
176
|
+
/**
|
|
177
|
+
* Picked Dolphin fields + `people` map (referenceId → display name) for the organization.
|
|
178
|
+
*/
|
|
179
|
+
export async function buildNuggetAgentDataPayload(
|
|
180
|
+
apiOpts: OmadeusApiOptions,
|
|
181
|
+
fullRecord: Record<string, unknown> | null,
|
|
182
|
+
): Promise<Record<string, unknown> | null> {
|
|
183
|
+
if (!fullRecord) {
|
|
184
|
+
return null;
|
|
185
|
+
}
|
|
186
|
+
const base = pickNuggetFieldsForAgent(fullRecord);
|
|
187
|
+
return mergePeopleIntoNuggetAgentPayload(apiOpts, fullRecord, base);
|
|
188
|
+
}
|
|
189
|
+
|
|
165
190
|
/**
|
|
166
191
|
* Augments the user message so the agent receives Dolphin nugget/task data and can reply with a summary.
|
|
167
192
|
* On miss or API error, the agent still gets instructions to respond helpfully.
|
|
168
193
|
*/
|
|
169
|
-
export function appendNuggetLookupContextForAgent(
|
|
194
|
+
export async function appendNuggetLookupContextForAgent(
|
|
170
195
|
rawBody: string,
|
|
171
196
|
nuggetNumber: number,
|
|
172
197
|
record: Record<string, unknown> | null,
|
|
198
|
+
apiOpts: OmadeusApiOptions,
|
|
173
199
|
fetchError?: string,
|
|
174
|
-
): string {
|
|
200
|
+
): Promise<string> {
|
|
175
201
|
const header = `[Omadeus nugget/task N${nuggetNumber}]`;
|
|
176
202
|
|
|
177
203
|
if (fetchError) {
|
|
@@ -188,9 +214,46 @@ export function appendNuggetLookupContextForAgent(
|
|
|
188
214
|
);
|
|
189
215
|
}
|
|
190
216
|
|
|
191
|
-
const payload = pickNuggetFieldsForAgent(record);
|
|
217
|
+
const payload = (await buildNuggetAgentDataPayload(apiOpts, record)) ?? pickNuggetFieldsForAgent(record);
|
|
218
|
+
return (
|
|
219
|
+
`${rawBody}\n\n${header} Data from Omadeus (summarize for someone tracking this work — status, ownership, timeline, project; plain language. **For assignees and anyone in \`people\` / \`*FirstName\` fields, use those names; never read raw *ReferenceId numbers to the user as a person.**):\n` +
|
|
220
|
+
`${JSON.stringify(payload, null, 2)}`
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Enriches a Task or Nugget **Jaguar room** with Dolphin data matched by this chat's `roomId`, so the
|
|
226
|
+
* agent can answer "status" without a bare `N###` in the message.
|
|
227
|
+
*/
|
|
228
|
+
export async function appendNuggetContextForTaskOrNuggetRoom(
|
|
229
|
+
rawBody: string,
|
|
230
|
+
roomId: number,
|
|
231
|
+
roomName: string | null,
|
|
232
|
+
record: Record<string, unknown> | null,
|
|
233
|
+
apiOpts: OmadeusApiOptions,
|
|
234
|
+
fetchError?: string,
|
|
235
|
+
): Promise<string> {
|
|
236
|
+
const roomLabel = roomName?.trim() ? `room ${roomId} ("${roomName.trim()}")` : `room ${roomId}`;
|
|
237
|
+
|
|
238
|
+
if (fetchError) {
|
|
239
|
+
return (
|
|
240
|
+
`${rawBody}\n\n[Omadeus, this task/nugget ${roomLabel}] Lookup failed: ${fetchError}.\n` +
|
|
241
|
+
`Answer from this thread. Do not tell the user to "use the Omadeus platform" or similar — give a direct reply or a concrete next step.`
|
|
242
|
+
);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
if (!record) {
|
|
246
|
+
return (
|
|
247
|
+
`${rawBody}\n\n[Omadeus, this task/nugget ${roomLabel}] No Dolphin row matched this room id in search yet.\n` +
|
|
248
|
+
`Answer from the conversation; be honest if you cannot see live fields. Do not hand-wave to "the platform".`
|
|
249
|
+
);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
const n = readNuggetNumber(record);
|
|
253
|
+
const nLabel = n !== undefined ? `N${n}` : "nugget";
|
|
254
|
+
const payload = (await buildNuggetAgentDataPayload(apiOpts, record)) ?? pickNuggetFieldsForAgent(record);
|
|
192
255
|
return (
|
|
193
|
-
`${rawBody}\n\n${
|
|
256
|
+
`${rawBody}\n\n[Omadeus ${nLabel} for this chat room] The following is live task/nugget data — **answer the user with this** (stage, status, title, who, due date; plain language. **For assignee/owner, use \`people\` and name fields; never recite *ReferenceId numbers (e.g. 210) as a person's name.**). \`task/...\` in the UI is not an OpenClaw session key.\n` +
|
|
194
257
|
`${JSON.stringify(payload, null, 2)}`
|
|
195
258
|
);
|
|
196
259
|
}
|
package/src/onboarding.ts
CHANGED
|
@@ -1,25 +1,58 @@
|
|
|
1
1
|
import type { ChannelSetupWizard, OpenClawConfig, WizardPrompter } from "openclaw/plugin-sdk/setup";
|
|
2
|
-
import { DEFAULT_ACCOUNT_ID
|
|
2
|
+
import { DEFAULT_ACCOUNT_ID } from "openclaw/plugin-sdk/setup";
|
|
3
3
|
import {
|
|
4
|
-
listMemberChannelViews,
|
|
5
4
|
listOrganizationMembers,
|
|
6
5
|
listOrganizations,
|
|
7
6
|
} from "./api/auth.api.js";
|
|
7
|
+
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
11
|
import type {
|
|
12
12
|
OmadeusChannelConfig,
|
|
13
13
|
OmadeusChannelView,
|
|
14
|
+
OmadeusInboundEntityKind,
|
|
14
15
|
OmadeusOrganizationMember,
|
|
15
16
|
} from "./types.js";
|
|
16
17
|
|
|
17
18
|
const channel = "omadeus" as const;
|
|
19
|
+
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
|
+
];
|
|
18
30
|
|
|
19
31
|
type CoreConfig = OpenClawConfig & {
|
|
20
32
|
channels?: { omadeus?: OmadeusChannelConfig };
|
|
21
33
|
};
|
|
22
34
|
|
|
35
|
+
type SelectOption = {
|
|
36
|
+
value: string;
|
|
37
|
+
label: string;
|
|
38
|
+
hint?: string;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
type MultiSelectPrompter = {
|
|
42
|
+
multiSelect?: (args: {
|
|
43
|
+
message: string;
|
|
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[]>;
|
|
54
|
+
};
|
|
55
|
+
|
|
23
56
|
function getOmadeusSection(cfg: OpenClawConfig): OmadeusChannelConfig | undefined {
|
|
24
57
|
return getOmadeusChannelConfig(cfg as CoreConfig);
|
|
25
58
|
}
|
|
@@ -34,7 +67,6 @@ async function noteOmadeusAuthHelp(prompter: WizardPrompter): Promise<void> {
|
|
|
34
67
|
`CAS URL: ${OMADEUS_CAS_URL}`,
|
|
35
68
|
`Maestro URL: ${OMADEUS_MAESTRO_URL}`,
|
|
36
69
|
"Env vars supported: OMADEUS_EMAIL, OMADEUS_PASSWORD, OMADEUS_ORGANIZATION_ID.",
|
|
37
|
-
`Docs: ${formatDocsLink("/channels/omadeus", "omadeus")}`,
|
|
38
70
|
].join("\n"),
|
|
39
71
|
"Omadeus setup",
|
|
40
72
|
);
|
|
@@ -97,14 +129,9 @@ async function promptChannelSelection(params: {
|
|
|
97
129
|
maestroUrl: string;
|
|
98
130
|
sessionToken: string;
|
|
99
131
|
memberReferenceId: number;
|
|
100
|
-
|
|
101
|
-
}): Promise<{
|
|
102
|
-
|
|
103
|
-
selectedChannelTitle: string;
|
|
104
|
-
selectedChannelPrivateRoomId?: number;
|
|
105
|
-
selectedChannelPublicRoomId?: number;
|
|
106
|
-
}> {
|
|
107
|
-
const { prompter, maestroUrl, sessionToken, memberReferenceId, existing } = params;
|
|
132
|
+
existingChannelViewIds?: number[];
|
|
133
|
+
}): Promise<OmadeusChannelView[]> {
|
|
134
|
+
const { prompter, maestroUrl, sessionToken, memberReferenceId, existingChannelViewIds } = params;
|
|
108
135
|
const channels = await listMemberChannelViews({
|
|
109
136
|
maestroUrl,
|
|
110
137
|
sessionToken,
|
|
@@ -115,31 +142,26 @@ async function promptChannelSelection(params: {
|
|
|
115
142
|
if (channels.length === 0) {
|
|
116
143
|
throw new Error("No channels found for this account.");
|
|
117
144
|
}
|
|
118
|
-
const selected = await
|
|
119
|
-
|
|
145
|
+
const selected = await promptMultiSelect({
|
|
146
|
+
prompter,
|
|
147
|
+
message: "Which channels should OpenClaw listen to?",
|
|
120
148
|
options: channels.map((item) => ({
|
|
121
149
|
value: String(item.id),
|
|
122
150
|
label: item.title || `Channel ${item.id}`,
|
|
151
|
+
hint: [item.privateRoomId ? `private:${item.privateRoomId}` : undefined, item.publicRoomId ? `public:${item.publicRoomId}` : undefined]
|
|
152
|
+
.filter(Boolean)
|
|
153
|
+
.join(" | "),
|
|
123
154
|
})),
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
? String
|
|
127
|
-
: String(channels[0]!.id),
|
|
155
|
+
initialValues:
|
|
156
|
+
existingChannelViewIds && existingChannelViewIds.length > 0
|
|
157
|
+
? existingChannelViewIds.map(String)
|
|
158
|
+
: [String(channels[0]!.id)],
|
|
128
159
|
});
|
|
129
|
-
const chosen = channels.
|
|
130
|
-
if (
|
|
131
|
-
throw new Error("
|
|
160
|
+
const chosen = channels.filter((item) => selected.includes(String(item.id)));
|
|
161
|
+
if (chosen.length === 0) {
|
|
162
|
+
throw new Error("At least one channel must be selected.");
|
|
132
163
|
}
|
|
133
|
-
return
|
|
134
|
-
selectedChannelViewId: chosen.id,
|
|
135
|
-
selectedChannelTitle: chosen.title,
|
|
136
|
-
...(typeof chosen.privateRoomId === "number"
|
|
137
|
-
? { selectedChannelPrivateRoomId: chosen.privateRoomId }
|
|
138
|
-
: {}),
|
|
139
|
-
...(typeof chosen.publicRoomId === "number"
|
|
140
|
-
? { selectedChannelPublicRoomId: chosen.publicRoomId }
|
|
141
|
-
: {}),
|
|
142
|
-
};
|
|
164
|
+
return chosen;
|
|
143
165
|
}
|
|
144
166
|
|
|
145
167
|
function memberLabel(member: OmadeusOrganizationMember): string {
|
|
@@ -162,93 +184,129 @@ function memberHint(member: OmadeusOrganizationMember): string | undefined {
|
|
|
162
184
|
return parts.length > 0 ? parts.join(" | ") : undefined;
|
|
163
185
|
}
|
|
164
186
|
|
|
165
|
-
function
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
187
|
+
async function promptMultiSelect(params: {
|
|
188
|
+
prompter: WizardPrompter;
|
|
189
|
+
message: string;
|
|
190
|
+
options: SelectOption[];
|
|
191
|
+
initialValues?: string[];
|
|
192
|
+
}): Promise<string[]> {
|
|
193
|
+
const multi = params.prompter as unknown as MultiSelectPrompter;
|
|
194
|
+
const runMulti = multi.multiSelect ?? multi.multiselect;
|
|
195
|
+
if (runMulti) {
|
|
196
|
+
return runMulti({
|
|
197
|
+
message: params.message,
|
|
198
|
+
options: params.options,
|
|
199
|
+
initialValues: params.initialValues,
|
|
200
|
+
initialValue: params.initialValues,
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const selected = new Set(params.initialValues ?? []);
|
|
205
|
+
while (true) {
|
|
206
|
+
const next = await params.prompter.select({
|
|
207
|
+
message: `${params.message} (${selected.size} selected)`,
|
|
208
|
+
options: [
|
|
209
|
+
{ value: DONE, label: selected.size > 0 ? "Done" : "Done (select none)" },
|
|
210
|
+
...params.options.map((option) => ({
|
|
211
|
+
...option,
|
|
212
|
+
label: selected.has(option.value) ? `[selected] ${option.label}` : option.label,
|
|
213
|
+
})),
|
|
214
|
+
],
|
|
215
|
+
initialValue: DONE,
|
|
216
|
+
});
|
|
217
|
+
const value = String(next);
|
|
218
|
+
if (value === DONE) {
|
|
219
|
+
return [...selected];
|
|
220
|
+
}
|
|
221
|
+
if (selected.has(value)) {
|
|
222
|
+
selected.delete(value);
|
|
223
|
+
} else {
|
|
224
|
+
selected.add(value);
|
|
225
|
+
}
|
|
172
226
|
}
|
|
173
|
-
return members.filter((member) => {
|
|
174
|
-
const fields = [
|
|
175
|
-
String(member.referenceId),
|
|
176
|
-
member.title ?? "",
|
|
177
|
-
member.firstName ?? "",
|
|
178
|
-
member.lastName ?? "",
|
|
179
|
-
member.email ?? "",
|
|
180
|
-
].map((value) => value.toLowerCase());
|
|
181
|
-
return fields.some((value) => value.includes(q));
|
|
182
|
-
});
|
|
183
227
|
}
|
|
184
228
|
|
|
185
|
-
async function
|
|
186
|
-
prompter: WizardPrompter;
|
|
229
|
+
async function loadSelectableMembers(params: {
|
|
187
230
|
maestroUrl: string;
|
|
188
231
|
sessionToken: string;
|
|
189
232
|
organizationId: number;
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
prompter,
|
|
195
|
-
maestroUrl,
|
|
196
|
-
sessionToken,
|
|
197
|
-
organizationId,
|
|
198
|
-
existingMemberReferenceId,
|
|
199
|
-
fallbackMemberReferenceId,
|
|
200
|
-
} = params;
|
|
201
|
-
|
|
202
|
-
const members = (
|
|
233
|
+
excludeReferenceIds?: number[];
|
|
234
|
+
}): Promise<OmadeusOrganizationMember[]> {
|
|
235
|
+
const excluded = new Set(params.excludeReferenceIds ?? []);
|
|
236
|
+
return (
|
|
203
237
|
await listOrganizationMembers({
|
|
204
|
-
maestroUrl,
|
|
205
|
-
sessionToken,
|
|
206
|
-
organizationId,
|
|
238
|
+
maestroUrl: params.maestroUrl,
|
|
239
|
+
sessionToken: params.sessionToken,
|
|
240
|
+
organizationId: params.organizationId,
|
|
207
241
|
})
|
|
208
242
|
)
|
|
209
|
-
.filter((member) => member.isSystem !== true)
|
|
243
|
+
.filter((member) => member.isSystem !== true && !excluded.has(member.referenceId))
|
|
210
244
|
.sort((a, b) => memberLabel(a).localeCompare(memberLabel(b)));
|
|
245
|
+
}
|
|
211
246
|
|
|
247
|
+
function memberOptions(members: OmadeusOrganizationMember[]): SelectOption[] {
|
|
248
|
+
return members.map((member) => ({
|
|
249
|
+
value: String(member.referenceId),
|
|
250
|
+
label: memberLabel(member),
|
|
251
|
+
hint: memberHint(member),
|
|
252
|
+
}));
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function readReferenceIds(values: string[]): number[] {
|
|
256
|
+
return values
|
|
257
|
+
.map((value) => Number(value))
|
|
258
|
+
.filter((value) => Number.isInteger(value) && value > 0);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
async function promptSenderAllowlist(params: {
|
|
262
|
+
prompter: WizardPrompter;
|
|
263
|
+
message: string;
|
|
264
|
+
members: OmadeusOrganizationMember[];
|
|
265
|
+
existingReferenceIds?: number[];
|
|
266
|
+
}): Promise<number[] | undefined> {
|
|
267
|
+
const { prompter, message, members, existingReferenceIds } = params;
|
|
212
268
|
if (members.length === 0) {
|
|
213
269
|
throw new Error("No organization members found.");
|
|
214
270
|
}
|
|
215
271
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
continue;
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
const defaultRef = existingMemberReferenceId ?? fallbackMemberReferenceId;
|
|
230
|
-
const selected = await prompter.select({
|
|
231
|
-
message: "Which member should OpenClaw listen to?",
|
|
232
|
-
options: filtered.map((member) => ({
|
|
233
|
-
value: String(member.referenceId),
|
|
234
|
-
label: memberLabel(member),
|
|
235
|
-
hint: memberHint(member),
|
|
236
|
-
})),
|
|
237
|
-
initialValue:
|
|
238
|
-
defaultRef !== undefined && filtered.some((member) => member.referenceId === defaultRef)
|
|
239
|
-
? String(defaultRef)
|
|
240
|
-
: String(filtered[0]!.referenceId),
|
|
241
|
-
});
|
|
242
|
-
const chosen = filtered.find((member) => String(member.referenceId) === String(selected));
|
|
243
|
-
if (!chosen) {
|
|
244
|
-
await prompter.note("Could not resolve selected member. Please retry.", "Omadeus member");
|
|
245
|
-
continue;
|
|
246
|
-
}
|
|
247
|
-
return {
|
|
248
|
-
memberReferenceId: chosen.referenceId,
|
|
249
|
-
memberTitle: memberLabel(chosen),
|
|
250
|
-
};
|
|
272
|
+
const mode = await prompter.select({
|
|
273
|
+
message,
|
|
274
|
+
options: [
|
|
275
|
+
{ value: "all", label: "All users", hint: "No sender allowlist" },
|
|
276
|
+
{ value: "specific", label: "Specific users", hint: "Select one or more users" },
|
|
277
|
+
],
|
|
278
|
+
initialValue: existingReferenceIds && existingReferenceIds.length > 0 ? "specific" : "all",
|
|
279
|
+
});
|
|
280
|
+
if (String(mode) === "all") {
|
|
281
|
+
return undefined;
|
|
251
282
|
}
|
|
283
|
+
|
|
284
|
+
const selected = await promptMultiSelect({
|
|
285
|
+
prompter,
|
|
286
|
+
message: `${message} (specific users)`,
|
|
287
|
+
options: memberOptions(members),
|
|
288
|
+
initialValues: existingReferenceIds?.map(String),
|
|
289
|
+
});
|
|
290
|
+
return readReferenceIds(selected);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
async function promptEntityKindSelection(params: {
|
|
294
|
+
prompter: WizardPrompter;
|
|
295
|
+
existingKinds?: OmadeusInboundEntityKind[];
|
|
296
|
+
}): Promise<OmadeusInboundEntityKind[]> {
|
|
297
|
+
const selected = await promptMultiSelect({
|
|
298
|
+
prompter: params.prompter,
|
|
299
|
+
message: "Which entity room types should OpenClaw listen to?",
|
|
300
|
+
options: ALL_ENTITY_KINDS.map((kind) => ({
|
|
301
|
+
value: kind,
|
|
302
|
+
label: kind,
|
|
303
|
+
})),
|
|
304
|
+
initialValues:
|
|
305
|
+
params.existingKinds && params.existingKinds.length > 0
|
|
306
|
+
? params.existingKinds
|
|
307
|
+
: ALL_ENTITY_KINDS,
|
|
308
|
+
});
|
|
309
|
+
return ALL_ENTITY_KINDS.filter((kind) => selected.includes(kind));
|
|
252
310
|
}
|
|
253
311
|
|
|
254
312
|
export const omadeusSetupWizard: ChannelSetupWizard = {
|
|
@@ -372,26 +430,79 @@ export const omadeusSetupWizard: ChannelSetupWizard = {
|
|
|
372
430
|
throw new Error("Authentication is required to list channels.");
|
|
373
431
|
}
|
|
374
432
|
|
|
375
|
-
|
|
376
|
-
|
|
433
|
+
if (typeof selfReferenceId !== "number") {
|
|
434
|
+
throw new Error("Authentication did not return an Omadeus member reference ID.");
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
const members = await loadSelectableMembers({
|
|
377
438
|
maestroUrl,
|
|
378
439
|
sessionToken,
|
|
379
440
|
organizationId,
|
|
380
|
-
|
|
381
|
-
|
|
441
|
+
excludeReferenceIds: [selfReferenceId],
|
|
442
|
+
});
|
|
443
|
+
const existingInbound = section.inbound;
|
|
444
|
+
|
|
445
|
+
const directSenderIds = await promptSenderAllowlist({
|
|
446
|
+
prompter,
|
|
447
|
+
message: "Which users can DM OpenClaw directly?",
|
|
448
|
+
members,
|
|
449
|
+
existingReferenceIds: existingInbound?.direct?.allowedSenderReferenceIds,
|
|
382
450
|
});
|
|
383
451
|
|
|
384
|
-
const
|
|
452
|
+
const selectedChannels = await promptChannelSelection({
|
|
385
453
|
prompter,
|
|
386
454
|
maestroUrl,
|
|
387
455
|
sessionToken,
|
|
388
|
-
memberReferenceId:
|
|
389
|
-
|
|
456
|
+
memberReferenceId: selfReferenceId,
|
|
457
|
+
existingChannelViewIds: existingInbound?.channels?.allowedChannelViewIds,
|
|
390
458
|
});
|
|
391
459
|
|
|
460
|
+
const channelSenderIds = await promptSenderAllowlist({
|
|
461
|
+
prompter,
|
|
462
|
+
message: "Which users can trigger OpenClaw from allowed channels?",
|
|
463
|
+
members,
|
|
464
|
+
existingReferenceIds: existingInbound?.channels?.allowedSenderReferenceIds,
|
|
465
|
+
});
|
|
466
|
+
|
|
467
|
+
const entityKinds = await promptEntityKindSelection({
|
|
468
|
+
prompter,
|
|
469
|
+
existingKinds: existingInbound?.entities?.allowedKinds,
|
|
470
|
+
});
|
|
471
|
+
|
|
472
|
+
const entitySenderIds =
|
|
473
|
+
entityKinds.length > 0
|
|
474
|
+
? await promptSenderAllowlist({
|
|
475
|
+
prompter,
|
|
476
|
+
message: "Which users can trigger OpenClaw from entity rooms?",
|
|
477
|
+
members,
|
|
478
|
+
existingReferenceIds: existingInbound?.entities?.allowedSenderReferenceIds,
|
|
479
|
+
})
|
|
480
|
+
: undefined;
|
|
481
|
+
|
|
482
|
+
const channelRoomIds = selectedChannels
|
|
483
|
+
.flatMap((selectedChannel) => [
|
|
484
|
+
selectedChannel.publicRoomId,
|
|
485
|
+
selectedChannel.privateRoomId,
|
|
486
|
+
])
|
|
487
|
+
.filter((id): id is number => typeof id === "number");
|
|
488
|
+
const channelViewIds = selectedChannels.map((selectedChannel) => selectedChannel.id);
|
|
489
|
+
const channelTitles = selectedChannels
|
|
490
|
+
.map((selectedChannel) => selectedChannel.title || `Channel ${selectedChannel.id}`)
|
|
491
|
+
.join(", ");
|
|
492
|
+
|
|
493
|
+
const senderSummary = (ids: number[] | undefined) =>
|
|
494
|
+
ids && ids.length > 0 ? ids.join(", ") : "all users";
|
|
495
|
+
const entityKindSummary =
|
|
496
|
+
entityKinds.length > 0 ? entityKinds.join(", ") : "none (entity rooms disabled)";
|
|
497
|
+
|
|
392
498
|
await prompter.note(
|
|
393
|
-
|
|
394
|
-
|
|
499
|
+
[
|
|
500
|
+
`Inbound policy (Jaguar chat):`,
|
|
501
|
+
`- Direct messages: enabled for ${senderSummary(directSenderIds)} (no @mention required).`,
|
|
502
|
+
`- Channels "${channelTitles}": rooms ${channelRoomIds.join(", ") || "(no room ids)"} from ${senderSummary(channelSenderIds)}; @mention not required in those rooms.`,
|
|
503
|
+
`- Entity rooms (${entityKindSummary}): ${senderSummary(entitySenderIds)}; @mention required.`,
|
|
504
|
+
].join("\n"),
|
|
505
|
+
"Omadeus inbound policy",
|
|
395
506
|
);
|
|
396
507
|
|
|
397
508
|
next = {
|
|
@@ -399,7 +510,6 @@ export const omadeusSetupWizard: ChannelSetupWizard = {
|
|
|
399
510
|
channels: {
|
|
400
511
|
...next.channels,
|
|
401
512
|
omadeus: {
|
|
402
|
-
...getOmadeusSection(next),
|
|
403
513
|
enabled: true,
|
|
404
514
|
casUrl,
|
|
405
515
|
maestroUrl,
|
|
@@ -407,15 +517,27 @@ export const omadeusSetupWizard: ChannelSetupWizard = {
|
|
|
407
517
|
password,
|
|
408
518
|
organizationId,
|
|
409
519
|
...(sessionToken ? { sessionToken } : {}),
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
520
|
+
inbound: {
|
|
521
|
+
version: 1,
|
|
522
|
+
direct: {
|
|
523
|
+
enabled: true,
|
|
524
|
+
...(directSenderIds ? { allowedSenderReferenceIds: directSenderIds } : {}),
|
|
525
|
+
requireMention: "never",
|
|
526
|
+
},
|
|
527
|
+
channels: {
|
|
528
|
+
enabled: true,
|
|
529
|
+
allowedRoomIds: channelRoomIds,
|
|
530
|
+
allowedChannelViewIds: channelViewIds,
|
|
531
|
+
...(channelSenderIds ? { allowedSenderReferenceIds: channelSenderIds } : {}),
|
|
532
|
+
requireMention: "outsideAllowlist",
|
|
533
|
+
},
|
|
534
|
+
entities: {
|
|
535
|
+
enabled: entityKinds.length > 0,
|
|
536
|
+
allowedKinds: entityKinds,
|
|
537
|
+
...(entitySenderIds ? { allowedSenderReferenceIds: entitySenderIds } : {}),
|
|
538
|
+
requireMention: "always",
|
|
539
|
+
},
|
|
540
|
+
},
|
|
419
541
|
},
|
|
420
542
|
},
|
|
421
543
|
};
|
package/src/types.ts
CHANGED
|
@@ -2,6 +2,63 @@
|
|
|
2
2
|
// Omadeus config shape (stored under channels.omadeus in OpenClaw config)
|
|
3
3
|
// ---------------------------------------------------------------------------
|
|
4
4
|
|
|
5
|
+
export type OmadeusInboundMentionPolicy = "never" | "always" | "outsideAllowlist";
|
|
6
|
+
|
|
7
|
+
export type OmadeusInboundDirectPolicy = {
|
|
8
|
+
enabled: boolean;
|
|
9
|
+
allowedSenderReferenceIds?: number[];
|
|
10
|
+
requireMention?: "never" | "always";
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export type OmadeusInboundChannelsPolicy = {
|
|
14
|
+
enabled: boolean;
|
|
15
|
+
allowedRoomIds?: number[];
|
|
16
|
+
allowedChannelViewIds?: number[];
|
|
17
|
+
allowedSenderReferenceIds?: number[];
|
|
18
|
+
requireMention?: OmadeusInboundMentionPolicy;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
/** Jaguar `subscribableKind` values treated as entity chat (not DM, not channel). */
|
|
22
|
+
export type OmadeusInboundEntityKind =
|
|
23
|
+
| "task"
|
|
24
|
+
| "nugget"
|
|
25
|
+
| "project"
|
|
26
|
+
| "release"
|
|
27
|
+
| "sprint"
|
|
28
|
+
| "summary"
|
|
29
|
+
| "client"
|
|
30
|
+
| "folder";
|
|
31
|
+
|
|
32
|
+
/** Jaguar entity chats (`subscribableKind`) — DMs and channel rooms use other values. */
|
|
33
|
+
export const OMADEUS_INBOUND_ENTITY_KINDS: readonly OmadeusInboundEntityKind[] = [
|
|
34
|
+
"task",
|
|
35
|
+
"nugget",
|
|
36
|
+
"project",
|
|
37
|
+
"release",
|
|
38
|
+
"sprint",
|
|
39
|
+
"summary",
|
|
40
|
+
"client",
|
|
41
|
+
"folder",
|
|
42
|
+
];
|
|
43
|
+
|
|
44
|
+
export const OMADEUS_INBOUND_ENTITY_KIND_SET: ReadonlySet<string> = new Set(OMADEUS_INBOUND_ENTITY_KINDS);
|
|
45
|
+
|
|
46
|
+
export type OmadeusInboundEntitiesPolicy = {
|
|
47
|
+
enabled: boolean;
|
|
48
|
+
allowedKinds?: OmadeusInboundEntityKind[];
|
|
49
|
+
allowedRoomIds?: number[];
|
|
50
|
+
allowedSenderReferenceIds?: number[];
|
|
51
|
+
requireMention?: OmadeusInboundMentionPolicy;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
/** Jaguar chat ingress policy (DMs, channel rooms, entity rooms). */
|
|
55
|
+
export type OmadeusInboundPolicy = {
|
|
56
|
+
version?: number;
|
|
57
|
+
direct?: OmadeusInboundDirectPolicy;
|
|
58
|
+
channels?: OmadeusInboundChannelsPolicy;
|
|
59
|
+
entities?: OmadeusInboundEntitiesPolicy;
|
|
60
|
+
};
|
|
61
|
+
|
|
5
62
|
export type OmadeusChannelConfig = {
|
|
6
63
|
enabled?: boolean;
|
|
7
64
|
casUrl?: string;
|
|
@@ -11,13 +68,8 @@ export type OmadeusChannelConfig = {
|
|
|
11
68
|
organizationId?: number;
|
|
12
69
|
/** Cached Omadeus session JWT obtained during onboarding/startup. */
|
|
13
70
|
sessionToken?: string;
|
|
14
|
-
/**
|
|
15
|
-
|
|
16
|
-
/** Selected channel metadata used for inbound filtering. */
|
|
17
|
-
selectedChannelViewId?: number;
|
|
18
|
-
selectedChannelTitle?: string;
|
|
19
|
-
selectedChannelPrivateRoomId?: number;
|
|
20
|
-
selectedChannelPublicRoomId?: number;
|
|
71
|
+
/** Jaguar chat ingress allowlists and mention rules. */
|
|
72
|
+
inbound?: OmadeusInboundPolicy;
|
|
21
73
|
};
|
|
22
74
|
|
|
23
75
|
export type ResolvedOmadeusAccount = {
|
|
@@ -101,6 +153,13 @@ export type OmadeusJwtPayload = {
|
|
|
101
153
|
// Jaguar socket message (chat — DMs, nugget rooms, task rooms, etc.)
|
|
102
154
|
// ---------------------------------------------------------------------------
|
|
103
155
|
|
|
156
|
+
/**
|
|
157
|
+
* Omadeus subscribable **type** on Jaguar chat payloads (room context).
|
|
158
|
+
*
|
|
159
|
+
* - **direct** — DM from a user.
|
|
160
|
+
* - **channel** — message in a channel room.
|
|
161
|
+
* - **nugget** … **folder** — entity-associated chat (tasks, work items, hierarchy).
|
|
162
|
+
*/
|
|
104
163
|
export type OmadeusSubscribableType =
|
|
105
164
|
| "direct"
|
|
106
165
|
| "channel"
|
|
@@ -112,6 +171,15 @@ export type OmadeusSubscribableType =
|
|
|
112
171
|
| "client"
|
|
113
172
|
| "folder"
|
|
114
173
|
| (string & {});
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Omadeus subscribable **kind** on Jaguar chat payloads (same coarse buckets as `OmadeusSubscribableType`;
|
|
177
|
+
* routing uses `subscribableKind` in the plugin).
|
|
178
|
+
*
|
|
179
|
+
* - **direct** — DM from a user.
|
|
180
|
+
* - **channel** — message in a channel room.
|
|
181
|
+
* - **task**, **nugget**, **project**, **release**, **sprint**, **summary**, **client**, **folder** — entity chat.
|
|
182
|
+
*/
|
|
115
183
|
export type OmadeusSubscribableKind =
|
|
116
184
|
| "task"
|
|
117
185
|
| "direct"
|
|
@@ -181,6 +249,8 @@ export type OmadeusInboundMessage = {
|
|
|
181
249
|
roomName: string | null;
|
|
182
250
|
subscribableType: OmadeusSubscribableType;
|
|
183
251
|
subscribableKind: OmadeusSubscribableKind;
|
|
252
|
+
/** When present, used with `inbound.channels.allowedChannelViewIds`. */
|
|
253
|
+
channelViewId?: number;
|
|
184
254
|
isMention: boolean;
|
|
185
255
|
timestamp: number;
|
|
186
256
|
};
|