@shadowob/openclaw-shadowob 1.1.3-dev.281 → 1.1.4
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.
|
@@ -22,6 +22,9 @@ function mentionTargetsBot(params) {
|
|
|
22
22
|
return mention.username?.toLowerCase() === botUsername;
|
|
23
23
|
});
|
|
24
24
|
}
|
|
25
|
+
function mentionsTargetServerApp(mentions) {
|
|
26
|
+
return mentions.some((mention) => mention.kind === "app" && (mention.appKey || mention.targetId));
|
|
27
|
+
}
|
|
25
28
|
function formatShadowMentionsForAgent(mentions) {
|
|
26
29
|
if (mentions.length === 0) return "";
|
|
27
30
|
const lines = mentions.map((mention) => {
|
|
@@ -32,6 +35,9 @@ function formatShadowMentionsForAgent(mentions) {
|
|
|
32
35
|
if (mention.kind === "server") {
|
|
33
36
|
return `- ${label} [server] serverId=${mention.serverId ?? mention.targetId} slug=${mention.serverSlug ?? ""}`;
|
|
34
37
|
}
|
|
38
|
+
if (mention.kind === "app") {
|
|
39
|
+
return `- ${label} [server-app] appKey=${mention.appKey ?? mention.targetId} appId=${mention.appId ?? mention.targetId} serverId=${mention.serverId ?? ""} server=${mention.serverName ?? ""}`;
|
|
40
|
+
}
|
|
35
41
|
if (mention.kind === "user" || mention.kind === "buddy") {
|
|
36
42
|
return `- ${label} [${mention.kind}] userId=${mention.userId ?? mention.targetId} username=${mention.username ?? ""}`;
|
|
37
43
|
}
|
|
@@ -40,8 +46,9 @@ function formatShadowMentionsForAgent(mentions) {
|
|
|
40
46
|
return [
|
|
41
47
|
"Shadow mentions:",
|
|
42
48
|
...lines,
|
|
43
|
-
"To mention a Shadow entity in a reply, write its visible handle (for example @username or #channel); Shadow will resolve it before delivery."
|
|
44
|
-
|
|
49
|
+
"To mention a Shadow entity in a reply, write its visible handle (for example @username or #channel); Shadow will resolve it before delivery.",
|
|
50
|
+
mentionsTargetServerApp(mentions) ? 'If a server app is mentioned, operate it through the Shadow CLI only: first run `shadowob app discover --server "<serverId-or-slug>" --json`, then run `shadowob app call "<appKey>" <command> --server "<serverId-or-slug>" --json-input \'<raw-command-input-json>\' --json`. Do not use curl, fetch, raw HTTP routes, or the JavaScript SDK for server-app commands. Use the mentioned appKey/serverId; do not ask the user to describe the CLI path.' : ""
|
|
51
|
+
].filter(Boolean).join("\n");
|
|
45
52
|
}
|
|
46
53
|
function mentionContextFields(mentions) {
|
|
47
54
|
if (mentions.length === 0) return {};
|
|
@@ -66,6 +73,14 @@ function mentionContextFields(mentions) {
|
|
|
66
73
|
serverId: mention.serverId ?? mention.targetId,
|
|
67
74
|
serverSlug: mention.serverSlug,
|
|
68
75
|
serverName: mention.serverName
|
|
76
|
+
})),
|
|
77
|
+
MentionedApps: mentions.filter((mention) => mention.kind === "app").map((mention) => ({
|
|
78
|
+
appId: mention.appId ?? mention.targetId,
|
|
79
|
+
appKey: mention.appKey,
|
|
80
|
+
appName: mention.appName,
|
|
81
|
+
serverId: mention.serverId,
|
|
82
|
+
serverSlug: mention.serverSlug,
|
|
83
|
+
serverName: mention.serverName
|
|
69
84
|
}))
|
|
70
85
|
};
|
|
71
86
|
}
|
|
@@ -162,6 +177,8 @@ async function buildInteractiveResponseContext(params) {
|
|
|
162
177
|
const responsePrompt = sourceInteractive && typeof sourceInteractive === "object" && !Array.isArray(sourceInteractive) ? sourceInteractive.responsePrompt : void 0;
|
|
163
178
|
const lines = [
|
|
164
179
|
"Shadow interactive response received.",
|
|
180
|
+
"Use the submitted values once. Do not separately restate or grade the submitted form unless the source command explicitly asks for an evaluation.",
|
|
181
|
+
"If the next step is another Shadow interactive dialog, send that dialog only and do not add a separate normal text reply for the same step.",
|
|
165
182
|
`Source message: ${source?.content ?? "(unavailable)"}`,
|
|
166
183
|
typeof sourcePrompt === "string" && sourcePrompt.trim() ? `Source prompt: ${sourcePrompt.trim()}` : "",
|
|
167
184
|
typeof responsePrompt === "string" && responsePrompt.trim() ? `Follow-up instruction: ${responsePrompt.trim()}` : "",
|
|
@@ -358,7 +375,7 @@ function evaluateShadowMessagePreflight(params) {
|
|
|
358
375
|
const structuredMentions = getShadowMessageMentions(message);
|
|
359
376
|
const escapedBotUsername = botUsername.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
360
377
|
const mentionRegex = new RegExp(`@${escapedBotUsername}(?:\\s|$)`, "i");
|
|
361
|
-
const wasMentionedExplicitly = mentionTargetsBot({ mentions: structuredMentions, botUserId, botUsername }) || mentionRegex.test(message.content);
|
|
378
|
+
const wasMentionedExplicitly = mentionTargetsBot({ mentions: structuredMentions, botUserId, botUsername }) || mentionsTargetServerApp(structuredMentions) || mentionRegex.test(message.content);
|
|
362
379
|
if (policy?.mentionOnly && !wasMentionedExplicitly) {
|
|
363
380
|
return {
|
|
364
381
|
ok: false,
|
|
@@ -543,6 +560,7 @@ function resolveSessionStore(cfg) {
|
|
|
543
560
|
// src/monitor/slash-commands.ts
|
|
544
561
|
import fsPromises2 from "fs/promises";
|
|
545
562
|
var SLASH_COMMAND_RE = /^\/([a-zA-Z][a-zA-Z0-9._-]{0,63})(?:\s+([\s\S]*))?$/;
|
|
563
|
+
var DEFAULT_SLASH_COMMANDS_PATH = "/etc/shadowob/slash-commands.json";
|
|
546
564
|
function normalizeSlashCommandName(value) {
|
|
547
565
|
if (typeof value !== "string") return null;
|
|
548
566
|
const name = value.trim().replace(/^\/+/, "");
|
|
@@ -641,6 +659,7 @@ function normalizeShadowSlashCommands(input) {
|
|
|
641
659
|
)
|
|
642
660
|
].filter((alias) => alias.toLowerCase() !== key) : void 0;
|
|
643
661
|
const interaction = normalizeSlashInteraction(record.interaction);
|
|
662
|
+
const dispatch = readString(record.dispatch, 40);
|
|
644
663
|
commands.push({
|
|
645
664
|
name,
|
|
646
665
|
...typeof record.description === "string" && record.description.trim() ? { description: record.description.trim().slice(0, 240) } : {},
|
|
@@ -648,13 +667,14 @@ function normalizeShadowSlashCommands(input) {
|
|
|
648
667
|
...typeof record.packId === "string" && record.packId.trim() ? { packId: record.packId.trim().slice(0, 80) } : {},
|
|
649
668
|
...typeof record.sourcePath === "string" && record.sourcePath.trim() ? { sourcePath: record.sourcePath.trim().slice(0, 500) } : {},
|
|
650
669
|
...typeof record.body === "string" && record.body.trim() ? { body: record.body.trim().slice(0, 2e4) } : {},
|
|
670
|
+
...dispatch === "agent" || dispatch === "passthrough" ? { dispatch } : {},
|
|
651
671
|
...interaction ? { interaction } : {}
|
|
652
672
|
});
|
|
653
673
|
}
|
|
654
674
|
return commands.slice(0, 200);
|
|
655
675
|
}
|
|
656
676
|
function toPublicSlashCommands(commands) {
|
|
657
|
-
return commands.map(({ body: _body, ...command }) => command);
|
|
677
|
+
return commands.map(({ body: _body, dispatch: _dispatch, ...command }) => command);
|
|
658
678
|
}
|
|
659
679
|
async function fileExists(path) {
|
|
660
680
|
try {
|
|
@@ -676,10 +696,27 @@ async function loadSlashCommandFile(indexPath, runtime) {
|
|
|
676
696
|
return [];
|
|
677
697
|
}
|
|
678
698
|
}
|
|
699
|
+
function logDuplicateSlashCommands(sources, runtime) {
|
|
700
|
+
const owners = /* @__PURE__ */ new Map();
|
|
701
|
+
for (const source of sources) {
|
|
702
|
+
for (const command of source.commands) {
|
|
703
|
+
const key = command.name.toLowerCase();
|
|
704
|
+
const existingPath = owners.get(key);
|
|
705
|
+
if (existingPath) {
|
|
706
|
+
runtime.log?.(
|
|
707
|
+
`[slash] Ignoring duplicate command /${command.name} from ${source.path}; already defined by ${existingPath}`
|
|
708
|
+
);
|
|
709
|
+
continue;
|
|
710
|
+
}
|
|
711
|
+
owners.set(key, source.path);
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
}
|
|
679
715
|
async function runtimeExtensionSlashCommandPaths(runtime) {
|
|
680
716
|
const candidates = [
|
|
681
717
|
process.env.SHADOW_RUNTIME_EXTENSIONS_PATH,
|
|
682
718
|
process.env.OPENCLAW_RUNTIME_EXTENSIONS_PATH,
|
|
719
|
+
"/etc/shadowob/runtime-extensions.json",
|
|
683
720
|
"/etc/openclaw/runtime-extensions.json"
|
|
684
721
|
].filter((path) => Boolean(path));
|
|
685
722
|
const paths = [];
|
|
@@ -703,16 +740,25 @@ async function runtimeExtensionSlashCommandPaths(runtime) {
|
|
|
703
740
|
return paths;
|
|
704
741
|
}
|
|
705
742
|
async function loadShadowSlashCommands(runtime) {
|
|
743
|
+
const defaultIndexPath = process.env.SHADOW_DEFAULT_SLASH_COMMANDS_PATH || DEFAULT_SLASH_COMMANDS_PATH;
|
|
706
744
|
const paths = [
|
|
745
|
+
defaultIndexPath,
|
|
707
746
|
process.env.SHADOW_SLASH_COMMANDS_PATH,
|
|
708
747
|
...await runtimeExtensionSlashCommandPaths(runtime)
|
|
709
748
|
].filter((path) => Boolean(path));
|
|
710
749
|
const seenPaths = [...new Set(paths)];
|
|
711
|
-
const
|
|
712
|
-
const
|
|
713
|
-
|
|
750
|
+
const existingPaths = (await Promise.all(seenPaths.map(async (path) => await fileExists(path) ? path : null))).filter((path) => Boolean(path));
|
|
751
|
+
const sources = await Promise.all(
|
|
752
|
+
existingPaths.map(async (path) => ({
|
|
753
|
+
path,
|
|
754
|
+
commands: await loadSlashCommandFile(path, runtime)
|
|
755
|
+
}))
|
|
756
|
+
);
|
|
757
|
+
logDuplicateSlashCommands(sources, runtime);
|
|
758
|
+
const merged = normalizeShadowSlashCommands(sources.flatMap((source) => source.commands));
|
|
759
|
+
if (existingPaths.length > 1) {
|
|
714
760
|
runtime.log?.(
|
|
715
|
-
`[slash] Merged ${merged.length} slash command(s) from ${
|
|
761
|
+
`[slash] Merged ${merged.length} slash command(s) from ${existingPaths.length} source(s)`
|
|
716
762
|
);
|
|
717
763
|
}
|
|
718
764
|
return merged;
|
|
@@ -1137,6 +1183,66 @@ function buildChannelContextForAgent(info, channelId) {
|
|
|
1137
1183
|
`Shadow channel id: ${channelId}`
|
|
1138
1184
|
].join("\n");
|
|
1139
1185
|
}
|
|
1186
|
+
function normalizeStringList(value) {
|
|
1187
|
+
if (!Array.isArray(value)) return [];
|
|
1188
|
+
return value.filter((item) => typeof item === "string" && item.trim().length > 0);
|
|
1189
|
+
}
|
|
1190
|
+
function isSenderCommandAuthorized(policyConfig, senderId) {
|
|
1191
|
+
const triggerUserIds = normalizeStringList(
|
|
1192
|
+
policyConfig?.allowedTriggerUserIds ?? policyConfig?.triggerUserIds
|
|
1193
|
+
);
|
|
1194
|
+
if (triggerUserIds.length > 0) return triggerUserIds.includes(senderId);
|
|
1195
|
+
const ownerId = typeof policyConfig?.ownerId === "string" ? policyConfig.ownerId.trim() : "";
|
|
1196
|
+
if (ownerId && ownerId === senderId) return true;
|
|
1197
|
+
const activeTenantIds = normalizeStringList(policyConfig?.activeTenantIds);
|
|
1198
|
+
return activeTenantIds.includes(senderId);
|
|
1199
|
+
}
|
|
1200
|
+
function resolveOwnerAllowFrom(policyConfig) {
|
|
1201
|
+
const ownerId = typeof policyConfig?.ownerId === "string" ? policyConfig.ownerId.trim() : "";
|
|
1202
|
+
return ownerId ? [ownerId] : void 0;
|
|
1203
|
+
}
|
|
1204
|
+
async function buildMentionedServerAppSkillsContext(params) {
|
|
1205
|
+
const appRefs = /* @__PURE__ */ new Map();
|
|
1206
|
+
for (const mention of params.mentions) {
|
|
1207
|
+
if (mention.kind !== "app") continue;
|
|
1208
|
+
const appKey = mention.appKey ?? mention.targetId;
|
|
1209
|
+
const server = mention.serverId ?? mention.serverSlug ?? params.serverInfo?.serverId;
|
|
1210
|
+
if (!appKey || !server) continue;
|
|
1211
|
+
appRefs.set(`${server}:${appKey}`, {
|
|
1212
|
+
appKey,
|
|
1213
|
+
server,
|
|
1214
|
+
label: mention.label || mention.sourceToken || mention.token || appKey
|
|
1215
|
+
});
|
|
1216
|
+
}
|
|
1217
|
+
if (appRefs.size === 0) return "";
|
|
1218
|
+
const documents = await Promise.all(
|
|
1219
|
+
Array.from(appRefs.values()).map(async (ref) => {
|
|
1220
|
+
try {
|
|
1221
|
+
const skill = await params.client.getServerAppSkills(ref.server, ref.appKey);
|
|
1222
|
+
return [
|
|
1223
|
+
`## ${ref.label}`,
|
|
1224
|
+
`Server reference: ${ref.server}`,
|
|
1225
|
+
`App key: ${ref.appKey}`,
|
|
1226
|
+
"",
|
|
1227
|
+
skill.markdown
|
|
1228
|
+
].join("\n");
|
|
1229
|
+
} catch (err) {
|
|
1230
|
+
params.runtime.error?.(
|
|
1231
|
+
`[server-app] Failed loading skills for ${ref.appKey} on ${ref.server}: ${String(err)}`
|
|
1232
|
+
);
|
|
1233
|
+
return "";
|
|
1234
|
+
}
|
|
1235
|
+
})
|
|
1236
|
+
);
|
|
1237
|
+
const loaded = documents.filter(Boolean);
|
|
1238
|
+
if (loaded.length === 0) return "";
|
|
1239
|
+
return [
|
|
1240
|
+
"Injected Shadow Server App Skills:",
|
|
1241
|
+
"These instructions are authoritative for mentioned server apps in this message. Use the Shadow CLI path described below so Shadow can bind identity, app grants, and policy.",
|
|
1242
|
+
"",
|
|
1243
|
+
...loaded
|
|
1244
|
+
].join("\n");
|
|
1245
|
+
}
|
|
1140
1246
|
async function processShadowMessage(params) {
|
|
1141
1247
|
const {
|
|
1142
1248
|
message,
|
|
@@ -1198,6 +1304,7 @@ async function processShadowMessage(params) {
|
|
|
1198
1304
|
slashCommands
|
|
1199
1305
|
});
|
|
1200
1306
|
const slashCommandMatch = matchShadowSlashCommand(cleanBody, slashCommands);
|
|
1307
|
+
const slashCommandPassThrough = slashCommandMatch?.command.dispatch === "passthrough";
|
|
1201
1308
|
if (slashCommandMatch) {
|
|
1202
1309
|
runtime.log?.(
|
|
1203
1310
|
`[slash] Matched /${slashCommandMatch.invokedName} -> /${slashCommandMatch.command.name}`
|
|
@@ -1220,17 +1327,22 @@ async function processShadowMessage(params) {
|
|
|
1220
1327
|
});
|
|
1221
1328
|
return;
|
|
1222
1329
|
}
|
|
1223
|
-
const baseBodyForAgent = slashCommandMatch ? formatSlashCommandPrompt(cleanBody, slashCommandMatch) : cleanBody;
|
|
1330
|
+
const baseBodyForAgent = slashCommandMatch && !slashCommandPassThrough ? formatSlashCommandPrompt(cleanBody, slashCommandMatch) : cleanBody;
|
|
1331
|
+
const commandBody = slashCommandPassThrough ? cleanBody : slashCommandMatch?.args ?? cleanBody;
|
|
1332
|
+
const ownerAllowFrom = resolveOwnerAllowFrom(preflight.policyConfig);
|
|
1224
1333
|
const structuredMentions = getShadowMessageMentions(message);
|
|
1225
1334
|
const mentionContext = formatShadowMentionsForAgent(structuredMentions);
|
|
1226
1335
|
const serverInfo = channelServerMap.get(channelId);
|
|
1227
1336
|
const channelLabel = serverInfo ? `#${serverInfo.channelName}` : `channel:${channelId}`;
|
|
1228
1337
|
const conversationLabel = serverInfo ? `${serverInfo.serverName} ${channelLabel}` : peerId;
|
|
1229
|
-
const messageBodyForAgent = interactiveResponseContext.text
|
|
1230
|
-
|
|
1231
|
-
User message:
|
|
1232
|
-
${baseBodyForAgent}` : baseBodyForAgent;
|
|
1338
|
+
const messageBodyForAgent = interactiveResponseContext.text || baseBodyForAgent;
|
|
1233
1339
|
const client = new ShadowClient2(account.serverUrl, account.token);
|
|
1340
|
+
const serverAppSkillsContext = await buildMentionedServerAppSkillsContext({
|
|
1341
|
+
mentions: structuredMentions,
|
|
1342
|
+
client,
|
|
1343
|
+
serverInfo,
|
|
1344
|
+
runtime
|
|
1345
|
+
});
|
|
1234
1346
|
const viewerCommerceContext = await buildCommerceViewerContextForAgent({
|
|
1235
1347
|
account,
|
|
1236
1348
|
client,
|
|
@@ -1241,6 +1353,7 @@ ${baseBodyForAgent}` : baseBodyForAgent;
|
|
|
1241
1353
|
buildCommerceContextForAgent(account),
|
|
1242
1354
|
viewerCommerceContext,
|
|
1243
1355
|
mentionContext,
|
|
1356
|
+
serverAppSkillsContext,
|
|
1244
1357
|
messageBodyForAgent
|
|
1245
1358
|
].filter(Boolean).join("\n\n");
|
|
1246
1359
|
const body = core.channel.reply.formatAgentEnvelope({
|
|
@@ -1252,12 +1365,15 @@ ${baseBodyForAgent}` : baseBodyForAgent;
|
|
|
1252
1365
|
});
|
|
1253
1366
|
const escapedBotUsername = botUsername.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1254
1367
|
const mentionRegex = new RegExp(`@${escapedBotUsername}(?:\\s|$)`, "i");
|
|
1255
|
-
const wasMentioned = mentionTargetsBot({ mentions: structuredMentions, botUserId, botUsername }) || mentionRegex.test(message.content);
|
|
1368
|
+
const wasMentioned = mentionTargetsBot({ mentions: structuredMentions, botUserId, botUsername }) || mentionsTargetServerApp(structuredMentions) || Boolean(slashCommandMatch) || mentionRegex.test(message.content);
|
|
1256
1369
|
const ctxPayload = core.channel.reply.finalizeInboundContext({
|
|
1257
1370
|
Body: body,
|
|
1258
1371
|
BodyForAgent: bodyForAgent,
|
|
1259
1372
|
RawBody: rawBody,
|
|
1260
|
-
CommandBody:
|
|
1373
|
+
CommandBody: commandBody,
|
|
1374
|
+
BodyForCommands: commandBody,
|
|
1375
|
+
CommandAuthorized: isSenderCommandAuthorized(preflight.policyConfig, senderId),
|
|
1376
|
+
CommandSource: "text",
|
|
1261
1377
|
From: `shadowob:user:${senderId}`,
|
|
1262
1378
|
To: `shadowob:channel:${channelId}`,
|
|
1263
1379
|
SessionKey: route.sessionKey,
|
|
@@ -1274,6 +1390,8 @@ ${baseBodyForAgent}` : baseBodyForAgent;
|
|
|
1274
1390
|
...mentionContextFields(structuredMentions),
|
|
1275
1391
|
OriginatingChannel: "shadowob",
|
|
1276
1392
|
OriginatingTo: `shadowob:channel:${channelId}`,
|
|
1393
|
+
NativeChannelId: channelId,
|
|
1394
|
+
...ownerAllowFrom ? { OwnerAllowFrom: ownerAllowFrom } : {},
|
|
1277
1395
|
...serverInfo ? {
|
|
1278
1396
|
ServerId: serverInfo.serverId,
|
|
1279
1397
|
ServerSlug: serverInfo.serverSlug,
|
|
@@ -1922,9 +2040,7 @@ async function monitorShadowProvider(options) {
|
|
|
1922
2040
|
channelName: ch.name
|
|
1923
2041
|
});
|
|
1924
2042
|
channelPolicies.set(ch.id, {
|
|
1925
|
-
|
|
1926
|
-
reply: true,
|
|
1927
|
-
mentionOnly: false,
|
|
2043
|
+
...ch.policy,
|
|
1928
2044
|
config: { ...ch.policy.config, ...accessConfig2 }
|
|
1929
2045
|
});
|
|
1930
2046
|
if (!allChannelIds.includes(ch.id)) {
|
|
@@ -1945,9 +2061,6 @@ async function monitorShadowProvider(options) {
|
|
|
1945
2061
|
if (channelServerMap.has(channelId)) continue;
|
|
1946
2062
|
channelPolicies.set(channelId, {
|
|
1947
2063
|
...existing2,
|
|
1948
|
-
listen: true,
|
|
1949
|
-
reply: true,
|
|
1950
|
-
mentionOnly: false,
|
|
1951
2064
|
config: { ...existing2.config, ...accessConfig2 }
|
|
1952
2065
|
});
|
|
1953
2066
|
}
|
|
@@ -1958,23 +2071,24 @@ async function monitorShadowProvider(options) {
|
|
|
1958
2071
|
})();
|
|
1959
2072
|
return;
|
|
1960
2073
|
}
|
|
1961
|
-
const
|
|
2074
|
+
const existing = channelPolicies.get(data.channelId);
|
|
2075
|
+
const mentionOnly = data.mentionOnly ?? existing?.mentionOnly ?? false;
|
|
2076
|
+
const reply = data.reply ?? existing?.reply ?? true;
|
|
1962
2077
|
const accessConfig = buildAccessPolicyConfig(remoteConfig);
|
|
1963
2078
|
runtime.log?.(
|
|
1964
|
-
`[ws] Received agent:policy-changed for channel ${data.channelId}: mentionOnly=${mentionOnly}, reply=${
|
|
2079
|
+
`[ws] Received agent:policy-changed for channel ${data.channelId}: mentionOnly=${mentionOnly}, reply=${reply}, config=${JSON.stringify(data.config ?? {})}`
|
|
1965
2080
|
);
|
|
1966
|
-
const existing = channelPolicies.get(data.channelId);
|
|
1967
2081
|
if (existing) {
|
|
1968
2082
|
channelPolicies.set(data.channelId, {
|
|
1969
2083
|
...existing,
|
|
1970
2084
|
mentionOnly,
|
|
1971
|
-
reply
|
|
2085
|
+
reply,
|
|
1972
2086
|
config: { ...existing.config, ...accessConfig, ...data.config ?? {} }
|
|
1973
2087
|
});
|
|
1974
2088
|
} else {
|
|
1975
2089
|
channelPolicies.set(data.channelId, {
|
|
1976
2090
|
listen: true,
|
|
1977
|
-
reply
|
|
2091
|
+
reply,
|
|
1978
2092
|
mentionOnly,
|
|
1979
2093
|
config: { ...accessConfig, ...data.config ?? {} }
|
|
1980
2094
|
});
|
package/dist/index.js
CHANGED
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
resolveOutboundMentions,
|
|
16
16
|
setShadowRuntime,
|
|
17
17
|
tryGetShadowRuntime
|
|
18
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-4G572ZLC.js";
|
|
19
19
|
|
|
20
20
|
// index.ts
|
|
21
21
|
import { defineChannelPluginEntry } from "openclaw/plugin-sdk/core";
|
|
@@ -808,6 +808,7 @@ var shadowAgentPromptHints = [
|
|
|
808
808
|
"- When a Shadow user asks for buttons, choices, a select menu, a form, or approval, prefer sending a Shadow interactive dialog instead of plain text options.",
|
|
809
809
|
"- ShadowOwnBuddy enables inline buttons, forms, and file uploads for Shadow channels by default. Do not tell the user that `shadowob.capabilities.inlineButtons` or file sending is unavailable; use the message tool instead.",
|
|
810
810
|
'- Shadow interactive dialogs use the shared message tool with `action: "send"` plus `target`, `message`, `kind`, `prompt`, and shape fields. `message` is required by the shared tool; set `message` and `prompt` to the same user-visible text unless there is a specific reason not to. Supported `kind` values are `buttons`, `select`, `form`, and `approval`; Shadow stores these as `metadata.interactive` so the user can answer in-channel.',
|
|
811
|
+
"- When your next action is a Shadow interactive dialog, put the full user-visible prompt in that dialog and do not also send a separate ordinary reply in the same turn. This prevents duplicate form-plus-commentary messages.",
|
|
811
812
|
'- Example buttons dialog: `action: "send"`, `target: "shadowob:channel:<ChannelId>"`, `message: "Choose the next step"`, `kind: "buttons"`, `prompt: "Choose the next step"`, `buttons: [{"id":"icp","label":"ICP / JTBD","value":"icp"}]`.',
|
|
812
813
|
'- Example form dialog: `action: "send"`, `target: "shadowob:channel:<ChannelId>"`, `message: "Fill the decision inputs"`, `kind: "form"`, `fields: [{"id":"decision","label":"Decision","kind":"textarea","required":true}]`.',
|
|
813
814
|
'- When Shadow context includes CommerceOfferIds and the user is interested in buying, viewing pricing, or receiving a product card, call the shared message tool yourself: `action: "send"`, `target: "shadowob:channel:<ChannelId>"`, `message: "A natural sales message"`, `commerceOfferId: "<CommerceOfferId>"`. Plain final text will not attach a product card.',
|
|
@@ -959,7 +960,7 @@ shadowPlugin.gateway = {
|
|
|
959
960
|
lastError: null
|
|
960
961
|
});
|
|
961
962
|
ctx.log?.info(`Starting Shadow connection for account ${accountId}`);
|
|
962
|
-
const { monitorShadowProvider: monitorShadowProvider2 } = await import("./monitor-
|
|
963
|
+
const { monitorShadowProvider: monitorShadowProvider2 } = await import("./monitor-XDUBTELB.js");
|
|
963
964
|
await monitorShadowProvider2({
|
|
964
965
|
account,
|
|
965
966
|
accountId,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shadowob/openclaw-shadowob",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.4",
|
|
4
4
|
"description": "OpenClaw Shadow channel plugin — enables AI agents to interact in Shadow server channels",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -58,7 +58,7 @@
|
|
|
58
58
|
"dependencies": {
|
|
59
59
|
"zod": "^3.25.67",
|
|
60
60
|
"openclaw": "^2026.5.7",
|
|
61
|
-
"@shadowob/sdk": "1.1.
|
|
61
|
+
"@shadowob/sdk": "1.1.4"
|
|
62
62
|
},
|
|
63
63
|
"devDependencies": {
|
|
64
64
|
"@types/node": "^22.15.21",
|
package/skills/shadowob/SKILL.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: shadowob
|
|
3
|
-
description: "
|
|
3
|
+
description: "Use when live Shadow context or actions are needed: channel/DM history, pins, members, server/channel/workspace/shop/app/agent data, or sending/managing Shadow content via the shadowob CLI."
|
|
4
4
|
metadata:
|
|
5
5
|
{
|
|
6
6
|
"openclaw":
|
|
@@ -17,6 +17,10 @@ allowed-tools: ["exec"]
|
|
|
17
17
|
|
|
18
18
|
Use `shadowob` CLI to interact with Shadow servers.
|
|
19
19
|
|
|
20
|
+
Activate this skill when you need current Shadow context, such as recent channel or DM history,
|
|
21
|
+
pinned messages, member/server/channel state, workspace/shop/app/agent data, or when you need to
|
|
22
|
+
send or manage Shadow content. Prefer narrow `--json` reads before acting.
|
|
23
|
+
|
|
20
24
|
## Quickstart
|
|
21
25
|
|
|
22
26
|
```bash
|
|
@@ -66,11 +70,6 @@ shadowob servers leave <server-id>
|
|
|
66
70
|
# Members
|
|
67
71
|
shadowob servers members <server-id> --json
|
|
68
72
|
|
|
69
|
-
# Homepage
|
|
70
|
-
shadowob servers homepage <server-id>
|
|
71
|
-
shadowob servers homepage <server-id> --set <file.html>
|
|
72
|
-
shadowob servers homepage <server-id> --clear
|
|
73
|
-
|
|
74
73
|
# Discover public servers
|
|
75
74
|
shadowob servers discover --json
|
|
76
75
|
```
|
|
@@ -179,19 +178,27 @@ shadowob workspace stats <server-id> --json
|
|
|
179
178
|
shadowob workspace children <server-id> [--parent-id <id>] --json
|
|
180
179
|
|
|
181
180
|
# Files
|
|
182
|
-
shadowob workspace files get <file-id> --json
|
|
183
|
-
shadowob workspace files create <server-id> --name <name> [--content <text>] [--parent-id <id>] --json
|
|
184
|
-
shadowob workspace files update <file-id> [--name <name>] [--content <text>] --json
|
|
185
|
-
shadowob workspace files delete <file-id>
|
|
181
|
+
shadowob workspace files get <server-id> <file-id> --json
|
|
186
182
|
shadowob workspace files upload <server-id> --file <path> [--name <name>] [--parent-id <id>] --json
|
|
187
|
-
shadowob workspace files
|
|
183
|
+
shadowob workspace files update <server-id> <file-id> [--name <name>] [--parent-id <id>] --json
|
|
184
|
+
shadowob workspace files delete <server-id> <file-id>
|
|
185
|
+
shadowob workspace files search <server-id> [--search-text <text>] [--ext <ext>] [--parent-id <id>] --json
|
|
186
|
+
# Note: files download is not yet implemented in CLI; download via contentRef URL instead.
|
|
188
187
|
|
|
189
188
|
# Folders
|
|
190
189
|
shadowob workspace folders create <server-id> --name <name> [--parent-id <id>] --json
|
|
191
|
-
shadowob workspace folders update <folder-id> --name <name> --json
|
|
192
|
-
shadowob workspace folders delete <folder-id>
|
|
190
|
+
shadowob workspace folders update <server-id> <folder-id> [--name <name>] [--parent-id <id>] --json
|
|
191
|
+
shadowob workspace folders delete <server-id> <folder-id>
|
|
193
192
|
```
|
|
194
193
|
|
|
194
|
+
### Workspace Node Metadata
|
|
195
|
+
|
|
196
|
+
Each workspace node has a `flags` JSONB field with optional metadata:
|
|
197
|
+
|
|
198
|
+
- **Access control**: `flags.access = { scope: "server" | "channel", serverId, channelId? }`. All nodes have at least `scope: "server"` + `serverId`. Channel-scoped nodes require channel membership for access.
|
|
199
|
+
- **Traceability**: `flags.source = "channel_message_attachment"` with `channelId` and `messageId` for files uploaded via channel messages, enabling reverse lookup to the originating message.
|
|
200
|
+
- **Path is server-computed**: `path` is derived from parent path + name, maintained server-side. Do not set path manually — it is auto-updated on rename/move.
|
|
201
|
+
|
|
195
202
|
## Shop
|
|
196
203
|
|
|
197
204
|
```bash
|
|
@@ -216,7 +223,23 @@ shadowob shop wallet balance --json
|
|
|
216
223
|
## Apps
|
|
217
224
|
|
|
218
225
|
```bash
|
|
219
|
-
#
|
|
226
|
+
# Server App integrations
|
|
227
|
+
shadowob app list --server <server-id-or-slug> --json
|
|
228
|
+
shadowob app preview --server <server-id-or-slug> --manifest-url <manifest-url> --json
|
|
229
|
+
shadowob app discover --server <server-id-or-slug> --json
|
|
230
|
+
shadowob app inspect <app-key> --server <server-id-or-slug> --json
|
|
231
|
+
shadowob app skills <app-key> --server <server-id-or-slug>
|
|
232
|
+
shadowob app call <app-key> <command> --server <server-id-or-slug> --json-input '<raw-command-input-json>' --json
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
For server App commands, use the `shadowob app` CLI path only. Do not use curl, fetch, raw HTTP
|
|
236
|
+
routes, or the JavaScript SDK to call server App commands. Pass the command input object directly
|
|
237
|
+
to `--json-input`, for example `{"title":"Example","priority":"high"}`; the CLI wraps the HTTP
|
|
238
|
+
request for you and binds Shadow OAuth identity, server membership, App grants, and command policy.
|
|
239
|
+
When a channel message mentions a server App, use the mentioned app key/server id directly.
|
|
240
|
+
|
|
241
|
+
```bash
|
|
242
|
+
# Legacy workspace apps
|
|
220
243
|
shadowob apps list <server-id> --json
|
|
221
244
|
|
|
222
245
|
# Get app
|