@openclaw/nextcloud-talk 2026.5.2 → 2026.5.3-beta.2

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 (61) hide show
  1. package/dist/api.js +2 -0
  2. package/dist/channel-BVVRsVr5.js +1788 -0
  3. package/dist/channel-plugin-api.js +2 -0
  4. package/dist/contract-api.js +2 -0
  5. package/dist/doctor-contract-CYlB-4Bf.js +7 -0
  6. package/dist/doctor-contract-api.js +2 -0
  7. package/dist/index.js +22 -0
  8. package/dist/runtime-api-BcCzeRN9.js +15 -0
  9. package/dist/runtime-api.js +2 -0
  10. package/dist/secret-contract-api.js +2 -0
  11. package/dist/secret-contract-bczDw2-2.js +86 -0
  12. package/dist/setup-entry.js +15 -0
  13. package/package.json +14 -6
  14. package/api.ts +0 -1
  15. package/channel-plugin-api.ts +0 -1
  16. package/contract-api.ts +0 -4
  17. package/doctor-contract-api.ts +0 -1
  18. package/index.ts +0 -20
  19. package/runtime-api.ts +0 -33
  20. package/secret-contract-api.ts +0 -5
  21. package/setup-entry.ts +0 -13
  22. package/src/accounts.ts +0 -139
  23. package/src/approval-auth.test.ts +0 -17
  24. package/src/approval-auth.ts +0 -27
  25. package/src/channel-api.ts +0 -5
  26. package/src/channel.adapters.ts +0 -52
  27. package/src/channel.core.test.ts +0 -75
  28. package/src/channel.lifecycle.test.ts +0 -81
  29. package/src/channel.ts +0 -195
  30. package/src/config-schema.ts +0 -79
  31. package/src/core.test.ts +0 -397
  32. package/src/doctor-contract.ts +0 -9
  33. package/src/doctor.test.ts +0 -40
  34. package/src/doctor.ts +0 -10
  35. package/src/gateway.ts +0 -109
  36. package/src/inbound.authz.test.ts +0 -149
  37. package/src/inbound.behavior.test.ts +0 -202
  38. package/src/inbound.ts +0 -320
  39. package/src/monitor-runtime.ts +0 -138
  40. package/src/monitor.replay.test.ts +0 -279
  41. package/src/monitor.test-fixtures.ts +0 -30
  42. package/src/monitor.test-harness.ts +0 -59
  43. package/src/monitor.ts +0 -385
  44. package/src/normalize.ts +0 -44
  45. package/src/policy.ts +0 -180
  46. package/src/replay-guard.ts +0 -128
  47. package/src/room-info.test.ts +0 -116
  48. package/src/room-info.ts +0 -148
  49. package/src/runtime.ts +0 -9
  50. package/src/secret-contract.ts +0 -103
  51. package/src/secret-input.ts +0 -4
  52. package/src/send.cfg-threading.test.ts +0 -153
  53. package/src/send.runtime.ts +0 -8
  54. package/src/send.ts +0 -236
  55. package/src/session-route.ts +0 -40
  56. package/src/setup-core.ts +0 -248
  57. package/src/setup-surface.ts +0 -190
  58. package/src/setup.test.ts +0 -422
  59. package/src/signature.ts +0 -82
  60. package/src/types.ts +0 -193
  61. package/tsconfig.json +0 -16
@@ -1,149 +0,0 @@
1
- import { describe, expect, it, vi } from "vitest";
2
- import type { PluginRuntime, RuntimeEnv } from "../runtime-api.js";
3
- import type { ResolvedNextcloudTalkAccount } from "./accounts.js";
4
- import { handleNextcloudTalkInbound } from "./inbound.js";
5
- import { setNextcloudTalkRuntime } from "./runtime.js";
6
- import type { CoreConfig, NextcloudTalkInboundMessage } from "./types.js";
7
-
8
- function installInboundAuthzRuntime(params: {
9
- readAllowFromStore: () => Promise<string[]>;
10
- buildMentionRegexes: () => RegExp[];
11
- }) {
12
- setNextcloudTalkRuntime({
13
- channel: {
14
- pairing: {
15
- readAllowFromStore: params.readAllowFromStore,
16
- },
17
- commands: {
18
- shouldHandleTextCommands: () => false,
19
- },
20
- text: {
21
- hasControlCommand: () => false,
22
- },
23
- mentions: {
24
- buildMentionRegexes: params.buildMentionRegexes,
25
- matchesMentionPatterns: () => false,
26
- },
27
- },
28
- } as unknown as PluginRuntime);
29
- }
30
-
31
- function createTestRuntimeEnv(): RuntimeEnv {
32
- return {
33
- log: vi.fn(),
34
- error: vi.fn(),
35
- } as unknown as RuntimeEnv;
36
- }
37
-
38
- describe("nextcloud-talk inbound authz", () => {
39
- it("does not treat DM pairing-store entries as group allowlist entries", async () => {
40
- const readAllowFromStore = vi.fn(async () => ["attacker"]);
41
- const buildMentionRegexes = vi.fn(() => [/@openclaw/i]);
42
-
43
- installInboundAuthzRuntime({ readAllowFromStore, buildMentionRegexes });
44
-
45
- const message: NextcloudTalkInboundMessage = {
46
- messageId: "m-1",
47
- roomToken: "room-1",
48
- roomName: "Room 1",
49
- senderId: "attacker",
50
- senderName: "Attacker",
51
- text: "hello",
52
- mediaType: "text/plain",
53
- timestamp: Date.now(),
54
- isGroupChat: true,
55
- };
56
-
57
- const account: ResolvedNextcloudTalkAccount = {
58
- accountId: "default",
59
- enabled: true,
60
- baseUrl: "",
61
- secret: "",
62
- secretSource: "none", // pragma: allowlist secret
63
- config: {
64
- dmPolicy: "pairing",
65
- allowFrom: [],
66
- groupPolicy: "allowlist",
67
- groupAllowFrom: [],
68
- },
69
- };
70
-
71
- const config: CoreConfig = {
72
- channels: {
73
- "nextcloud-talk": {
74
- dmPolicy: "pairing",
75
- allowFrom: [],
76
- groupPolicy: "allowlist",
77
- groupAllowFrom: [],
78
- },
79
- },
80
- };
81
-
82
- await handleNextcloudTalkInbound({
83
- message,
84
- account,
85
- config,
86
- runtime: createTestRuntimeEnv(),
87
- });
88
-
89
- expect(readAllowFromStore).toHaveBeenCalledWith({
90
- channel: "nextcloud-talk",
91
- accountId: "default",
92
- });
93
- expect(buildMentionRegexes).not.toHaveBeenCalled();
94
- });
95
-
96
- it("matches group rooms by token instead of colliding room names", async () => {
97
- const readAllowFromStore = vi.fn(async () => []);
98
- const buildMentionRegexes = vi.fn(() => [/@openclaw/i]);
99
-
100
- installInboundAuthzRuntime({ readAllowFromStore, buildMentionRegexes });
101
-
102
- const message: NextcloudTalkInboundMessage = {
103
- messageId: "m-2",
104
- roomToken: "room-attacker",
105
- roomName: "Room Trusted",
106
- senderId: "trusted-user",
107
- senderName: "Trusted User",
108
- text: "hello",
109
- mediaType: "text/plain",
110
- timestamp: Date.now(),
111
- isGroupChat: true,
112
- };
113
-
114
- const account: ResolvedNextcloudTalkAccount = {
115
- accountId: "default",
116
- enabled: true,
117
- baseUrl: "",
118
- secret: "",
119
- secretSource: "none",
120
- config: {
121
- dmPolicy: "pairing",
122
- allowFrom: [],
123
- groupPolicy: "allowlist",
124
- groupAllowFrom: ["trusted-user"],
125
- rooms: {
126
- "room-trusted": {
127
- enabled: true,
128
- },
129
- },
130
- },
131
- };
132
-
133
- await handleNextcloudTalkInbound({
134
- message,
135
- account,
136
- config: {
137
- channels: {
138
- "nextcloud-talk": {
139
- groupPolicy: "allowlist",
140
- groupAllowFrom: ["trusted-user"],
141
- },
142
- },
143
- },
144
- runtime: createTestRuntimeEnv(),
145
- });
146
-
147
- expect(buildMentionRegexes).not.toHaveBeenCalled();
148
- });
149
- });
@@ -1,202 +0,0 @@
1
- import { beforeEach, describe, expect, it, vi } from "vitest";
2
- import type { PluginRuntime, RuntimeEnv } from "../runtime-api.js";
3
- import type { ResolvedNextcloudTalkAccount } from "./accounts.js";
4
- import { handleNextcloudTalkInbound } from "./inbound.js";
5
- import { setNextcloudTalkRuntime } from "./runtime.js";
6
- import type { CoreConfig, NextcloudTalkInboundMessage } from "./types.js";
7
-
8
- const {
9
- createChannelPairingControllerMock,
10
- dispatchInboundReplyWithBaseMock,
11
- readStoreAllowFromForDmPolicyMock,
12
- resolveDmGroupAccessWithCommandGateMock,
13
- resolveAllowlistProviderRuntimeGroupPolicyMock,
14
- resolveDefaultGroupPolicyMock,
15
- warnMissingProviderGroupPolicyFallbackOnceMock,
16
- } = vi.hoisted(() => {
17
- return {
18
- createChannelPairingControllerMock: vi.fn(),
19
- dispatchInboundReplyWithBaseMock: vi.fn(),
20
- readStoreAllowFromForDmPolicyMock: vi.fn(),
21
- resolveDmGroupAccessWithCommandGateMock: vi.fn(),
22
- resolveAllowlistProviderRuntimeGroupPolicyMock: vi.fn(),
23
- resolveDefaultGroupPolicyMock: vi.fn(),
24
- warnMissingProviderGroupPolicyFallbackOnceMock: vi.fn(),
25
- };
26
- });
27
-
28
- const sendMessageNextcloudTalkMock = vi.hoisted(() => vi.fn());
29
- const resolveNextcloudTalkRoomKindMock = vi.hoisted(() => vi.fn());
30
-
31
- vi.mock("../runtime-api.js", async () => {
32
- const actual = await vi.importActual<typeof import("../runtime-api.js")>("../runtime-api.js");
33
- return {
34
- ...actual,
35
- createChannelPairingController: createChannelPairingControllerMock,
36
- dispatchInboundReplyWithBase: dispatchInboundReplyWithBaseMock,
37
- readStoreAllowFromForDmPolicy: readStoreAllowFromForDmPolicyMock,
38
- resolveDmGroupAccessWithCommandGate: resolveDmGroupAccessWithCommandGateMock,
39
- resolveAllowlistProviderRuntimeGroupPolicy: resolveAllowlistProviderRuntimeGroupPolicyMock,
40
- resolveDefaultGroupPolicy: resolveDefaultGroupPolicyMock,
41
- warnMissingProviderGroupPolicyFallbackOnce: warnMissingProviderGroupPolicyFallbackOnceMock,
42
- };
43
- });
44
-
45
- vi.mock("./send.js", () => ({
46
- sendMessageNextcloudTalk: sendMessageNextcloudTalkMock,
47
- }));
48
-
49
- vi.mock("./room-info.js", async () => {
50
- const actual = await vi.importActual<typeof import("./room-info.js")>("./room-info.js");
51
- return {
52
- ...actual,
53
- resolveNextcloudTalkRoomKind: resolveNextcloudTalkRoomKindMock,
54
- };
55
- });
56
-
57
- function installRuntime(params?: {
58
- buildMentionRegexes?: () => RegExp[];
59
- matchesMentionPatterns?: (body: string, regexes: RegExp[]) => boolean;
60
- }) {
61
- setNextcloudTalkRuntime({
62
- channel: {
63
- pairing: {
64
- readAllowFromStore: vi.fn(async () => []),
65
- upsertPairingRequest: vi.fn(async () => ({ code: "123456", created: true })),
66
- },
67
- commands: {
68
- shouldHandleTextCommands: vi.fn(() => false),
69
- },
70
- text: {
71
- hasControlCommand: vi.fn(() => false),
72
- },
73
- mentions: {
74
- buildMentionRegexes: params?.buildMentionRegexes ?? vi.fn(() => []),
75
- matchesMentionPatterns: params?.matchesMentionPatterns ?? vi.fn(() => false),
76
- },
77
- },
78
- } as unknown as PluginRuntime);
79
- }
80
-
81
- function createRuntimeEnv() {
82
- return {
83
- log: vi.fn(),
84
- error: vi.fn(),
85
- } as unknown as RuntimeEnv;
86
- }
87
-
88
- function createAccount(
89
- overrides?: Partial<ResolvedNextcloudTalkAccount>,
90
- ): ResolvedNextcloudTalkAccount {
91
- return {
92
- accountId: "default",
93
- enabled: true,
94
- baseUrl: "https://cloud.example.com",
95
- secret: "secret",
96
- secretSource: "config",
97
- config: {
98
- dmPolicy: "pairing",
99
- allowFrom: [],
100
- groupPolicy: "allowlist",
101
- groupAllowFrom: [],
102
- },
103
- ...overrides,
104
- };
105
- }
106
-
107
- function createMessage(
108
- overrides?: Partial<NextcloudTalkInboundMessage>,
109
- ): NextcloudTalkInboundMessage {
110
- return {
111
- messageId: "msg-1",
112
- roomToken: "room-1",
113
- roomName: "Room 1",
114
- senderId: "user-1",
115
- senderName: "Alice",
116
- text: "hello",
117
- mediaType: "text/plain",
118
- timestamp: Date.now(),
119
- isGroupChat: false,
120
- ...overrides,
121
- };
122
- }
123
-
124
- describe("nextcloud-talk inbound behavior", () => {
125
- beforeEach(() => {
126
- vi.clearAllMocks();
127
- installRuntime();
128
- resolveNextcloudTalkRoomKindMock.mockResolvedValue("direct");
129
- resolveDefaultGroupPolicyMock.mockReturnValue("allowlist");
130
- resolveAllowlistProviderRuntimeGroupPolicyMock.mockReturnValue({
131
- groupPolicy: "allowlist",
132
- providerMissingFallbackApplied: false,
133
- });
134
- warnMissingProviderGroupPolicyFallbackOnceMock.mockReturnValue(undefined);
135
- readStoreAllowFromForDmPolicyMock.mockResolvedValue([]);
136
- });
137
-
138
- // The DM pairing assertion currently depends on a mocked runtime barrel that Vitest
139
- // does not bind reliably for this extension package.
140
- it.skip("issues a DM pairing challenge and sends the challenge text", async () => {
141
- createChannelPairingControllerMock.mockReturnValue({
142
- readStoreForDmPolicy: vi.fn(),
143
- issueChallenge: vi.fn(),
144
- });
145
- resolveDmGroupAccessWithCommandGateMock.mockReturnValue({
146
- decision: "pairing",
147
- reason: "pairing_required",
148
- commandAuthorized: false,
149
- effectiveGroupAllowFrom: [],
150
- });
151
- sendMessageNextcloudTalkMock.mockResolvedValue(undefined);
152
-
153
- const statusSink = vi.fn();
154
- await handleNextcloudTalkInbound({
155
- message: createMessage(),
156
- account: createAccount(),
157
- config: { channels: { "nextcloud-talk": {} } } as CoreConfig,
158
- runtime: createRuntimeEnv(),
159
- statusSink,
160
- });
161
- });
162
-
163
- it("drops unmentioned group traffic before dispatch", async () => {
164
- installRuntime({
165
- buildMentionRegexes: vi.fn(() => [/@openclaw/i]),
166
- matchesMentionPatterns: vi.fn(() => false),
167
- });
168
- createChannelPairingControllerMock.mockReturnValue({
169
- readStoreForDmPolicy: vi.fn(),
170
- issueChallenge: vi.fn(),
171
- });
172
- resolveNextcloudTalkRoomKindMock.mockResolvedValue("group");
173
- resolveDmGroupAccessWithCommandGateMock.mockReturnValue({
174
- decision: "allow",
175
- reason: "allow",
176
- commandAuthorized: false,
177
- effectiveGroupAllowFrom: ["user-1"],
178
- });
179
- const runtime = createRuntimeEnv();
180
-
181
- await handleNextcloudTalkInbound({
182
- message: createMessage({
183
- roomToken: "room-group",
184
- roomName: "Ops",
185
- isGroupChat: true,
186
- }),
187
- account: createAccount({
188
- config: {
189
- dmPolicy: "pairing",
190
- allowFrom: [],
191
- groupPolicy: "allowlist",
192
- groupAllowFrom: ["user-1"],
193
- },
194
- }),
195
- config: { channels: { "nextcloud-talk": {} } } as CoreConfig,
196
- runtime,
197
- });
198
-
199
- expect(dispatchInboundReplyWithBaseMock).not.toHaveBeenCalled();
200
- expect(runtime.log).toHaveBeenCalledWith("nextcloud-talk: drop room room-group (no mention)");
201
- });
202
- });
package/src/inbound.ts DELETED
@@ -1,320 +0,0 @@
1
- import { normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime";
2
- import {
3
- GROUP_POLICY_BLOCKED_LABEL,
4
- createChannelPairingController,
5
- deliverFormattedTextWithAttachments,
6
- dispatchInboundReplyWithBase,
7
- logInboundDrop,
8
- readStoreAllowFromForDmPolicy,
9
- resolveAllowlistProviderRuntimeGroupPolicy,
10
- resolveDefaultGroupPolicy,
11
- resolveDmGroupAccessWithCommandGate,
12
- warnMissingProviderGroupPolicyFallbackOnce,
13
- type OpenClawConfig,
14
- type OutboundReplyPayload,
15
- type RuntimeEnv,
16
- } from "../runtime-api.js";
17
- import type { ResolvedNextcloudTalkAccount } from "./accounts.js";
18
- import {
19
- normalizeNextcloudTalkAllowlist,
20
- resolveNextcloudTalkAllowlistMatch,
21
- resolveNextcloudTalkGroupAllow,
22
- resolveNextcloudTalkMentionGate,
23
- resolveNextcloudTalkRequireMention,
24
- resolveNextcloudTalkRoomMatch,
25
- } from "./policy.js";
26
- import { resolveNextcloudTalkRoomKind } from "./room-info.js";
27
- import { getNextcloudTalkRuntime } from "./runtime.js";
28
- import { sendMessageNextcloudTalk } from "./send.js";
29
- import type { CoreConfig, NextcloudTalkInboundMessage } from "./types.js";
30
-
31
- const CHANNEL_ID = "nextcloud-talk" as const;
32
-
33
- async function deliverNextcloudTalkReply(params: {
34
- cfg: CoreConfig;
35
- payload: OutboundReplyPayload;
36
- roomToken: string;
37
- accountId: string;
38
- statusSink?: (patch: { lastOutboundAt?: number }) => void;
39
- }): Promise<void> {
40
- const { cfg, payload, roomToken, accountId, statusSink } = params;
41
- await deliverFormattedTextWithAttachments({
42
- payload,
43
- send: async ({ text, replyToId }) => {
44
- await sendMessageNextcloudTalk(roomToken, text, {
45
- cfg,
46
- accountId,
47
- replyTo: replyToId,
48
- });
49
- statusSink?.({ lastOutboundAt: Date.now() });
50
- },
51
- });
52
- }
53
-
54
- export async function handleNextcloudTalkInbound(params: {
55
- message: NextcloudTalkInboundMessage;
56
- account: ResolvedNextcloudTalkAccount;
57
- config: CoreConfig;
58
- runtime: RuntimeEnv;
59
- statusSink?: (patch: { lastInboundAt?: number; lastOutboundAt?: number }) => void;
60
- }): Promise<void> {
61
- const { message, account, config, runtime, statusSink } = params;
62
- const core = getNextcloudTalkRuntime();
63
- const pairing = createChannelPairingController({
64
- core,
65
- channel: CHANNEL_ID,
66
- accountId: account.accountId,
67
- });
68
-
69
- const rawBody = message.text?.trim() ?? "";
70
- if (!rawBody) {
71
- return;
72
- }
73
-
74
- const roomKind = await resolveNextcloudTalkRoomKind({
75
- account,
76
- roomToken: message.roomToken,
77
- runtime,
78
- });
79
- const isGroup = roomKind === "direct" ? false : roomKind === "group" ? true : message.isGroupChat;
80
- const senderId = message.senderId;
81
- const senderName = message.senderName;
82
- const roomToken = message.roomToken;
83
- const roomName = message.roomName;
84
-
85
- statusSink?.({ lastInboundAt: message.timestamp });
86
-
87
- const dmPolicy = account.config.dmPolicy ?? "pairing";
88
- const defaultGroupPolicy = resolveDefaultGroupPolicy(config as OpenClawConfig);
89
- const { groupPolicy, providerMissingFallbackApplied } =
90
- resolveAllowlistProviderRuntimeGroupPolicy({
91
- providerConfigPresent:
92
- ((config.channels as Record<string, unknown> | undefined)?.["nextcloud-talk"] ??
93
- undefined) !== undefined,
94
- groupPolicy: account.config.groupPolicy,
95
- defaultGroupPolicy,
96
- });
97
- warnMissingProviderGroupPolicyFallbackOnce({
98
- providerMissingFallbackApplied,
99
- providerKey: "nextcloud-talk",
100
- accountId: account.accountId,
101
- blockedLabel: GROUP_POLICY_BLOCKED_LABEL.room,
102
- log: (message) => runtime.log?.(message),
103
- });
104
-
105
- const configAllowFrom = normalizeNextcloudTalkAllowlist(account.config.allowFrom);
106
- const configGroupAllowFrom = normalizeNextcloudTalkAllowlist(account.config.groupAllowFrom);
107
- const storeAllowFrom = await readStoreAllowFromForDmPolicy({
108
- provider: CHANNEL_ID,
109
- accountId: account.accountId,
110
- dmPolicy,
111
- readStore: pairing.readStoreForDmPolicy,
112
- });
113
- const storeAllowList = normalizeNextcloudTalkAllowlist(storeAllowFrom);
114
-
115
- const roomMatch = resolveNextcloudTalkRoomMatch({
116
- rooms: account.config.rooms,
117
- roomToken,
118
- });
119
- const roomConfig = roomMatch.roomConfig;
120
- if (isGroup && !roomMatch.allowed) {
121
- runtime.log?.(`nextcloud-talk: drop room ${roomToken} (not allowlisted)`);
122
- return;
123
- }
124
- if (roomConfig?.enabled === false) {
125
- runtime.log?.(`nextcloud-talk: drop room ${roomToken} (disabled)`);
126
- return;
127
- }
128
-
129
- const roomAllowFrom = normalizeNextcloudTalkAllowlist(roomConfig?.allowFrom);
130
-
131
- const allowTextCommands = core.channel.commands.shouldHandleTextCommands({
132
- cfg: config as OpenClawConfig,
133
- surface: CHANNEL_ID,
134
- });
135
- const useAccessGroups =
136
- (config.commands as Record<string, unknown> | undefined)?.useAccessGroups !== false;
137
- const hasControlCommand = core.channel.text.hasControlCommand(rawBody, config as OpenClawConfig);
138
- const access = resolveDmGroupAccessWithCommandGate({
139
- isGroup,
140
- dmPolicy,
141
- groupPolicy,
142
- allowFrom: configAllowFrom,
143
- groupAllowFrom: configGroupAllowFrom,
144
- storeAllowFrom: storeAllowList,
145
- isSenderAllowed: (allowFrom) =>
146
- resolveNextcloudTalkAllowlistMatch({
147
- allowFrom,
148
- senderId,
149
- }).allowed,
150
- command: {
151
- useAccessGroups,
152
- allowTextCommands,
153
- hasControlCommand,
154
- },
155
- });
156
- const commandAuthorized = access.commandAuthorized;
157
- const effectiveGroupAllowFrom = access.effectiveGroupAllowFrom;
158
-
159
- if (isGroup) {
160
- if (access.decision !== "allow") {
161
- runtime.log?.(`nextcloud-talk: drop group sender ${senderId} (reason=${access.reason})`);
162
- return;
163
- }
164
- const groupAllow = resolveNextcloudTalkGroupAllow({
165
- groupPolicy,
166
- outerAllowFrom: effectiveGroupAllowFrom,
167
- innerAllowFrom: roomAllowFrom,
168
- senderId,
169
- });
170
- if (!groupAllow.allowed) {
171
- runtime.log?.(`nextcloud-talk: drop group sender ${senderId} (policy=${groupPolicy})`);
172
- return;
173
- }
174
- } else {
175
- if (access.decision !== "allow") {
176
- if (access.decision === "pairing") {
177
- await pairing.issueChallenge({
178
- senderId,
179
- senderIdLine: `Your Nextcloud user id: ${senderId}`,
180
- meta: { name: senderName || undefined },
181
- sendPairingReply: async (text) => {
182
- await sendMessageNextcloudTalk(roomToken, text, {
183
- cfg: config,
184
- accountId: account.accountId,
185
- });
186
- statusSink?.({ lastOutboundAt: Date.now() });
187
- },
188
- onReplyError: (err) => {
189
- runtime.error?.(`nextcloud-talk: pairing reply failed for ${senderId}: ${String(err)}`);
190
- },
191
- });
192
- }
193
- runtime.log?.(`nextcloud-talk: drop DM sender ${senderId} (reason=${access.reason})`);
194
- return;
195
- }
196
- }
197
-
198
- if (access.shouldBlockControlCommand) {
199
- logInboundDrop({
200
- log: (message) => runtime.log?.(message),
201
- channel: CHANNEL_ID,
202
- reason: "control command (unauthorized)",
203
- target: senderId,
204
- });
205
- return;
206
- }
207
-
208
- const mentionRegexes = core.channel.mentions.buildMentionRegexes(config as OpenClawConfig);
209
- const wasMentioned = mentionRegexes.length
210
- ? core.channel.mentions.matchesMentionPatterns(rawBody, mentionRegexes)
211
- : false;
212
- const shouldRequireMention = isGroup
213
- ? resolveNextcloudTalkRequireMention({
214
- roomConfig,
215
- wildcardConfig: roomMatch.wildcardConfig,
216
- })
217
- : false;
218
- const mentionGate = resolveNextcloudTalkMentionGate({
219
- isGroup,
220
- requireMention: shouldRequireMention,
221
- wasMentioned,
222
- allowTextCommands,
223
- hasControlCommand,
224
- commandAuthorized,
225
- });
226
- if (isGroup && mentionGate.shouldSkip) {
227
- runtime.log?.(`nextcloud-talk: drop room ${roomToken} (no mention)`);
228
- return;
229
- }
230
-
231
- const route = core.channel.routing.resolveAgentRoute({
232
- cfg: config as OpenClawConfig,
233
- channel: CHANNEL_ID,
234
- accountId: account.accountId,
235
- peer: {
236
- kind: isGroup ? "group" : "direct",
237
- id: isGroup ? roomToken : senderId,
238
- },
239
- });
240
-
241
- const fromLabel = isGroup ? `room:${roomName || roomToken}` : senderName || `user:${senderId}`;
242
- const storePath = core.channel.session.resolveStorePath(
243
- (config.session as Record<string, unknown> | undefined)?.store as string | undefined,
244
- {
245
- agentId: route.agentId,
246
- },
247
- );
248
- const envelopeOptions = core.channel.reply.resolveEnvelopeFormatOptions(config as OpenClawConfig);
249
- const previousTimestamp = core.channel.session.readSessionUpdatedAt({
250
- storePath,
251
- sessionKey: route.sessionKey,
252
- });
253
- const body = core.channel.reply.formatAgentEnvelope({
254
- channel: "Nextcloud Talk",
255
- from: fromLabel,
256
- timestamp: message.timestamp,
257
- previousTimestamp,
258
- envelope: envelopeOptions,
259
- body: rawBody,
260
- });
261
-
262
- const groupSystemPrompt = normalizeOptionalString(roomConfig?.systemPrompt);
263
-
264
- const ctxPayload = core.channel.reply.finalizeInboundContext({
265
- Body: body,
266
- BodyForAgent: rawBody,
267
- RawBody: rawBody,
268
- CommandBody: rawBody,
269
- From: isGroup ? `nextcloud-talk:room:${roomToken}` : `nextcloud-talk:${senderId}`,
270
- To: `nextcloud-talk:${roomToken}`,
271
- SessionKey: route.sessionKey,
272
- AccountId: route.accountId,
273
- ChatType: isGroup ? "group" : "direct",
274
- ConversationLabel: fromLabel,
275
- SenderName: senderName || undefined,
276
- SenderId: senderId,
277
- GroupSubject: isGroup ? roomName || roomToken : undefined,
278
- GroupSystemPrompt: isGroup ? groupSystemPrompt : undefined,
279
- Provider: CHANNEL_ID,
280
- Surface: CHANNEL_ID,
281
- WasMentioned: isGroup ? wasMentioned : undefined,
282
- MessageSid: message.messageId,
283
- Timestamp: message.timestamp,
284
- OriginatingChannel: CHANNEL_ID,
285
- OriginatingTo: `nextcloud-talk:${roomToken}`,
286
- CommandAuthorized: commandAuthorized,
287
- });
288
-
289
- await dispatchInboundReplyWithBase({
290
- cfg: config as OpenClawConfig,
291
- channel: CHANNEL_ID,
292
- accountId: account.accountId,
293
- route,
294
- storePath,
295
- ctxPayload,
296
- core,
297
- deliver: async (payload) => {
298
- await deliverNextcloudTalkReply({
299
- cfg: config,
300
- payload,
301
- roomToken,
302
- accountId: account.accountId,
303
- statusSink,
304
- });
305
- },
306
- onRecordError: (err) => {
307
- runtime.error?.(`nextcloud-talk: failed updating session meta: ${String(err)}`);
308
- },
309
- onDispatchError: (err, info) => {
310
- runtime.error?.(`nextcloud-talk ${info.kind} reply failed: ${String(err)}`);
311
- },
312
- replyOptions: {
313
- skillFilter: roomConfig?.skills,
314
- disableBlockStreaming:
315
- typeof account.config.blockStreaming === "boolean"
316
- ? !account.config.blockStreaming
317
- : undefined,
318
- },
319
- });
320
- }