@shadowob/openclaw-shadowob 1.1.3-dev.251 → 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.
@@ -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 loadLocalSlashCommands(runtime) {
660
- const indexPath = process.env.SHADOW_SLASH_COMMANDS_PATH;
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 loadLocalSlashCommands(runtime);
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 processChannelMessageWithRetry(message, "catchup");
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) return;
1810
- const mentionOnly = data.mentionOnly ?? false;
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: data.reply ?? existing.reply,
1820
- config: data.config ?? existing.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: data.reply ?? true,
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 (!refreshed && !channelPolicies.has(data.channelId)) {
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 processChannelMessageWithRetry(message, "ws");
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-2R7AKKRH.js";
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-ZZHGWMZF.js");
962
+ const { monitorShadowProvider: monitorShadowProvider2 } = await import("./monitor-PUXW5FI2.js");
963
963
  await monitorShadowProvider2({
964
964
  account,
965
965
  accountId,
@@ -5,7 +5,7 @@ import {
5
5
  normalizeShadowSlashCommands,
6
6
  resolveShadowAgentIdFromConfig,
7
7
  shouldCatchUpShadowMessage
8
- } from "./chunk-2R7AKKRH.js";
8
+ } from "./chunk-ATPTVU3K.js";
9
9
  export {
10
10
  formatSlashCommandPrompt,
11
11
  matchShadowSlashCommand,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shadowob/openclaw-shadowob",
3
- "version": "1.1.3-dev.251",
3
+ "version": "1.1.3-dev.261",
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.251"
61
+ "@shadowob/sdk": "1.1.3-dev.261"
62
62
  },
63
63
  "devDependencies": {
64
64
  "@types/node": "^22.15.21",