neoagent 3.2.1-beta.9 → 3.3.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/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 +59 -4
- package/flutter_app/lib/main_models.dart +171 -22
- package/flutter_app/lib/main_operations.dart +0 -49
- package/flutter_app/lib/main_settings.dart +338 -0
- package/flutter_app/lib/src/backend_client.dart +19 -0
- package/package.json +1 -1
- package/server/db/database.js +2 -2
- package/server/http/routes.js +1 -0
- 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 +85079 -83904
- package/server/routes/behavior.js +80 -0
- package/server/routes/settings.js +4 -0
- package/server/services/agents/manager.js +1 -1
- package/server/services/ai/history.js +1 -1
- package/server/services/ai/loop/agent_engine_core.js +110 -17
- package/server/services/ai/loop/blank_recovery.js +5 -4
- package/server/services/ai/loop/completion_judge.js +226 -33
- package/server/services/ai/loop/conversation_loop.js +92 -34
- package/server/services/ai/loop/messaging_delivery.js +47 -0
- package/server/services/ai/loop/progress_classification.js +2 -0
- package/server/services/ai/loopPolicy.js +24 -2
- package/server/services/ai/messagingFallback.js +17 -17
- package/server/services/ai/model_failure_cache.js +7 -0
- package/server/services/ai/systemPrompt.js +31 -122
- package/server/services/ai/taskAnalysis.js +86 -12
- package/server/services/ai/toolEvidence.js +68 -224
- package/server/services/ai/tools.js +60 -19
- package/server/services/behavior/config.js +251 -0
- package/server/services/behavior/defaults.js +68 -0
- package/server/services/behavior/delivery.js +176 -0
- package/server/services/behavior/index.js +43 -0
- package/server/services/behavior/model_client.js +35 -0
- package/server/services/behavior/modules/agent_identity.js +37 -0
- package/server/services/behavior/modules/channel_style.js +28 -0
- package/server/services/behavior/modules/index.js +29 -0
- package/server/services/behavior/modules/norms.js +101 -0
- package/server/services/behavior/modules/persona.js +172 -0
- package/server/services/behavior/modules/persona_prompt.js +238 -0
- package/server/services/behavior/modules/social_memory.js +86 -0
- package/server/services/behavior/modules/social_observability.js +94 -0
- package/server/services/behavior/modules/social_signals.js +41 -0
- package/server/services/behavior/modules/theory_of_mind.js +118 -0
- package/server/services/behavior/modules/turn_taking.js +238 -0
- package/server/services/behavior/pipeline.js +341 -0
- package/server/services/behavior/registry.js +78 -0
- package/server/services/behavior/signals.js +107 -0
- package/server/services/behavior/state.js +99 -0
- package/server/services/behavior/system_prompt.js +75 -0
- package/server/services/memory/manager.js +14 -33
- package/server/services/messaging/access_policy.js +269 -74
- package/server/services/messaging/automation.js +158 -27
- package/server/services/messaging/discord.js +11 -1
- package/server/services/messaging/formatting_guides.js +5 -4
- package/server/services/messaging/http_platforms.js +73 -28
- package/server/services/messaging/inbound_queue.js +74 -13
- package/server/services/messaging/inbound_store.js +33 -0
- package/server/services/messaging/manager.js +57 -5
- package/server/services/messaging/telegram.js +10 -1
- package/server/services/messaging/whatsapp.js +11 -0
- package/server/services/social_reach/channels/social_video.js +1 -1
- package/server/services/social_video/captions.js +2 -2
- package/server/services/social_video/service.js +194 -29
- package/server/services/voice/message.js +1 -1
- package/server/services/voice/runtime.js +2 -2
- package/server/utils/logger.js +19 -0
- package/server/services/ai/terminal_reply.js +0 -57
|
@@ -1824,6 +1824,13 @@ class MemoryManager {
|
|
|
1824
1824
|
const agentId = this._agentId(userId, options);
|
|
1825
1825
|
const normalized = normalizeMemoryCandidates(candidates);
|
|
1826
1826
|
const memoryIds = [];
|
|
1827
|
+
const scope = normalizeScope(options.scope, agentId);
|
|
1828
|
+
const sourceRef = normalizeSourceRef(options.sourceRef || {
|
|
1829
|
+
sourceType: 'conversation_consolidation',
|
|
1830
|
+
sourceId: options.runId || options.conversationId || null,
|
|
1831
|
+
sourceLabel: options.conversationId ? 'Conversation memory' : 'Agent memory',
|
|
1832
|
+
});
|
|
1833
|
+
const metadata = parseJsonObject(options.metadata, {});
|
|
1827
1834
|
|
|
1828
1835
|
for (const candidate of normalized) {
|
|
1829
1836
|
const memoryId = await this.saveMemory(
|
|
@@ -1835,22 +1842,16 @@ class MemoryManager {
|
|
|
1835
1842
|
agentId,
|
|
1836
1843
|
confidence: candidate.confidence,
|
|
1837
1844
|
facts: [candidate],
|
|
1838
|
-
sourceRef
|
|
1839
|
-
|
|
1840
|
-
sourceId: options.runId || options.conversationId || null,
|
|
1841
|
-
sourceLabel: options.conversationId ? 'Conversation memory' : 'Agent memory',
|
|
1842
|
-
},
|
|
1843
|
-
scope: {
|
|
1844
|
-
scopeType: 'agent',
|
|
1845
|
-
scopeId: agentId,
|
|
1846
|
-
},
|
|
1845
|
+
sourceRef,
|
|
1846
|
+
scope,
|
|
1847
1847
|
metadata: {
|
|
1848
1848
|
relation: candidate.relation,
|
|
1849
1849
|
isStatic: candidate.isStatic,
|
|
1850
1850
|
evidence: candidate.evidence || null,
|
|
1851
|
-
trustLevel: 'user_or_verified_conversation',
|
|
1851
|
+
trustLevel: metadata.trustLevel || 'user_or_verified_conversation',
|
|
1852
1852
|
conversationId: options.conversationId || null,
|
|
1853
1853
|
runId: options.runId || null,
|
|
1854
|
+
...metadata,
|
|
1854
1855
|
},
|
|
1855
1856
|
signal: options.signal,
|
|
1856
1857
|
},
|
|
@@ -2971,30 +2972,10 @@ class MemoryManager {
|
|
|
2971
2972
|
async buildContext(userId = null, options = {}) {
|
|
2972
2973
|
let ctx = '';
|
|
2973
2974
|
const agentId = this._agentId(userId, options);
|
|
2974
|
-
|
|
2975
|
-
const behaviorNotes = this.getAssistantBehaviorNotes(userId, { agentId });
|
|
2976
|
-
if (behaviorNotes) {
|
|
2977
|
-
ctx += `## Assistant Behavior Notes\n`;
|
|
2978
|
-
ctx += `These are durable preferences for how the assistant should usually behave. Follow system rules and the active user request first.\n`;
|
|
2979
|
-
ctx += `${behaviorNotes}\n\n`;
|
|
2980
|
-
}
|
|
2981
|
-
|
|
2982
|
-
if (userId != null) {
|
|
2983
|
-
const selfState = this.getAssistantSelfState(userId, { agentId });
|
|
2984
|
-
if (Object.keys(selfState.identity || {}).length || Object.keys(selfState.focus || {}).length) {
|
|
2985
|
-
ctx += `## Assistant Self State\n`;
|
|
2986
|
-
if (Object.keys(selfState.identity || {}).length) {
|
|
2987
|
-
ctx += `Identity: ${JSON.stringify(selfState.identity)}\n`;
|
|
2988
|
-
}
|
|
2989
|
-
if (Object.keys(selfState.focus || {}).length) {
|
|
2990
|
-
ctx += `Focus: ${JSON.stringify(selfState.focus)}\n`;
|
|
2991
|
-
}
|
|
2992
|
-
ctx += '\n';
|
|
2993
|
-
}
|
|
2994
|
-
}
|
|
2975
|
+
const sharedAudience = options.audience === 'shared';
|
|
2995
2976
|
|
|
2996
2977
|
// 2. Core memory — always-relevant user facts
|
|
2997
|
-
if (userId != null) {
|
|
2978
|
+
if (userId != null && !sharedAudience) {
|
|
2998
2979
|
const core = this.getCoreMemory(userId, { agentId });
|
|
2999
2980
|
const filteredCore = Object.fromEntries(
|
|
3000
2981
|
Object.entries(core).filter(([key]) => key !== 'active_context')
|
|
@@ -3009,7 +2990,7 @@ class MemoryManager {
|
|
|
3009
2990
|
}
|
|
3010
2991
|
}
|
|
3011
2992
|
|
|
3012
|
-
if (userId != null) {
|
|
2993
|
+
if (userId != null && !sharedAudience) {
|
|
3013
2994
|
const profile = this.getUserProfile(userId, { agentId });
|
|
3014
2995
|
if (profile.static.length || profile.dynamic.length) {
|
|
3015
2996
|
ctx += `## Auto-Maintained User Profile\n`;
|
|
@@ -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,20 +603,22 @@ 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
|
}
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
614
|
+
const allowUntagged = allowUntaggedForContext(policy, context);
|
|
615
|
+
return {
|
|
616
|
+
allowed: true,
|
|
617
|
+
reason: 'allowed',
|
|
618
|
+
policy,
|
|
619
|
+
allowUntagged,
|
|
620
|
+
participationHint: allowUntagged ? 'automatic' : 'mention_only',
|
|
621
|
+
};
|
|
516
622
|
}
|
|
517
623
|
|
|
518
624
|
return { allowed: false, reason: 'unsupported_context', policy };
|
|
@@ -529,27 +635,85 @@ function labelForScope(scope) {
|
|
|
529
635
|
}
|
|
530
636
|
}
|
|
531
637
|
|
|
532
|
-
function makeSuggestion({
|
|
638
|
+
function makeSuggestion({
|
|
639
|
+
scope,
|
|
640
|
+
value,
|
|
641
|
+
label,
|
|
642
|
+
bucket,
|
|
643
|
+
spaceScope = null,
|
|
644
|
+
spaceValue = null,
|
|
645
|
+
spaceLabel = null,
|
|
646
|
+
}) {
|
|
533
647
|
if (!scope || !value || !bucket) return null;
|
|
648
|
+
const rule = {
|
|
649
|
+
scope,
|
|
650
|
+
value,
|
|
651
|
+
...(spaceScope && spaceValue ? { spaceScope, spaceValue } : {}),
|
|
652
|
+
...(spaceLabel ? { spaceLabel } : {}),
|
|
653
|
+
};
|
|
534
654
|
return {
|
|
535
655
|
label: label || `Allow ${labelForScope(scope)} ${value}`,
|
|
536
656
|
prefixedId: scope === 'phone_number' ? value : `${scope}:${value}`,
|
|
537
657
|
bucket,
|
|
538
|
-
rule
|
|
658
|
+
rule,
|
|
539
659
|
};
|
|
540
660
|
}
|
|
541
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
|
+
|
|
542
695
|
function buildBlockedSenderSuggestions(platform, context, options = {}) {
|
|
543
696
|
const suggestions = [];
|
|
544
697
|
const senderLabel = String(options.senderName || context.senderId || '').trim();
|
|
545
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);
|
|
546
703
|
|
|
547
704
|
if (context.isDirect) {
|
|
548
|
-
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) {
|
|
549
713
|
suggestions.push(makeSuggestion({
|
|
550
714
|
scope: 'phone_number',
|
|
551
715
|
value: context.phoneNumber,
|
|
552
|
-
label: `Allow
|
|
716
|
+
label: `Allow sender (${senderLabel || context.phoneNumber})`,
|
|
553
717
|
bucket: 'directRules',
|
|
554
718
|
}));
|
|
555
719
|
} else if (context.senderId) {
|
|
@@ -569,56 +733,53 @@ function buildBlockedSenderSuggestions(platform, context, options = {}) {
|
|
|
569
733
|
}));
|
|
570
734
|
}
|
|
571
735
|
} else if (context.isShared) {
|
|
572
|
-
|
|
736
|
+
const sharedSpace = sharedSpaceFromContext(
|
|
737
|
+
context,
|
|
738
|
+
options,
|
|
739
|
+
capabilities.sharedSpaceRuleScopes,
|
|
740
|
+
);
|
|
741
|
+
if (actorValue && canUseSharedActor && sharedSpace) {
|
|
573
742
|
suggestions.push(makeSuggestion({
|
|
574
|
-
scope:
|
|
575
|
-
value:
|
|
576
|
-
label: `Allow sender (${senderLabel ||
|
|
577
|
-
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,
|
|
578
750
|
}));
|
|
579
|
-
}
|
|
580
|
-
if (context.serverId) {
|
|
581
751
|
suggestions.push(makeSuggestion({
|
|
582
|
-
scope:
|
|
583
|
-
value:
|
|
584
|
-
label: `Allow
|
|
585
|
-
bucket: '
|
|
752
|
+
scope: actorScope,
|
|
753
|
+
value: actorValue,
|
|
754
|
+
label: `Allow sender everywhere (${senderLabel || actorValue})`,
|
|
755
|
+
bucket: 'sharedActorRules',
|
|
586
756
|
}));
|
|
587
757
|
}
|
|
588
|
-
if (
|
|
589
|
-
suggestions.push(makeSuggestion({
|
|
590
|
-
scope: 'channel',
|
|
591
|
-
value: context.channelId,
|
|
592
|
-
label: `Allow channel (${options.channelLabel || context.channelId})`,
|
|
593
|
-
bucket: 'sharedSpaceRules',
|
|
594
|
-
}));
|
|
595
|
-
} else if (context.roomId) {
|
|
596
|
-
suggestions.push(makeSuggestion({
|
|
597
|
-
scope: 'room',
|
|
598
|
-
value: context.roomId,
|
|
599
|
-
label: `Allow room (${options.roomLabel || context.roomId})`,
|
|
600
|
-
bucket: 'sharedSpaceRules',
|
|
601
|
-
}));
|
|
602
|
-
} else if (context.groupId) {
|
|
758
|
+
if (sharedSpace) {
|
|
603
759
|
suggestions.push(makeSuggestion({
|
|
604
|
-
scope:
|
|
605
|
-
value:
|
|
606
|
-
label: `Allow
|
|
607
|
-
bucket: 'sharedSpaceRules',
|
|
608
|
-
}));
|
|
609
|
-
} else if (chatId) {
|
|
610
|
-
suggestions.push(makeSuggestion({
|
|
611
|
-
scope: 'chat',
|
|
612
|
-
value: chatId,
|
|
613
|
-
label: `Allow chat (${chatId})`,
|
|
760
|
+
scope: sharedSpace.scope,
|
|
761
|
+
value: sharedSpace.value,
|
|
762
|
+
label: `Allow everyone in this ${labelForScope(sharedSpace.scope)} (${sharedSpace.label})`,
|
|
614
763
|
bucket: 'sharedSpaceRules',
|
|
615
764
|
}));
|
|
616
765
|
}
|
|
617
766
|
}
|
|
618
767
|
|
|
619
768
|
return suggestions.filter(Boolean).filter((item, index, list) => {
|
|
620
|
-
const key =
|
|
621
|
-
|
|
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;
|
|
622
783
|
});
|
|
623
784
|
}
|
|
624
785
|
|
|
@@ -648,11 +809,22 @@ function summarizeAccessPolicy(platform, policyInput) {
|
|
|
648
809
|
];
|
|
649
810
|
if (capabilities.supportsSharedPolicy) {
|
|
650
811
|
parts.push(`shared spaces ${policy.sharedPolicy}`);
|
|
651
|
-
if (capabilities.
|
|
652
|
-
|
|
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
|
+
}
|
|
653
822
|
}
|
|
654
823
|
}
|
|
655
|
-
const ruleCount = policy.directRules.length
|
|
824
|
+
const ruleCount = policy.directRules.length
|
|
825
|
+
+ policy.sharedSpaceRules.length
|
|
826
|
+
+ policy.sharedActorRules.length
|
|
827
|
+
+ policy.sharedMemberRules.length;
|
|
656
828
|
if (ruleCount > 0) {
|
|
657
829
|
parts.push(`${ruleCount} rule${ruleCount == 1 ? '' : 's'}`);
|
|
658
830
|
}
|
|
@@ -661,10 +833,29 @@ function summarizeAccessPolicy(platform, policyInput) {
|
|
|
661
833
|
|
|
662
834
|
function classifyRecentTarget(platform, row) {
|
|
663
835
|
const chatId = String(row.platform_chat_id || '').trim();
|
|
664
|
-
const sender = String(row.sender || row.sender_id || '').trim();
|
|
665
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();
|
|
666
845
|
const senderName = String(metadata.senderName || metadata.sender_name || row.sender_name || '').trim();
|
|
667
|
-
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();
|
|
668
859
|
if (!chatId && !sender) return null;
|
|
669
860
|
|
|
670
861
|
const isDirect = !String(metadata.isGroup || '').match(/^(true|1)$/i) && (!chatId || chatId === sender || chatId === `dm_${sender}`);
|
|
@@ -714,6 +905,10 @@ function contextFromMessage(msg) {
|
|
|
714
905
|
const chatId = String(msg.chatId || '').trim();
|
|
715
906
|
const senderId = String(msg.sender || '').trim();
|
|
716
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
|
+
: '';
|
|
717
912
|
return {
|
|
718
913
|
platform: msg.platform,
|
|
719
914
|
senderId,
|
|
@@ -725,7 +920,7 @@ function contextFromMessage(msg) {
|
|
|
725
920
|
serverId: String(msg.guildId || msg.serverId || '').trim(),
|
|
726
921
|
roomId: String(msg.roomId || '').trim(),
|
|
727
922
|
roleIds: Array.isArray(msg.roleIds) ? msg.roleIds.map(String) : [],
|
|
728
|
-
phoneNumber
|
|
923
|
+
phoneNumber,
|
|
729
924
|
wasMentioned: msg.wasMentioned === true,
|
|
730
925
|
normalizedChatId,
|
|
731
926
|
};
|