@tjamescouch/agentchat 0.25.0 → 0.26.0
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/README.md +53 -4
- package/dist/lib/callback-engine.d.ts +66 -0
- package/dist/lib/callback-engine.d.ts.map +1 -0
- package/dist/lib/callback-engine.js +199 -0
- package/dist/lib/callback-engine.js.map +1 -0
- package/dist/lib/client.d.ts +3 -1
- package/dist/lib/client.d.ts.map +1 -1
- package/dist/lib/client.js +3 -2
- package/dist/lib/client.js.map +1 -1
- package/dist/lib/escalation.d.ts +99 -0
- package/dist/lib/escalation.d.ts.map +1 -0
- package/dist/lib/escalation.js +286 -0
- package/dist/lib/escalation.js.map +1 -0
- package/dist/lib/escalation.test.d.ts +8 -0
- package/dist/lib/escalation.test.d.ts.map +1 -0
- package/dist/lib/escalation.test.js +348 -0
- package/dist/lib/escalation.test.js.map +1 -0
- package/dist/lib/floor-control.d.ts +77 -0
- package/dist/lib/floor-control.d.ts.map +1 -0
- package/dist/lib/floor-control.js +166 -0
- package/dist/lib/floor-control.js.map +1 -0
- package/dist/lib/moderation-plugins/escalation-plugin.d.ts +31 -0
- package/dist/lib/moderation-plugins/escalation-plugin.d.ts.map +1 -0
- package/dist/lib/moderation-plugins/escalation-plugin.js +97 -0
- package/dist/lib/moderation-plugins/escalation-plugin.js.map +1 -0
- package/dist/lib/moderation-plugins/link-detector-plugin.d.ts +29 -0
- package/dist/lib/moderation-plugins/link-detector-plugin.d.ts.map +1 -0
- package/dist/lib/moderation-plugins/link-detector-plugin.js +61 -0
- package/dist/lib/moderation-plugins/link-detector-plugin.js.map +1 -0
- package/dist/lib/moderation.d.ts +142 -0
- package/dist/lib/moderation.d.ts.map +1 -0
- package/dist/lib/moderation.js +192 -0
- package/dist/lib/moderation.js.map +1 -0
- package/dist/lib/moderation.test.d.ts +7 -0
- package/dist/lib/moderation.test.d.ts.map +1 -0
- package/dist/lib/moderation.test.js +275 -0
- package/dist/lib/moderation.test.js.map +1 -0
- package/dist/lib/protocol.d.ts +5 -0
- package/dist/lib/protocol.d.ts.map +1 -1
- package/dist/lib/protocol.js +18 -2
- package/dist/lib/protocol.js.map +1 -1
- package/dist/lib/security.d.ts.map +1 -1
- package/dist/lib/security.js +13 -4
- package/dist/lib/security.js.map +1 -1
- package/dist/lib/server/handlers/message.d.ts.map +1 -1
- package/dist/lib/server/handlers/message.js +22 -2
- package/dist/lib/server/handlers/message.js.map +1 -1
- package/dist/lib/server.d.ts +8 -0
- package/dist/lib/server.d.ts.map +1 -1
- package/dist/lib/server.js +115 -2
- package/dist/lib/server.js.map +1 -1
- package/dist/lib/types.d.ts +13 -1
- package/dist/lib/types.d.ts.map +1 -1
- package/dist/lib/types.js +5 -0
- package/dist/lib/types.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Moderation Plugin Architecture
|
|
3
|
+
*
|
|
4
|
+
* Chain-of-responsibility pattern for message moderation.
|
|
5
|
+
* Plugins are checked in order; strictest action wins.
|
|
6
|
+
* Supports per-channel configuration and admin bypass.
|
|
7
|
+
*/
|
|
8
|
+
/** Moderation actions in order of severity */
|
|
9
|
+
export declare enum ModerationActionType {
|
|
10
|
+
ALLOW = "allow",
|
|
11
|
+
WARN = "warn",
|
|
12
|
+
THROTTLE = "throttle",
|
|
13
|
+
BLOCK = "block",// silently drop the message
|
|
14
|
+
TIMEOUT = "timeout",// disconnect temporarily
|
|
15
|
+
KICK = "kick"
|
|
16
|
+
}
|
|
17
|
+
/** Event passed to moderation plugins */
|
|
18
|
+
export interface ModerationEvent {
|
|
19
|
+
/** Agent ID (if identified) */
|
|
20
|
+
agentId?: string;
|
|
21
|
+
/** Agent display name */
|
|
22
|
+
agentName?: string;
|
|
23
|
+
/** Connection IP */
|
|
24
|
+
ip?: string;
|
|
25
|
+
/** Channel the message targets (if channel message) */
|
|
26
|
+
channel?: string;
|
|
27
|
+
/** Message content */
|
|
28
|
+
content?: string;
|
|
29
|
+
/** Message type (MSG, JOIN, etc.) */
|
|
30
|
+
messageType: string;
|
|
31
|
+
/** Whether this agent has a verified persistent identity */
|
|
32
|
+
verified?: boolean;
|
|
33
|
+
/** Whether this agent has admin privileges */
|
|
34
|
+
isAdmin?: boolean;
|
|
35
|
+
/** Connection age in ms */
|
|
36
|
+
connectionAgeMs?: number;
|
|
37
|
+
/** Timestamp */
|
|
38
|
+
timestamp: number;
|
|
39
|
+
}
|
|
40
|
+
/** Action returned by a moderation plugin */
|
|
41
|
+
export interface ModerationAction {
|
|
42
|
+
type: ModerationActionType;
|
|
43
|
+
/** Human-readable reason */
|
|
44
|
+
reason: string;
|
|
45
|
+
/** Plugin that generated this action */
|
|
46
|
+
plugin: string;
|
|
47
|
+
/** Additional data (throttle duration, timeout duration, etc.) */
|
|
48
|
+
metadata?: Record<string, unknown>;
|
|
49
|
+
}
|
|
50
|
+
/** Result from the pipeline (aggregated from all plugins) */
|
|
51
|
+
export interface ModerationResult {
|
|
52
|
+
/** The final action (strictest wins) */
|
|
53
|
+
action: ModerationAction;
|
|
54
|
+
/** All actions from all plugins (for logging) */
|
|
55
|
+
allActions: ModerationAction[];
|
|
56
|
+
/** Whether an admin bypass was applied */
|
|
57
|
+
adminBypassed: boolean;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Interface that all moderation plugins must implement.
|
|
61
|
+
*/
|
|
62
|
+
export interface ModerationPlugin {
|
|
63
|
+
/** Unique plugin name */
|
|
64
|
+
readonly name: string;
|
|
65
|
+
/**
|
|
66
|
+
* What to do if this plugin throws an error.
|
|
67
|
+
* 'open' = allow message through (default, safe for non-critical plugins)
|
|
68
|
+
* 'closed' = block message (safe for security-critical plugins)
|
|
69
|
+
*/
|
|
70
|
+
readonly failBehavior?: 'open' | 'closed';
|
|
71
|
+
/**
|
|
72
|
+
* Check a message event and return a moderation action.
|
|
73
|
+
* Return ALLOW to let the message through.
|
|
74
|
+
* Return anything stricter to flag/block/escalate.
|
|
75
|
+
* Async to support plugins that need external lookups (reputation, blocklists, etc.)
|
|
76
|
+
*/
|
|
77
|
+
check(event: ModerationEvent): ModerationAction | Promise<ModerationAction>;
|
|
78
|
+
/**
|
|
79
|
+
* Optional: called when a connection disconnects.
|
|
80
|
+
* Useful for cleanup of per-connection state.
|
|
81
|
+
*/
|
|
82
|
+
onDisconnect?(agentId: string): void;
|
|
83
|
+
/**
|
|
84
|
+
* Optional: called periodically for cleanup.
|
|
85
|
+
* Return number of stale entries cleaned up.
|
|
86
|
+
*/
|
|
87
|
+
cleanup?(): number;
|
|
88
|
+
}
|
|
89
|
+
export interface ModerationPipelineOptions {
|
|
90
|
+
/** Enable admin bypass (default: true) */
|
|
91
|
+
adminBypass?: boolean;
|
|
92
|
+
/** Logger function */
|
|
93
|
+
logger?: (event: string, data: Record<string, unknown>) => void;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Moderation pipeline. Runs messages through registered plugins
|
|
97
|
+
* in order and returns the strictest action.
|
|
98
|
+
*/
|
|
99
|
+
export declare class ModerationPipeline {
|
|
100
|
+
/** Global plugins (apply to all channels) */
|
|
101
|
+
private globalPlugins;
|
|
102
|
+
/** Per-channel plugin overrides */
|
|
103
|
+
private channelPlugins;
|
|
104
|
+
private adminBypass;
|
|
105
|
+
private logger;
|
|
106
|
+
constructor(options?: ModerationPipelineOptions);
|
|
107
|
+
/**
|
|
108
|
+
* Register a global plugin (applies to all channels).
|
|
109
|
+
*/
|
|
110
|
+
register(plugin: ModerationPlugin): void;
|
|
111
|
+
/**
|
|
112
|
+
* Register a plugin for a specific channel only.
|
|
113
|
+
*/
|
|
114
|
+
registerForChannel(channel: string, plugin: ModerationPlugin): void;
|
|
115
|
+
/**
|
|
116
|
+
* Remove a global plugin by name.
|
|
117
|
+
*/
|
|
118
|
+
unregister(pluginName: string): boolean;
|
|
119
|
+
/**
|
|
120
|
+
* Run an event through the moderation pipeline.
|
|
121
|
+
* Returns the strictest action from all applicable plugins.
|
|
122
|
+
* Async to support plugins that need external lookups.
|
|
123
|
+
*/
|
|
124
|
+
check(event: ModerationEvent): Promise<ModerationResult>;
|
|
125
|
+
/**
|
|
126
|
+
* Notify all plugins of a disconnect.
|
|
127
|
+
*/
|
|
128
|
+
onDisconnect(agentId: string): void;
|
|
129
|
+
/**
|
|
130
|
+
* Run cleanup on all plugins.
|
|
131
|
+
*/
|
|
132
|
+
cleanup(): number;
|
|
133
|
+
/**
|
|
134
|
+
* List registered plugins.
|
|
135
|
+
*/
|
|
136
|
+
listPlugins(): Array<{
|
|
137
|
+
name: string;
|
|
138
|
+
scope: string;
|
|
139
|
+
channel?: string;
|
|
140
|
+
}>;
|
|
141
|
+
}
|
|
142
|
+
//# sourceMappingURL=moderation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"moderation.d.ts","sourceRoot":"","sources":["../../lib/moderation.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,8CAA8C;AAC9C,oBAAY,oBAAoB;IAC9B,KAAK,UAAU;IACf,IAAI,SAAS;IACb,QAAQ,aAAa;IACrB,KAAK,UAAU,CAAO,4BAA4B;IAClD,OAAO,YAAY,CAAG,yBAAyB;IAC/C,IAAI,SAAS;CACd;AAYD,yCAAyC;AACzC,MAAM,WAAW,eAAe;IAC9B,+BAA+B;IAC/B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,yBAAyB;IACzB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,oBAAoB;IACpB,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,uDAAuD;IACvD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,sBAAsB;IACtB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,qCAAqC;IACrC,WAAW,EAAE,MAAM,CAAC;IACpB,4DAA4D;IAC5D,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,8CAA8C;IAC9C,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,2BAA2B;IAC3B,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,gBAAgB;IAChB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,6CAA6C;AAC7C,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,oBAAoB,CAAC;IAC3B,4BAA4B;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,wCAAwC;IACxC,MAAM,EAAE,MAAM,CAAC;IACf,kEAAkE;IAClE,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACpC;AAED,6DAA6D;AAC7D,MAAM,WAAW,gBAAgB;IAC/B,wCAAwC;IACxC,MAAM,EAAE,gBAAgB,CAAC;IACzB,iDAAiD;IACjD,UAAU,EAAE,gBAAgB,EAAE,CAAC;IAC/B,0CAA0C;IAC1C,aAAa,EAAE,OAAO,CAAC;CACxB;AAID;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,yBAAyB;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAEtB;;;;OAIG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,GAAG,QAAQ,CAAC;IAE1C;;;;;OAKG;IACH,KAAK,CAAC,KAAK,EAAE,eAAe,GAAG,gBAAgB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAAC;IAE5E;;;OAGG;IACH,YAAY,CAAC,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IAErC;;;OAGG;IACH,OAAO,CAAC,IAAI,MAAM,CAAC;CACpB;AAID,MAAM,WAAW,yBAAyB;IACxC,0CAA0C;IAC1C,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,sBAAsB;IACtB,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;CACjE;AAED;;;GAGG;AACH,qBAAa,kBAAkB;IAC7B,6CAA6C;IAC7C,OAAO,CAAC,aAAa,CAA0B;IAE/C,mCAAmC;IACnC,OAAO,CAAC,cAAc,CAA8C;IAEpE,OAAO,CAAC,WAAW,CAAU;IAC7B,OAAO,CAAC,MAAM,CAAyD;gBAE3D,OAAO,GAAE,yBAA8B;IAKnD;;OAEG;IACH,QAAQ,CAAC,MAAM,EAAE,gBAAgB,GAAG,IAAI;IAKxC;;OAEG;IACH,kBAAkB,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,gBAAgB,GAAG,IAAI;IAQnE;;OAEG;IACH,UAAU,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO;IASvC;;;;OAIG;IACG,KAAK,CAAC,KAAK,EAAE,eAAe,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAgF9D;;OAEG;IACH,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAWnC;;OAEG;IACH,OAAO,IAAI,MAAM;IAajB;;OAEG;IACH,WAAW,IAAI,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CAYxE"}
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Moderation Plugin Architecture
|
|
3
|
+
*
|
|
4
|
+
* Chain-of-responsibility pattern for message moderation.
|
|
5
|
+
* Plugins are checked in order; strictest action wins.
|
|
6
|
+
* Supports per-channel configuration and admin bypass.
|
|
7
|
+
*/
|
|
8
|
+
// ============ Types ============
|
|
9
|
+
/** Moderation actions in order of severity */
|
|
10
|
+
export var ModerationActionType;
|
|
11
|
+
(function (ModerationActionType) {
|
|
12
|
+
ModerationActionType["ALLOW"] = "allow";
|
|
13
|
+
ModerationActionType["WARN"] = "warn";
|
|
14
|
+
ModerationActionType["THROTTLE"] = "throttle";
|
|
15
|
+
ModerationActionType["BLOCK"] = "block";
|
|
16
|
+
ModerationActionType["TIMEOUT"] = "timeout";
|
|
17
|
+
ModerationActionType["KICK"] = "kick";
|
|
18
|
+
})(ModerationActionType || (ModerationActionType = {}));
|
|
19
|
+
/** Severity ordering for strictest-wins logic */
|
|
20
|
+
const ACTION_SEVERITY = {
|
|
21
|
+
[ModerationActionType.ALLOW]: 0,
|
|
22
|
+
[ModerationActionType.WARN]: 1,
|
|
23
|
+
[ModerationActionType.THROTTLE]: 2,
|
|
24
|
+
[ModerationActionType.BLOCK]: 3,
|
|
25
|
+
[ModerationActionType.TIMEOUT]: 4,
|
|
26
|
+
[ModerationActionType.KICK]: 5,
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* Moderation pipeline. Runs messages through registered plugins
|
|
30
|
+
* in order and returns the strictest action.
|
|
31
|
+
*/
|
|
32
|
+
export class ModerationPipeline {
|
|
33
|
+
/** Global plugins (apply to all channels) */
|
|
34
|
+
globalPlugins = [];
|
|
35
|
+
/** Per-channel plugin overrides */
|
|
36
|
+
channelPlugins = new Map();
|
|
37
|
+
adminBypass;
|
|
38
|
+
logger;
|
|
39
|
+
constructor(options = {}) {
|
|
40
|
+
this.adminBypass = options.adminBypass !== false;
|
|
41
|
+
this.logger = options.logger || (() => { });
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Register a global plugin (applies to all channels).
|
|
45
|
+
*/
|
|
46
|
+
register(plugin) {
|
|
47
|
+
this.globalPlugins.push(plugin);
|
|
48
|
+
this.logger('moderation_plugin_registered', { name: plugin.name, scope: 'global' });
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Register a plugin for a specific channel only.
|
|
52
|
+
*/
|
|
53
|
+
registerForChannel(channel, plugin) {
|
|
54
|
+
if (!this.channelPlugins.has(channel)) {
|
|
55
|
+
this.channelPlugins.set(channel, []);
|
|
56
|
+
}
|
|
57
|
+
this.channelPlugins.get(channel).push(plugin);
|
|
58
|
+
this.logger('moderation_plugin_registered', { name: plugin.name, scope: 'channel', channel });
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Remove a global plugin by name.
|
|
62
|
+
*/
|
|
63
|
+
unregister(pluginName) {
|
|
64
|
+
const idx = this.globalPlugins.findIndex(p => p.name === pluginName);
|
|
65
|
+
if (idx >= 0) {
|
|
66
|
+
this.globalPlugins.splice(idx, 1);
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Run an event through the moderation pipeline.
|
|
73
|
+
* Returns the strictest action from all applicable plugins.
|
|
74
|
+
* Async to support plugins that need external lookups.
|
|
75
|
+
*/
|
|
76
|
+
async check(event) {
|
|
77
|
+
// Admin bypass — skip all checks
|
|
78
|
+
if (this.adminBypass && event.isAdmin) {
|
|
79
|
+
return {
|
|
80
|
+
action: { type: ModerationActionType.ALLOW, reason: 'Admin bypass', plugin: 'pipeline' },
|
|
81
|
+
allActions: [],
|
|
82
|
+
adminBypassed: true,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
const allActions = [];
|
|
86
|
+
const runPlugin = async (plugin) => {
|
|
87
|
+
try {
|
|
88
|
+
const action = await plugin.check(event);
|
|
89
|
+
allActions.push(action);
|
|
90
|
+
}
|
|
91
|
+
catch (err) {
|
|
92
|
+
const failMode = plugin.failBehavior || 'open';
|
|
93
|
+
this.logger('moderation_plugin_error', {
|
|
94
|
+
plugin: plugin.name,
|
|
95
|
+
error: err.message,
|
|
96
|
+
failBehavior: failMode,
|
|
97
|
+
});
|
|
98
|
+
if (failMode === 'closed') {
|
|
99
|
+
allActions.push({
|
|
100
|
+
type: ModerationActionType.BLOCK,
|
|
101
|
+
reason: `Plugin ${plugin.name} error (fail-closed)`,
|
|
102
|
+
plugin: plugin.name,
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
// fail-open: just skip this plugin's action (implicit ALLOW)
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
// Run global plugins
|
|
109
|
+
for (const plugin of this.globalPlugins) {
|
|
110
|
+
await runPlugin(plugin);
|
|
111
|
+
}
|
|
112
|
+
// Run channel-specific plugins (if applicable)
|
|
113
|
+
if (event.channel) {
|
|
114
|
+
const channelSpecific = this.channelPlugins.get(event.channel);
|
|
115
|
+
if (channelSpecific) {
|
|
116
|
+
for (const plugin of channelSpecific) {
|
|
117
|
+
await runPlugin(plugin);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
// Find the strictest action
|
|
122
|
+
let strictest = {
|
|
123
|
+
type: ModerationActionType.ALLOW,
|
|
124
|
+
reason: 'No moderation action',
|
|
125
|
+
plugin: 'pipeline',
|
|
126
|
+
};
|
|
127
|
+
for (const action of allActions) {
|
|
128
|
+
if (ACTION_SEVERITY[action.type] > ACTION_SEVERITY[strictest.type]) {
|
|
129
|
+
strictest = action;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
// Log non-allow actions
|
|
133
|
+
if (strictest.type !== ModerationActionType.ALLOW) {
|
|
134
|
+
this.logger('moderation_action', {
|
|
135
|
+
action: strictest.type,
|
|
136
|
+
plugin: strictest.plugin,
|
|
137
|
+
reason: strictest.reason,
|
|
138
|
+
agentId: event.agentId,
|
|
139
|
+
channel: event.channel,
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
return {
|
|
143
|
+
action: strictest,
|
|
144
|
+
allActions,
|
|
145
|
+
adminBypassed: false,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Notify all plugins of a disconnect.
|
|
150
|
+
*/
|
|
151
|
+
onDisconnect(agentId) {
|
|
152
|
+
for (const plugin of this.globalPlugins) {
|
|
153
|
+
plugin.onDisconnect?.(agentId);
|
|
154
|
+
}
|
|
155
|
+
for (const plugins of this.channelPlugins.values()) {
|
|
156
|
+
for (const plugin of plugins) {
|
|
157
|
+
plugin.onDisconnect?.(agentId);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Run cleanup on all plugins.
|
|
163
|
+
*/
|
|
164
|
+
cleanup() {
|
|
165
|
+
let total = 0;
|
|
166
|
+
for (const plugin of this.globalPlugins) {
|
|
167
|
+
total += plugin.cleanup?.() || 0;
|
|
168
|
+
}
|
|
169
|
+
for (const plugins of this.channelPlugins.values()) {
|
|
170
|
+
for (const plugin of plugins) {
|
|
171
|
+
total += plugin.cleanup?.() || 0;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
return total;
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* List registered plugins.
|
|
178
|
+
*/
|
|
179
|
+
listPlugins() {
|
|
180
|
+
const result = [];
|
|
181
|
+
for (const p of this.globalPlugins) {
|
|
182
|
+
result.push({ name: p.name, scope: 'global' });
|
|
183
|
+
}
|
|
184
|
+
for (const [channel, plugins] of this.channelPlugins) {
|
|
185
|
+
for (const p of plugins) {
|
|
186
|
+
result.push({ name: p.name, scope: 'channel', channel });
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
return result;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
//# sourceMappingURL=moderation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"moderation.js","sourceRoot":"","sources":["../../lib/moderation.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,kCAAkC;AAElC,8CAA8C;AAC9C,MAAM,CAAN,IAAY,oBAOX;AAPD,WAAY,oBAAoB;IAC9B,uCAAe,CAAA;IACf,qCAAa,CAAA;IACb,6CAAqB,CAAA;IACrB,uCAAe,CAAA;IACf,2CAAmB,CAAA;IACnB,qCAAa,CAAA;AACf,CAAC,EAPW,oBAAoB,KAApB,oBAAoB,QAO/B;AAED,iDAAiD;AACjD,MAAM,eAAe,GAAyC;IAC5D,CAAC,oBAAoB,CAAC,KAAK,CAAC,EAAE,CAAC;IAC/B,CAAC,oBAAoB,CAAC,IAAI,CAAC,EAAE,CAAC;IAC9B,CAAC,oBAAoB,CAAC,QAAQ,CAAC,EAAE,CAAC;IAClC,CAAC,oBAAoB,CAAC,KAAK,CAAC,EAAE,CAAC;IAC/B,CAAC,oBAAoB,CAAC,OAAO,CAAC,EAAE,CAAC;IACjC,CAAC,oBAAoB,CAAC,IAAI,CAAC,EAAE,CAAC;CAC/B,CAAC;AA6FF;;;GAGG;AACH,MAAM,OAAO,kBAAkB;IAC7B,6CAA6C;IACrC,aAAa,GAAuB,EAAE,CAAC;IAE/C,mCAAmC;IAC3B,cAAc,GAAoC,IAAI,GAAG,EAAE,CAAC;IAE5D,WAAW,CAAU;IACrB,MAAM,CAAyD;IAEvE,YAAY,UAAqC,EAAE;QACjD,IAAI,CAAC,WAAW,GAAG,OAAO,CAAC,WAAW,KAAK,KAAK,CAAC;QACjD,IAAI,CAAC,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;IAC7C,CAAC;IAED;;OAEG;IACH,QAAQ,CAAC,MAAwB;QAC/B,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QAChC,IAAI,CAAC,MAAM,CAAC,8BAA8B,EAAE,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC,CAAC;IACtF,CAAC;IAED;;OAEG;IACH,kBAAkB,CAAC,OAAe,EAAE,MAAwB;QAC1D,IAAI,CAAC,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC;YACtC,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;QACvC,CAAC;QACD,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,OAAO,CAAE,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QAC/C,IAAI,CAAC,MAAM,CAAC,8BAA8B,EAAE,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,KAAK,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC,CAAC;IAChG,CAAC;IAED;;OAEG;IACH,UAAU,CAAC,UAAkB;QAC3B,MAAM,GAAG,GAAG,IAAI,CAAC,aAAa,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,UAAU,CAAC,CAAC;QACrE,IAAI,GAAG,IAAI,CAAC,EAAE,CAAC;YACb,IAAI,CAAC,aAAa,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;YAClC,OAAO,IAAI,CAAC;QACd,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC;IAED;;;;OAIG;IACH,KAAK,CAAC,KAAK,CAAC,KAAsB;QAChC,iCAAiC;QACjC,IAAI,IAAI,CAAC,WAAW,IAAI,KAAK,CAAC,OAAO,EAAE,CAAC;YACtC,OAAO;gBACL,MAAM,EAAE,EAAE,IAAI,EAAE,oBAAoB,CAAC,KAAK,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,EAAE,UAAU,EAAE;gBACxF,UAAU,EAAE,EAAE;gBACd,aAAa,EAAE,IAAI;aACpB,CAAC;QACJ,CAAC;QAED,MAAM,UAAU,GAAuB,EAAE,CAAC;QAE1C,MAAM,SAAS,GAAG,KAAK,EAAE,MAAwB,EAAiB,EAAE;YAClE,IAAI,CAAC;gBACH,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;gBACzC,UAAU,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;YAC1B,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,MAAM,QAAQ,GAAG,MAAM,CAAC,YAAY,IAAI,MAAM,CAAC;gBAC/C,IAAI,CAAC,MAAM,CAAC,yBAAyB,EAAE;oBACrC,MAAM,EAAE,MAAM,CAAC,IAAI;oBACnB,KAAK,EAAG,GAAa,CAAC,OAAO;oBAC7B,YAAY,EAAE,QAAQ;iBACvB,CAAC,CAAC;gBACH,IAAI,QAAQ,KAAK,QAAQ,EAAE,CAAC;oBAC1B,UAAU,CAAC,IAAI,CAAC;wBACd,IAAI,EAAE,oBAAoB,CAAC,KAAK;wBAChC,MAAM,EAAE,UAAU,MAAM,CAAC,IAAI,sBAAsB;wBACnD,MAAM,EAAE,MAAM,CAAC,IAAI;qBACpB,CAAC,CAAC;gBACL,CAAC;gBACD,6DAA6D;YAC/D,CAAC;QACH,CAAC,CAAC;QAEF,qBAAqB;QACrB,KAAK,MAAM,MAAM,IAAI,IAAI,CAAC,aAAa,EAAE,CAAC;YACxC,MAAM,SAAS,CAAC,MAAM,CAAC,CAAC;QAC1B,CAAC;QAED,+CAA+C;QAC/C,IAAI,KAAK,CAAC,OAAO,EAAE,CAAC;YAClB,MAAM,eAAe,GAAG,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;YAC/D,IAAI,eAAe,EAAE,CAAC;gBACpB,KAAK,MAAM,MAAM,IAAI,eAAe,EAAE,CAAC;oBACrC,MAAM,SAAS,CAAC,MAAM,CAAC,CAAC;gBAC1B,CAAC;YACH,CAAC;QACH,CAAC;QAED,4BAA4B;QAC5B,IAAI,SAAS,GAAqB;YAChC,IAAI,EAAE,oBAAoB,CAAC,KAAK;YAChC,MAAM,EAAE,sBAAsB;YAC9B,MAAM,EAAE,UAAU;SACnB,CAAC;QAEF,KAAK,MAAM,MAAM,IAAI,UAAU,EAAE,CAAC;YAChC,IAAI,eAAe,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,eAAe,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC;gBACnE,SAAS,GAAG,MAAM,CAAC;YACrB,CAAC;QACH,CAAC;QAED,wBAAwB;QACxB,IAAI,SAAS,CAAC,IAAI,KAAK,oBAAoB,CAAC,KAAK,EAAE,CAAC;YAClD,IAAI,CAAC,MAAM,CAAC,mBAAmB,EAAE;gBAC/B,MAAM,EAAE,SAAS,CAAC,IAAI;gBACtB,MAAM,EAAE,SAAS,CAAC,MAAM;gBACxB,MAAM,EAAE,SAAS,CAAC,MAAM;gBACxB,OAAO,EAAE,KAAK,CAAC,OAAO;gBACtB,OAAO,EAAE,KAAK,CAAC,OAAO;aACvB,CAAC,CAAC;QACL,CAAC;QAED,OAAO;YACL,MAAM,EAAE,SAAS;YACjB,UAAU;YACV,aAAa,EAAE,KAAK;SACrB,CAAC;IACJ,CAAC;IAED;;OAEG;IACH,YAAY,CAAC,OAAe;QAC1B,KAAK,MAAM,MAAM,IAAI,IAAI,CAAC,aAAa,EAAE,CAAC;YACxC,MAAM,CAAC,YAAY,EAAE,CAAC,OAAO,CAAC,CAAC;QACjC,CAAC;QACD,KAAK,MAAM,OAAO,IAAI,IAAI,CAAC,cAAc,CAAC,MAAM,EAAE,EAAE,CAAC;YACnD,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;gBAC7B,MAAM,CAAC,YAAY,EAAE,CAAC,OAAO,CAAC,CAAC;YACjC,CAAC;QACH,CAAC;IACH,CAAC;IAED;;OAEG;IACH,OAAO;QACL,IAAI,KAAK,GAAG,CAAC,CAAC;QACd,KAAK,MAAM,MAAM,IAAI,IAAI,CAAC,aAAa,EAAE,CAAC;YACxC,KAAK,IAAI,MAAM,CAAC,OAAO,EAAE,EAAE,IAAI,CAAC,CAAC;QACnC,CAAC;QACD,KAAK,MAAM,OAAO,IAAI,IAAI,CAAC,cAAc,CAAC,MAAM,EAAE,EAAE,CAAC;YACnD,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;gBAC7B,KAAK,IAAI,MAAM,CAAC,OAAO,EAAE,EAAE,IAAI,CAAC,CAAC;YACnC,CAAC;QACH,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC;IAED;;OAEG;IACH,WAAW;QACT,MAAM,MAAM,GAA6D,EAAE,CAAC;QAC5E,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,aAAa,EAAE,CAAC;YACnC,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC,CAAC;QACjD,CAAC;QACD,KAAK,MAAM,CAAC,OAAO,EAAE,OAAO,CAAC,IAAI,IAAI,CAAC,cAAc,EAAE,CAAC;YACrD,KAAK,MAAM,CAAC,IAAI,OAAO,EAAE,CAAC;gBACxB,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC,CAAC;YAC3D,CAAC;QACH,CAAC;QACD,OAAO,MAAM,CAAC;IAChB,CAAC;CACF"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"moderation.test.d.ts","sourceRoot":"","sources":["../../lib/moderation.test.ts"],"names":[],"mappings":"AAAA;;;;GAIG"}
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for Moderation Pipeline and Plugins
|
|
3
|
+
*
|
|
4
|
+
* Run with: npx tsx lib/moderation.test.ts
|
|
5
|
+
*/
|
|
6
|
+
import { describe, it } from 'node:test';
|
|
7
|
+
import assert from 'node:assert/strict';
|
|
8
|
+
import { ModerationPipeline, ModerationActionType } from './moderation.js';
|
|
9
|
+
import { EscalationPlugin } from './moderation-plugins/escalation-plugin.js';
|
|
10
|
+
import { LinkDetectorPlugin } from './moderation-plugins/link-detector-plugin.js';
|
|
11
|
+
function makeEvent(overrides = {}) {
|
|
12
|
+
return {
|
|
13
|
+
messageType: 'MSG',
|
|
14
|
+
timestamp: Date.now(),
|
|
15
|
+
...overrides,
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
describe('ModerationPipeline', () => {
|
|
19
|
+
it('allows messages when no plugins registered', async () => {
|
|
20
|
+
const pipeline = new ModerationPipeline();
|
|
21
|
+
const result = await pipeline.check(makeEvent());
|
|
22
|
+
assert.equal(result.action.type, ModerationActionType.ALLOW);
|
|
23
|
+
});
|
|
24
|
+
it('applies admin bypass', async () => {
|
|
25
|
+
const pipeline = new ModerationPipeline();
|
|
26
|
+
// Register a plugin that always blocks
|
|
27
|
+
const blockerPlugin = {
|
|
28
|
+
name: 'always-block',
|
|
29
|
+
check: () => ({
|
|
30
|
+
type: ModerationActionType.BLOCK,
|
|
31
|
+
reason: 'Blocked',
|
|
32
|
+
plugin: 'always-block',
|
|
33
|
+
}),
|
|
34
|
+
};
|
|
35
|
+
pipeline.register(blockerPlugin);
|
|
36
|
+
// Admin should bypass
|
|
37
|
+
const result = await pipeline.check(makeEvent({ isAdmin: true }));
|
|
38
|
+
assert.equal(result.adminBypassed, true);
|
|
39
|
+
assert.equal(result.action.type, ModerationActionType.ALLOW);
|
|
40
|
+
});
|
|
41
|
+
it('strictest action wins', async () => {
|
|
42
|
+
const pipeline = new ModerationPipeline();
|
|
43
|
+
const warnPlugin = {
|
|
44
|
+
name: 'warn-plugin',
|
|
45
|
+
check: () => ({ type: ModerationActionType.WARN, reason: 'Warning', plugin: 'warn-plugin' }),
|
|
46
|
+
};
|
|
47
|
+
const blockPlugin = {
|
|
48
|
+
name: 'block-plugin',
|
|
49
|
+
check: () => ({ type: ModerationActionType.BLOCK, reason: 'Blocked', plugin: 'block-plugin' }),
|
|
50
|
+
};
|
|
51
|
+
pipeline.register(warnPlugin);
|
|
52
|
+
pipeline.register(blockPlugin);
|
|
53
|
+
const result = await pipeline.check(makeEvent());
|
|
54
|
+
assert.equal(result.action.type, ModerationActionType.BLOCK);
|
|
55
|
+
assert.equal(result.action.plugin, 'block-plugin');
|
|
56
|
+
assert.equal(result.allActions.length, 2);
|
|
57
|
+
});
|
|
58
|
+
it('supports per-channel plugins', async () => {
|
|
59
|
+
const pipeline = new ModerationPipeline();
|
|
60
|
+
const strictPlugin = {
|
|
61
|
+
name: 'strict',
|
|
62
|
+
check: () => ({ type: ModerationActionType.BLOCK, reason: 'Strict', plugin: 'strict' }),
|
|
63
|
+
};
|
|
64
|
+
pipeline.registerForChannel('#moderated', strictPlugin);
|
|
65
|
+
// Message to #moderated gets blocked
|
|
66
|
+
const r1 = await pipeline.check(makeEvent({ channel: '#moderated' }));
|
|
67
|
+
assert.equal(r1.action.type, ModerationActionType.BLOCK);
|
|
68
|
+
// Message to #general is fine
|
|
69
|
+
const r2 = await pipeline.check(makeEvent({ channel: '#general' }));
|
|
70
|
+
assert.equal(r2.action.type, ModerationActionType.ALLOW);
|
|
71
|
+
});
|
|
72
|
+
it('combines global and channel plugins', async () => {
|
|
73
|
+
const pipeline = new ModerationPipeline();
|
|
74
|
+
const globalWarn = {
|
|
75
|
+
name: 'global-warn',
|
|
76
|
+
check: () => ({ type: ModerationActionType.WARN, reason: 'Global warning', plugin: 'global-warn' }),
|
|
77
|
+
};
|
|
78
|
+
const channelBlock = {
|
|
79
|
+
name: 'channel-block',
|
|
80
|
+
check: () => ({ type: ModerationActionType.BLOCK, reason: 'Channel blocked', plugin: 'channel-block' }),
|
|
81
|
+
};
|
|
82
|
+
pipeline.register(globalWarn);
|
|
83
|
+
pipeline.registerForChannel('#strict', channelBlock);
|
|
84
|
+
// #strict gets the stricter action
|
|
85
|
+
const r1 = await pipeline.check(makeEvent({ channel: '#strict' }));
|
|
86
|
+
assert.equal(r1.action.type, ModerationActionType.BLOCK);
|
|
87
|
+
assert.equal(r1.allActions.length, 2);
|
|
88
|
+
// Other channels only get the warn
|
|
89
|
+
const r2 = await pipeline.check(makeEvent({ channel: '#general' }));
|
|
90
|
+
assert.equal(r2.action.type, ModerationActionType.WARN);
|
|
91
|
+
});
|
|
92
|
+
it('unregisters plugins', async () => {
|
|
93
|
+
const pipeline = new ModerationPipeline();
|
|
94
|
+
const plugin = {
|
|
95
|
+
name: 'removable',
|
|
96
|
+
check: () => ({ type: ModerationActionType.BLOCK, reason: 'Block', plugin: 'removable' }),
|
|
97
|
+
};
|
|
98
|
+
pipeline.register(plugin);
|
|
99
|
+
assert.equal((await pipeline.check(makeEvent())).action.type, ModerationActionType.BLOCK);
|
|
100
|
+
pipeline.unregister('removable');
|
|
101
|
+
assert.equal((await pipeline.check(makeEvent())).action.type, ModerationActionType.ALLOW);
|
|
102
|
+
});
|
|
103
|
+
it('lists registered plugins', () => {
|
|
104
|
+
const pipeline = new ModerationPipeline();
|
|
105
|
+
const p1 = { name: 'a', check: () => ({ type: ModerationActionType.ALLOW, reason: '', plugin: 'a' }) };
|
|
106
|
+
const p2 = { name: 'b', check: () => ({ type: ModerationActionType.ALLOW, reason: '', plugin: 'b' }) };
|
|
107
|
+
pipeline.register(p1);
|
|
108
|
+
pipeline.registerForChannel('#test', p2);
|
|
109
|
+
const list = pipeline.listPlugins();
|
|
110
|
+
assert.equal(list.length, 2);
|
|
111
|
+
assert.equal(list[0].name, 'a');
|
|
112
|
+
assert.equal(list[0].scope, 'global');
|
|
113
|
+
assert.equal(list[1].name, 'b');
|
|
114
|
+
assert.equal(list[1].scope, 'channel');
|
|
115
|
+
assert.equal(list[1].channel, '#test');
|
|
116
|
+
});
|
|
117
|
+
it('calls onDisconnect on all plugins', () => {
|
|
118
|
+
const pipeline = new ModerationPipeline();
|
|
119
|
+
const disconnected = [];
|
|
120
|
+
const plugin = {
|
|
121
|
+
name: 'tracker',
|
|
122
|
+
check: () => ({ type: ModerationActionType.ALLOW, reason: '', plugin: 'tracker' }),
|
|
123
|
+
onDisconnect: (id) => disconnected.push(id),
|
|
124
|
+
};
|
|
125
|
+
pipeline.register(plugin);
|
|
126
|
+
pipeline.onDisconnect('agent123');
|
|
127
|
+
assert.equal(disconnected.length, 1);
|
|
128
|
+
assert.equal(disconnected[0], 'agent123');
|
|
129
|
+
});
|
|
130
|
+
it('supports async plugins', async () => {
|
|
131
|
+
const pipeline = new ModerationPipeline();
|
|
132
|
+
const asyncPlugin = {
|
|
133
|
+
name: 'async-blocker',
|
|
134
|
+
check: async (_event) => {
|
|
135
|
+
// Simulate async lookup
|
|
136
|
+
await new Promise(r => setTimeout(r, 10));
|
|
137
|
+
return { type: ModerationActionType.BLOCK, reason: 'Async block', plugin: 'async-blocker' };
|
|
138
|
+
},
|
|
139
|
+
};
|
|
140
|
+
pipeline.register(asyncPlugin);
|
|
141
|
+
const result = await pipeline.check(makeEvent());
|
|
142
|
+
assert.equal(result.action.type, ModerationActionType.BLOCK);
|
|
143
|
+
assert.equal(result.action.plugin, 'async-blocker');
|
|
144
|
+
});
|
|
145
|
+
it('fail-open plugin error allows message through', async () => {
|
|
146
|
+
const pipeline = new ModerationPipeline();
|
|
147
|
+
const brokenPlugin = {
|
|
148
|
+
name: 'broken-open',
|
|
149
|
+
failBehavior: 'open',
|
|
150
|
+
check: () => { throw new Error('Plugin crashed'); },
|
|
151
|
+
};
|
|
152
|
+
pipeline.register(brokenPlugin);
|
|
153
|
+
const result = await pipeline.check(makeEvent());
|
|
154
|
+
// fail-open: message goes through
|
|
155
|
+
assert.equal(result.action.type, ModerationActionType.ALLOW);
|
|
156
|
+
});
|
|
157
|
+
it('fail-closed plugin error blocks message', async () => {
|
|
158
|
+
const pipeline = new ModerationPipeline();
|
|
159
|
+
const brokenPlugin = {
|
|
160
|
+
name: 'broken-closed',
|
|
161
|
+
failBehavior: 'closed',
|
|
162
|
+
check: () => { throw new Error('Plugin crashed'); },
|
|
163
|
+
};
|
|
164
|
+
pipeline.register(brokenPlugin);
|
|
165
|
+
const result = await pipeline.check(makeEvent());
|
|
166
|
+
// fail-closed: message is blocked
|
|
167
|
+
assert.equal(result.action.type, ModerationActionType.BLOCK);
|
|
168
|
+
assert.ok(result.action.reason.includes('fail-closed'));
|
|
169
|
+
});
|
|
170
|
+
it('defaults to fail-open when failBehavior not specified', async () => {
|
|
171
|
+
const pipeline = new ModerationPipeline();
|
|
172
|
+
const brokenPlugin = {
|
|
173
|
+
name: 'broken-default',
|
|
174
|
+
// no failBehavior set
|
|
175
|
+
check: () => { throw new Error('Plugin crashed'); },
|
|
176
|
+
};
|
|
177
|
+
pipeline.register(brokenPlugin);
|
|
178
|
+
const result = await pipeline.check(makeEvent());
|
|
179
|
+
assert.equal(result.action.type, ModerationActionType.ALLOW);
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
describe('LinkDetectorPlugin', () => {
|
|
183
|
+
it('allows messages without URLs', () => {
|
|
184
|
+
const plugin = new LinkDetectorPlugin();
|
|
185
|
+
const action = plugin.check(makeEvent({ content: 'Hello everyone' }));
|
|
186
|
+
assert.equal(action.type, ModerationActionType.ALLOW);
|
|
187
|
+
});
|
|
188
|
+
it('warns new connections posting URLs', () => {
|
|
189
|
+
const plugin = new LinkDetectorPlugin({ minConnectionAgeMs: 300000 });
|
|
190
|
+
const action = plugin.check(makeEvent({
|
|
191
|
+
content: 'Check out https://example.com',
|
|
192
|
+
connectionAgeMs: 5000, // 5 seconds old
|
|
193
|
+
}));
|
|
194
|
+
assert.equal(action.type, ModerationActionType.WARN);
|
|
195
|
+
assert.ok(action.reason.includes('New connection'));
|
|
196
|
+
});
|
|
197
|
+
it('allows old connections to post URLs', () => {
|
|
198
|
+
const plugin = new LinkDetectorPlugin({ minConnectionAgeMs: 300000 });
|
|
199
|
+
const action = plugin.check(makeEvent({
|
|
200
|
+
content: 'Check out https://example.com',
|
|
201
|
+
connectionAgeMs: 600000, // 10 minutes old
|
|
202
|
+
}));
|
|
203
|
+
assert.equal(action.type, ModerationActionType.ALLOW);
|
|
204
|
+
});
|
|
205
|
+
it('allows verified agents to post URLs', () => {
|
|
206
|
+
const plugin = new LinkDetectorPlugin({ minConnectionAgeMs: 300000 });
|
|
207
|
+
const action = plugin.check(makeEvent({
|
|
208
|
+
content: 'Check out https://example.com',
|
|
209
|
+
connectionAgeMs: 1000, // very new
|
|
210
|
+
verified: true,
|
|
211
|
+
}));
|
|
212
|
+
assert.equal(action.type, ModerationActionType.ALLOW);
|
|
213
|
+
});
|
|
214
|
+
it('blocks URLs matching blocked patterns', () => {
|
|
215
|
+
const plugin = new LinkDetectorPlugin({
|
|
216
|
+
blockedPatterns: ['botsforpeace\\.ai'],
|
|
217
|
+
});
|
|
218
|
+
const action = plugin.check(makeEvent({
|
|
219
|
+
content: 'Join us at https://botsforpeace.ai',
|
|
220
|
+
connectionAgeMs: 600000,
|
|
221
|
+
verified: true,
|
|
222
|
+
}));
|
|
223
|
+
assert.equal(action.type, ModerationActionType.BLOCK);
|
|
224
|
+
assert.ok(action.reason.includes('Blocked URL'));
|
|
225
|
+
});
|
|
226
|
+
it('skips non-MSG events', () => {
|
|
227
|
+
const plugin = new LinkDetectorPlugin();
|
|
228
|
+
const action = plugin.check(makeEvent({
|
|
229
|
+
messageType: 'JOIN',
|
|
230
|
+
content: 'https://example.com',
|
|
231
|
+
}));
|
|
232
|
+
assert.equal(action.type, ModerationActionType.ALLOW);
|
|
233
|
+
});
|
|
234
|
+
it('configurable untrusted action', () => {
|
|
235
|
+
const plugin = new LinkDetectorPlugin({
|
|
236
|
+
untrustedAction: ModerationActionType.BLOCK,
|
|
237
|
+
minConnectionAgeMs: 300000,
|
|
238
|
+
});
|
|
239
|
+
const action = plugin.check(makeEvent({
|
|
240
|
+
content: 'https://spam.example.com',
|
|
241
|
+
connectionAgeMs: 1000,
|
|
242
|
+
}));
|
|
243
|
+
assert.equal(action.type, ModerationActionType.BLOCK);
|
|
244
|
+
});
|
|
245
|
+
});
|
|
246
|
+
describe('EscalationPlugin', () => {
|
|
247
|
+
it('allows by default', () => {
|
|
248
|
+
const plugin = new EscalationPlugin();
|
|
249
|
+
const action = plugin.check(makeEvent({ agentId: 'test' }));
|
|
250
|
+
assert.equal(action.type, ModerationActionType.ALLOW);
|
|
251
|
+
});
|
|
252
|
+
it('warns after repeated violations', () => {
|
|
253
|
+
const plugin = new EscalationPlugin({ warnAfterViolations: 2 });
|
|
254
|
+
plugin.recordViolation('test');
|
|
255
|
+
const action = plugin.recordViolation('test');
|
|
256
|
+
assert.equal(action.type, ModerationActionType.WARN);
|
|
257
|
+
});
|
|
258
|
+
it('reports throttle state via check()', () => {
|
|
259
|
+
const plugin = new EscalationPlugin({ warnAfterViolations: 2, throttleAfterViolations: 4 });
|
|
260
|
+
for (let i = 0; i < 4; i++)
|
|
261
|
+
plugin.recordViolation('test');
|
|
262
|
+
// Now check() should report throttled
|
|
263
|
+
const action = plugin.check(makeEvent({ agentId: 'test' }));
|
|
264
|
+
assert.equal(action.type, ModerationActionType.THROTTLE);
|
|
265
|
+
});
|
|
266
|
+
it('exposes stats', () => {
|
|
267
|
+
const plugin = new EscalationPlugin({ warnAfterViolations: 2 });
|
|
268
|
+
plugin.recordViolation('a');
|
|
269
|
+
plugin.recordViolation('a');
|
|
270
|
+
const stats = plugin.stats();
|
|
271
|
+
assert.equal(stats.tracked, 1);
|
|
272
|
+
assert.equal(stats.warned, 1);
|
|
273
|
+
});
|
|
274
|
+
});
|
|
275
|
+
//# sourceMappingURL=moderation.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"moderation.test.js","sourceRoot":"","sources":["../../lib/moderation.test.ts"],"names":[],"mappings":"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"}
|