neoagent 3.2.1-beta.12 → 3.2.1-beta.13
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/flutter_app/lib/main_app_shell.dart +178 -34
- package/flutter_app/lib/main_chat.dart +542 -80
- package/flutter_app/lib/main_controller.dart +13 -0
- package/flutter_app/lib/main_models.dart +167 -20
- package/package.json +1 -1
- package/server/public/.last_build_id +1 -1
- package/server/public/assets/fonts/MaterialIcons-Regular.otf +0 -0
- package/server/public/flutter_bootstrap.js +1 -1
- package/server/public/main.dart.js +76649 -76021
- package/server/services/behavior/modules/turn_taking.js +15 -14
- package/server/services/behavior/pipeline.js +24 -7
- package/server/services/messaging/access_policy.js +264 -73
- package/server/services/messaging/automation.js +1 -1
- package/server/services/messaging/http_platforms.js +69 -25
- package/server/services/messaging/manager.js +39 -5
|
@@ -102,6 +102,20 @@ async function shouldEngage(ctx) {
|
|
|
102
102
|
};
|
|
103
103
|
}
|
|
104
104
|
|
|
105
|
+
if (msg.wasMentioned || msg.repliedToAgent) {
|
|
106
|
+
return {
|
|
107
|
+
decision: 'speak',
|
|
108
|
+
needScore: 1,
|
|
109
|
+
confidence: 1,
|
|
110
|
+
reasonCodes: [msg.repliedToAgent ? 'reply_to_agent' : 'addressed'],
|
|
111
|
+
urgency: 'medium',
|
|
112
|
+
rationale: 'Platform metadata directly addresses the agent.',
|
|
113
|
+
tokenPath: 'gate_skip',
|
|
114
|
+
latencyMs: Date.now() - startedAt,
|
|
115
|
+
turnEpoch,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
105
119
|
const mode = config.participationMode || 'automatic';
|
|
106
120
|
if (mode === 'always') {
|
|
107
121
|
return {
|
|
@@ -116,7 +130,7 @@ async function shouldEngage(ctx) {
|
|
|
116
130
|
turnEpoch,
|
|
117
131
|
};
|
|
118
132
|
}
|
|
119
|
-
if (mode === 'mention_only'
|
|
133
|
+
if (mode === 'mention_only') {
|
|
120
134
|
return {
|
|
121
135
|
decision: 'stay_silent',
|
|
122
136
|
needScore: 0,
|
|
@@ -129,19 +143,6 @@ async function shouldEngage(ctx) {
|
|
|
129
143
|
turnEpoch,
|
|
130
144
|
};
|
|
131
145
|
}
|
|
132
|
-
if (mode === 'mention_only') {
|
|
133
|
-
return {
|
|
134
|
-
decision: 'speak',
|
|
135
|
-
needScore: 1,
|
|
136
|
-
confidence: 1,
|
|
137
|
-
reasonCodes: [msg.repliedToAgent ? 'reply_to_agent' : 'addressed'],
|
|
138
|
-
urgency: 'medium',
|
|
139
|
-
rationale: 'The room is mention-only and platform metadata directly addressed the agent.',
|
|
140
|
-
tokenPath: 'gate_skip',
|
|
141
|
-
latencyMs: Date.now() - startedAt,
|
|
142
|
-
turnEpoch,
|
|
143
|
-
};
|
|
144
|
-
}
|
|
145
146
|
|
|
146
147
|
const threadState = getThreadState(userId, agentId, msg.platform, msg.chatId);
|
|
147
148
|
const roomMessages = loadRecentRoomMessages({
|
|
@@ -25,13 +25,6 @@ function createBehaviorPipeline(deps = {}) {
|
|
|
25
25
|
chatId: msg.chatId,
|
|
26
26
|
isGroup: Boolean(msg.isGroup),
|
|
27
27
|
});
|
|
28
|
-
if (
|
|
29
|
-
msg.isGroup
|
|
30
|
-
&& config.participationModeSource === 'default'
|
|
31
|
-
&& msg.accessPolicyRequireMention === true
|
|
32
|
-
) {
|
|
33
|
-
config.participationMode = 'mention_only';
|
|
34
|
-
}
|
|
35
28
|
return config;
|
|
36
29
|
}
|
|
37
30
|
|
|
@@ -65,6 +58,30 @@ function createBehaviorPipeline(deps = {}) {
|
|
|
65
58
|
const config = effectiveConfig(userId, agentId, msg);
|
|
66
59
|
const turnEpoch = Number(msg.behaviorTurnEpoch)
|
|
67
60
|
|| noteInbound({ userId, agentId, msg });
|
|
61
|
+
if (
|
|
62
|
+
msg.isGroup
|
|
63
|
+
&& msg.accessPolicyAllowUntagged === false
|
|
64
|
+
&& !msg.wasMentioned
|
|
65
|
+
&& !msg.repliedToAgent
|
|
66
|
+
) {
|
|
67
|
+
return {
|
|
68
|
+
engage: false,
|
|
69
|
+
decision: {
|
|
70
|
+
decision: 'stay_silent',
|
|
71
|
+
needScore: 0,
|
|
72
|
+
confidence: 1,
|
|
73
|
+
reasonCodes: ['untagged_disabled_for_shared_space'],
|
|
74
|
+
urgency: 'low',
|
|
75
|
+
rationale: 'Untagged responses are disabled for this shared space.',
|
|
76
|
+
tokenPath: 'gate_skip',
|
|
77
|
+
latencyMs: 0,
|
|
78
|
+
turnEpoch,
|
|
79
|
+
},
|
|
80
|
+
config,
|
|
81
|
+
promptBlocks: [],
|
|
82
|
+
observeResult: null,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
68
85
|
if (config.enabled === false) {
|
|
69
86
|
return {
|
|
70
87
|
engage: true,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const ACCESS_MODES = Object.freeze(['allowlist', 'open', 'disabled']);
|
|
4
|
+
const ACCESS_POLICY_SCHEMA_VERSION = 3;
|
|
4
5
|
const ACCESS_MODE_SET = new Set(ACCESS_MODES);
|
|
5
6
|
const RULE_SCOPES = Object.freeze([
|
|
6
7
|
'user',
|
|
@@ -44,6 +45,7 @@ function capabilityTemplate(overrides = {}) {
|
|
|
44
45
|
supportsDirectPolicy: true,
|
|
45
46
|
supportsSharedPolicy: true,
|
|
46
47
|
supportsMentionGate: false,
|
|
48
|
+
supportsUntaggedGroupToggle: true,
|
|
47
49
|
supportsDiscovery: false,
|
|
48
50
|
directRuleScopes: DIRECT_RULE_SCOPES,
|
|
49
51
|
sharedSpaceRuleScopes: SHARED_SPACE_RULE_SCOPES,
|
|
@@ -55,7 +57,7 @@ function capabilityTemplate(overrides = {}) {
|
|
|
55
57
|
|
|
56
58
|
const PLATFORM_CAPABILITIES = Object.freeze({
|
|
57
59
|
whatsapp: capabilityTemplate({
|
|
58
|
-
supportsMentionGate:
|
|
60
|
+
supportsMentionGate: true,
|
|
59
61
|
supportsDiscovery: true,
|
|
60
62
|
directRuleScopes: Object.freeze(['phone_number', 'user', 'chat']),
|
|
61
63
|
sharedSpaceRuleScopes: Object.freeze(['group', 'chat']),
|
|
@@ -65,6 +67,7 @@ const PLATFORM_CAPABILITIES = Object.freeze({
|
|
|
65
67
|
telnyx: capabilityTemplate({
|
|
66
68
|
supportsSharedPolicy: false,
|
|
67
69
|
supportsMentionGate: false,
|
|
70
|
+
supportsUntaggedGroupToggle: false,
|
|
68
71
|
supportsDiscovery: false,
|
|
69
72
|
directRuleScopes: Object.freeze(['phone_number']),
|
|
70
73
|
sharedSpaceRuleScopes: Object.freeze([]),
|
|
@@ -96,14 +99,14 @@ const PLATFORM_CAPABILITIES = Object.freeze({
|
|
|
96
99
|
manualEntryHint: 'Add a Slack user or channel id.',
|
|
97
100
|
}),
|
|
98
101
|
google_chat: capabilityTemplate({
|
|
99
|
-
supportsMentionGate:
|
|
102
|
+
supportsMentionGate: true,
|
|
100
103
|
supportsDiscovery: false,
|
|
101
104
|
directRuleScopes: Object.freeze(['user', 'chat']),
|
|
102
105
|
sharedSpaceRuleScopes: Object.freeze(['room', 'chat']),
|
|
103
106
|
sharedActorRuleScopes: Object.freeze(['user']),
|
|
104
107
|
}),
|
|
105
108
|
teams: capabilityTemplate({
|
|
106
|
-
supportsMentionGate:
|
|
109
|
+
supportsMentionGate: true,
|
|
107
110
|
supportsDiscovery: false,
|
|
108
111
|
directRuleScopes: Object.freeze(['user', 'chat']),
|
|
109
112
|
sharedSpaceRuleScopes: Object.freeze(['channel', 'chat']),
|
|
@@ -117,7 +120,7 @@ const PLATFORM_CAPABILITIES = Object.freeze({
|
|
|
117
120
|
sharedActorRuleScopes: Object.freeze(['user']),
|
|
118
121
|
}),
|
|
119
122
|
signal: capabilityTemplate({
|
|
120
|
-
supportsMentionGate:
|
|
123
|
+
supportsMentionGate: true,
|
|
121
124
|
supportsDiscovery: false,
|
|
122
125
|
directRuleScopes: Object.freeze(['phone_number', 'user', 'chat']),
|
|
123
126
|
sharedSpaceRuleScopes: Object.freeze(['group', 'chat']),
|
|
@@ -230,12 +233,15 @@ function defaultSharedPolicyForPlatform(platform) {
|
|
|
230
233
|
function createDefaultAccessPolicy(platform) {
|
|
231
234
|
const capabilities = getPlatformAccessCapabilities(platform);
|
|
232
235
|
return {
|
|
236
|
+
schemaVersion: ACCESS_POLICY_SCHEMA_VERSION,
|
|
233
237
|
directPolicy: 'allowlist',
|
|
234
238
|
sharedPolicy: capabilities.supportsSharedPolicy ? 'allowlist' : 'disabled',
|
|
235
|
-
|
|
239
|
+
defaultAllowUntaggedInShared: true,
|
|
236
240
|
directRules: [],
|
|
237
241
|
sharedSpaceRules: [],
|
|
238
242
|
sharedActorRules: [],
|
|
243
|
+
sharedMemberRules: [],
|
|
244
|
+
sharedParticipationRules: [],
|
|
239
245
|
};
|
|
240
246
|
}
|
|
241
247
|
|
|
@@ -253,7 +259,12 @@ function dedupeRules(rules) {
|
|
|
253
259
|
const seen = new Set();
|
|
254
260
|
const result = [];
|
|
255
261
|
for (const rule of rules) {
|
|
256
|
-
const key =
|
|
262
|
+
const key = [
|
|
263
|
+
rule.scope,
|
|
264
|
+
rule.value,
|
|
265
|
+
rule.spaceScope || '',
|
|
266
|
+
rule.spaceValue || '',
|
|
267
|
+
].join(':');
|
|
257
268
|
if (seen.has(key)) continue;
|
|
258
269
|
seen.add(key);
|
|
259
270
|
result.push(rule);
|
|
@@ -261,6 +272,31 @@ function dedupeRules(rules) {
|
|
|
261
272
|
return result;
|
|
262
273
|
}
|
|
263
274
|
|
|
275
|
+
function normalizeSharedMemberRule(rule, actorScopes, spaceScopes) {
|
|
276
|
+
const actorRule = normalizeRule(rule, actorScopes);
|
|
277
|
+
if (!actorRule) return null;
|
|
278
|
+
const spaceScope = normalizeScope(rule.spaceScope);
|
|
279
|
+
if (!RULE_SCOPE_SET.has(spaceScope) || !spaceScopes.has(spaceScope)) return null;
|
|
280
|
+
const spaceValue = sanitizeValue(spaceScope, rule.spaceValue);
|
|
281
|
+
if (!spaceValue) return null;
|
|
282
|
+
const spaceLabel = String(rule.spaceLabel || '').trim();
|
|
283
|
+
return {
|
|
284
|
+
...actorRule,
|
|
285
|
+
spaceScope,
|
|
286
|
+
spaceValue,
|
|
287
|
+
...(spaceLabel ? { spaceLabel } : {}),
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function normalizeSharedParticipationRule(rule, spaceScopes) {
|
|
292
|
+
const normalized = normalizeRule(rule, spaceScopes);
|
|
293
|
+
if (!normalized) return null;
|
|
294
|
+
return {
|
|
295
|
+
...normalized,
|
|
296
|
+
allowUntagged: rule.allowUntagged === true,
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
|
|
264
300
|
function normalizeAccessPolicy(platform, value) {
|
|
265
301
|
const capabilities = getPlatformAccessCapabilities(platform);
|
|
266
302
|
const defaults = createDefaultAccessPolicy(platform);
|
|
@@ -268,28 +304,68 @@ function normalizeAccessPolicy(platform, value) {
|
|
|
268
304
|
const sharedSpaceScopes = new Set(capabilities.sharedSpaceRuleScopes);
|
|
269
305
|
const sharedActorScopes = new Set(capabilities.sharedActorRuleScopes);
|
|
270
306
|
const raw = value && typeof value === 'object' ? value : {};
|
|
307
|
+
let rawSharedSpaceRules = Array.isArray(raw.sharedSpaceRules) ? raw.sharedSpaceRules : [];
|
|
308
|
+
let rawSharedActorRules = Array.isArray(raw.sharedActorRules) ? raw.sharedActorRules : [];
|
|
309
|
+
let rawSharedMemberRules = Array.isArray(raw.sharedMemberRules) ? raw.sharedMemberRules : [];
|
|
310
|
+
const rawSharedParticipationRules = Array.isArray(raw.sharedParticipationRules)
|
|
311
|
+
? raw.sharedParticipationRules
|
|
312
|
+
: [];
|
|
313
|
+
let rawSharedPolicy = raw.sharedPolicy;
|
|
314
|
+
const isLegacyParticipationPolicy = Number(raw.schemaVersion || 0) < 3;
|
|
315
|
+
|
|
316
|
+
if (
|
|
317
|
+
Number(raw.schemaVersion || 0) < 2
|
|
318
|
+
&& rawSharedMemberRules.length === 0
|
|
319
|
+
&& rawSharedActorRules.length > 0
|
|
320
|
+
) {
|
|
321
|
+
if (normalizeMode(rawSharedPolicy, defaults.sharedPolicy) === 'open') {
|
|
322
|
+
rawSharedPolicy = 'allowlist';
|
|
323
|
+
rawSharedSpaceRules = [];
|
|
324
|
+
} else if (rawSharedSpaceRules.length > 0) {
|
|
325
|
+
rawSharedMemberRules = rawSharedActorRules.flatMap((actorRule) =>
|
|
326
|
+
rawSharedSpaceRules.map((spaceRule) => ({
|
|
327
|
+
...actorRule,
|
|
328
|
+
spaceScope: spaceRule.scope,
|
|
329
|
+
spaceValue: spaceRule.value,
|
|
330
|
+
spaceLabel: spaceRule.label,
|
|
331
|
+
})));
|
|
332
|
+
rawSharedSpaceRules = [];
|
|
333
|
+
rawSharedActorRules = [];
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
271
337
|
const normalized = {
|
|
338
|
+
schemaVersion: ACCESS_POLICY_SCHEMA_VERSION,
|
|
272
339
|
directPolicy: normalizeMode(raw.directPolicy, defaults.directPolicy),
|
|
273
|
-
sharedPolicy: normalizeMode(
|
|
274
|
-
|
|
275
|
-
?
|
|
276
|
-
|
|
340
|
+
sharedPolicy: normalizeMode(rawSharedPolicy, defaults.sharedPolicy),
|
|
341
|
+
defaultAllowUntaggedInShared: capabilities.supportsUntaggedGroupToggle
|
|
342
|
+
? (isLegacyParticipationPolicy
|
|
343
|
+
? raw.requireMentionInShared !== true
|
|
344
|
+
: raw.defaultAllowUntaggedInShared !== false)
|
|
345
|
+
: true,
|
|
277
346
|
directRules: dedupeRules((Array.isArray(raw.directRules) ? raw.directRules : [])
|
|
278
347
|
.map((rule) => normalizeRule(rule, directScopes))
|
|
279
348
|
.filter(Boolean)),
|
|
280
|
-
sharedSpaceRules: dedupeRules(
|
|
349
|
+
sharedSpaceRules: dedupeRules(rawSharedSpaceRules
|
|
281
350
|
.map((rule) => normalizeRule(rule, sharedSpaceScopes))
|
|
282
351
|
.filter(Boolean)),
|
|
283
|
-
sharedActorRules: dedupeRules(
|
|
352
|
+
sharedActorRules: dedupeRules(rawSharedActorRules
|
|
284
353
|
.map((rule) => normalizeRule(rule, sharedActorScopes))
|
|
285
354
|
.filter(Boolean)),
|
|
355
|
+
sharedMemberRules: dedupeRules(rawSharedMemberRules
|
|
356
|
+
.map((rule) => normalizeSharedMemberRule(rule, sharedActorScopes, sharedSpaceScopes))
|
|
357
|
+
.filter(Boolean)),
|
|
358
|
+
sharedParticipationRules: dedupeRules(rawSharedParticipationRules
|
|
359
|
+
.map((rule) => normalizeSharedParticipationRule(rule, sharedSpaceScopes))
|
|
360
|
+
.filter(Boolean)),
|
|
286
361
|
};
|
|
287
|
-
|
|
288
362
|
if (!capabilities.supportsSharedPolicy) {
|
|
289
363
|
normalized.sharedPolicy = 'disabled';
|
|
290
|
-
normalized.
|
|
364
|
+
normalized.defaultAllowUntaggedInShared = true;
|
|
291
365
|
normalized.sharedSpaceRules = [];
|
|
292
366
|
normalized.sharedActorRules = [];
|
|
367
|
+
normalized.sharedMemberRules = [];
|
|
368
|
+
normalized.sharedParticipationRules = [];
|
|
293
369
|
}
|
|
294
370
|
|
|
295
371
|
return normalized;
|
|
@@ -318,6 +394,8 @@ function migrateLegacyWhitelist(platform, entries) {
|
|
|
318
394
|
directRules: [],
|
|
319
395
|
sharedSpaceRules: [],
|
|
320
396
|
sharedActorRules: [],
|
|
397
|
+
sharedMemberRules: [],
|
|
398
|
+
sharedParticipationRules: [],
|
|
321
399
|
};
|
|
322
400
|
const list = Array.isArray(entries)
|
|
323
401
|
? entries.map((item) => String(item || '').trim()).filter(Boolean)
|
|
@@ -421,6 +499,8 @@ function migrateLegacyWhitelist(platform, entries) {
|
|
|
421
499
|
directRules: state.directRules,
|
|
422
500
|
sharedSpaceRules: state.sharedSpaceRules,
|
|
423
501
|
sharedActorRules: state.sharedActorRules,
|
|
502
|
+
sharedMemberRules: state.sharedMemberRules,
|
|
503
|
+
sharedParticipationRules: state.sharedParticipationRules,
|
|
424
504
|
});
|
|
425
505
|
}
|
|
426
506
|
|
|
@@ -474,6 +554,29 @@ function contextMatchesRule(rule, context) {
|
|
|
474
554
|
}
|
|
475
555
|
}
|
|
476
556
|
|
|
557
|
+
function contextMatchesSharedMemberRule(rule, context) {
|
|
558
|
+
if (!contextMatchesRule(rule, context)) return false;
|
|
559
|
+
return contextMatchesRule({
|
|
560
|
+
scope: rule.spaceScope,
|
|
561
|
+
value: rule.spaceValue,
|
|
562
|
+
}, context);
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
function allowUntaggedForContext(policy, context) {
|
|
566
|
+
const scopePriority = {
|
|
567
|
+
group: 4,
|
|
568
|
+
channel: 4,
|
|
569
|
+
room: 4,
|
|
570
|
+
chat: 3,
|
|
571
|
+
server: 2,
|
|
572
|
+
};
|
|
573
|
+
const rule = policy.sharedParticipationRules
|
|
574
|
+
.filter((item) => contextMatchesRule(item, context))
|
|
575
|
+
.sort((left, right) =>
|
|
576
|
+
(scopePriority[right.scope] || 0) - (scopePriority[left.scope] || 0))[0];
|
|
577
|
+
return rule ? rule.allowUntagged : policy.defaultAllowUntaggedInShared;
|
|
578
|
+
}
|
|
579
|
+
|
|
477
580
|
function evaluateAccessPolicy(policyInput, context, platform) {
|
|
478
581
|
const capabilities = getPlatformAccessCapabilities(platform);
|
|
479
582
|
const policy = normalizeAccessPolicy(platform, policyInput);
|
|
@@ -485,7 +588,8 @@ function evaluateAccessPolicy(policyInput, context, platform) {
|
|
|
485
588
|
return { allowed: false, reason: 'direct_disabled', policy };
|
|
486
589
|
}
|
|
487
590
|
if (policy.directPolicy === 'allowlist') {
|
|
488
|
-
const directMatch = policy.directRules.some((rule) => contextMatchesRule(rule, context))
|
|
591
|
+
const directMatch = policy.directRules.some((rule) => contextMatchesRule(rule, context))
|
|
592
|
+
|| policy.sharedActorRules.some((rule) => contextMatchesRule(rule, context));
|
|
489
593
|
if (!directMatch) {
|
|
490
594
|
return { allowed: false, reason: 'direct_not_allowed', policy };
|
|
491
595
|
}
|
|
@@ -499,23 +603,21 @@ function evaluateAccessPolicy(policyInput, context, platform) {
|
|
|
499
603
|
}
|
|
500
604
|
if (policy.sharedPolicy === 'allowlist') {
|
|
501
605
|
const sharedSpaceMatch = policy.sharedSpaceRules.some((rule) => contextMatchesRule(rule, context));
|
|
502
|
-
if (!sharedSpaceMatch) {
|
|
503
|
-
return { allowed: false, reason: 'shared_space_not_allowed', policy };
|
|
504
|
-
}
|
|
505
|
-
}
|
|
506
|
-
if (policy.sharedActorRules.length > 0) {
|
|
507
606
|
const sharedActorMatch = policy.sharedActorRules.some((rule) => contextMatchesRule(rule, context));
|
|
508
|
-
|
|
509
|
-
|
|
607
|
+
const sharedMemberMatch = policy.sharedMemberRules.some(
|
|
608
|
+
(rule) => contextMatchesSharedMemberRule(rule, context),
|
|
609
|
+
);
|
|
610
|
+
if (!sharedSpaceMatch && !sharedActorMatch && !sharedMemberMatch) {
|
|
611
|
+
return { allowed: false, reason: 'shared_not_allowed', policy };
|
|
510
612
|
}
|
|
511
613
|
}
|
|
614
|
+
const allowUntagged = allowUntaggedForContext(policy, context);
|
|
512
615
|
return {
|
|
513
616
|
allowed: true,
|
|
514
617
|
reason: 'allowed',
|
|
515
618
|
policy,
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
: 'automatic',
|
|
619
|
+
allowUntagged,
|
|
620
|
+
participationHint: allowUntagged ? 'automatic' : 'mention_only',
|
|
519
621
|
};
|
|
520
622
|
}
|
|
521
623
|
|
|
@@ -533,27 +635,85 @@ function labelForScope(scope) {
|
|
|
533
635
|
}
|
|
534
636
|
}
|
|
535
637
|
|
|
536
|
-
function makeSuggestion({
|
|
638
|
+
function makeSuggestion({
|
|
639
|
+
scope,
|
|
640
|
+
value,
|
|
641
|
+
label,
|
|
642
|
+
bucket,
|
|
643
|
+
spaceScope = null,
|
|
644
|
+
spaceValue = null,
|
|
645
|
+
spaceLabel = null,
|
|
646
|
+
}) {
|
|
537
647
|
if (!scope || !value || !bucket) return null;
|
|
648
|
+
const rule = {
|
|
649
|
+
scope,
|
|
650
|
+
value,
|
|
651
|
+
...(spaceScope && spaceValue ? { spaceScope, spaceValue } : {}),
|
|
652
|
+
...(spaceLabel ? { spaceLabel } : {}),
|
|
653
|
+
};
|
|
538
654
|
return {
|
|
539
655
|
label: label || `Allow ${labelForScope(scope)} ${value}`,
|
|
540
656
|
prefixedId: scope === 'phone_number' ? value : `${scope}:${value}`,
|
|
541
657
|
bucket,
|
|
542
|
-
rule
|
|
658
|
+
rule,
|
|
543
659
|
};
|
|
544
660
|
}
|
|
545
661
|
|
|
662
|
+
function sharedSpaceFromContext(context, options = {}, allowedScopes = []) {
|
|
663
|
+
const scopes = new Set(allowedScopes);
|
|
664
|
+
if (context.channelId && scopes.has('channel')) {
|
|
665
|
+
return {
|
|
666
|
+
scope: 'channel',
|
|
667
|
+
value: context.channelId,
|
|
668
|
+
label: options.channelLabel || context.channelId,
|
|
669
|
+
};
|
|
670
|
+
}
|
|
671
|
+
if (context.roomId && scopes.has('room')) {
|
|
672
|
+
return {
|
|
673
|
+
scope: 'room',
|
|
674
|
+
value: context.roomId,
|
|
675
|
+
label: options.roomLabel || context.roomId,
|
|
676
|
+
};
|
|
677
|
+
}
|
|
678
|
+
if (context.groupId && scopes.has('group')) {
|
|
679
|
+
return {
|
|
680
|
+
scope: 'group',
|
|
681
|
+
value: context.groupId,
|
|
682
|
+
label: options.groupLabel || context.groupId,
|
|
683
|
+
};
|
|
684
|
+
}
|
|
685
|
+
if (context.chatId && scopes.has('chat')) {
|
|
686
|
+
return {
|
|
687
|
+
scope: 'chat',
|
|
688
|
+
value: context.chatId,
|
|
689
|
+
label: context.chatId,
|
|
690
|
+
};
|
|
691
|
+
}
|
|
692
|
+
return null;
|
|
693
|
+
}
|
|
694
|
+
|
|
546
695
|
function buildBlockedSenderSuggestions(platform, context, options = {}) {
|
|
547
696
|
const suggestions = [];
|
|
548
697
|
const senderLabel = String(options.senderName || context.senderId || '').trim();
|
|
549
698
|
const chatId = String(context.chatId || '').trim();
|
|
699
|
+
const capabilities = getPlatformAccessCapabilities(platform);
|
|
700
|
+
const actorScope = context.phoneNumber ? 'phone_number' : 'user';
|
|
701
|
+
const actorValue = String(context.phoneNumber || context.senderId || '').trim();
|
|
702
|
+
const canUseSharedActor = capabilities.sharedActorRuleScopes.includes(actorScope);
|
|
550
703
|
|
|
551
704
|
if (context.isDirect) {
|
|
552
|
-
if (
|
|
705
|
+
if (actorValue && canUseSharedActor) {
|
|
706
|
+
suggestions.push(makeSuggestion({
|
|
707
|
+
scope: actorScope,
|
|
708
|
+
value: actorValue,
|
|
709
|
+
label: `Allow sender everywhere (${senderLabel || actorValue})`,
|
|
710
|
+
bucket: 'sharedActorRules',
|
|
711
|
+
}));
|
|
712
|
+
} else if (context.phoneNumber) {
|
|
553
713
|
suggestions.push(makeSuggestion({
|
|
554
714
|
scope: 'phone_number',
|
|
555
715
|
value: context.phoneNumber,
|
|
556
|
-
label: `Allow
|
|
716
|
+
label: `Allow sender (${senderLabel || context.phoneNumber})`,
|
|
557
717
|
bucket: 'directRules',
|
|
558
718
|
}));
|
|
559
719
|
} else if (context.senderId) {
|
|
@@ -573,56 +733,53 @@ function buildBlockedSenderSuggestions(platform, context, options = {}) {
|
|
|
573
733
|
}));
|
|
574
734
|
}
|
|
575
735
|
} else if (context.isShared) {
|
|
576
|
-
|
|
736
|
+
const sharedSpace = sharedSpaceFromContext(
|
|
737
|
+
context,
|
|
738
|
+
options,
|
|
739
|
+
capabilities.sharedSpaceRuleScopes,
|
|
740
|
+
);
|
|
741
|
+
if (actorValue && canUseSharedActor && sharedSpace) {
|
|
577
742
|
suggestions.push(makeSuggestion({
|
|
578
|
-
scope:
|
|
579
|
-
value:
|
|
580
|
-
label: `Allow sender (${senderLabel ||
|
|
581
|
-
bucket: '
|
|
743
|
+
scope: actorScope,
|
|
744
|
+
value: actorValue,
|
|
745
|
+
label: `Allow sender in this ${labelForScope(sharedSpace.scope)} only (${senderLabel || actorValue})`,
|
|
746
|
+
bucket: 'sharedMemberRules',
|
|
747
|
+
spaceScope: sharedSpace.scope,
|
|
748
|
+
spaceValue: sharedSpace.value,
|
|
749
|
+
spaceLabel: sharedSpace.label,
|
|
582
750
|
}));
|
|
583
|
-
}
|
|
584
|
-
if (context.serverId) {
|
|
585
751
|
suggestions.push(makeSuggestion({
|
|
586
|
-
scope:
|
|
587
|
-
value:
|
|
588
|
-
label: `Allow
|
|
589
|
-
bucket: '
|
|
752
|
+
scope: actorScope,
|
|
753
|
+
value: actorValue,
|
|
754
|
+
label: `Allow sender everywhere (${senderLabel || actorValue})`,
|
|
755
|
+
bucket: 'sharedActorRules',
|
|
590
756
|
}));
|
|
591
757
|
}
|
|
592
|
-
if (
|
|
593
|
-
suggestions.push(makeSuggestion({
|
|
594
|
-
scope: 'channel',
|
|
595
|
-
value: context.channelId,
|
|
596
|
-
label: `Allow channel (${options.channelLabel || context.channelId})`,
|
|
597
|
-
bucket: 'sharedSpaceRules',
|
|
598
|
-
}));
|
|
599
|
-
} else if (context.roomId) {
|
|
600
|
-
suggestions.push(makeSuggestion({
|
|
601
|
-
scope: 'room',
|
|
602
|
-
value: context.roomId,
|
|
603
|
-
label: `Allow room (${options.roomLabel || context.roomId})`,
|
|
604
|
-
bucket: 'sharedSpaceRules',
|
|
605
|
-
}));
|
|
606
|
-
} else if (context.groupId) {
|
|
758
|
+
if (sharedSpace) {
|
|
607
759
|
suggestions.push(makeSuggestion({
|
|
608
|
-
scope:
|
|
609
|
-
value:
|
|
610
|
-
label: `Allow
|
|
611
|
-
bucket: 'sharedSpaceRules',
|
|
612
|
-
}));
|
|
613
|
-
} else if (chatId) {
|
|
614
|
-
suggestions.push(makeSuggestion({
|
|
615
|
-
scope: 'chat',
|
|
616
|
-
value: chatId,
|
|
617
|
-
label: `Allow chat (${chatId})`,
|
|
760
|
+
scope: sharedSpace.scope,
|
|
761
|
+
value: sharedSpace.value,
|
|
762
|
+
label: `Allow everyone in this ${labelForScope(sharedSpace.scope)} (${sharedSpace.label})`,
|
|
618
763
|
bucket: 'sharedSpaceRules',
|
|
619
764
|
}));
|
|
620
765
|
}
|
|
621
766
|
}
|
|
622
767
|
|
|
623
768
|
return suggestions.filter(Boolean).filter((item, index, list) => {
|
|
624
|
-
const key =
|
|
625
|
-
|
|
769
|
+
const key = [
|
|
770
|
+
item.bucket,
|
|
771
|
+
item.rule.scope,
|
|
772
|
+
item.rule.value,
|
|
773
|
+
item.rule.spaceScope || '',
|
|
774
|
+
item.rule.spaceValue || '',
|
|
775
|
+
].join(':');
|
|
776
|
+
return list.findIndex((entry) => [
|
|
777
|
+
entry.bucket,
|
|
778
|
+
entry.rule.scope,
|
|
779
|
+
entry.rule.value,
|
|
780
|
+
entry.rule.spaceScope || '',
|
|
781
|
+
entry.rule.spaceValue || '',
|
|
782
|
+
].join(':') === key) === index;
|
|
626
783
|
});
|
|
627
784
|
}
|
|
628
785
|
|
|
@@ -652,11 +809,22 @@ function summarizeAccessPolicy(platform, policyInput) {
|
|
|
652
809
|
];
|
|
653
810
|
if (capabilities.supportsSharedPolicy) {
|
|
654
811
|
parts.push(`shared spaces ${policy.sharedPolicy}`);
|
|
655
|
-
if (capabilities.
|
|
656
|
-
|
|
812
|
+
if (capabilities.supportsUntaggedGroupToggle) {
|
|
813
|
+
const mentionOnlyCount = policy.sharedParticipationRules
|
|
814
|
+
.filter((rule) => !rule.allowUntagged).length;
|
|
815
|
+
if (!policy.defaultAllowUntaggedInShared) {
|
|
816
|
+
parts.push('untagged responses off by default');
|
|
817
|
+
} else {
|
|
818
|
+
parts.push(mentionOnlyCount > 0
|
|
819
|
+
? `${mentionOnlyCount} tagged-only shared space${mentionOnlyCount === 1 ? '' : 's'}`
|
|
820
|
+
: 'social intelligence for untagged messages');
|
|
821
|
+
}
|
|
657
822
|
}
|
|
658
823
|
}
|
|
659
|
-
const ruleCount = policy.directRules.length
|
|
824
|
+
const ruleCount = policy.directRules.length
|
|
825
|
+
+ policy.sharedSpaceRules.length
|
|
826
|
+
+ policy.sharedActorRules.length
|
|
827
|
+
+ policy.sharedMemberRules.length;
|
|
660
828
|
if (ruleCount > 0) {
|
|
661
829
|
parts.push(`${ruleCount} rule${ruleCount == 1 ? '' : 's'}`);
|
|
662
830
|
}
|
|
@@ -665,10 +833,29 @@ function summarizeAccessPolicy(platform, policyInput) {
|
|
|
665
833
|
|
|
666
834
|
function classifyRecentTarget(platform, row) {
|
|
667
835
|
const chatId = String(row.platform_chat_id || '').trim();
|
|
668
|
-
const sender = String(row.sender || row.sender_id || '').trim();
|
|
669
836
|
const metadata = row.metadata && typeof row.metadata === 'object' ? row.metadata : {};
|
|
837
|
+
const sender = String(
|
|
838
|
+
row.sender
|
|
839
|
+
|| row.sender_id
|
|
840
|
+
|| metadata.sender
|
|
841
|
+
|| metadata.senderId
|
|
842
|
+
|| metadata.sender_id
|
|
843
|
+
|| '',
|
|
844
|
+
).trim();
|
|
670
845
|
const senderName = String(metadata.senderName || metadata.sender_name || row.sender_name || '').trim();
|
|
671
|
-
const groupName = String(
|
|
846
|
+
const groupName = String(
|
|
847
|
+
metadata.groupName
|
|
848
|
+
|| metadata.group_name
|
|
849
|
+
|| metadata.channelName
|
|
850
|
+
|| metadata.channel_name
|
|
851
|
+
|| metadata.roomName
|
|
852
|
+
|| metadata.room_name
|
|
853
|
+
|| metadata.serverName
|
|
854
|
+
|| metadata.server_name
|
|
855
|
+
|| metadata.guildName
|
|
856
|
+
|| metadata.guild_name
|
|
857
|
+
|| '',
|
|
858
|
+
).trim();
|
|
672
859
|
if (!chatId && !sender) return null;
|
|
673
860
|
|
|
674
861
|
const isDirect = !String(metadata.isGroup || '').match(/^(true|1)$/i) && (!chatId || chatId === sender || chatId === `dm_${sender}`);
|
|
@@ -718,6 +905,10 @@ function contextFromMessage(msg) {
|
|
|
718
905
|
const chatId = String(msg.chatId || '').trim();
|
|
719
906
|
const senderId = String(msg.sender || '').trim();
|
|
720
907
|
const normalizedChatId = chatId.startsWith('dm_') ? chatId.slice(3) : chatId;
|
|
908
|
+
const capabilities = getPlatformAccessCapabilities(msg.platform);
|
|
909
|
+
const phoneNumber = capabilities.directRuleScopes.includes('phone_number')
|
|
910
|
+
? normalizePhone(msg.phoneNumber || senderId)
|
|
911
|
+
: '';
|
|
721
912
|
return {
|
|
722
913
|
platform: msg.platform,
|
|
723
914
|
senderId,
|
|
@@ -729,7 +920,7 @@ function contextFromMessage(msg) {
|
|
|
729
920
|
serverId: String(msg.guildId || msg.serverId || '').trim(),
|
|
730
921
|
roomId: String(msg.roomId || '').trim(),
|
|
731
922
|
roleIds: Array.isArray(msg.roleIds) ? msg.roleIds.map(String) : [],
|
|
732
|
-
phoneNumber
|
|
923
|
+
phoneNumber,
|
|
733
924
|
wasMentioned: msg.wasMentioned === true,
|
|
734
925
|
normalizedChatId,
|
|
735
926
|
};
|
|
@@ -532,7 +532,7 @@ async function isAllowedMessagingSender({ io, userId, msg }) {
|
|
|
532
532
|
const policy = parseStoredAccessPolicy(msg.platform, policyRow?.value, legacyRow?.value);
|
|
533
533
|
const decision = evaluateAccessPolicy(policy, contextFromMessage(msg), msg.platform);
|
|
534
534
|
if (decision.allowed) {
|
|
535
|
-
msg.
|
|
535
|
+
msg.accessPolicyAllowUntagged = decision.allowUntagged !== false;
|
|
536
536
|
return true;
|
|
537
537
|
}
|
|
538
538
|
|