@brantrusnak/openclaw-omadeus 1.0.1 → 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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # OpenClaw Omadeus Plugin
2
2
 
3
- Omadeus channel plugin for [OpenClaw](https://www.npmjs.com/package/openclaw).
3
+ [Omadeus](https://omadeus.com) plugin for [OpenClaw](https://www.npmjs.com/package/openclaw).
4
4
 
5
5
  This plugin connects OpenClaw to Omadeus over WebSocket so OpenClaw can listen
6
6
  for Omadeus messages and reply through the selected Omadeus channel.
@@ -12,11 +12,77 @@
12
12
  "password": { "type": "string" },
13
13
  "organizationId": { "type": "number" },
14
14
  "sessionToken": { "type": "string" },
15
- "selectedMemberReferenceId": { "type": "number" },
16
- "selectedChannelViewId": { "type": "number" },
17
- "selectedChannelTitle": { "type": "string" },
18
- "selectedChannelPrivateRoomId": { "type": "number" },
19
- "selectedChannelPublicRoomId": { "type": "number" }
15
+ "inbound": {
16
+ "type": "object",
17
+ "additionalProperties": false,
18
+ "properties": {
19
+ "version": { "type": "integer", "minimum": 1 },
20
+ "direct": {
21
+ "type": "object",
22
+ "additionalProperties": false,
23
+ "properties": {
24
+ "enabled": { "type": "boolean" },
25
+ "allowedSenderReferenceIds": {
26
+ "type": "array",
27
+ "items": { "type": "number" }
28
+ },
29
+ "requireMention": { "type": "string", "enum": ["never", "always"] }
30
+ },
31
+ "required": ["enabled"]
32
+ },
33
+ "channels": {
34
+ "type": "object",
35
+ "additionalProperties": false,
36
+ "properties": {
37
+ "enabled": { "type": "boolean" },
38
+ "allowedRoomIds": { "type": "array", "items": { "type": "number" } },
39
+ "allowedChannelViewIds": { "type": "array", "items": { "type": "number" } },
40
+ "allowedSenderReferenceIds": {
41
+ "type": "array",
42
+ "items": { "type": "number" }
43
+ },
44
+ "requireMention": {
45
+ "type": "string",
46
+ "enum": ["never", "always", "outsideAllowlist"]
47
+ }
48
+ },
49
+ "required": ["enabled"]
50
+ },
51
+ "entities": {
52
+ "type": "object",
53
+ "additionalProperties": false,
54
+ "properties": {
55
+ "enabled": { "type": "boolean" },
56
+ "allowedKinds": {
57
+ "type": "array",
58
+ "items": {
59
+ "type": "string",
60
+ "enum": [
61
+ "task",
62
+ "nugget",
63
+ "project",
64
+ "release",
65
+ "sprint",
66
+ "summary",
67
+ "client",
68
+ "folder"
69
+ ]
70
+ }
71
+ },
72
+ "allowedRoomIds": { "type": "array", "items": { "type": "number" } },
73
+ "allowedSenderReferenceIds": {
74
+ "type": "array",
75
+ "items": { "type": "number" }
76
+ },
77
+ "requireMention": {
78
+ "type": "string",
79
+ "enum": ["never", "always", "outsideAllowlist"]
80
+ }
81
+ },
82
+ "required": ["enabled"]
83
+ }
84
+ }
85
+ }
20
86
  }
21
87
  },
22
88
  "channelConfigs": {
@@ -32,11 +98,77 @@
32
98
  "password": { "type": "string" },
33
99
  "organizationId": { "type": "number" },
34
100
  "sessionToken": { "type": "string" },
35
- "selectedMemberReferenceId": { "type": "number" },
36
- "selectedChannelViewId": { "type": "number" },
37
- "selectedChannelTitle": { "type": "string" },
38
- "selectedChannelPrivateRoomId": { "type": "number" },
39
- "selectedChannelPublicRoomId": { "type": "number" }
101
+ "inbound": {
102
+ "type": "object",
103
+ "additionalProperties": false,
104
+ "properties": {
105
+ "version": { "type": "integer", "minimum": 1 },
106
+ "direct": {
107
+ "type": "object",
108
+ "additionalProperties": false,
109
+ "properties": {
110
+ "enabled": { "type": "boolean" },
111
+ "allowedSenderReferenceIds": {
112
+ "type": "array",
113
+ "items": { "type": "number" }
114
+ },
115
+ "requireMention": { "type": "string", "enum": ["never", "always"] }
116
+ },
117
+ "required": ["enabled"]
118
+ },
119
+ "channels": {
120
+ "type": "object",
121
+ "additionalProperties": false,
122
+ "properties": {
123
+ "enabled": { "type": "boolean" },
124
+ "allowedRoomIds": { "type": "array", "items": { "type": "number" } },
125
+ "allowedChannelViewIds": { "type": "array", "items": { "type": "number" } },
126
+ "allowedSenderReferenceIds": {
127
+ "type": "array",
128
+ "items": { "type": "number" }
129
+ },
130
+ "requireMention": {
131
+ "type": "string",
132
+ "enum": ["never", "always", "outsideAllowlist"]
133
+ }
134
+ },
135
+ "required": ["enabled"]
136
+ },
137
+ "entities": {
138
+ "type": "object",
139
+ "additionalProperties": false,
140
+ "properties": {
141
+ "enabled": { "type": "boolean" },
142
+ "allowedKinds": {
143
+ "type": "array",
144
+ "items": {
145
+ "type": "string",
146
+ "enum": [
147
+ "task",
148
+ "nugget",
149
+ "project",
150
+ "release",
151
+ "sprint",
152
+ "summary",
153
+ "client",
154
+ "folder"
155
+ ]
156
+ }
157
+ },
158
+ "allowedRoomIds": { "type": "array", "items": { "type": "number" } },
159
+ "allowedSenderReferenceIds": {
160
+ "type": "array",
161
+ "items": { "type": "number" }
162
+ },
163
+ "requireMention": {
164
+ "type": "string",
165
+ "enum": ["never", "always", "outsideAllowlist"]
166
+ }
167
+ },
168
+ "required": ["enabled"]
169
+ }
170
+ }
171
+ }
40
172
  }
41
173
  },
42
174
  "uiHints": {
@@ -63,24 +195,8 @@
63
195
  "sensitive": true,
64
196
  "advanced": true
65
197
  },
66
- "selectedMemberReferenceId": {
67
- "label": "Selected member reference ID",
68
- "advanced": true
69
- },
70
- "selectedChannelViewId": {
71
- "label": "Selected channel view ID",
72
- "advanced": true
73
- },
74
- "selectedChannelTitle": {
75
- "label": "Selected channel title",
76
- "advanced": true
77
- },
78
- "selectedChannelPrivateRoomId": {
79
- "label": "Selected private room ID",
80
- "advanced": true
81
- },
82
- "selectedChannelPublicRoomId": {
83
- "label": "Selected public room ID",
198
+ "inbound": {
199
+ "label": "Inbound policy (Jaguar chat)",
84
200
  "advanced": true
85
201
  }
86
202
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brantrusnak/openclaw-omadeus",
3
- "version": "1.0.1",
3
+ "version": "1.0.2",
4
4
  "private": false,
5
5
  "description": "OpenClaw Omadeus project management channel plugin",
6
6
  "homepage": "https://github.com/brantrusnak/openclaw-omadeus-plugin#readme",
@@ -54,10 +54,8 @@
54
54
  "channel": {
55
55
  "id": "omadeus",
56
56
  "label": "Omadeus",
57
- "selectionLabel": "Omadeus (WebSocket)",
58
- "docsPath": "/channels/omadeus",
59
- "docsLabel": "omadeus",
60
- "blurb": "Omadeus project management — tasks, chat rooms, and sprints.",
57
+ "selectionLabel": "Omadeus (API + WebSocket)",
58
+ "blurb": "AI-native project management that knows your role, speaks your language, and keeps your team in sync. No noise.",
61
59
  "order": 70
62
60
  },
63
61
  "install": {
@@ -1,7 +1,6 @@
1
1
  import { getCasSession, setCasSession } from "../store.js";
2
2
  import type {
3
3
  CasAuthorizationCodeResponse,
4
- OmadeusChannelView,
5
4
  OmadeusOrganizationMember,
6
5
  OmadeusOrganization,
7
6
  OmadeusSessionTokenResponse,
@@ -139,34 +138,6 @@ export async function listOrganizations(params: {
139
138
  return (await res.json()) as OmadeusOrganization[];
140
139
  }
141
140
 
142
- export async function listMemberChannelViews(params: {
143
- maestroUrl: string;
144
- sessionToken: string;
145
- memberReferenceId: number;
146
- skip?: number;
147
- take?: number;
148
- }): Promise<OmadeusChannelView[]> {
149
- const { maestroUrl, sessionToken, memberReferenceId, skip = 0, take = 100 } = params;
150
- const qs = new URLSearchParams({
151
- skip: String(skip),
152
- take: String(take),
153
- sort: "-recentMessageAt",
154
- });
155
- const url = `${maestroUrl}/jaguar/apiv1/members/${memberReferenceId}/channelviews?${qs.toString()}`;
156
- const res = await fetch(url, {
157
- method: "LIST",
158
- headers: {
159
- Authorization: `Bearer ${sessionToken}`,
160
- "Content-Type": "application/json;charset=UTF-8",
161
- },
162
- });
163
- if (!res.ok) {
164
- const text = await res.text().catch(() => "");
165
- throw new Error(`Omadeus list channel views failed (${res.status}): ${text}`);
166
- }
167
- return (await res.json()) as OmadeusChannelView[];
168
- }
169
-
170
141
  export async function listOrganizationMembers(params: {
171
142
  maestroUrl: string;
172
143
  sessionToken: string;
@@ -0,0 +1,29 @@
1
+ import type { OmadeusChannelView } from "../types.js";
2
+
3
+ export async function listMemberChannelViews(params: {
4
+ maestroUrl: string;
5
+ sessionToken: string;
6
+ memberReferenceId: number;
7
+ skip?: number;
8
+ take?: number;
9
+ }): Promise<OmadeusChannelView[]> {
10
+ const { maestroUrl, sessionToken, memberReferenceId, skip = 0, take = 100 } = params;
11
+ const qs = new URLSearchParams({
12
+ skip: String(skip),
13
+ take: String(take),
14
+ sort: "-recentMessageAt",
15
+ });
16
+ const url = `${maestroUrl}/jaguar/apiv1/members/${memberReferenceId}/channelviews?${qs.toString()}`;
17
+ const res = await fetch(url, {
18
+ method: "LIST",
19
+ headers: {
20
+ Authorization: `Bearer ${sessionToken}`,
21
+ "Content-Type": "application/json;charset=UTF-8",
22
+ },
23
+ });
24
+ if (!res.ok) {
25
+ const text = await res.text().catch(() => "");
26
+ throw new Error(`Omadeus list channel views failed (${res.status}): ${text}`);
27
+ }
28
+ return (await res.json()) as OmadeusChannelView[];
29
+ }
@@ -85,20 +85,23 @@ function extractRows(payload: unknown): Record<string, unknown>[] {
85
85
  }
86
86
 
87
87
  /**
88
- * Dolphin SEARCH on nuggetviews returns an array of nugget/task rows.
89
- * User-facing `N111` corresponds to `number: 111` on each row (not `id`).
88
+ * Dolphin SEARCH on nuggetviews arbitrary text query (e.g. N###, task title, or room id string).
89
+ * Prefer filtering results with `findNuggetRowByNumber` or `findNuggetRowByRoomId`.
90
90
  */
91
- export async function searchNuggetByNumber(
91
+ export async function searchNuggetRowsByTextQuery(
92
92
  opts: OmadeusApiOptions,
93
- params: NuggetSearchParams,
94
- ): Promise<Record<string, unknown> | null> {
95
- const query = `N${params.nuggetNumber}`;
93
+ params: { query: string; take?: number; signal?: AbortSignal },
94
+ ): Promise<Record<string, unknown>[]> {
95
+ const take = params.take ?? 100;
96
+ const q = params.query.trim();
97
+ if (!q) {
98
+ return [];
99
+ }
96
100
  const search = new URLSearchParams();
97
- search.set("take", "100");
98
-
101
+ search.set("take", String(take));
99
102
  const res = await dolphinFetch(opts, `/nuggetviews?${search.toString()}`, {
100
103
  method: "SEARCH",
101
- body: JSON.stringify({ query }),
104
+ body: JSON.stringify({ query: q }),
102
105
  signal: params.signal,
103
106
  });
104
107
  if (!res.ok) {
@@ -106,7 +109,75 @@ export async function searchNuggetByNumber(
106
109
  throw new Error(`Omadeus nugget search failed (${res.status}): ${text.slice(0, 200)}`);
107
110
  }
108
111
  const payload = (await res.json()) as unknown;
109
- const rows = extractRows(payload);
112
+ return extractRows(payload);
113
+ }
114
+
115
+ /**
116
+ * Picks a row whose private/public/shared task room id matches a Jaguar `roomId`.
117
+ */
118
+ export function findNuggetRowByRoomId(
119
+ rows: Record<string, unknown>[],
120
+ roomId: number,
121
+ ): Record<string, unknown> | undefined {
122
+ for (const row of rows) {
123
+ for (const key of ["privateRoomId", "publicRoomId", "sharedRoomId"] as const) {
124
+ if (readNumberField(row, key) === roomId) {
125
+ return row;
126
+ }
127
+ }
128
+ }
129
+ return undefined;
130
+ }
131
+
132
+ export type FindNuggetByTaskRoomParams = {
133
+ roomId: number;
134
+ roomName?: string | null;
135
+ signal?: AbortSignal;
136
+ };
137
+
138
+ /**
139
+ * Resolve the nugget/task row for a Jaguar Task or Nugget **chat room** by matching
140
+ * `privateRoomId` / `publicRoomId` / `sharedRoomId` to `roomId` in Dolphin `nuggetviews` search results.
141
+ * Tries search by `roomName` first (usually matches the task title), then by the numeric `roomId` as text.
142
+ */
143
+ export async function findNuggetByTaskChannelRoom(
144
+ opts: OmadeusApiOptions,
145
+ params: FindNuggetByTaskRoomParams,
146
+ ): Promise<Record<string, unknown> | null> {
147
+ const { roomId, roomName, signal } = params;
148
+ const tryQueries: string[] = [];
149
+ if (typeof roomName === "string" && roomName.trim()) {
150
+ tryQueries.push(roomName.trim());
151
+ }
152
+ tryQueries.push(String(roomId));
153
+ const tried = new Set<string>();
154
+ for (const query of tryQueries) {
155
+ if (tried.has(query)) {
156
+ continue;
157
+ }
158
+ tried.add(query);
159
+ const rows = await searchNuggetRowsByTextQuery(opts, { query, take: 100, signal });
160
+ const match = findNuggetRowByRoomId(rows, roomId);
161
+ if (match) {
162
+ return match;
163
+ }
164
+ }
165
+ return null;
166
+ }
167
+
168
+ /**
169
+ * Dolphin SEARCH on nuggetviews returns an array of nugget/task rows.
170
+ * User-facing `N111` corresponds to `number: 111` on each row (not `id`).
171
+ */
172
+ export async function searchNuggetByNumber(
173
+ opts: OmadeusApiOptions,
174
+ params: NuggetSearchParams,
175
+ ): Promise<Record<string, unknown> | null> {
176
+ const rows = await searchNuggetRowsByTextQuery(opts, {
177
+ query: `N${params.nuggetNumber}`,
178
+ take: 100,
179
+ signal: params.signal,
180
+ });
110
181
  const match = findNuggetRowByNumber(rows, params.nuggetNumber);
111
182
  return match ?? null;
112
183
  }
package/src/channel.ts CHANGED
@@ -83,11 +83,7 @@ const omadeusConfigAdapter = createTopLevelChannelConfigAdapter<Account>({
83
83
  "password",
84
84
  "organizationId",
85
85
  "sessionToken",
86
- "selectedMemberReferenceId",
87
- "selectedChannelViewId",
88
- "selectedChannelTitle",
89
- "selectedChannelPrivateRoomId",
90
- "selectedChannelPublicRoomId",
86
+ "inbound",
91
87
  ],
92
88
  // Keep adapter contract satisfied even though Omadeus no longer uses DM allowlists.
93
89
  resolveAllowFrom: () => [],
@@ -188,10 +184,10 @@ export const omadeusPlugin: ChannelPlugin<Account> = {
188
184
  meta: {
189
185
  id: "omadeus",
190
186
  label: "Omadeus",
191
- selectionLabel: "Omadeus (WebSocket)",
192
- docsPath: "/channels/omadeus",
193
- docsLabel: "omadeus",
194
- blurb: "Omadeus project management tasks, chat rooms, and sprints.",
187
+ selectionLabel: "Omadeus (API + WebSocket)",
188
+ docsPath: "",
189
+ docsLabel: "",
190
+ blurb: "AI-native project management that knows your role, speaks your language, and keeps your team in sync. No noise.",
195
191
  },
196
192
  capabilities: {
197
193
  chatTypes: ["direct", "group"],
@@ -205,6 +201,9 @@ export const omadeusPlugin: ChannelPlugin<Account> = {
205
201
  messageToolHints: () => [
206
202
  "- Omadeus routing: **send** uses **room id** (`to` / `target`, e.g. `room:117947` or `117947`). **edit**, **delete**, **react** use the Jaguar **message** `id` (`messageId`, or the current inbound message from context).",
207
203
  "- Create Omadeus task/nugget: use `action=send` with params `{ op: \"create_task\"|\"create_nugget\", title, description, priority?, stage?, kind?, memberReferenceId?, clientId?, folderId? }`.",
204
+ "- Omadeus **Task** and **Nugget** are distinct product types (Jaguar `subscribableKind`). **Project**, **Sprint**, **Release**, **Folder**, **Client**, **Summary**, etc. also have entity chat rooms. User \"task\" / \"the task\" → map to **this room's** `subscribableKind` (Task vs Nugget vs other), not an OpenClaw background task.",
205
+ "- In Task or Nugget rooms, inbound may include **Dolphin nuggetviews** JSON for this chat's `roomId` — **summarize that** for status questions. The payload may include a **`people`** object (Omadeus member names). Use those for assignees; do not read `referenceId` numbers as names. Do not tell the user to go use the Omadeus app instead of answering from that data or the thread.",
206
+ "- `session_status` / SessionKey: **OpenClaw** gateway only. Use the inbound SessionKey, \"current\", or the hint in **entity** rooms — never a fake `task/<...>` string from a title.",
208
207
  `- Reactions only allow these emojis (others are ignored): ${ALLOWED_OMADEUS_REACTION_EMOJI_LIST.join(" ")}`,
209
208
  "- Reply in chat with plain text; use the message tool for proactive sends, edits, deletes, or reactions.",
210
209
  ],
@@ -711,6 +710,7 @@ export const omadeusPlugin: ChannelPlugin<Account> = {
711
710
  runtime: ctx.runtime,
712
711
  log,
713
712
  outboundDeps,
713
+ selfReferenceId,
714
714
  });
715
715
 
716
716
  // Jaguar socket (chat — DMs, nugget/task/project rooms)