@shadowob/openclaw-shadowob 1.1.3-dev.251 → 1.1.3-dev.271
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.
|
@@ -656,8 +656,15 @@ function normalizeShadowSlashCommands(input) {
|
|
|
656
656
|
function toPublicSlashCommands(commands) {
|
|
657
657
|
return commands.map(({ body: _body, ...command }) => command);
|
|
658
658
|
}
|
|
659
|
-
async function
|
|
660
|
-
|
|
659
|
+
async function fileExists(path) {
|
|
660
|
+
try {
|
|
661
|
+
await fsPromises2.access(path);
|
|
662
|
+
return true;
|
|
663
|
+
} catch {
|
|
664
|
+
return false;
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
async function loadSlashCommandFile(indexPath, runtime) {
|
|
661
668
|
if (!indexPath) return [];
|
|
662
669
|
try {
|
|
663
670
|
const raw = await fsPromises2.readFile(indexPath, "utf-8");
|
|
@@ -669,6 +676,47 @@ async function loadLocalSlashCommands(runtime) {
|
|
|
669
676
|
return [];
|
|
670
677
|
}
|
|
671
678
|
}
|
|
679
|
+
async function runtimeExtensionSlashCommandPaths(runtime) {
|
|
680
|
+
const candidates = [
|
|
681
|
+
process.env.SHADOW_RUNTIME_EXTENSIONS_PATH,
|
|
682
|
+
process.env.OPENCLAW_RUNTIME_EXTENSIONS_PATH,
|
|
683
|
+
"/etc/openclaw/runtime-extensions.json"
|
|
684
|
+
].filter((path) => Boolean(path));
|
|
685
|
+
const paths = [];
|
|
686
|
+
for (const manifestPath of [...new Set(candidates)]) {
|
|
687
|
+
if (!await fileExists(manifestPath)) continue;
|
|
688
|
+
try {
|
|
689
|
+
const raw = await fsPromises2.readFile(manifestPath, "utf-8");
|
|
690
|
+
const manifest = JSON.parse(raw);
|
|
691
|
+
const artifacts = Array.isArray(manifest.artifacts) ? manifest.artifacts : [];
|
|
692
|
+
for (const artifact of artifacts) {
|
|
693
|
+
if (!artifact || typeof artifact !== "object" || Array.isArray(artifact)) continue;
|
|
694
|
+
const record = artifact;
|
|
695
|
+
if (record.kind === "shadow.slashCommands" && typeof record.path === "string") {
|
|
696
|
+
paths.push(record.path);
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
} catch (err) {
|
|
700
|
+
runtime.error?.(`[slash] Failed to read runtime extensions ${manifestPath}: ${String(err)}`);
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
return paths;
|
|
704
|
+
}
|
|
705
|
+
async function loadShadowSlashCommands(runtime) {
|
|
706
|
+
const paths = [
|
|
707
|
+
process.env.SHADOW_SLASH_COMMANDS_PATH,
|
|
708
|
+
...await runtimeExtensionSlashCommandPaths(runtime)
|
|
709
|
+
].filter((path) => Boolean(path));
|
|
710
|
+
const seenPaths = [...new Set(paths)];
|
|
711
|
+
const loaded = await Promise.all(seenPaths.map((path) => loadSlashCommandFile(path, runtime)));
|
|
712
|
+
const merged = normalizeShadowSlashCommands(loaded.flat());
|
|
713
|
+
if (seenPaths.length > 1) {
|
|
714
|
+
runtime.log?.(
|
|
715
|
+
`[slash] Merged ${merged.length} slash command(s) from ${seenPaths.length} source(s)`
|
|
716
|
+
);
|
|
717
|
+
}
|
|
718
|
+
return merged;
|
|
719
|
+
}
|
|
672
720
|
async function registerAgentSlashCommands(params) {
|
|
673
721
|
const baseUrl = params.account.serverUrl.replace(/\/api\/?$/, "").replace(/\/$/, "");
|
|
674
722
|
const response = await fetch(`${baseUrl}/api/agents/${params.agentId}/slash-commands`, {
|
|
@@ -1369,6 +1417,34 @@ ${baseBodyForAgent}` : baseBodyForAgent;
|
|
|
1369
1417
|
}
|
|
1370
1418
|
}
|
|
1371
1419
|
|
|
1420
|
+
// src/monitor/message-queue.ts
|
|
1421
|
+
function queueKeyForMessage(message) {
|
|
1422
|
+
return `${message.channelId}:${message.threadId ?? ""}`;
|
|
1423
|
+
}
|
|
1424
|
+
function createShadowMessageProcessingQueue(options) {
|
|
1425
|
+
const queues = /* @__PURE__ */ new Map();
|
|
1426
|
+
return {
|
|
1427
|
+
enqueue(message, source) {
|
|
1428
|
+
const key = queueKeyForMessage(message);
|
|
1429
|
+
const previous = queues.get(key) ?? Promise.resolve();
|
|
1430
|
+
const task = previous.catch(() => void 0).then(() => {
|
|
1431
|
+
if (options.isStopped?.()) {
|
|
1432
|
+
options.onSkipped?.(message, source);
|
|
1433
|
+
return;
|
|
1434
|
+
}
|
|
1435
|
+
return options.process(message, source);
|
|
1436
|
+
}).finally(() => {
|
|
1437
|
+
if (queues.get(key) === task) queues.delete(key);
|
|
1438
|
+
});
|
|
1439
|
+
queues.set(key, task);
|
|
1440
|
+
return task;
|
|
1441
|
+
},
|
|
1442
|
+
pendingKeys() {
|
|
1443
|
+
return [...queues.keys()];
|
|
1444
|
+
}
|
|
1445
|
+
};
|
|
1446
|
+
}
|
|
1447
|
+
|
|
1372
1448
|
// src/runtime.ts
|
|
1373
1449
|
import { createPluginRuntimeStore } from "openclaw/plugin-sdk/runtime-store";
|
|
1374
1450
|
var store = createPluginRuntimeStore(
|
|
@@ -1475,7 +1551,7 @@ async function monitorShadowProvider(options) {
|
|
|
1475
1551
|
} else {
|
|
1476
1552
|
runtime.log?.(`[config] Resolved agentId: ${agentId}`);
|
|
1477
1553
|
}
|
|
1478
|
-
const slashCommands = await
|
|
1554
|
+
const slashCommands = await loadShadowSlashCommands(runtime);
|
|
1479
1555
|
if (agentId) {
|
|
1480
1556
|
try {
|
|
1481
1557
|
await runShadowApiOperation(
|
|
@@ -1495,6 +1571,23 @@ async function monitorShadowProvider(options) {
|
|
|
1495
1571
|
const messageWatermarks = await loadMessageWatermarks(accountId);
|
|
1496
1572
|
const processedMessageIds = /* @__PURE__ */ new Set();
|
|
1497
1573
|
const catchupInFlight = /* @__PURE__ */ new Map();
|
|
1574
|
+
const buildAccessPolicyConfig = (config2) => {
|
|
1575
|
+
const activeTenantIds = config2?.activeTenantIds ?? [];
|
|
1576
|
+
const allowedTriggerUserIds = config2?.allowedTriggerUserIds ?? [config2?.ownerId, ...activeTenantIds].filter((id) => Boolean(id));
|
|
1577
|
+
return {
|
|
1578
|
+
allowedTriggerUserIds,
|
|
1579
|
+
triggerUserIds: allowedTriggerUserIds,
|
|
1580
|
+
ownerId: config2?.ownerId,
|
|
1581
|
+
activeTenantIds,
|
|
1582
|
+
replyRequiresMention: false
|
|
1583
|
+
};
|
|
1584
|
+
};
|
|
1585
|
+
const buildDefaultAccessPolicy = (config2) => ({
|
|
1586
|
+
listen: true,
|
|
1587
|
+
reply: true,
|
|
1588
|
+
mentionOnly: false,
|
|
1589
|
+
config: buildAccessPolicyConfig(config2)
|
|
1590
|
+
});
|
|
1498
1591
|
const rememberProcessedMessage = (messageId) => {
|
|
1499
1592
|
processedMessageIds.add(messageId);
|
|
1500
1593
|
if (processedMessageIds.size > MAX_TRACKED_MESSAGE_IDS) {
|
|
@@ -1610,12 +1703,7 @@ async function monitorShadowProvider(options) {
|
|
|
1610
1703
|
for (const ch of directChannels) {
|
|
1611
1704
|
if (!allChannelIds.includes(ch.id)) allChannelIds.push(ch.id);
|
|
1612
1705
|
if (!channelPolicies.has(ch.id)) {
|
|
1613
|
-
channelPolicies.set(ch.id,
|
|
1614
|
-
listen: true,
|
|
1615
|
-
reply: true,
|
|
1616
|
-
mentionOnly: false,
|
|
1617
|
-
config: {}
|
|
1618
|
-
});
|
|
1706
|
+
channelPolicies.set(ch.id, buildDefaultAccessPolicy(remoteConfig));
|
|
1619
1707
|
}
|
|
1620
1708
|
}
|
|
1621
1709
|
runtime.log?.(`[config] Monitoring ${directChannels.length} direct channel(s)`);
|
|
@@ -1676,6 +1764,13 @@ async function monitorShadowProvider(options) {
|
|
|
1676
1764
|
);
|
|
1677
1765
|
}
|
|
1678
1766
|
};
|
|
1767
|
+
const messageQueue = createShadowMessageProcessingQueue({
|
|
1768
|
+
process: processChannelMessageWithRetry,
|
|
1769
|
+
isStopped: () => stopped,
|
|
1770
|
+
onSkipped: (message, source) => {
|
|
1771
|
+
runtime.log?.(`[${source}] Monitor stopped, skipping queued message ${message.id}`);
|
|
1772
|
+
}
|
|
1773
|
+
});
|
|
1679
1774
|
const catchUpChannel = async (channelId, reason) => {
|
|
1680
1775
|
try {
|
|
1681
1776
|
const result = await client.getMessages(channelId, 50);
|
|
@@ -1700,7 +1795,7 @@ async function monitorShadowProvider(options) {
|
|
|
1700
1795
|
}
|
|
1701
1796
|
for (const message of candidates) {
|
|
1702
1797
|
rememberProcessedMessage(message.id);
|
|
1703
|
-
await
|
|
1798
|
+
await messageQueue.enqueue(message, "catchup");
|
|
1704
1799
|
}
|
|
1705
1800
|
} catch (err) {
|
|
1706
1801
|
runtime.error?.(`[catchup] Failed for channel ${channelId}: ${String(err)}`);
|
|
@@ -1806,8 +1901,65 @@ async function monitorShadowProvider(options) {
|
|
|
1806
1901
|
"agent:policy-changed",
|
|
1807
1902
|
(data) => {
|
|
1808
1903
|
if (data.agentId !== agentId) return;
|
|
1809
|
-
if (!data.channelId)
|
|
1810
|
-
|
|
1904
|
+
if (!data.channelId) {
|
|
1905
|
+
void (async () => {
|
|
1906
|
+
try {
|
|
1907
|
+
const updatedConfig = await runShadowApiOperation(
|
|
1908
|
+
"refresh remote config after access policy change",
|
|
1909
|
+
() => client.getAgentConfig(agentId),
|
|
1910
|
+
{ runtime, abortSignal }
|
|
1911
|
+
);
|
|
1912
|
+
remoteConfig = updatedConfig;
|
|
1913
|
+
const remoteChannelIds = /* @__PURE__ */ new Set();
|
|
1914
|
+
const accessConfig2 = buildAccessPolicyConfig(updatedConfig);
|
|
1915
|
+
for (const server of updatedConfig.servers) {
|
|
1916
|
+
for (const ch of server.channels) {
|
|
1917
|
+
remoteChannelIds.add(ch.id);
|
|
1918
|
+
channelServerMap.set(ch.id, {
|
|
1919
|
+
serverId: server.id,
|
|
1920
|
+
serverSlug: server.slug ?? server.id,
|
|
1921
|
+
serverName: server.name,
|
|
1922
|
+
channelName: ch.name
|
|
1923
|
+
});
|
|
1924
|
+
channelPolicies.set(ch.id, {
|
|
1925
|
+
listen: true,
|
|
1926
|
+
reply: true,
|
|
1927
|
+
mentionOnly: false,
|
|
1928
|
+
config: { ...ch.policy.config, ...accessConfig2 }
|
|
1929
|
+
});
|
|
1930
|
+
if (!allChannelIds.includes(ch.id)) {
|
|
1931
|
+
allChannelIds.push(ch.id);
|
|
1932
|
+
void socket.joinChannel(ch.id);
|
|
1933
|
+
}
|
|
1934
|
+
}
|
|
1935
|
+
}
|
|
1936
|
+
for (const [channelId] of channelServerMap) {
|
|
1937
|
+
if (remoteChannelIds.has(channelId)) continue;
|
|
1938
|
+
channelServerMap.delete(channelId);
|
|
1939
|
+
channelPolicies.delete(channelId);
|
|
1940
|
+
const idx = allChannelIds.indexOf(channelId);
|
|
1941
|
+
if (idx !== -1) allChannelIds.splice(idx, 1);
|
|
1942
|
+
socket.leaveChannel(channelId);
|
|
1943
|
+
}
|
|
1944
|
+
for (const [channelId, existing2] of channelPolicies) {
|
|
1945
|
+
if (channelServerMap.has(channelId)) continue;
|
|
1946
|
+
channelPolicies.set(channelId, {
|
|
1947
|
+
...existing2,
|
|
1948
|
+
listen: true,
|
|
1949
|
+
reply: true,
|
|
1950
|
+
mentionOnly: false,
|
|
1951
|
+
config: { ...existing2.config, ...accessConfig2 }
|
|
1952
|
+
});
|
|
1953
|
+
}
|
|
1954
|
+
runtime.log?.("[config] Refreshed Buddy owner/tenant access policy");
|
|
1955
|
+
} catch (err) {
|
|
1956
|
+
runtime.error?.(`[config] Failed to refresh access policy: ${String(err)}`);
|
|
1957
|
+
}
|
|
1958
|
+
})();
|
|
1959
|
+
return;
|
|
1960
|
+
}
|
|
1961
|
+
const mentionOnly = false;
|
|
1962
|
+
const accessConfig = buildAccessPolicyConfig(remoteConfig);
|
|
1811
1963
|
runtime.log?.(
|
|
1812
1964
|
`[ws] Received agent:policy-changed for channel ${data.channelId}: mentionOnly=${mentionOnly}, reply=${data.reply}, config=${JSON.stringify(data.config ?? {})}`
|
|
1813
1965
|
);
|
|
@@ -1816,15 +1968,15 @@ async function monitorShadowProvider(options) {
|
|
|
1816
1968
|
channelPolicies.set(data.channelId, {
|
|
1817
1969
|
...existing,
|
|
1818
1970
|
mentionOnly,
|
|
1819
|
-
reply:
|
|
1820
|
-
config: data.config ??
|
|
1971
|
+
reply: true,
|
|
1972
|
+
config: { ...existing.config, ...accessConfig, ...data.config ?? {} }
|
|
1821
1973
|
});
|
|
1822
1974
|
} else {
|
|
1823
1975
|
channelPolicies.set(data.channelId, {
|
|
1824
1976
|
listen: true,
|
|
1825
|
-
reply:
|
|
1977
|
+
reply: true,
|
|
1826
1978
|
mentionOnly,
|
|
1827
|
-
config: data.config ?? {}
|
|
1979
|
+
config: { ...accessConfig, ...data.config ?? {} }
|
|
1828
1980
|
});
|
|
1829
1981
|
}
|
|
1830
1982
|
}
|
|
@@ -1875,12 +2027,12 @@ async function monitorShadowProvider(options) {
|
|
|
1875
2027
|
if (!refreshed) {
|
|
1876
2028
|
await resolveChannelContext(data.channelId, "member-added");
|
|
1877
2029
|
}
|
|
1878
|
-
if (!
|
|
2030
|
+
if (!channelPolicies.has(data.channelId)) {
|
|
1879
2031
|
const defaultPolicy = {
|
|
1880
2032
|
listen: true,
|
|
1881
2033
|
reply: true,
|
|
1882
2034
|
mentionOnly: false,
|
|
1883
|
-
config:
|
|
2035
|
+
config: buildAccessPolicyConfig(remoteConfig)
|
|
1884
2036
|
};
|
|
1885
2037
|
channelPolicies.set(data.channelId, defaultPolicy);
|
|
1886
2038
|
}
|
|
@@ -1924,7 +2076,7 @@ async function monitorShadowProvider(options) {
|
|
|
1924
2076
|
runtime.log?.(`[ws] Message from unmonitored channel ${message.channelId}, ignoring`);
|
|
1925
2077
|
return;
|
|
1926
2078
|
}
|
|
1927
|
-
void
|
|
2079
|
+
void messageQueue.enqueue(message, "ws");
|
|
1928
2080
|
});
|
|
1929
2081
|
socket.connect();
|
|
1930
2082
|
const stop = () => {
|
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-ATPTVU3K.js";
|
|
19
19
|
|
|
20
20
|
// index.ts
|
|
21
21
|
import { defineChannelPluginEntry } from "openclaw/plugin-sdk/core";
|
|
@@ -959,7 +959,7 @@ shadowPlugin.gateway = {
|
|
|
959
959
|
lastError: null
|
|
960
960
|
});
|
|
961
961
|
ctx.log?.info(`Starting Shadow connection for account ${accountId}`);
|
|
962
|
-
const { monitorShadowProvider: monitorShadowProvider2 } = await import("./monitor-
|
|
962
|
+
const { monitorShadowProvider: monitorShadowProvider2 } = await import("./monitor-PUXW5FI2.js");
|
|
963
963
|
await monitorShadowProvider2({
|
|
964
964
|
account,
|
|
965
965
|
accountId,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shadowob/openclaw-shadowob",
|
|
3
|
-
"version": "1.1.3-dev.
|
|
3
|
+
"version": "1.1.3-dev.271",
|
|
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.3-dev.
|
|
61
|
+
"@shadowob/sdk": "1.1.3-dev.271"
|
|
62
62
|
},
|
|
63
63
|
"devDependencies": {
|
|
64
64
|
"@types/node": "^22.15.21",
|