@shadowob/openclaw-shadowob 1.1.1 → 1.1.3-dev.261

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.
@@ -1,585 +0,0 @@
1
- // src/channel.ts
2
- import { ShadowClient as ShadowClient2 } from "@shadowob/sdk";
3
- import { createChatChannelPlugin } from "openclaw/plugin-sdk/core";
4
-
5
- // src/config.ts
6
- var DEFAULT_ACCOUNT_ID = "default";
7
- function getShadowBlock(cfg) {
8
- const channels = cfg.channels;
9
- return channels?.shadowob ?? channels?.["openclaw-shadowob"] ?? channels?.shadow;
10
- }
11
- function getAccountConfig(cfg, accountId) {
12
- const shadow = getShadowBlock(cfg);
13
- if (!shadow) return null;
14
- const accounts = shadow.accounts;
15
- if (accountId === DEFAULT_ACCOUNT_ID) {
16
- const fromAccounts = accounts?.[DEFAULT_ACCOUNT_ID];
17
- const baseLevel = {
18
- token: typeof shadow.token === "string" ? shadow.token : void 0,
19
- serverUrl: typeof shadow.serverUrl === "string" ? shadow.serverUrl : void 0,
20
- enabled: typeof shadow.enabled === "boolean" ? shadow.enabled : void 0
21
- };
22
- const merged = {
23
- ...fromAccounts,
24
- // Base-level fields take precedence (convenience shorthand)
25
- ...Object.fromEntries(Object.entries(baseLevel).filter(([, v]) => v !== void 0))
26
- };
27
- if (merged.token) {
28
- return {
29
- token: merged.token,
30
- serverUrl: merged.serverUrl ?? "https://shadowob.com",
31
- enabled: merged.enabled
32
- };
33
- }
34
- return fromAccounts ?? null;
35
- }
36
- return accounts?.[accountId] ?? null;
37
- }
38
- function listAccountIds(cfg) {
39
- const shadow = getShadowBlock(cfg);
40
- if (!shadow) return [];
41
- const accounts = shadow.accounts;
42
- const ids = [];
43
- if (accounts) {
44
- ids.push(...Object.keys(accounts));
45
- }
46
- const hasBaseLevelConfig = typeof shadow.token === "string" || typeof shadow.serverUrl === "string";
47
- if (hasBaseLevelConfig && !ids.includes(DEFAULT_ACCOUNT_ID)) {
48
- ids.push(DEFAULT_ACCOUNT_ID);
49
- }
50
- return ids;
51
- }
52
-
53
- // src/outbound.ts
54
- import { ShadowClient } from "@shadowob/sdk";
55
- function parseTarget(to) {
56
- const parts = to.split(":");
57
- const prefix = parts[0];
58
- if ((prefix === "shadowob" || prefix === "openclaw-shadowob" || prefix === "shadow") && parts[1] === "channel" && parts[2]) {
59
- return { channelId: parts[2] };
60
- }
61
- if ((prefix === "shadowob" || prefix === "openclaw-shadowob" || prefix === "shadow") && parts[1] === "thread" && parts[2]) {
62
- return { threadId: parts[2] };
63
- }
64
- return { channelId: to };
65
- }
66
- function resolveClient(cfg, accountId) {
67
- const account = getAccountConfig(cfg, accountId ?? DEFAULT_ACCOUNT_ID);
68
- if (!account) return null;
69
- return { client: new ShadowClient(account.serverUrl, account.token), account };
70
- }
71
- var shadowOutbound = {
72
- deliveryMode: "direct",
73
- chunker: null,
74
- textChunkLimit: 4e3,
75
- attachedResults: {
76
- sendText: async (params) => {
77
- const resolved = resolveClient(params.cfg, params.accountId);
78
- if (!resolved) throw new Error("Shadow account not configured");
79
- const { client } = resolved;
80
- const { channelId, threadId: parsedThreadId } = parseTarget(params.to);
81
- const threadId = params.threadId ?? parsedThreadId;
82
- let message;
83
- if (threadId) {
84
- message = await client.sendToThread(threadId, params.text);
85
- } else if (channelId) {
86
- message = await client.sendMessage(channelId, params.text, {
87
- replyToId: params.replyToMessageId
88
- });
89
- } else {
90
- throw new Error("Could not resolve target channel or thread");
91
- }
92
- return { messageId: message.id };
93
- }
94
- },
95
- base: {
96
- sendMedia: async (params) => {
97
- const resolved = resolveClient(params.cfg, params.accountId);
98
- if (!resolved) throw new Error("Shadow account not configured");
99
- const { client } = resolved;
100
- const { channelId, threadId: parsedThreadId } = parseTarget(params.to);
101
- const threadId = params.threadId ?? parsedThreadId;
102
- const mediaUrls = [params.mediaUrl ?? params.filePath, ...params.mediaUrls ?? []].filter(
103
- Boolean
104
- );
105
- const content = params.text || "\u200B";
106
- let message;
107
- if (threadId) {
108
- message = await client.sendToThread(threadId, content);
109
- } else if (channelId) {
110
- message = await client.sendMessage(channelId, content, {
111
- replyToId: params.replyToMessageId
112
- });
113
- } else {
114
- throw new Error("Could not resolve target channel or thread");
115
- }
116
- for (const mediaUrl of mediaUrls) {
117
- try {
118
- await client.uploadMediaFromUrl(mediaUrl, message.id);
119
- } catch {
120
- if (threadId) {
121
- await client.sendToThread(threadId, mediaUrl);
122
- } else if (channelId) {
123
- await client.sendMessage(channelId, mediaUrl);
124
- }
125
- }
126
- }
127
- }
128
- }
129
- };
130
-
131
- // src/channel.ts
132
- function resolveAccount(cfg, accountId) {
133
- const account = getAccountConfig(cfg, accountId ?? DEFAULT_ACCOUNT_ID);
134
- if (!account) {
135
- return { token: "", serverUrl: "https://shadowob.com", enabled: false };
136
- }
137
- return account;
138
- }
139
- function inspectAccount(cfg, accountId) {
140
- const account = getAccountConfig(cfg, accountId ?? DEFAULT_ACCOUNT_ID);
141
- return {
142
- enabled: account?.enabled !== false,
143
- configured: !!account?.token?.trim(),
144
- tokenStatus: account?.token?.trim() ? "available" : "missing"
145
- };
146
- }
147
- var shadowPlugin = createChatChannelPlugin({
148
- base: {
149
- id: "shadowob",
150
- meta: {
151
- id: "shadowob",
152
- label: "ShadowOwnBuddy",
153
- selectionLabel: "ShadowOwnBuddy (Server)",
154
- docsPath: "/channels/shadowob",
155
- blurb: "Shadow server channel integration \u2014 chat with AI agents in Shadow channels",
156
- aliases: ["shadow-server", "openclaw-shadowob"]
157
- },
158
- capabilities: {
159
- chatTypes: ["channel", "thread"],
160
- reactions: true,
161
- threads: true,
162
- media: true,
163
- reply: true,
164
- edit: true,
165
- unsend: true
166
- },
167
- config: {
168
- listAccountIds: (cfg) => listAccountIds(cfg),
169
- inspectAccount,
170
- resolveAccount: (cfg, accountId) => {
171
- return resolveAccount(cfg, accountId);
172
- },
173
- defaultAccountId: () => DEFAULT_ACCOUNT_ID,
174
- isConfigured: (account) => {
175
- return !!account?.token?.trim();
176
- },
177
- isEnabled: (account) => {
178
- return account?.enabled !== false;
179
- },
180
- describeAccount: (account) => ({
181
- accountId: DEFAULT_ACCOUNT_ID,
182
- enabled: account?.enabled !== false,
183
- configured: !!account?.token?.trim()
184
- })
185
- },
186
- setup: {
187
- resolveAccountId: ({ accountId }) => accountId ?? DEFAULT_ACCOUNT_ID,
188
- applyAccountConfig: ({ cfg }) => cfg
189
- }
190
- },
191
- // DM security: define allowlist-based DM policy
192
- security: {
193
- dm: {
194
- channelKey: "shadowob",
195
- resolvePolicy: (account) => {
196
- return void 0;
197
- },
198
- resolveAllowFrom: (_account) => [],
199
- defaultPolicy: "allowlist"
200
- }
201
- },
202
- // Threading: how replies are delivered (config-driven with fallback)
203
- threading: {
204
- topLevelReplyToMode: "reply",
205
- resolveReplyToMode: ({ cfg }) => {
206
- const shadow = cfg.channels?.shadowob ?? cfg.channels?.["openclaw-shadowob"];
207
- const mode = shadow?.replyToMode;
208
- if (mode === "first" || mode === "all" || mode === "off") return mode;
209
- return "first";
210
- }
211
- },
212
- // Outbound: send messages to the platform
213
- outbound: shadowOutbound
214
- // ── Additional adapters (set directly on the plugin object) ──────────────
215
- // The createChatChannelPlugin helper builds the standard ChannelPlugin.
216
- // We extend it below with adapters that the helper doesn't cover.
217
- });
218
- shadowPlugin.meta = {
219
- id: "shadowob",
220
- label: "ShadowOwnBuddy",
221
- selectionLabel: "ShadowOwnBuddy (Server)",
222
- docsPath: "/channels/shadowob",
223
- blurb: "Shadow server channel integration \u2014 chat with AI agents in Shadow channels",
224
- aliases: ["shadow-server", "openclaw-shadowob"]
225
- };
226
- shadowPlugin.capabilities = {
227
- chatTypes: ["channel", "thread"],
228
- reactions: true,
229
- threads: true,
230
- media: true,
231
- reply: true,
232
- edit: true,
233
- unsend: true
234
- };
235
- shadowPlugin.reload = {
236
- configPrefixes: ["channels.shadowob"]
237
- };
238
- shadowPlugin.defaults = {
239
- queue: { debounceMs: 500 }
240
- };
241
- shadowPlugin.configSchema = {
242
- schema: {
243
- type: "object",
244
- properties: {
245
- token: { type: "string", description: "Agent JWT token" },
246
- serverUrl: { type: "string", description: "Shadow server URL" },
247
- enabled: { type: "boolean" },
248
- accounts: {
249
- type: "object",
250
- additionalProperties: {
251
- type: "object",
252
- properties: {
253
- token: { type: "string" },
254
- serverUrl: { type: "string" },
255
- enabled: { type: "boolean" }
256
- },
257
- required: ["token", "serverUrl"]
258
- }
259
- }
260
- }
261
- },
262
- uiHints: {
263
- token: {
264
- label: "Agent Token",
265
- sensitive: true,
266
- placeholder: "Paste the JWT token generated in Shadow \u2192 Agents"
267
- },
268
- serverUrl: {
269
- label: "Server URL",
270
- placeholder: "https://shadowob.com"
271
- },
272
- enabled: {
273
- label: "Enabled"
274
- }
275
- }
276
- };
277
- shadowPlugin.agentPrompt = {
278
- messageToolHints: () => [
279
- '- Shadow server management: use `action: "get-server"` with `serverId` (slug or UUID) to fetch server info including homepage HTML.',
280
- '- Shadow homepage decoration: use `action: "update-homepage"` with `serverId` (slug or UUID) and `html` (full HTML string) to update the server\'s homepage. Set `html` to null to reset to default.',
281
- "- The server slug or ID is provided in the message context as ServerSlug/ServerId when the message originates from a Shadow channel.",
282
- "- When a user asks to customize/decorate the server homepage, first use `get-server` to see current state, then generate beautiful HTML and use `update-homepage` to apply it.",
283
- '- Connection diagnostics: use `action: "get-connection-status"` (no params) to probe all configured Shadow accounts and report connection health.'
284
- ]
285
- };
286
- shadowPlugin.mentions = {
287
- stripPatterns: () => ["@[\\w-]+"]
288
- };
289
- shadowPlugin.streaming = {
290
- blockStreamingCoalesceDefaults: { minChars: 1500, idleMs: 1e3 }
291
- };
292
- shadowPlugin.messaging = {
293
- normalizeTarget: (raw) => {
294
- if (/^(shadowob|openclaw-shadowob):(channel|thread):.+$/i.test(raw)) return raw;
295
- if (/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(raw)) {
296
- return `shadowob:channel:${raw}`;
297
- }
298
- return void 0;
299
- },
300
- targetResolver: {
301
- looksLikeId: (raw) => /^(shadowob|openclaw-shadowob):(channel|thread):.+$/i.test(raw) || /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(raw),
302
- hint: "Provide a Shadow channel UUID or shadowob:channel:<uuid>"
303
- }
304
- };
305
- shadowPlugin.status = {
306
- defaultRuntime: {
307
- accountId: DEFAULT_ACCOUNT_ID,
308
- running: false,
309
- lastStartAt: null,
310
- lastStopAt: null,
311
- lastError: null
312
- },
313
- probeAccount: async ({
314
- account,
315
- timeoutMs
316
- }) => {
317
- const controller = new AbortController();
318
- const timeout = setTimeout(() => controller.abort(), timeoutMs);
319
- try {
320
- const client = new ShadowClient2(account.serverUrl, account.token);
321
- const me = await client.getMe();
322
- return { ok: true, user: me };
323
- } catch (err) {
324
- return { ok: false, error: err instanceof Error ? err.message : String(err) };
325
- } finally {
326
- clearTimeout(timeout);
327
- }
328
- },
329
- buildAccountSnapshot: ({
330
- account,
331
- runtime,
332
- probe
333
- }) => ({
334
- accountId: DEFAULT_ACCOUNT_ID,
335
- enabled: account?.enabled !== false,
336
- configured: !!account?.token?.trim(),
337
- running: runtime?.running ?? false,
338
- lastStartAt: runtime?.lastStartAt ?? null,
339
- lastStopAt: runtime?.lastStopAt ?? null,
340
- lastError: runtime?.lastError ?? null,
341
- probe
342
- }),
343
- buildChannelSummary: ({
344
- snapshot
345
- }) => ({
346
- configured: snapshot.configured ?? false,
347
- running: snapshot.running ?? false,
348
- lastStartAt: snapshot.lastStartAt ?? null,
349
- lastStopAt: snapshot.lastStopAt ?? null,
350
- lastError: snapshot.lastError ?? null,
351
- probe: snapshot.probe
352
- })
353
- };
354
- shadowPlugin.gateway = {
355
- startAccount: async (ctx) => {
356
- const account = ctx.account;
357
- const accountId = ctx.accountId;
358
- ctx.setStatus({
359
- accountId,
360
- running: true,
361
- lastStartAt: Date.now(),
362
- lastError: null
363
- });
364
- ctx.log?.info(`Starting Shadow connection for account ${accountId}`);
365
- const { monitorShadowProvider } = await import("./monitor-L5CUPMSN.js");
366
- await monitorShadowProvider({
367
- account,
368
- accountId,
369
- config: ctx.cfg,
370
- runtime: {
371
- log: (msg) => ctx.log?.info(msg),
372
- error: (msg) => ctx.log?.error(msg)
373
- },
374
- abortSignal: ctx.abortSignal
375
- });
376
- },
377
- stopAccount: async (ctx) => {
378
- ctx.setStatus({
379
- accountId: ctx.accountId,
380
- running: false,
381
- lastStopAt: Date.now()
382
- });
383
- ctx.log?.info(`Stopped Shadow connection for account ${ctx.accountId}`);
384
- }
385
- };
386
- var SHADOW_ACTIONS = [
387
- "send",
388
- "sendAttachment",
389
- "react",
390
- "edit",
391
- "delete",
392
- "reply",
393
- "thread-create",
394
- "thread-reply",
395
- "pin",
396
- "unpin",
397
- "update-homepage",
398
- "get-server",
399
- "get-connection-status"
400
- ];
401
- shadowPlugin.actions = {
402
- describeMessageTool: () => null,
403
- supportsAction: ({ action }) => SHADOW_ACTIONS.includes(action),
404
- handleAction: async (ctx) => {
405
- const textResult = (value) => ({
406
- content: [
407
- {
408
- type: "text",
409
- text: JSON.stringify(value)
410
- }
411
- ],
412
- details: value
413
- });
414
- const account = getAccountConfig(ctx.cfg, ctx.accountId ?? DEFAULT_ACCOUNT_ID);
415
- if (!account) {
416
- return textResult({ ok: false, error: "Shadow account not configured" });
417
- }
418
- const action = String(ctx.action);
419
- const { params } = ctx;
420
- if (action === "sendAttachment") {
421
- try {
422
- const client = new ShadowClient2(account.serverUrl, account.token);
423
- const to = params.to ?? "";
424
- const text = params.message ?? params.caption ?? "";
425
- const filename = params.filename || "file";
426
- const contentType = params.contentType || params.mimeType || "application/octet-stream";
427
- const base64Buffer = params.buffer;
428
- const mediaUrl = params.media ?? params.path ?? params.filePath ?? "";
429
- const { channelId, threadId: parsedThreadId } = parseTarget(to);
430
- const threadId = params.threadId ?? parsedThreadId;
431
- const content = text || "\u200B";
432
- let message;
433
- if (threadId) {
434
- message = await client.sendToThread(threadId, content);
435
- } else if (channelId) {
436
- message = await client.sendMessage(channelId, content, {
437
- replyToId: params.replyTo
438
- });
439
- } else {
440
- return textResult({
441
- ok: false,
442
- error: "Could not resolve target channel or thread"
443
- });
444
- }
445
- if (base64Buffer) {
446
- const raw = base64Buffer.includes(",") ? base64Buffer.split(",")[1] ?? "" : base64Buffer;
447
- if (!raw) throw new Error("Invalid base64 attachment payload");
448
- const bytes = Buffer.from(raw, "base64");
449
- const blob = new Blob([Uint8Array.from(bytes)], { type: contentType });
450
- await client.uploadMedia(blob, filename, contentType, message.id);
451
- } else if (mediaUrl) {
452
- await client.uploadMediaFromUrl(mediaUrl, message.id);
453
- } else {
454
- return textResult({
455
- ok: false,
456
- error: "No buffer or media URL provided for attachment"
457
- });
458
- }
459
- return textResult({
460
- ok: true,
461
- action: "sendAttachment",
462
- messageId: message.id,
463
- filename
464
- });
465
- } catch (err) {
466
- return textResult({ ok: false, error: err instanceof Error ? err.message : String(err) });
467
- }
468
- }
469
- if (action === "react") {
470
- const client = new ShadowClient2(account.serverUrl, account.token);
471
- const messageId = params.messageId ?? params.message_id ?? "";
472
- const emoji = params.emoji ?? params.reaction ?? "";
473
- if (!messageId || !emoji) {
474
- return textResult({ ok: false, error: "messageId and emoji are required" });
475
- }
476
- try {
477
- await client.addReaction(messageId, emoji);
478
- return textResult({ ok: true, action: "react", messageId, emoji });
479
- } catch (err) {
480
- return textResult({ ok: false, error: String(err) });
481
- }
482
- }
483
- if (action === "edit") {
484
- const client = new ShadowClient2(account.serverUrl, account.token);
485
- const messageId = params.messageId ?? params.message_id ?? "";
486
- const content = params.message ?? params.content ?? "";
487
- if (!messageId || !content) {
488
- return textResult({ ok: false, error: "messageId and content are required" });
489
- }
490
- try {
491
- await client.editMessage(messageId, content);
492
- return textResult({ ok: true, action: "edit", messageId });
493
- } catch (err) {
494
- return textResult({ ok: false, error: String(err) });
495
- }
496
- }
497
- if (action === "delete") {
498
- const client = new ShadowClient2(account.serverUrl, account.token);
499
- const messageId = params.messageId ?? params.message_id ?? "";
500
- if (!messageId) {
501
- return textResult({ ok: false, error: "messageId is required" });
502
- }
503
- try {
504
- await client.deleteMessage(messageId);
505
- return textResult({ ok: true, action: "delete", messageId });
506
- } catch (err) {
507
- return textResult({ ok: false, error: String(err) });
508
- }
509
- }
510
- if (action === "pin" || action === "unpin") {
511
- return textResult({ ok: false, error: `${action} is not yet supported for Shadow channels` });
512
- }
513
- if (action === "get-server") {
514
- const serverId = params.serverId ?? params.server_id ?? params.server ?? "";
515
- if (!serverId) {
516
- return textResult({ ok: false, error: "serverId is required" });
517
- }
518
- try {
519
- const client = new ShadowClient2(account.serverUrl, account.token);
520
- const server = await client.getServer(serverId);
521
- return textResult({ ok: true, action: "get-server", server });
522
- } catch (err) {
523
- return textResult({ ok: false, error: String(err) });
524
- }
525
- }
526
- if (action === "update-homepage") {
527
- const serverId = params.serverId ?? params.server_id ?? params.server ?? "";
528
- const html = params.html ?? params.homepageHtml ?? params.homepage_html ?? null;
529
- if (!serverId) {
530
- return textResult({ ok: false, error: "serverId is required" });
531
- }
532
- try {
533
- const client = new ShadowClient2(account.serverUrl, account.token);
534
- const result = await client.updateServerHomepage(serverId, html);
535
- return textResult({
536
- ok: true,
537
- action: "update-homepage",
538
- serverId: result.id,
539
- slug: result.slug,
540
- homepageHtml: result.homepageHtml ? `(${result.homepageHtml.length} chars)` : null
541
- });
542
- } catch (err) {
543
- return textResult({ ok: false, error: String(err) });
544
- }
545
- }
546
- if (action === "get-connection-status") {
547
- const accountIds = listAccountIds(ctx.cfg);
548
- const results = await Promise.all(
549
- accountIds.map(async (id) => {
550
- const acc = getAccountConfig(ctx.cfg, id);
551
- if (!acc) return { accountId: id, configured: false, ok: false, error: "not configured" };
552
- if (!acc.token?.trim())
553
- return { accountId: id, configured: false, ok: false, error: "no token" };
554
- try {
555
- const client = new ShadowClient2(acc.serverUrl, acc.token);
556
- const me = await client.getMe();
557
- return {
558
- accountId: id,
559
- configured: true,
560
- enabled: acc.enabled !== false,
561
- ok: true,
562
- serverUrl: acc.serverUrl,
563
- user: me
564
- };
565
- } catch (err) {
566
- return {
567
- accountId: id,
568
- configured: true,
569
- enabled: acc.enabled !== false,
570
- ok: false,
571
- serverUrl: acc.serverUrl,
572
- error: err instanceof Error ? err.message : String(err)
573
- };
574
- }
575
- })
576
- );
577
- return textResult({ ok: true, action: "get-connection-status", accounts: results });
578
- }
579
- return textResult({ ok: false, error: `Action ${action} not yet implemented` });
580
- }
581
- };
582
-
583
- export {
584
- shadowPlugin
585
- };
@@ -1,6 +0,0 @@
1
- import {
2
- monitorShadowProvider
3
- } from "./chunk-JGLINAN5.js";
4
- export {
5
- monitorShadowProvider
6
- };