@nextclaw/channel-plugin-feishu 0.2.14 → 0.2.15

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.
Files changed (65) hide show
  1. package/index.ts +2 -2
  2. package/package.json +1 -2
  3. package/src/accounts.ts +2 -2
  4. package/src/bitable.ts +1 -1
  5. package/src/bot.test.ts +1 -1
  6. package/src/bot.ts +2 -2
  7. package/src/card-action.ts +1 -1
  8. package/src/channel.test.ts +1 -1
  9. package/src/channel.ts +3 -3
  10. package/src/chat.ts +1 -1
  11. package/src/config-schema.ts +1 -1
  12. package/src/dedup.ts +1 -1
  13. package/src/directory.test.ts +1 -1
  14. package/src/directory.ts +2 -2
  15. package/src/docx.account-selection.test.ts +1 -1
  16. package/src/docx.ts +1 -1
  17. package/src/drive.ts +1 -1
  18. package/src/dynamic-agent.ts +1 -1
  19. package/src/media.ts +1 -1
  20. package/src/monitor.account.ts +1 -1
  21. package/src/monitor.reaction.test.ts +1 -1
  22. package/src/monitor.startup.test.ts +1 -1
  23. package/src/monitor.startup.ts +1 -1
  24. package/src/monitor.state.ts +1 -1
  25. package/src/monitor.transport.ts +1 -1
  26. package/src/monitor.ts +1 -1
  27. package/src/monitor.webhook.test-helpers.ts +1 -1
  28. package/src/nextclaw-sdk/account-id.ts +31 -0
  29. package/src/nextclaw-sdk/compat.ts +8 -0
  30. package/src/nextclaw-sdk/core-channel.ts +296 -0
  31. package/src/nextclaw-sdk/core-pairing.ts +224 -0
  32. package/src/nextclaw-sdk/core.ts +26 -0
  33. package/src/nextclaw-sdk/dedupe.ts +246 -0
  34. package/src/nextclaw-sdk/feishu.ts +77 -0
  35. package/src/nextclaw-sdk/history.ts +127 -0
  36. package/src/nextclaw-sdk/network-body.ts +245 -0
  37. package/src/nextclaw-sdk/network-fetch.ts +129 -0
  38. package/src/nextclaw-sdk/network-webhook.ts +182 -0
  39. package/src/nextclaw-sdk/network.ts +13 -0
  40. package/src/nextclaw-sdk/runtime-store.ts +26 -0
  41. package/src/nextclaw-sdk/secrets-config.ts +109 -0
  42. package/src/nextclaw-sdk/secrets-core.ts +170 -0
  43. package/src/nextclaw-sdk/secrets-prompt.ts +305 -0
  44. package/src/nextclaw-sdk/secrets.ts +18 -0
  45. package/src/nextclaw-sdk/types.ts +300 -0
  46. package/src/onboarding.status.test.ts +1 -1
  47. package/src/onboarding.ts +2 -2
  48. package/src/outbound.ts +1 -1
  49. package/src/perm.ts +1 -1
  50. package/src/policy.ts +2 -2
  51. package/src/reactions.ts +1 -1
  52. package/src/reply-dispatcher.ts +1 -1
  53. package/src/runtime.ts +2 -2
  54. package/src/secret-input.ts +1 -1
  55. package/src/send-target.test.ts +1 -1
  56. package/src/send-target.ts +1 -1
  57. package/src/send.test.ts +1 -1
  58. package/src/send.ts +1 -1
  59. package/src/streaming-card.ts +1 -1
  60. package/src/tool-account-routing.test.ts +1 -1
  61. package/src/tool-account.ts +1 -1
  62. package/src/tool-factory-test-harness.ts +1 -1
  63. package/src/types.ts +1 -1
  64. package/src/typing.ts +1 -1
  65. package/src/wiki.ts +1 -1
package/index.ts CHANGED
@@ -1,5 +1,5 @@
1
- import type { OpenClawPluginApi } from "openclaw/plugin-sdk/feishu";
2
- import { emptyPluginConfigSchema } from "openclaw/plugin-sdk/feishu";
1
+ import type { OpenClawPluginApi } from "./src/nextclaw-sdk/feishu.js";
2
+ import { emptyPluginConfigSchema } from "./src/nextclaw-sdk/feishu.js";
3
3
  import { registerFeishuBitableTools } from "./src/bitable.js";
4
4
  import { feishuPlugin } from "./src/channel.js";
5
5
  import { registerFeishuChatTools } from "./src/chat.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nextclaw/channel-plugin-feishu",
3
- "version": "0.2.14",
3
+ "version": "0.2.15",
4
4
  "private": false,
5
5
  "description": "NextClaw Feishu/Lark channel plugin with doc/wiki/drive tools.",
6
6
  "type": "module",
@@ -43,7 +43,6 @@
43
43
  "@larksuiteoapi/node-sdk": "^1.59.0",
44
44
  "@sinclair/typebox": "0.34.48",
45
45
  "https-proxy-agent": "^8.0.0",
46
- "openclaw": "2026.3.13",
47
46
  "zod": "^4.3.6"
48
47
  },
49
48
  "scripts": {
package/src/accounts.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "openclaw/plugin-sdk/account-id";
2
- import type { ClawdbotConfig } from "openclaw/plugin-sdk/feishu";
1
+ import { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "./nextclaw-sdk/account-id.js";
2
+ import type { ClawdbotConfig } from "./nextclaw-sdk/feishu.js";
3
3
  import { normalizeResolvedSecretInputString, normalizeSecretInputString } from "./secret-input.js";
4
4
  import type {
5
5
  FeishuConfig,
package/src/bitable.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import type * as Lark from "@larksuiteoapi/node-sdk";
2
2
  import { Type } from "@sinclair/typebox";
3
- import type { OpenClawPluginApi } from "openclaw/plugin-sdk/feishu";
3
+ import type { OpenClawPluginApi } from "./nextclaw-sdk/feishu.js";
4
4
  import { listEnabledFeishuAccounts } from "./accounts.js";
5
5
  import { createFeishuToolClient } from "./tool-account.js";
6
6
 
package/src/bot.test.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { ClawdbotConfig, PluginRuntime, RuntimeEnv } from "openclaw/plugin-sdk/feishu";
1
+ import type { ClawdbotConfig, PluginRuntime, RuntimeEnv } from "./nextclaw-sdk/feishu.js";
2
2
  import { beforeEach, describe, expect, it, vi } from "vitest";
3
3
  import { createPluginRuntimeMock } from "../../test-utils/plugin-runtime-mock.js";
4
4
  import type { FeishuMessageEvent } from "./bot.js";
package/src/bot.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { ClawdbotConfig, RuntimeEnv } from "openclaw/plugin-sdk/feishu";
1
+ import type { ClawdbotConfig, RuntimeEnv } from "./nextclaw-sdk/feishu.js";
2
2
  import {
3
3
  buildAgentMediaPayload,
4
4
  buildPendingHistoryContextFromMap,
@@ -12,7 +12,7 @@ import {
12
12
  resolveOpenProviderRuntimeGroupPolicy,
13
13
  resolveDefaultGroupPolicy,
14
14
  warnMissingProviderGroupPolicyFallbackOnce,
15
- } from "openclaw/plugin-sdk/feishu";
15
+ } from "./nextclaw-sdk/feishu.js";
16
16
  import { resolveFeishuAccount } from "./accounts.js";
17
17
  import { createFeishuClient } from "./client.js";
18
18
  import { finalizeFeishuMessageProcessing, tryRecordMessagePersistent } from "./dedup.js";
@@ -1,4 +1,4 @@
1
- import type { ClawdbotConfig, RuntimeEnv } from "openclaw/plugin-sdk/feishu";
1
+ import type { ClawdbotConfig, RuntimeEnv } from "./nextclaw-sdk/feishu.js";
2
2
  import { resolveFeishuAccount } from "./accounts.js";
3
3
  import { handleFeishuMessage, type FeishuMessageEvent } from "./bot.js";
4
4
 
@@ -1,4 +1,4 @@
1
- import type { OpenClawConfig } from "openclaw/plugin-sdk/feishu";
1
+ import type { OpenClawConfig } from "./nextclaw-sdk/feishu.js";
2
2
  import { describe, expect, it, vi } from "vitest";
3
3
 
4
4
  const probeFeishuMock = vi.hoisted(() => vi.fn());
package/src/channel.ts CHANGED
@@ -2,15 +2,15 @@ import {
2
2
  collectAllowlistProviderRestrictSendersWarnings,
3
3
  formatAllowFromLowercase,
4
4
  mapAllowFromEntries,
5
- } from "openclaw/plugin-sdk/compat";
6
- import type { ChannelMeta, ChannelPlugin, ClawdbotConfig } from "openclaw/plugin-sdk/feishu";
5
+ } from "./nextclaw-sdk/compat.js";
6
+ import type { ChannelMeta, ChannelPlugin, ClawdbotConfig } from "./nextclaw-sdk/feishu.js";
7
7
  import {
8
8
  buildProbeChannelStatusSummary,
9
9
  buildRuntimeAccountStatusSnapshot,
10
10
  createDefaultChannelRuntimeState,
11
11
  DEFAULT_ACCOUNT_ID,
12
12
  PAIRING_APPROVED_MESSAGE,
13
- } from "openclaw/plugin-sdk/feishu";
13
+ } from "./nextclaw-sdk/feishu.js";
14
14
  import {
15
15
  resolveFeishuAccount,
16
16
  resolveFeishuCredentials,
package/src/chat.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import type * as Lark from "@larksuiteoapi/node-sdk";
2
- import type { OpenClawPluginApi } from "openclaw/plugin-sdk/feishu";
2
+ import type { OpenClawPluginApi } from "./nextclaw-sdk/feishu.js";
3
3
  import { listEnabledFeishuAccounts } from "./accounts.js";
4
4
  import { FeishuChatSchema, type FeishuChatParams } from "./chat-schema.js";
5
5
  import { createFeishuClient } from "./client.js";
@@ -1,4 +1,4 @@
1
- import { normalizeAccountId } from "openclaw/plugin-sdk/account-id";
1
+ import { normalizeAccountId } from "./nextclaw-sdk/account-id.js";
2
2
  import { z } from "zod";
3
3
  export { z };
4
4
  import { buildSecretInputSchema, hasConfiguredSecretInput } from "./secret-input.js";
package/src/dedup.ts CHANGED
@@ -4,7 +4,7 @@ import {
4
4
  createDedupeCache,
5
5
  createPersistentDedupe,
6
6
  readJsonFileWithFallback,
7
- } from "openclaw/plugin-sdk/feishu";
7
+ } from "./nextclaw-sdk/feishu.js";
8
8
 
9
9
  // Persistent TTL: 24 hours — survives restarts & WebSocket reconnects.
10
10
  const DEDUP_TTL_MS = 24 * 60 * 60 * 1000;
@@ -1,4 +1,4 @@
1
- import type { ClawdbotConfig } from "openclaw/plugin-sdk/feishu";
1
+ import type { ClawdbotConfig } from "./nextclaw-sdk/feishu.js";
2
2
  import { describe, expect, it, vi } from "vitest";
3
3
 
4
4
  vi.mock("./accounts.js", () => ({
package/src/directory.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  import {
2
2
  listDirectoryGroupEntriesFromMapKeysAndAllowFrom,
3
3
  listDirectoryUserEntriesFromAllowFromAndMapKeys,
4
- } from "openclaw/plugin-sdk/compat";
5
- import type { ClawdbotConfig } from "openclaw/plugin-sdk/feishu";
4
+ } from "./nextclaw-sdk/compat.js";
5
+ import type { ClawdbotConfig } from "./nextclaw-sdk/feishu.js";
6
6
  import { resolveFeishuAccount } from "./accounts.js";
7
7
  import { createFeishuClient } from "./client.js";
8
8
  import { normalizeFeishuTarget } from "./targets.js";
@@ -1,4 +1,4 @@
1
- import type { OpenClawPluginApi } from "openclaw/plugin-sdk/feishu";
1
+ import type { OpenClawPluginApi } from "./nextclaw-sdk/feishu.js";
2
2
  import { describe, expect, test, vi } from "vitest";
3
3
  import { registerFeishuDocTools } from "./docx.js";
4
4
  import { createToolFactoryHarness } from "./tool-factory-test-harness.js";
package/src/docx.ts CHANGED
@@ -4,7 +4,7 @@ import { isAbsolute } from "node:path";
4
4
  import { basename } from "node:path";
5
5
  import type * as Lark from "@larksuiteoapi/node-sdk";
6
6
  import { Type } from "@sinclair/typebox";
7
- import type { OpenClawPluginApi } from "openclaw/plugin-sdk/feishu";
7
+ import type { OpenClawPluginApi } from "./nextclaw-sdk/feishu.js";
8
8
  import { listEnabledFeishuAccounts } from "./accounts.js";
9
9
  import { FeishuDocSchema, type FeishuDocParams } from "./doc-schema.js";
10
10
  import { BATCH_SIZE, insertBlocksInBatches } from "./docx-batch-insert.js";
package/src/drive.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import type * as Lark from "@larksuiteoapi/node-sdk";
2
- import type { OpenClawPluginApi } from "openclaw/plugin-sdk/feishu";
2
+ import type { OpenClawPluginApi } from "./nextclaw-sdk/feishu.js";
3
3
  import { listEnabledFeishuAccounts } from "./accounts.js";
4
4
  import { FeishuDriveSchema, type FeishuDriveParams } from "./drive-schema.js";
5
5
  import { createFeishuToolClient, resolveAnyEnabledFeishuToolsConfig } from "./tool-account.js";
@@ -1,7 +1,7 @@
1
1
  import fs from "node:fs";
2
2
  import os from "node:os";
3
3
  import path from "node:path";
4
- import type { OpenClawConfig, PluginRuntime } from "openclaw/plugin-sdk/feishu";
4
+ import type { OpenClawConfig, PluginRuntime } from "./nextclaw-sdk/feishu.js";
5
5
  import type { DynamicAgentCreationConfig } from "./types.js";
6
6
 
7
7
  export type MaybeCreateDynamicAgentResult = {
package/src/media.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import fs from "fs";
2
2
  import path from "path";
3
3
  import { Readable } from "stream";
4
- import { withTempDownloadPath, type ClawdbotConfig } from "openclaw/plugin-sdk/feishu";
4
+ import { withTempDownloadPath, type ClawdbotConfig } from "./nextclaw-sdk/feishu.js";
5
5
  import { resolveFeishuAccount } from "./accounts.js";
6
6
  import { createFeishuClient } from "./client.js";
7
7
  import { normalizeFeishuExternalKey } from "./external-keys.js";
@@ -1,6 +1,6 @@
1
1
  import * as crypto from "crypto";
2
2
  import * as Lark from "@larksuiteoapi/node-sdk";
3
- import type { ClawdbotConfig, RuntimeEnv, HistoryEntry } from "openclaw/plugin-sdk/feishu";
3
+ import type { ClawdbotConfig, RuntimeEnv, HistoryEntry } from "./nextclaw-sdk/feishu.js";
4
4
  import { resolveFeishuAccount } from "./accounts.js";
5
5
  import { raceWithTimeoutAndAbort } from "./async.js";
6
6
  import {
@@ -1,4 +1,4 @@
1
- import type { ClawdbotConfig, RuntimeEnv } from "openclaw/plugin-sdk/feishu";
1
+ import type { ClawdbotConfig, RuntimeEnv } from "./nextclaw-sdk/feishu.js";
2
2
  import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
3
3
  import { hasControlCommand } from "../../../src/auto-reply/command-detection.js";
4
4
  import {
@@ -1,4 +1,4 @@
1
- import type { ClawdbotConfig } from "openclaw/plugin-sdk/feishu";
1
+ import type { ClawdbotConfig } from "./nextclaw-sdk/feishu.js";
2
2
  import { afterEach, describe, expect, it, vi } from "vitest";
3
3
  import { monitorFeishuProvider, stopFeishuMonitor } from "./monitor.js";
4
4
 
@@ -1,4 +1,4 @@
1
- import type { RuntimeEnv } from "openclaw/plugin-sdk/feishu";
1
+ import type { RuntimeEnv } from "./nextclaw-sdk/feishu.js";
2
2
  import { probeFeishu } from "./probe.js";
3
3
  import type { ResolvedFeishuAccount } from "./types.js";
4
4
 
@@ -6,7 +6,7 @@ import {
6
6
  type RuntimeEnv,
7
7
  WEBHOOK_ANOMALY_COUNTER_DEFAULTS as WEBHOOK_ANOMALY_COUNTER_DEFAULTS_FROM_SDK,
8
8
  WEBHOOK_RATE_LIMIT_DEFAULTS as WEBHOOK_RATE_LIMIT_DEFAULTS_FROM_SDK,
9
- } from "openclaw/plugin-sdk/feishu";
9
+ } from "./nextclaw-sdk/feishu.js";
10
10
 
11
11
  export const wsClients = new Map<string, Lark.WSClient>();
12
12
  export const httpServers = new Map<string, http.Server>();
@@ -6,7 +6,7 @@ import {
6
6
  readJsonBodyWithLimit,
7
7
  type RuntimeEnv,
8
8
  installRequestBodyLimitGuard,
9
- } from "openclaw/plugin-sdk/feishu";
9
+ } from "./nextclaw-sdk/feishu.js";
10
10
  import { createFeishuWSClient } from "./client.js";
11
11
  import {
12
12
  botNames,
package/src/monitor.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { ClawdbotConfig, RuntimeEnv } from "openclaw/plugin-sdk/feishu";
1
+ import type { ClawdbotConfig, RuntimeEnv } from "./nextclaw-sdk/feishu.js";
2
2
  import { listEnabledFeishuAccounts, resolveFeishuAccount } from "./accounts.js";
3
3
  import {
4
4
  monitorSingleAccount,
@@ -1,6 +1,6 @@
1
1
  import { createServer } from "node:http";
2
2
  import type { AddressInfo } from "node:net";
3
- import type { ClawdbotConfig } from "openclaw/plugin-sdk/feishu";
3
+ import type { ClawdbotConfig } from "./nextclaw-sdk/feishu.js";
4
4
  import { vi } from "vitest";
5
5
  import type { monitorFeishuProvider } from "./monitor.js";
6
6
 
@@ -0,0 +1,31 @@
1
+ export const DEFAULT_ACCOUNT_ID = "default";
2
+
3
+ export function normalizeAccountId(accountId?: string | null): string {
4
+ const trimmed = accountId?.trim();
5
+ return trimmed || DEFAULT_ACCOUNT_ID;
6
+ }
7
+
8
+ export function normalizeOptionalAccountId(accountId?: string | null): string | undefined {
9
+ const trimmed = accountId?.trim();
10
+ return trimmed ? normalizeAccountId(trimmed) : undefined;
11
+ }
12
+
13
+ const VALID_AGENT_ID_RE = /^[a-z0-9][a-z0-9_-]{0,63}$/i;
14
+ const INVALID_AGENT_CHARS_RE = /[^a-z0-9_-]+/g;
15
+
16
+ export function normalizeAgentId(value?: string | null): string {
17
+ const trimmed = value?.trim();
18
+ if (!trimmed) {
19
+ return "main";
20
+ }
21
+ if (VALID_AGENT_ID_RE.test(trimmed)) {
22
+ return trimmed.toLowerCase();
23
+ }
24
+ const normalized = trimmed
25
+ .toLowerCase()
26
+ .replace(INVALID_AGENT_CHARS_RE, "-")
27
+ .replace(/^-+/, "")
28
+ .replace(/-+$/, "")
29
+ .slice(0, 64);
30
+ return normalized || "main";
31
+ }
@@ -0,0 +1,8 @@
1
+ export {
2
+ collectAllowlistProviderRestrictSendersWarnings,
3
+ formatAllowFromLowercase,
4
+ listDirectoryGroupEntriesFromMapKeysAndAllowFrom,
5
+ listDirectoryUserEntriesFromAllowFromAndMapKeys,
6
+ mapAllowFromEntries,
7
+ } from "./core.js";
8
+ export { createPluginRuntimeStore } from "./runtime-store.js";
@@ -0,0 +1,296 @@
1
+ import type { GroupPolicy, OpenClawConfig } from "./types.js";
2
+
3
+ export function emptyPluginConfigSchema(): {
4
+ type: "object";
5
+ additionalProperties: false;
6
+ properties: Record<string, unknown>;
7
+ } {
8
+ return {
9
+ type: "object",
10
+ additionalProperties: false,
11
+ properties: {},
12
+ };
13
+ }
14
+
15
+ const warnedMissingProviderGroupPolicy = new Set<string>();
16
+
17
+ export function resolveDefaultGroupPolicy(cfg: {
18
+ channels?: {
19
+ defaults?: {
20
+ groupPolicy?: GroupPolicy;
21
+ };
22
+ };
23
+ }): GroupPolicy | undefined {
24
+ return cfg.channels?.defaults?.groupPolicy;
25
+ }
26
+
27
+ export function resolveOpenProviderRuntimeGroupPolicy(params: {
28
+ providerConfigPresent: boolean;
29
+ groupPolicy?: GroupPolicy;
30
+ defaultGroupPolicy?: GroupPolicy;
31
+ }): {
32
+ groupPolicy: GroupPolicy;
33
+ providerMissingFallbackApplied: boolean;
34
+ } {
35
+ const groupPolicy = params.providerConfigPresent
36
+ ? (params.groupPolicy ?? params.defaultGroupPolicy ?? "open")
37
+ : (params.groupPolicy ?? "allowlist");
38
+ return {
39
+ groupPolicy,
40
+ providerMissingFallbackApplied: !params.providerConfigPresent && params.groupPolicy === undefined,
41
+ };
42
+ }
43
+
44
+ export function warnMissingProviderGroupPolicyFallbackOnce(params: {
45
+ providerMissingFallbackApplied: boolean;
46
+ providerKey: string;
47
+ accountId?: string;
48
+ blockedLabel?: string;
49
+ log: (message: string) => void;
50
+ }): boolean {
51
+ if (!params.providerMissingFallbackApplied) {
52
+ return false;
53
+ }
54
+ const key = `${params.providerKey}:${params.accountId ?? "*"}`;
55
+ if (warnedMissingProviderGroupPolicy.has(key)) {
56
+ return false;
57
+ }
58
+ warnedMissingProviderGroupPolicy.add(key);
59
+ const blockedLabel = params.blockedLabel?.trim() || "group messages";
60
+ params.log(
61
+ `${params.providerKey}: channels.${params.providerKey} is missing; defaulting groupPolicy to "allowlist" (${blockedLabel} blocked until explicitly configured).`,
62
+ );
63
+ return true;
64
+ }
65
+
66
+ export function evaluateSenderGroupAccessForPolicy(params: {
67
+ groupPolicy: GroupPolicy;
68
+ providerMissingFallbackApplied?: boolean;
69
+ groupAllowFrom: string[];
70
+ senderId: string;
71
+ isSenderAllowed: (senderId: string, allowFrom: string[]) => boolean;
72
+ }): {
73
+ allowed: boolean;
74
+ groupPolicy: GroupPolicy;
75
+ providerMissingFallbackApplied: boolean;
76
+ reason: "allowed" | "disabled" | "empty_allowlist" | "sender_not_allowlisted";
77
+ } {
78
+ if (params.groupPolicy === "disabled") {
79
+ return {
80
+ allowed: false,
81
+ groupPolicy: params.groupPolicy,
82
+ providerMissingFallbackApplied: Boolean(params.providerMissingFallbackApplied),
83
+ reason: "disabled",
84
+ };
85
+ }
86
+ if (params.groupPolicy === "allowlist") {
87
+ if (params.groupAllowFrom.length === 0) {
88
+ return {
89
+ allowed: false,
90
+ groupPolicy: params.groupPolicy,
91
+ providerMissingFallbackApplied: Boolean(params.providerMissingFallbackApplied),
92
+ reason: "empty_allowlist",
93
+ };
94
+ }
95
+ if (!params.isSenderAllowed(params.senderId, params.groupAllowFrom)) {
96
+ return {
97
+ allowed: false,
98
+ groupPolicy: params.groupPolicy,
99
+ providerMissingFallbackApplied: Boolean(params.providerMissingFallbackApplied),
100
+ reason: "sender_not_allowlisted",
101
+ };
102
+ }
103
+ }
104
+ return {
105
+ allowed: true,
106
+ groupPolicy: params.groupPolicy,
107
+ providerMissingFallbackApplied: Boolean(params.providerMissingFallbackApplied),
108
+ reason: "allowed",
109
+ };
110
+ }
111
+
112
+ export function createDefaultChannelRuntimeState<T extends Record<string, unknown>>(
113
+ accountId: string,
114
+ extra?: T,
115
+ ): {
116
+ accountId: string;
117
+ running: false;
118
+ lastStartAt: null;
119
+ lastStopAt: null;
120
+ lastError: null;
121
+ } & T {
122
+ return {
123
+ accountId,
124
+ running: false,
125
+ lastStartAt: null,
126
+ lastStopAt: null,
127
+ lastError: null,
128
+ ...(extra ?? ({} as T)),
129
+ };
130
+ }
131
+
132
+ export function buildProbeChannelStatusSummary<TExtra extends Record<string, unknown>>(
133
+ snapshot: {
134
+ configured?: boolean | null;
135
+ running?: boolean | null;
136
+ lastStartAt?: number | null;
137
+ lastStopAt?: number | null;
138
+ lastError?: string | null;
139
+ probe?: unknown;
140
+ lastProbeAt?: number | null;
141
+ },
142
+ extra?: TExtra,
143
+ ) {
144
+ return {
145
+ configured: snapshot.configured ?? false,
146
+ running: snapshot.running ?? false,
147
+ lastStartAt: snapshot.lastStartAt ?? null,
148
+ lastStopAt: snapshot.lastStopAt ?? null,
149
+ lastError: snapshot.lastError ?? null,
150
+ ...(extra ?? ({} as TExtra)),
151
+ probe: snapshot.probe,
152
+ lastProbeAt: snapshot.lastProbeAt ?? null,
153
+ };
154
+ }
155
+
156
+ export function buildRuntimeAccountStatusSnapshot(params: {
157
+ runtime?: {
158
+ running?: boolean | null;
159
+ lastStartAt?: number | null;
160
+ lastStopAt?: number | null;
161
+ lastError?: string | null;
162
+ } | null;
163
+ probe?: unknown;
164
+ }) {
165
+ return {
166
+ running: params.runtime?.running ?? false,
167
+ lastStartAt: params.runtime?.lastStartAt ?? null,
168
+ lastStopAt: params.runtime?.lastStopAt ?? null,
169
+ lastError: params.runtime?.lastError ?? null,
170
+ probe: params.probe,
171
+ };
172
+ }
173
+
174
+ export function mapAllowFromEntries(
175
+ allowFrom: Array<string | number> | null | undefined,
176
+ ): string[] {
177
+ return (allowFrom ?? []).map((entry) => String(entry));
178
+ }
179
+
180
+ export function formatAllowFromLowercase(params: {
181
+ allowFrom: Array<string | number>;
182
+ stripPrefixRe?: RegExp;
183
+ }): string[] {
184
+ return params.allowFrom
185
+ .map((entry) => String(entry).trim())
186
+ .filter(Boolean)
187
+ .map((entry) => (params.stripPrefixRe ? entry.replace(params.stripPrefixRe, "") : entry))
188
+ .map((entry) => entry.toLowerCase());
189
+ }
190
+
191
+ function applyDirectoryQueryAndLimit(
192
+ ids: string[],
193
+ params: { query?: string | null; limit?: number | null },
194
+ ): string[] {
195
+ const query = params.query?.trim().toLowerCase() || "";
196
+ const limit = typeof params.limit === "number" && params.limit > 0 ? params.limit : undefined;
197
+ const filtered = ids.filter((id) => (query ? id.toLowerCase().includes(query) : true));
198
+ return typeof limit === "number" ? filtered.slice(0, limit) : filtered;
199
+ }
200
+
201
+ function dedupeIds(ids: string[]): string[] {
202
+ return Array.from(new Set(ids));
203
+ }
204
+
205
+ function collectEntryIds(params: {
206
+ entries?: readonly unknown[];
207
+ normalizeId?: (entry: string) => string | null | undefined;
208
+ }): string[] {
209
+ return (params.entries ?? [])
210
+ .map((entry) => String(entry).trim())
211
+ .filter((entry) => Boolean(entry) && entry !== "*")
212
+ .map((entry) => {
213
+ const normalized = params.normalizeId ? params.normalizeId(entry) : entry;
214
+ return typeof normalized === "string" ? normalized.trim() : "";
215
+ })
216
+ .filter(Boolean);
217
+ }
218
+
219
+ function collectMapIds(params: {
220
+ map?: Record<string, unknown>;
221
+ normalizeId?: (entry: string) => string | null | undefined;
222
+ }): string[] {
223
+ return collectEntryIds({
224
+ entries: Object.keys(params.map ?? {}),
225
+ normalizeId: params.normalizeId,
226
+ });
227
+ }
228
+
229
+ export function listDirectoryUserEntriesFromAllowFromAndMapKeys(params: {
230
+ allowFrom?: readonly unknown[];
231
+ map?: Record<string, unknown>;
232
+ query?: string | null;
233
+ limit?: number | null;
234
+ normalizeAllowFromId?: (entry: string) => string | null | undefined;
235
+ normalizeMapKeyId?: (entry: string) => string | null | undefined;
236
+ }): Array<{ kind: "user"; id: string }> {
237
+ const ids = dedupeIds([
238
+ ...collectEntryIds({
239
+ entries: params.allowFrom,
240
+ normalizeId: params.normalizeAllowFromId,
241
+ }),
242
+ ...collectMapIds({
243
+ map: params.map,
244
+ normalizeId: params.normalizeMapKeyId,
245
+ }),
246
+ ]);
247
+ return applyDirectoryQueryAndLimit(ids, params).map((id) => ({ kind: "user", id }));
248
+ }
249
+
250
+ export function listDirectoryGroupEntriesFromMapKeysAndAllowFrom(params: {
251
+ groups?: Record<string, unknown>;
252
+ allowFrom?: readonly unknown[];
253
+ query?: string | null;
254
+ limit?: number | null;
255
+ normalizeMapKeyId?: (entry: string) => string | null | undefined;
256
+ normalizeAllowFromId?: (entry: string) => string | null | undefined;
257
+ }): Array<{ kind: "group"; id: string }> {
258
+ const ids = dedupeIds([
259
+ ...collectMapIds({
260
+ map: params.groups,
261
+ normalizeId: params.normalizeMapKeyId,
262
+ }),
263
+ ...collectEntryIds({
264
+ entries: params.allowFrom,
265
+ normalizeId: params.normalizeAllowFromId,
266
+ }),
267
+ ]);
268
+ return applyDirectoryQueryAndLimit(ids, params).map((id) => ({ kind: "group", id }));
269
+ }
270
+
271
+ export function collectAllowlistProviderRestrictSendersWarnings(params: {
272
+ cfg: OpenClawConfig;
273
+ providerConfigPresent: boolean;
274
+ configuredGroupPolicy?: GroupPolicy | null;
275
+ surface: string;
276
+ openScope: string;
277
+ groupPolicyPath: string;
278
+ groupAllowFromPath: string;
279
+ mentionGated?: boolean;
280
+ }): string[] {
281
+ const defaultGroupPolicy = resolveDefaultGroupPolicy(params.cfg as {
282
+ channels?: { defaults?: { groupPolicy?: GroupPolicy } };
283
+ });
284
+ const { groupPolicy } = resolveOpenProviderRuntimeGroupPolicy({
285
+ providerConfigPresent: params.providerConfigPresent,
286
+ groupPolicy: params.configuredGroupPolicy ?? undefined,
287
+ defaultGroupPolicy,
288
+ });
289
+ if (groupPolicy !== "open") {
290
+ return [];
291
+ }
292
+ const mentionSuffix = params.mentionGated === false ? "" : " (mention-gated)";
293
+ return [
294
+ `- ${params.surface}: groupPolicy="open" allows ${params.openScope} to trigger${mentionSuffix}. Set ${params.groupPolicyPath}="allowlist" + ${params.groupAllowFromPath} to restrict senders.`,
295
+ ];
296
+ }