@actagent/irc 2026.6.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.
- package/actagent.plugin.json +26 -0
- package/api.ts +11 -0
- package/channel-config-api.ts +2 -0
- package/channel-plugin-api.ts +3 -0
- package/configured-state.ts +9 -0
- package/contract-api.ts +5 -0
- package/index.test.ts +14 -0
- package/index.ts +21 -0
- package/package.json +44 -0
- package/runtime-api.test.ts +24 -0
- package/runtime-api.ts +3 -0
- package/secret-contract-api.ts +6 -0
- package/setup-entry.ts +14 -0
- package/src/accounts.test.ts +224 -0
- package/src/accounts.ts +240 -0
- package/src/channel-api.ts +7 -0
- package/src/channel-runtime.ts +4 -0
- package/src/channel.test.ts +17 -0
- package/src/channel.ts +367 -0
- package/src/client.test.ts +44 -0
- package/src/client.ts +443 -0
- package/src/config-schema.test.ts +117 -0
- package/src/config-schema.ts +97 -0
- package/src/config-ui-hints.ts +41 -0
- package/src/connect-options.test.ts +48 -0
- package/src/connect-options.ts +31 -0
- package/src/control-chars.test.ts +18 -0
- package/src/control-chars.ts +23 -0
- package/src/doctor.ts +55 -0
- package/src/gateway.ts +54 -0
- package/src/inbound.behavior.test.ts +247 -0
- package/src/inbound.ts +440 -0
- package/src/message-adapter.ts +29 -0
- package/src/monitor.test.ts +44 -0
- package/src/monitor.ts +150 -0
- package/src/normalize.test.ts +56 -0
- package/src/normalize.ts +111 -0
- package/src/outbound-base.ts +11 -0
- package/src/policy.test.ts +56 -0
- package/src/policy.ts +79 -0
- package/src/probe.test.ts +111 -0
- package/src/probe.ts +54 -0
- package/src/protocol.test.ts +49 -0
- package/src/protocol.ts +170 -0
- package/src/runtime-api.ts +42 -0
- package/src/runtime.ts +16 -0
- package/src/secret-contract.ts +104 -0
- package/src/send.test.ts +327 -0
- package/src/send.ts +122 -0
- package/src/setup-core.ts +152 -0
- package/src/setup-surface.ts +451 -0
- package/src/setup.test.ts +487 -0
- package/src/types.ts +101 -0
- package/tsconfig.json +16 -0
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// Irc tests cover connect options plugin behavior.
|
|
2
|
+
import { describe, expect, it } from "vitest";
|
|
3
|
+
import { buildIrcConnectOptions } from "./connect-options.js";
|
|
4
|
+
|
|
5
|
+
describe("buildIrcConnectOptions", () => {
|
|
6
|
+
it("copies resolved account connection fields and NickServ config", () => {
|
|
7
|
+
const account = {
|
|
8
|
+
host: "irc.libera.chat",
|
|
9
|
+
port: 6697,
|
|
10
|
+
tls: true,
|
|
11
|
+
nick: "actagent",
|
|
12
|
+
username: "actagent",
|
|
13
|
+
realname: "ACTAgent Bot",
|
|
14
|
+
password: "server-pass",
|
|
15
|
+
config: {
|
|
16
|
+
nickserv: {
|
|
17
|
+
enabled: true,
|
|
18
|
+
service: "NickServ",
|
|
19
|
+
password: "nickserv-pass",
|
|
20
|
+
register: true,
|
|
21
|
+
registerEmail: "bot@example.com",
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
expect(
|
|
27
|
+
buildIrcConnectOptions(account as never, {
|
|
28
|
+
connectTimeoutMs: 1234,
|
|
29
|
+
}),
|
|
30
|
+
).toEqual({
|
|
31
|
+
host: "irc.libera.chat",
|
|
32
|
+
port: 6697,
|
|
33
|
+
tls: true,
|
|
34
|
+
nick: "actagent",
|
|
35
|
+
username: "actagent",
|
|
36
|
+
realname: "ACTAgent Bot",
|
|
37
|
+
password: "server-pass",
|
|
38
|
+
nickserv: {
|
|
39
|
+
enabled: true,
|
|
40
|
+
service: "NickServ",
|
|
41
|
+
password: "nickserv-pass",
|
|
42
|
+
register: true,
|
|
43
|
+
registerEmail: "bot@example.com",
|
|
44
|
+
},
|
|
45
|
+
connectTimeoutMs: 1234,
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
});
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// Irc plugin module implements connect options behavior.
|
|
2
|
+
import type { ResolvedIrcAccount } from "./accounts.js";
|
|
3
|
+
import type { IrcClientOptions } from "./client.js";
|
|
4
|
+
|
|
5
|
+
type IrcConnectOverrides = Omit<
|
|
6
|
+
Partial<IrcClientOptions>,
|
|
7
|
+
"host" | "port" | "tls" | "nick" | "username" | "realname" | "password" | "nickserv"
|
|
8
|
+
>;
|
|
9
|
+
|
|
10
|
+
export function buildIrcConnectOptions(
|
|
11
|
+
account: ResolvedIrcAccount,
|
|
12
|
+
overrides: IrcConnectOverrides = {},
|
|
13
|
+
): IrcClientOptions {
|
|
14
|
+
return {
|
|
15
|
+
host: account.host,
|
|
16
|
+
port: account.port,
|
|
17
|
+
tls: account.tls,
|
|
18
|
+
nick: account.nick,
|
|
19
|
+
username: account.username,
|
|
20
|
+
realname: account.realname,
|
|
21
|
+
password: account.password,
|
|
22
|
+
nickserv: {
|
|
23
|
+
enabled: account.config.nickserv?.enabled,
|
|
24
|
+
service: account.config.nickserv?.service,
|
|
25
|
+
password: account.config.nickserv?.password,
|
|
26
|
+
register: account.config.nickserv?.register,
|
|
27
|
+
registerEmail: account.config.nickserv?.registerEmail,
|
|
28
|
+
},
|
|
29
|
+
...overrides,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// Irc tests cover control chars plugin behavior.
|
|
2
|
+
import { describe, expect, it } from "vitest";
|
|
3
|
+
import { hasIrcControlChars, isIrcControlChar, stripIrcControlChars } from "./control-chars.js";
|
|
4
|
+
|
|
5
|
+
describe("irc control char helpers", () => {
|
|
6
|
+
it("detects IRC control characters by codepoint", () => {
|
|
7
|
+
expect(isIrcControlChar(0x00)).toBe(true);
|
|
8
|
+
expect(isIrcControlChar(0x1f)).toBe(true);
|
|
9
|
+
expect(isIrcControlChar(0x7f)).toBe(true);
|
|
10
|
+
expect(isIrcControlChar(0x20)).toBe(false);
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it("detects and strips IRC control characters from strings", () => {
|
|
14
|
+
expect(hasIrcControlChars("hello\u0002world")).toBe(true);
|
|
15
|
+
expect(hasIrcControlChars("hello world")).toBe(false);
|
|
16
|
+
expect(stripIrcControlChars("he\u0002llo\u007f world")).toBe("hello world");
|
|
17
|
+
});
|
|
18
|
+
});
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// Irc plugin module implements control chars behavior.
|
|
2
|
+
export function isIrcControlChar(charCode: number): boolean {
|
|
3
|
+
return charCode <= 0x1f || charCode === 0x7f;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export function hasIrcControlChars(value: string): boolean {
|
|
7
|
+
for (const char of value) {
|
|
8
|
+
if (isIrcControlChar(char.charCodeAt(0))) {
|
|
9
|
+
return true;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function stripIrcControlChars(value: string): string {
|
|
16
|
+
let out = "";
|
|
17
|
+
for (const char of value) {
|
|
18
|
+
if (!isIrcControlChar(char.charCodeAt(0))) {
|
|
19
|
+
out += char;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return out;
|
|
23
|
+
}
|
package/src/doctor.ts
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// Irc plugin module implements doctor behavior.
|
|
2
|
+
import { createDangerousNameMatchingMutableAllowlistWarningCollector } from "actagent/plugin-sdk/channel-policy";
|
|
3
|
+
import { normalizeLowercaseStringOrEmpty } from "actagent/plugin-sdk/string-coerce-runtime";
|
|
4
|
+
|
|
5
|
+
function asObjectRecord(value: unknown): Record<string, unknown> | null {
|
|
6
|
+
return value && typeof value === "object" && !Array.isArray(value)
|
|
7
|
+
? (value as Record<string, unknown>)
|
|
8
|
+
: null;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function isIrcMutableAllowEntry(raw: string): boolean {
|
|
12
|
+
const text = normalizeLowercaseStringOrEmpty(raw);
|
|
13
|
+
if (!text || text === "*") {
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const normalized = text
|
|
18
|
+
.replace(/^irc:/, "")
|
|
19
|
+
.replace(/^user:/, "")
|
|
20
|
+
.trim();
|
|
21
|
+
|
|
22
|
+
return !normalized.includes("!") && !normalized.includes("@");
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export const collectIrcMutableAllowlistWarnings =
|
|
26
|
+
createDangerousNameMatchingMutableAllowlistWarningCollector({
|
|
27
|
+
channel: "irc",
|
|
28
|
+
detector: isIrcMutableAllowEntry,
|
|
29
|
+
collectLists: (scope) => {
|
|
30
|
+
const lists = [
|
|
31
|
+
{
|
|
32
|
+
pathLabel: `${scope.prefix}.allowFrom`,
|
|
33
|
+
list: scope.account.allowFrom,
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
pathLabel: `${scope.prefix}.groupAllowFrom`,
|
|
37
|
+
list: scope.account.groupAllowFrom,
|
|
38
|
+
},
|
|
39
|
+
];
|
|
40
|
+
const groups = asObjectRecord(scope.account.groups);
|
|
41
|
+
if (groups) {
|
|
42
|
+
for (const [groupKey, groupRaw] of Object.entries(groups)) {
|
|
43
|
+
const group = asObjectRecord(groupRaw);
|
|
44
|
+
if (!group) {
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
lists.push({
|
|
48
|
+
pathLabel: `${scope.prefix}.groups.${groupKey}.allowFrom`,
|
|
49
|
+
list: group.allowFrom,
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return lists;
|
|
54
|
+
},
|
|
55
|
+
});
|
package/src/gateway.ts
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// Irc plugin module implements gateway behavior.
|
|
2
|
+
import { runStoppablePassiveMonitor } from "actagent/plugin-sdk/extension-shared";
|
|
3
|
+
import type { ChannelAccountSnapshot } from "actagent/plugin-sdk/status-helpers";
|
|
4
|
+
import type { ResolvedIrcAccount } from "./accounts.js";
|
|
5
|
+
import { createAccountStatusSink } from "./channel-api.js";
|
|
6
|
+
import type { RuntimeEnv } from "./runtime-api.js";
|
|
7
|
+
import type { CoreConfig } from "./types.js";
|
|
8
|
+
|
|
9
|
+
type IrcChannelRuntimeModule = typeof import("./channel-runtime.js");
|
|
10
|
+
|
|
11
|
+
let ircChannelRuntimePromise: Promise<IrcChannelRuntimeModule> | undefined;
|
|
12
|
+
|
|
13
|
+
async function loadIrcChannelRuntime(): Promise<IrcChannelRuntimeModule> {
|
|
14
|
+
ircChannelRuntimePromise ??= import("./channel-runtime.js");
|
|
15
|
+
return await ircChannelRuntimePromise;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export async function startIrcGatewayAccount(ctx: {
|
|
19
|
+
cfg: CoreConfig;
|
|
20
|
+
accountId: string;
|
|
21
|
+
account: ResolvedIrcAccount;
|
|
22
|
+
runtime: RuntimeEnv;
|
|
23
|
+
abortSignal: AbortSignal;
|
|
24
|
+
setStatus: (next: ChannelAccountSnapshot) => void;
|
|
25
|
+
log?: {
|
|
26
|
+
info?: (message: string) => void;
|
|
27
|
+
};
|
|
28
|
+
}): Promise<void> {
|
|
29
|
+
const account = ctx.account;
|
|
30
|
+
const statusSink = createAccountStatusSink({
|
|
31
|
+
accountId: ctx.accountId,
|
|
32
|
+
setStatus: ctx.setStatus,
|
|
33
|
+
});
|
|
34
|
+
if (!account.configured) {
|
|
35
|
+
throw new Error(
|
|
36
|
+
`IRC is not configured for account "${account.accountId}" (need host and nick in channels.irc).`,
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
ctx.log?.info?.(
|
|
40
|
+
`[${account.accountId}] starting IRC provider (${account.host}:${account.port}${account.tls ? " tls" : ""})`,
|
|
41
|
+
);
|
|
42
|
+
const { monitorIrcProvider } = await loadIrcChannelRuntime();
|
|
43
|
+
await runStoppablePassiveMonitor({
|
|
44
|
+
abortSignal: ctx.abortSignal,
|
|
45
|
+
start: async () =>
|
|
46
|
+
await monitorIrcProvider({
|
|
47
|
+
accountId: account.accountId,
|
|
48
|
+
config: ctx.cfg,
|
|
49
|
+
runtime: ctx.runtime,
|
|
50
|
+
abortSignal: ctx.abortSignal,
|
|
51
|
+
statusSink,
|
|
52
|
+
}),
|
|
53
|
+
});
|
|
54
|
+
}
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
// Irc tests cover inbound.behavior plugin behavior.
|
|
2
|
+
import { createPluginRuntimeMock } from "actagent/plugin-sdk/channel-test-helpers";
|
|
3
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
4
|
+
import type { ResolvedIrcAccount } from "./accounts.js";
|
|
5
|
+
import { handleIrcInbound } from "./inbound.js";
|
|
6
|
+
import type { RuntimeEnv } from "./runtime-api.js";
|
|
7
|
+
import { clearIrcRuntime, setIrcRuntime } from "./runtime.js";
|
|
8
|
+
import type { CoreConfig, IrcInboundMessage } from "./types.js";
|
|
9
|
+
|
|
10
|
+
const {
|
|
11
|
+
buildMentionRegexesMock,
|
|
12
|
+
hasControlCommandMock,
|
|
13
|
+
matchesMentionPatternsMock,
|
|
14
|
+
readAllowFromStoreMock,
|
|
15
|
+
shouldHandleTextCommandsMock,
|
|
16
|
+
upsertPairingRequestMock,
|
|
17
|
+
} = vi.hoisted(() => {
|
|
18
|
+
return {
|
|
19
|
+
buildMentionRegexesMock: vi.fn(() => []),
|
|
20
|
+
hasControlCommandMock: vi.fn(() => false),
|
|
21
|
+
matchesMentionPatternsMock: vi.fn(() => false),
|
|
22
|
+
readAllowFromStoreMock: vi.fn(async () => []),
|
|
23
|
+
shouldHandleTextCommandsMock: vi.fn(() => false),
|
|
24
|
+
upsertPairingRequestMock: vi.fn(async () => ({ code: "CODE", created: true })),
|
|
25
|
+
};
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
function installIrcRuntime() {
|
|
29
|
+
setIrcRuntime({
|
|
30
|
+
channel: {
|
|
31
|
+
pairing: {
|
|
32
|
+
readAllowFromStore: readAllowFromStoreMock,
|
|
33
|
+
upsertPairingRequest: upsertPairingRequestMock,
|
|
34
|
+
},
|
|
35
|
+
commands: {
|
|
36
|
+
shouldHandleTextCommands: shouldHandleTextCommandsMock,
|
|
37
|
+
},
|
|
38
|
+
text: {
|
|
39
|
+
hasControlCommand: hasControlCommandMock,
|
|
40
|
+
},
|
|
41
|
+
mentions: {
|
|
42
|
+
buildMentionRegexes: buildMentionRegexesMock,
|
|
43
|
+
matchesMentionPatterns: matchesMentionPatternsMock,
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
} as never);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function createRuntimeEnv() {
|
|
50
|
+
return {
|
|
51
|
+
log: vi.fn(),
|
|
52
|
+
error: vi.fn(),
|
|
53
|
+
} as unknown as RuntimeEnv;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function createAccount(overrides?: Partial<ResolvedIrcAccount>): ResolvedIrcAccount {
|
|
57
|
+
return {
|
|
58
|
+
accountId: "default",
|
|
59
|
+
enabled: true,
|
|
60
|
+
server: "irc.example.com",
|
|
61
|
+
nick: "ACTAgent",
|
|
62
|
+
config: {
|
|
63
|
+
dmPolicy: "pairing",
|
|
64
|
+
allowFrom: [],
|
|
65
|
+
groupPolicy: "allowlist",
|
|
66
|
+
groupAllowFrom: [],
|
|
67
|
+
},
|
|
68
|
+
...overrides,
|
|
69
|
+
} as ResolvedIrcAccount;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function createMessage(overrides?: Partial<IrcInboundMessage>): IrcInboundMessage {
|
|
73
|
+
return {
|
|
74
|
+
messageId: "msg-1",
|
|
75
|
+
target: "alice",
|
|
76
|
+
senderNick: "alice",
|
|
77
|
+
senderUser: "ident",
|
|
78
|
+
senderHost: "example.com",
|
|
79
|
+
text: "hello",
|
|
80
|
+
timestamp: Date.now(),
|
|
81
|
+
isGroup: false,
|
|
82
|
+
...overrides,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function resetInboundMocks() {
|
|
87
|
+
buildMentionRegexesMock.mockReset().mockReturnValue([]);
|
|
88
|
+
hasControlCommandMock.mockReset().mockReturnValue(false);
|
|
89
|
+
matchesMentionPatternsMock.mockReset().mockReturnValue(false);
|
|
90
|
+
readAllowFromStoreMock.mockReset().mockResolvedValue([]);
|
|
91
|
+
shouldHandleTextCommandsMock.mockReset().mockReturnValue(false);
|
|
92
|
+
upsertPairingRequestMock.mockReset().mockResolvedValue({ code: "CODE", created: true });
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
describe("irc inbound behavior", () => {
|
|
96
|
+
beforeEach(() => {
|
|
97
|
+
resetInboundMocks();
|
|
98
|
+
installIrcRuntime();
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
afterEach(() => {
|
|
102
|
+
clearIrcRuntime();
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it("issues a DM pairing challenge and sends the reply to the sender nick", async () => {
|
|
106
|
+
const sendReply = vi.fn<(target: string, text: string, replyToId?: string) => Promise<void>>(
|
|
107
|
+
async () => {},
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
await handleIrcInbound({
|
|
111
|
+
message: createMessage(),
|
|
112
|
+
account: createAccount(),
|
|
113
|
+
config: { channels: { irc: {} } } as CoreConfig,
|
|
114
|
+
runtime: createRuntimeEnv(),
|
|
115
|
+
sendReply,
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
expect(upsertPairingRequestMock).toHaveBeenCalledWith({
|
|
119
|
+
channel: "irc",
|
|
120
|
+
accountId: "default",
|
|
121
|
+
id: "alice!ident@example.com",
|
|
122
|
+
meta: { name: "alice" },
|
|
123
|
+
});
|
|
124
|
+
expect(sendReply).toHaveBeenCalledTimes(1);
|
|
125
|
+
expect(sendReply).toHaveBeenCalledWith(
|
|
126
|
+
"alice",
|
|
127
|
+
[
|
|
128
|
+
"ACTAgent: access not configured.",
|
|
129
|
+
"",
|
|
130
|
+
"Your IRC id: alice!ident@example.com",
|
|
131
|
+
"Pairing code:",
|
|
132
|
+
"```",
|
|
133
|
+
"CODE",
|
|
134
|
+
"```",
|
|
135
|
+
"",
|
|
136
|
+
"Ask the bot owner to approve with:",
|
|
137
|
+
"```",
|
|
138
|
+
"actagent pairing approve irc CODE",
|
|
139
|
+
"```",
|
|
140
|
+
].join("\n"),
|
|
141
|
+
undefined,
|
|
142
|
+
);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it("drops unauthorized group control commands before dispatch", async () => {
|
|
146
|
+
const runtime = createRuntimeEnv();
|
|
147
|
+
shouldHandleTextCommandsMock.mockReturnValue(true);
|
|
148
|
+
hasControlCommandMock.mockReturnValue(true);
|
|
149
|
+
|
|
150
|
+
await handleIrcInbound({
|
|
151
|
+
message: createMessage({
|
|
152
|
+
target: "#ops",
|
|
153
|
+
isGroup: true,
|
|
154
|
+
text: "/admin",
|
|
155
|
+
}),
|
|
156
|
+
account: createAccount({
|
|
157
|
+
config: {
|
|
158
|
+
dmPolicy: "pairing",
|
|
159
|
+
allowFrom: [],
|
|
160
|
+
groupPolicy: "allowlist",
|
|
161
|
+
groupAllowFrom: ["bob!ident@example.com"],
|
|
162
|
+
groups: {
|
|
163
|
+
"#ops": {
|
|
164
|
+
allowFrom: ["alice!ident@example.com"],
|
|
165
|
+
},
|
|
166
|
+
},
|
|
167
|
+
},
|
|
168
|
+
}),
|
|
169
|
+
config: { channels: { irc: {} }, commands: { useAccessGroups: true } } as CoreConfig,
|
|
170
|
+
runtime,
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
expect(runtime.log).toHaveBeenCalledWith(
|
|
174
|
+
"irc: drop control command (unauthorized) target=alice!ident@example.com",
|
|
175
|
+
);
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
it("passes the shared reply pipeline for dispatched replies", async () => {
|
|
179
|
+
const coreRuntime = createPluginRuntimeMock();
|
|
180
|
+
setIrcRuntime(coreRuntime as never);
|
|
181
|
+
|
|
182
|
+
await handleIrcInbound({
|
|
183
|
+
message: createMessage(),
|
|
184
|
+
account: createAccount({
|
|
185
|
+
config: {
|
|
186
|
+
dmPolicy: "open",
|
|
187
|
+
allowFrom: ["*"],
|
|
188
|
+
groupPolicy: "allowlist",
|
|
189
|
+
groupAllowFrom: [],
|
|
190
|
+
},
|
|
191
|
+
}),
|
|
192
|
+
config: { channels: { irc: {} } } as CoreConfig,
|
|
193
|
+
runtime: createRuntimeEnv(),
|
|
194
|
+
sendReply: vi.fn(async () => {}),
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
const assembledRequest = (
|
|
198
|
+
coreRuntime.channel.inbound.dispatchReply as unknown as { mock: { calls: unknown[][] } }
|
|
199
|
+
).mock.calls[0]?.[0] as { replyPipeline?: unknown } | undefined;
|
|
200
|
+
expect(assembledRequest?.replyPipeline).toEqual({});
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
it("uses channel:# prefix for group channel From and OriginatingTo fields", async () => {
|
|
204
|
+
const coreRuntime = createPluginRuntimeMock();
|
|
205
|
+
const runtime = createRuntimeEnv();
|
|
206
|
+
setIrcRuntime(coreRuntime as never);
|
|
207
|
+
|
|
208
|
+
await handleIrcInbound({
|
|
209
|
+
message: createMessage({
|
|
210
|
+
target: "#ops",
|
|
211
|
+
isGroup: true,
|
|
212
|
+
senderNick: "alice",
|
|
213
|
+
senderUser: "ident",
|
|
214
|
+
senderHost: "example.com",
|
|
215
|
+
text: "hello",
|
|
216
|
+
}),
|
|
217
|
+
account: createAccount({
|
|
218
|
+
config: {
|
|
219
|
+
dmPolicy: "open",
|
|
220
|
+
allowFrom: ["*"],
|
|
221
|
+
groupPolicy: "open",
|
|
222
|
+
groupAllowFrom: [],
|
|
223
|
+
groups: {
|
|
224
|
+
"#ops": { enabled: true, requireMention: false },
|
|
225
|
+
},
|
|
226
|
+
},
|
|
227
|
+
}),
|
|
228
|
+
config: { channels: { irc: {} } } as CoreConfig,
|
|
229
|
+
runtime,
|
|
230
|
+
sendReply: vi.fn(async () => {}),
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
const ctx = (
|
|
234
|
+
coreRuntime.channel.reply.finalizeInboundContext as unknown as {
|
|
235
|
+
mock: { calls: unknown[][] };
|
|
236
|
+
}
|
|
237
|
+
).mock.calls[0]?.[0] as Record<string, unknown> | undefined;
|
|
238
|
+
expect(
|
|
239
|
+
(coreRuntime.channel.inbound.dispatchReply as unknown as { mock: { calls: unknown[][] } })
|
|
240
|
+
.mock.calls.length,
|
|
241
|
+
).toBe(1);
|
|
242
|
+
expect(runtime.log).not.toHaveBeenCalled();
|
|
243
|
+
expect(ctx?.From).toBe("channel:#ops");
|
|
244
|
+
expect(ctx?.To).toBe("channel:#ops");
|
|
245
|
+
expect(ctx?.OriginatingTo).toBe("channel:#ops");
|
|
246
|
+
});
|
|
247
|
+
});
|