evolcore 0.0.21 → 0.0.22
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/CHANGELOG.md +43 -0
- package/bin/codex-managed-hook.mjs +3 -0
- package/bin/install-codex-managed-hooks.mjs +3 -1
- package/dist/agents/claude-runner.js +14 -0
- package/dist/agents/codex-app-server-client.js +31 -5
- package/dist/agents/codex-runner.js +926 -121
- package/dist/aun/outbox.js +7 -0
- package/dist/channels/aun.js +209 -35
- package/dist/cli/daemon-commands.js +29 -8
- package/dist/cli/task-context.js +4 -0
- package/dist/cli/trigger-command.js +13 -4
- package/dist/config/config-field-policy.js +3 -0
- package/dist/config/config-manager.js +32 -5
- package/dist/config/contact-book-store.js +25 -3
- package/dist/core/auth/agent-delegation.js +12 -0
- package/dist/core/auth/auth-gateway.js +8 -0
- package/dist/core/auth/authorization-audit.js +66 -6
- package/dist/core/bootstrap-messages.js +8 -0
- package/dist/core/bootstrap-service.js +93 -25
- package/dist/core/command/command-handler.js +21 -0
- package/dist/core/command/menu-handler.js +9 -0
- package/dist/core/command/menu-protocol.js +1 -1
- package/dist/core/command/slash-handler.js +41 -18
- package/dist/core/data-migration.js +11 -1
- package/dist/core/event-catalog.js +32 -0
- package/dist/core/handoff/runtime.js +23 -3
- package/dist/core/message/im-renderer.js +7 -3
- package/dist/core/message/message-bridge.js +60 -2
- package/dist/core/message/message-log.js +33 -0
- package/dist/core/message/message-queue.js +21 -0
- package/dist/core/message/response-engine.js +172 -41
- package/dist/core/permission/ec-command-parser.js +272 -70
- package/dist/core/permission/protected-paths.js +11 -10
- package/dist/core/permission/tool-error-code.js +12 -0
- package/dist/core/permission/tool-policy.js +46 -5
- package/dist/core/session/session-manager.js +30 -0
- package/dist/core/session/session-renew.js +18 -1
- package/dist/core/session/session-turn-coordinator.js +5 -1
- package/dist/index.js +64 -5
- package/dist/ipc.js +97 -17
- package/dist/paths.js +18 -0
- package/dist/response-system/engines/v1/proactive-flow.js +7 -2
- package/dist/stats/price-resolver.js +4 -0
- package/dist/trigger/feedback.js +14 -2
- package/dist/trigger/parser.js +10 -1
- package/dist/trigger/scheduler.js +20 -3
- package/dist/utils/logger.js +9 -4
- package/dist/utils/tool-summary.js +59 -0
- package/dist/utils/windows-shell-trust.js +201 -0
- package/kits/docs/evolcore/INDEX.md +2 -2
- package/kits/docs/evolcore/agent-create.md +146 -0
- package/kits/docs/evolcore/agent.md +6 -0
- package/kits/docs/evolcore/group-collaboration.md +251 -0
- package/kits/docs/evolcore/group-rules.md +1 -19
- package/kits/docs/evolcore/group.md +3 -1
- package/kits/docs/evolcore/trigger.md +6 -3
- package/kits/docs/prompt-loading-architecture.md +6 -0
- package/kits/eck_message_manifest.json +6 -6
- package/kits/schemas/_meta.json +3 -2
- package/kits/schemas/agent-config.schema.12.json +427 -0
- package/kits/templates/message-fragments/item.md +1 -1
- package/kits/templates/system-fragments/bootstrap.md +2 -1
- package/kits/templates/system-fragments/commands.md +2 -2
- package/package.json +2 -2
package/dist/aun/outbox.js
CHANGED
|
@@ -215,6 +215,13 @@ export function findByDedupeKey(aid, dedupeKey, opts = {}, queue = 'default') {
|
|
|
215
215
|
: load(aid, queue);
|
|
216
216
|
return entries.find(entry => entry.dedupeKey === dedupeKey);
|
|
217
217
|
}
|
|
218
|
+
/** Find all durable entries belonging to an operation family. */
|
|
219
|
+
export function findByDedupePrefix(aid, dedupePrefix, opts = {}, queue = 'default') {
|
|
220
|
+
const entries = opts.includeTerminal
|
|
221
|
+
? readEntries(aid, queue).filter(entry => !isExpired(entry))
|
|
222
|
+
: load(aid, queue);
|
|
223
|
+
return entries.filter(entry => typeof entry.dedupeKey === 'string' && entry.dedupeKey.startsWith(dedupePrefix));
|
|
224
|
+
}
|
|
218
225
|
export function cleanup(aid, queue = 'default') {
|
|
219
226
|
const all = readEntries(aid, queue);
|
|
220
227
|
let expired = 0;
|
package/dist/channels/aun.js
CHANGED
|
@@ -34,7 +34,8 @@ import { deriveCausation, normalizeCausation } from '../core/causation/context.j
|
|
|
34
34
|
import { recordCausationSpan } from '../core/causation/audit.js';
|
|
35
35
|
import { refreshAgentDisplayName, resolveAgentDisplayName } from '../aun/aid/agentmd.js';
|
|
36
36
|
import { readInstalledEvolcoreVersion } from '../utils/evolcore-version.js';
|
|
37
|
-
import {
|
|
37
|
+
import { toolInputForDisplay } from '../utils/tool-summary.js';
|
|
38
|
+
import { bindPostBootstrapWelcomeOutboxSession, hasPendingPostBootstrapWelcomeOutbox, bootstrapInitialMessageOperationPrefix, postBootstrapWelcomeOperationId, preparePostBootstrapWelcomeOutbox, } from '../core/bootstrap-messages.js';
|
|
38
39
|
import { hasMentionAll, mentionEntryAids, mentionEntryTargets, } from '../core/message/mention-schema.js';
|
|
39
40
|
import { normalizeAunMentionEntries, } from '../aun/msg/mention-schema.js';
|
|
40
41
|
import { isDeliveryTarget, sameDeliveryTarget } from '../core/message/message-utils.js';
|
|
@@ -45,6 +46,9 @@ const AUN_INBOUND_DEDUP_TTL_MS = 7 * 24 * 60 * 60 * 1000;
|
|
|
45
46
|
const IMAGE_MIME_TYPE = /^image\/[!#$%&'*+.^_`|~0-9A-Za-z-]+$/i;
|
|
46
47
|
const AUN_ATTACHMENT_DOWNLOAD_ATTEMPTS = 3;
|
|
47
48
|
const AUN_ATTACHMENT_RETRY_DELAYS_MS = [250, 750];
|
|
49
|
+
const OBSERVER_GROUP_MEMBERS_TTL_MS = 60_000;
|
|
50
|
+
const OBSERVER_GROUP_MEMBERS_PAGE_SIZE = 200;
|
|
51
|
+
const OBSERVER_GROUP_MEMBERS_MAX_PAGES = 100;
|
|
48
52
|
const AUN_SEND_LOG_TEXT_MAX_LENGTH = 60;
|
|
49
53
|
export function formatAunSendLogText(text) {
|
|
50
54
|
return text.replace(/\s+/gu, ' ').trim().slice(0, AUN_SEND_LOG_TEXT_MAX_LENGTH);
|
|
@@ -1062,6 +1066,18 @@ export class AUNChannel {
|
|
|
1062
1066
|
.replace(/[ \t]+/g, ' ')
|
|
1063
1067
|
.trim();
|
|
1064
1068
|
}
|
|
1069
|
+
/** Normalize a command after a client-rendered friendly mention label. */
|
|
1070
|
+
normalizeSlashCommandText(text) {
|
|
1071
|
+
const trimmed = text.trim();
|
|
1072
|
+
const leadingLabels = trimmed.match(/^(?:@[^\s]+\s+)+(?=\/)/u);
|
|
1073
|
+
const commandText = leadingLabels
|
|
1074
|
+
? trimmed.slice(leadingLabels[0].length).trimStart()
|
|
1075
|
+
: trimmed;
|
|
1076
|
+
// Keep the command contract consistent for mixed friendly/canonical @
|
|
1077
|
+
// tokens: selector labels are removed above, then known AID-shaped tokens
|
|
1078
|
+
// anywhere in the command are removed by the existing helper.
|
|
1079
|
+
return this.stripAllMentions(commandText);
|
|
1080
|
+
}
|
|
1065
1081
|
parsePayloadMentionsOrReject(payload, context, messageId, seq) {
|
|
1066
1082
|
const raw = payload && typeof payload === 'object' && !Array.isArray(payload)
|
|
1067
1083
|
? payload.mentions
|
|
@@ -1277,6 +1293,8 @@ export class AUNChannel {
|
|
|
1277
1293
|
/** 撤权可能先于 outbox 卡片投递完成;迟到卡片注册后立即撤回。 */
|
|
1278
1294
|
invalidatedInteractions = new Map();
|
|
1279
1295
|
mentionModeResolver;
|
|
1296
|
+
/** Agent-level policy controlling whether group slash commands require structured mentions. */
|
|
1297
|
+
groupSlashRequireMentionsResolver;
|
|
1280
1298
|
static PROACTIVE_ALLOW_TYPES = new Set([
|
|
1281
1299
|
'text', 'quote', 'image', 'video', 'voice', 'file', 'json',
|
|
1282
1300
|
'merge', 'link', 'location', 'personal_card',
|
|
@@ -1401,6 +1419,14 @@ export class AUNChannel {
|
|
|
1401
1419
|
}
|
|
1402
1420
|
}
|
|
1403
1421
|
async _initClientInner() {
|
|
1422
|
+
this.observerGroupMembers.clear();
|
|
1423
|
+
for (const groupId of new Set([
|
|
1424
|
+
...this.observerGroupMembershipEpoch.keys(),
|
|
1425
|
+
...this.observerGroupMemberFetches.keys(),
|
|
1426
|
+
])) {
|
|
1427
|
+
this.observerGroupMembershipEpoch.set(groupId, (this.observerGroupMembershipEpoch.get(groupId) ?? 0) + 1);
|
|
1428
|
+
}
|
|
1429
|
+
this.observerGroupMemberFetches.clear();
|
|
1404
1430
|
// Clean up existing client if any
|
|
1405
1431
|
if (this.client) {
|
|
1406
1432
|
this.trace('OUT', 'client.close', { reason: 'initClient' });
|
|
@@ -1469,6 +1495,15 @@ export class AUNChannel {
|
|
|
1469
1495
|
logger.debug(`${this.logPrefix()}[DIAG] group.message_created: group_id=${gid} sender=${sender}`);
|
|
1470
1496
|
this.handleIncomingGroupMessage(data);
|
|
1471
1497
|
});
|
|
1498
|
+
client.on('group.changed', (data) => {
|
|
1499
|
+
if (!isCurrentClient())
|
|
1500
|
+
return;
|
|
1501
|
+
const record = data && typeof data === 'object' ? data : {};
|
|
1502
|
+
const envelope = record.envelope && typeof record.envelope === 'object'
|
|
1503
|
+
? record.envelope
|
|
1504
|
+
: {};
|
|
1505
|
+
this.invalidateObserverGroupMembers(envelope.group_id ?? record.group_id);
|
|
1506
|
+
});
|
|
1472
1507
|
}
|
|
1473
1508
|
const handleSdkState = (source) => (data) => {
|
|
1474
1509
|
if (!isCurrentClient())
|
|
@@ -2034,7 +2069,7 @@ export class AUNChannel {
|
|
|
2034
2069
|
// 显式排除 observer.inject:它是 owner 对本 agent 的控制消息,不应镜像给观察者
|
|
2035
2070
|
// (即便日后 from-owner 排除规则调整,也不会泄漏)。
|
|
2036
2071
|
if (inboundType !== AUNChannel.INJECT_REQUEST_TYPE) {
|
|
2037
|
-
this.forwardInbound(msg);
|
|
2072
|
+
void this.forwardInbound(msg);
|
|
2038
2073
|
}
|
|
2039
2074
|
// 回声过滤:自己发出的消息会被 gateway fanout 回来,
|
|
2040
2075
|
// 只有 from_aid == self 且 chat_id 不匹配时才丢弃(说明是其它实例发的)
|
|
@@ -2205,7 +2240,7 @@ export class AUNChannel {
|
|
|
2205
2240
|
}
|
|
2206
2241
|
// Observer forward (inbound):群聊消息在所有过滤之前转发原始明文 payload。
|
|
2207
2242
|
// forwardInbound 内部排除 self-echo 与 from-owner。
|
|
2208
|
-
this.forwardInbound(msg);
|
|
2243
|
+
void this.forwardInbound(msg);
|
|
2209
2244
|
logger.debug(`${this.logPrefix()}[DIAG-GRP] full_msg=${JSON.stringify(msg).substring(0, 500)}`);
|
|
2210
2245
|
if (!groupId || !senderAid) {
|
|
2211
2246
|
this.acknowledgeImmediately(messageId, seq);
|
|
@@ -2316,9 +2351,13 @@ export class AUNChannel {
|
|
|
2316
2351
|
const firstLineGroup = text.split('\n')[0] || '';
|
|
2317
2352
|
const hasEvolCoreTraceGroup = /\[EvolCore\.(receive|reply|agent)\]/.test(text);
|
|
2318
2353
|
const isEchoMsg = /echo/i.test(firstLineGroup) && !hasEvolCoreTraceGroup;
|
|
2354
|
+
const slashCommandText = this.normalizeSlashCommandText(text);
|
|
2319
2355
|
// 命令判定:剥离所有 @ 后看是否 / 开头(多 @ 场景如 @a @b /status 也能正确识别)。
|
|
2320
2356
|
// echo 消息走独立的 trace 流程,不参与命令语义判定。
|
|
2321
|
-
const isCommandMsg = !isEchoMsg &&
|
|
2357
|
+
const isCommandMsg = !isEchoMsg && slashCommandText.startsWith('/');
|
|
2358
|
+
const groupSlashRequireMentions = this.groupSlashRequireMentionsResolver
|
|
2359
|
+
? await this.groupSlashRequireMentionsResolver().catch(() => undefined) ?? true
|
|
2360
|
+
: true;
|
|
2322
2361
|
if (isEchoMsg) {
|
|
2323
2362
|
// 短 echo(≤10 字符)已在前面的快速通道命中并 return,这里只处理长 echo
|
|
2324
2363
|
// >10 字符:追加 trace,存 pending echo,跳过 mention 过滤继续走 Agent 流程
|
|
@@ -2345,9 +2384,10 @@ export class AUNChannel {
|
|
|
2345
2384
|
}
|
|
2346
2385
|
else {
|
|
2347
2386
|
// 非 echo 消息:mention 标记(不再过滤,交给响应模式决定)
|
|
2348
|
-
// slash
|
|
2349
|
-
//
|
|
2350
|
-
const enforceMention = mentionMode === 'mention-only'
|
|
2387
|
+
// slash 命令默认要求结构化 @;Agent 可通过
|
|
2388
|
+
// groupSlashRequireMentions=false 选择仅按正文命令格式处理。
|
|
2389
|
+
const enforceMention = (mentionMode === 'mention-only' && !isCommandMsg)
|
|
2390
|
+
|| (isCommandMsg && groupSlashRequireMentions);
|
|
2351
2391
|
const isMentioned = mentionedSelf || mentionedAll;
|
|
2352
2392
|
// 过滤逻辑下移到响应层:这里只标记,不过滤
|
|
2353
2393
|
// 但为了保持现有行为兼容(避免大量未 @ 消息涌入),暂时保留过滤
|
|
@@ -2355,14 +2395,14 @@ export class AUNChannel {
|
|
|
2355
2395
|
if (enforceMention && !isMentioned && !isOwnHandoff) {
|
|
2356
2396
|
this.acknowledgeImmediately(messageId, seq);
|
|
2357
2397
|
this.appendInboundPayloadJsonl(groupId, senderAid, messageId, msgEncrypted, true, logDescriptor, threadId, receivedAt, seq);
|
|
2358
|
-
logger.info(`${this.logPrefix()} Group dropped: unmentioned (group=${groupId} sender=${senderAid} mid=${messageId} mentionMode=${mentionMode} isCommand=${isCommandMsg} textPreview=${JSON.stringify(text.slice(0, 80))})`);
|
|
2398
|
+
logger.info(`${this.logPrefix()} Group dropped: unmentioned (group=${groupId} sender=${senderAid} mid=${messageId} mentionMode=${mentionMode} slashRequireMentions=${groupSlashRequireMentions} isCommand=${isCommandMsg} textPreview=${JSON.stringify(text.slice(0, 80))})`);
|
|
2359
2399
|
return;
|
|
2360
2400
|
}
|
|
2361
2401
|
}
|
|
2362
2402
|
// 命令消息:剥离所有 @(多 agent 被 @ 时各自拿到干净的 /status 各自执行);
|
|
2363
2403
|
// 普通消息:仅在唯一 @ 是自己时剥离,保留其他 @ 供 agent 感知。
|
|
2364
2404
|
const strippedText = isCommandMsg
|
|
2365
|
-
?
|
|
2405
|
+
? slashCommandText
|
|
2366
2406
|
: this.stripSelfMentionIfOnly(text, this._aid);
|
|
2367
2407
|
// Detect attachments before the empty-text guard (顶层 + 嵌套)
|
|
2368
2408
|
const rawAttachments = this.collectAllAttachments(payload);
|
|
@@ -2393,7 +2433,7 @@ export class AUNChannel {
|
|
|
2393
2433
|
: mentionedSelf
|
|
2394
2434
|
? (structMentionSelf ? 'mention.self(struct)' : 'mention.self(text)')
|
|
2395
2435
|
: `${mentionMode}.no-mention`;
|
|
2396
|
-
logger.info(`${this.logPrefix()} Group mention decision: mid=${messageId} group=${groupId} sender=${shortAid}(${displayName}) peerType=${peerIdentity.type} payloadType=${payloadType} mentionMode=${mentionMode} reason=${reason} structMentions=${JSON.stringify(payloadMentionEntries)} textMentionSelf=${textMentionSelf} textMentionAll=${textMentionAll} structMentionSelf=${structMentionSelf} structMentionAll=${structMentionAll} encrypt=${msgEncrypted} textPreview=${JSON.stringify(text.slice(0, 80))}`);
|
|
2436
|
+
logger.info(`${this.logPrefix()} Group mention decision: mid=${messageId} group=${groupId} sender=${shortAid}(${displayName}) peerType=${peerIdentity.type} payloadType=${payloadType} mentionMode=${mentionMode} slashRequireMentions=${groupSlashRequireMentions} reason=${reason} structMentions=${JSON.stringify(payloadMentionEntries)} textMentionSelf=${textMentionSelf} textMentionAll=${textMentionAll} structMentionSelf=${structMentionSelf} structMentionAll=${structMentionAll} encrypt=${msgEncrypted} textPreview=${JSON.stringify(text.slice(0, 80))}`);
|
|
2397
2437
|
// action_card_reply 已在 extractTextPayload 中消费,不分发给 agent
|
|
2398
2438
|
if (payloadType === 'action_card_reply')
|
|
2399
2439
|
return;
|
|
@@ -2564,6 +2604,9 @@ export class AUNChannel {
|
|
|
2564
2604
|
// observable / owners 不在此处缓存——由 daemon 注入 resolver,从 EvolAgent 的
|
|
2565
2605
|
// in-memory merged config(启动/重启/热重载时统一更新的唯一缓存)读取,避免重复缓存。
|
|
2566
2606
|
observerConfigResolver;
|
|
2607
|
+
observerGroupMembers = new Map();
|
|
2608
|
+
observerGroupMemberFetches = new Map();
|
|
2609
|
+
observerGroupMembershipEpoch = new Map();
|
|
2567
2610
|
/** 注入观察者配置读取器(daemon 侧从 EvolAgent merged config 读)。 */
|
|
2568
2611
|
setObserverConfigResolver(fn) {
|
|
2569
2612
|
this.observerConfigResolver = fn;
|
|
@@ -2582,23 +2625,129 @@ export class AUNChannel {
|
|
|
2582
2625
|
* data 为 SDK message.received / group.message_created 回调的整个对象,
|
|
2583
2626
|
* 不拆解、不重组——SDK 信封结构变化不影响此处。
|
|
2584
2627
|
*/
|
|
2628
|
+
async resolveObserverGroupMembers(groupId) {
|
|
2629
|
+
if (!groupId || !this.client)
|
|
2630
|
+
return undefined;
|
|
2631
|
+
const cached = this.observerGroupMembers.get(groupId);
|
|
2632
|
+
if (cached && cached.expiresAt > Date.now())
|
|
2633
|
+
return cached.aids;
|
|
2634
|
+
const pending = this.observerGroupMemberFetches.get(groupId);
|
|
2635
|
+
if (pending)
|
|
2636
|
+
return pending;
|
|
2637
|
+
const epoch = this.observerGroupMembershipEpoch.get(groupId) ?? 0;
|
|
2638
|
+
let request;
|
|
2639
|
+
request = (async () => {
|
|
2640
|
+
try {
|
|
2641
|
+
const result = await this.callAndTrace('group.get_members', {
|
|
2642
|
+
group_id: groupId,
|
|
2643
|
+
page: 1,
|
|
2644
|
+
size: OBSERVER_GROUP_MEMBERS_PAGE_SIZE,
|
|
2645
|
+
});
|
|
2646
|
+
const rawMembers = Array.isArray(result?.members)
|
|
2647
|
+
? result.members
|
|
2648
|
+
: Array.isArray(result?.items) ? result.items : undefined;
|
|
2649
|
+
if (!rawMembers)
|
|
2650
|
+
return undefined;
|
|
2651
|
+
const hasTotal = Number.isFinite(Number(result?.total));
|
|
2652
|
+
const hasMore = result?.has_more === true || result?.hasMore === true;
|
|
2653
|
+
if (!hasTotal && (hasMore || rawMembers.length >= OBSERVER_GROUP_MEMBERS_PAGE_SIZE))
|
|
2654
|
+
return undefined;
|
|
2655
|
+
const allMembers = [...rawMembers];
|
|
2656
|
+
const total = Number(result?.total);
|
|
2657
|
+
const pageSize = Number(result?.size) > 0 ? Number(result.size) : OBSERVER_GROUP_MEMBERS_PAGE_SIZE;
|
|
2658
|
+
const pageCount = Number.isFinite(total) && total > allMembers.length
|
|
2659
|
+
? Math.ceil(total / pageSize)
|
|
2660
|
+
: 1;
|
|
2661
|
+
if (pageCount > OBSERVER_GROUP_MEMBERS_MAX_PAGES)
|
|
2662
|
+
return undefined;
|
|
2663
|
+
for (let page = 2; page <= pageCount; page++) {
|
|
2664
|
+
const next = await this.callAndTrace('group.get_members', {
|
|
2665
|
+
group_id: groupId,
|
|
2666
|
+
page,
|
|
2667
|
+
size: OBSERVER_GROUP_MEMBERS_PAGE_SIZE,
|
|
2668
|
+
});
|
|
2669
|
+
const nextMembers = Array.isArray(next?.members)
|
|
2670
|
+
? next.members
|
|
2671
|
+
: Array.isArray(next?.items) ? next.items : undefined;
|
|
2672
|
+
if (!nextMembers)
|
|
2673
|
+
return undefined;
|
|
2674
|
+
allMembers.push(...nextMembers);
|
|
2675
|
+
}
|
|
2676
|
+
if (hasTotal && total > allMembers.length)
|
|
2677
|
+
return undefined;
|
|
2678
|
+
const aids = new Set();
|
|
2679
|
+
for (const member of allMembers) {
|
|
2680
|
+
const aid = typeof member === 'string'
|
|
2681
|
+
? member.trim()
|
|
2682
|
+
: typeof member?.aid === 'string' ? member.aid.trim()
|
|
2683
|
+
: typeof member?.member_aid === 'string' ? member.member_aid.trim() : '';
|
|
2684
|
+
if (aid)
|
|
2685
|
+
aids.add(aid);
|
|
2686
|
+
}
|
|
2687
|
+
if ((this.observerGroupMembershipEpoch.get(groupId) ?? 0) === epoch) {
|
|
2688
|
+
this.observerGroupMembers.set(groupId, { aids, expiresAt: Date.now() + OBSERVER_GROUP_MEMBERS_TTL_MS });
|
|
2689
|
+
}
|
|
2690
|
+
return aids;
|
|
2691
|
+
}
|
|
2692
|
+
catch (error) {
|
|
2693
|
+
logger.debug(`${this.logPrefix()} observer group membership lookup failed group=${groupId}: ${error}`);
|
|
2694
|
+
return undefined;
|
|
2695
|
+
}
|
|
2696
|
+
finally {
|
|
2697
|
+
if (this.observerGroupMemberFetches.get(groupId) === request) {
|
|
2698
|
+
this.observerGroupMemberFetches.delete(groupId);
|
|
2699
|
+
}
|
|
2700
|
+
}
|
|
2701
|
+
})();
|
|
2702
|
+
this.observerGroupMemberFetches.set(groupId, request);
|
|
2703
|
+
return request;
|
|
2704
|
+
}
|
|
2705
|
+
invalidateObserverGroupMembers(groupId) {
|
|
2706
|
+
if (typeof groupId !== 'string' || !groupId.trim())
|
|
2707
|
+
return;
|
|
2708
|
+
const key = groupId.trim();
|
|
2709
|
+
this.observerGroupMembers.delete(key);
|
|
2710
|
+
this.observerGroupMembershipEpoch.set(key, (this.observerGroupMembershipEpoch.get(key) ?? 0) + 1);
|
|
2711
|
+
}
|
|
2712
|
+
async observerRecipients(owners, envelope) {
|
|
2713
|
+
if (owners.length === 0)
|
|
2714
|
+
return owners;
|
|
2715
|
+
const groupId = typeof envelope.group_id === 'string' ? envelope.group_id.trim() : '';
|
|
2716
|
+
const members = groupId ? await this.resolveObserverGroupMembers(groupId) : undefined;
|
|
2717
|
+
// Unknown membership is fail-open for observation: only an explicit member
|
|
2718
|
+
// response may suppress the redundant owner DM.
|
|
2719
|
+
return owners.filter(owner => !(members?.has(owner)));
|
|
2720
|
+
}
|
|
2585
2721
|
forwardInbound(data) {
|
|
2586
2722
|
if (!this.connected || !this.client)
|
|
2587
|
-
return;
|
|
2723
|
+
return Promise.resolve();
|
|
2588
2724
|
if (payloadTypeForObserver(data)?.startsWith('menu.'))
|
|
2589
|
-
return;
|
|
2725
|
+
return Promise.resolve();
|
|
2590
2726
|
const { observable, owners } = this.getObserverConfig();
|
|
2591
2727
|
if (!observable || owners.length === 0)
|
|
2592
|
-
return;
|
|
2593
|
-
const env = (data?.envelope && typeof data.envelope === 'object')
|
|
2728
|
+
return Promise.resolve();
|
|
2729
|
+
const env = (data?.envelope && typeof data.envelope === 'object')
|
|
2730
|
+
? { ...data.envelope }
|
|
2731
|
+
: {};
|
|
2732
|
+
if (!env.group_id && typeof data.group_id === 'string')
|
|
2733
|
+
env.group_id = data.group_id;
|
|
2594
2734
|
const from = env.from ?? '';
|
|
2595
2735
|
if (this._aid && from === this._aid)
|
|
2596
|
-
return; // self-echo:已在出站转过
|
|
2736
|
+
return Promise.resolve(); // self-echo:已在出站转过
|
|
2597
2737
|
// 排除来源 owner(不把"owner A 发来的"再转回 A),但仍转给其他 owner。
|
|
2598
|
-
const
|
|
2599
|
-
if (
|
|
2600
|
-
return;
|
|
2601
|
-
|
|
2738
|
+
const candidateOwners = owners.filter(o => o !== from);
|
|
2739
|
+
if (candidateOwners.length === 0)
|
|
2740
|
+
return Promise.resolve();
|
|
2741
|
+
if (typeof env.group_id !== 'string' || !env.group_id.trim()) {
|
|
2742
|
+
this.emitForward('inbound', data, candidateOwners);
|
|
2743
|
+
return Promise.resolve();
|
|
2744
|
+
}
|
|
2745
|
+
return this.observerRecipients(candidateOwners, env)
|
|
2746
|
+
.then(recipientOwners => {
|
|
2747
|
+
if (recipientOwners.length > 0)
|
|
2748
|
+
this.emitForward('inbound', data, recipientOwners);
|
|
2749
|
+
})
|
|
2750
|
+
.catch(error => logger.debug(`${this.logPrefix()} observer inbound recipient lookup failed: ${error}`));
|
|
2602
2751
|
}
|
|
2603
2752
|
/**
|
|
2604
2753
|
* 出站转发:Agent 经 AUN 真实发出的消息原样转发给 owner。
|
|
@@ -2607,20 +2756,33 @@ export class AUNChannel {
|
|
|
2607
2756
|
*/
|
|
2608
2757
|
forwardOutbound(result) {
|
|
2609
2758
|
if (!this.connected || !this.client)
|
|
2610
|
-
return;
|
|
2759
|
+
return Promise.resolve();
|
|
2611
2760
|
if (payloadTypeForObserver(result)?.startsWith('menu.'))
|
|
2612
|
-
return;
|
|
2761
|
+
return Promise.resolve();
|
|
2613
2762
|
const { observable, owners } = this.getObserverConfig();
|
|
2614
2763
|
if (!observable || owners.length === 0)
|
|
2615
|
-
return;
|
|
2616
|
-
const env = (result?.envelope && typeof result.envelope === 'object')
|
|
2764
|
+
return Promise.resolve();
|
|
2765
|
+
const env = (result?.envelope && typeof result.envelope === 'object')
|
|
2766
|
+
? { ...result.envelope }
|
|
2767
|
+
: {};
|
|
2768
|
+
if (!env.group_id && typeof result.group_id === 'string')
|
|
2769
|
+
env.group_id = result.group_id;
|
|
2617
2770
|
const to = env.to ?? env.group_id ?? '';
|
|
2618
2771
|
// 过滤:若对端本身是 owner,不转发给该 owner(避免"回复你"转给你自己);
|
|
2619
2772
|
// 但仍转发给其他 owner。
|
|
2620
|
-
const
|
|
2621
|
-
if (
|
|
2622
|
-
return;
|
|
2623
|
-
|
|
2773
|
+
const candidateOwners = owners.filter(o => o !== to);
|
|
2774
|
+
if (candidateOwners.length === 0)
|
|
2775
|
+
return Promise.resolve();
|
|
2776
|
+
if (typeof env.group_id !== 'string' || !env.group_id.trim()) {
|
|
2777
|
+
this.emitForward('outbound', result, candidateOwners);
|
|
2778
|
+
return Promise.resolve();
|
|
2779
|
+
}
|
|
2780
|
+
return this.observerRecipients(candidateOwners, env)
|
|
2781
|
+
.then(recipientOwners => {
|
|
2782
|
+
if (recipientOwners.length > 0)
|
|
2783
|
+
this.emitForward('outbound', result, recipientOwners);
|
|
2784
|
+
})
|
|
2785
|
+
.catch(error => logger.debug(`${this.logPrefix()} observer outbound recipient lookup failed: ${error}`));
|
|
2624
2786
|
}
|
|
2625
2787
|
/**
|
|
2626
2788
|
* 实际投递 observer.forward 给每个 owner,使用 daemon 的全局 AUN 默认加密设置。
|
|
@@ -3104,6 +3266,9 @@ export class AUNChannel {
|
|
|
3104
3266
|
setMentionModeResolver(resolver) {
|
|
3105
3267
|
this.mentionModeResolver = resolver;
|
|
3106
3268
|
}
|
|
3269
|
+
setGroupSlashRequireMentionsResolver(resolver) {
|
|
3270
|
+
this.groupSlashRequireMentionsResolver = resolver;
|
|
3271
|
+
}
|
|
3107
3272
|
onRecall(handler) {
|
|
3108
3273
|
this.recallHandler = handler;
|
|
3109
3274
|
}
|
|
@@ -3711,9 +3876,15 @@ export class AUNChannel {
|
|
|
3711
3876
|
return truncated;
|
|
3712
3877
|
}
|
|
3713
3878
|
buildActivityPayload(envelope, context, item) {
|
|
3714
|
-
const
|
|
3879
|
+
const rawItem = item && typeof item === 'object' && !Array.isArray(item)
|
|
3715
3880
|
? { ...item }
|
|
3716
3881
|
: { kind: 'unknown', text: String(item ?? '') };
|
|
3882
|
+
const displayArguments = rawItem.kind === 'tool_call'
|
|
3883
|
+
? toolInputForDisplay(rawItem.name, rawItem.arguments)
|
|
3884
|
+
: undefined;
|
|
3885
|
+
const activityItem = rawItem.kind === 'tool_call' && displayArguments !== rawItem.arguments
|
|
3886
|
+
? { ...rawItem, arguments: displayArguments }
|
|
3887
|
+
: rawItem;
|
|
3717
3888
|
return {
|
|
3718
3889
|
...this.buildTaskPayloadBase(envelope, context),
|
|
3719
3890
|
type: 'activity',
|
|
@@ -5119,13 +5290,11 @@ export class AUNChannel {
|
|
|
5119
5290
|
const owner = getFirstStaticAgentOwner(aid);
|
|
5120
5291
|
if (!owner)
|
|
5121
5292
|
return;
|
|
5122
|
-
const
|
|
5123
|
-
|
|
5124
|
-
|
|
5125
|
-
|
|
5126
|
-
|
|
5127
|
-
.map(operationId => outbox.findByDedupeKey(aid, operationId, { includeTerminal: true }))
|
|
5128
|
-
.filter((entry) => !!entry?.channelId);
|
|
5293
|
+
const completionEntry = outbox.findByDedupeKey(aid, postBootstrapWelcomeOperationId(aid), { includeTerminal: true });
|
|
5294
|
+
const entries = [
|
|
5295
|
+
...outbox.findByDedupePrefix(aid, bootstrapInitialMessageOperationPrefix(aid), { includeTerminal: true }),
|
|
5296
|
+
...(completionEntry ? [completionEntry] : []),
|
|
5297
|
+
].filter((entry) => !!entry?.channelId);
|
|
5129
5298
|
for (const entry of entries) {
|
|
5130
5299
|
const delivery = { chatType: 'private' };
|
|
5131
5300
|
const nestedDelivery = entry.context?.delivery;
|
|
@@ -5915,6 +6084,11 @@ export class AUNChannelPlugin {
|
|
|
5915
6084
|
}, { cache: true }).mentionMode;
|
|
5916
6085
|
});
|
|
5917
6086
|
}
|
|
6087
|
+
if (typeof channel.setGroupSlashRequireMentionsResolver === 'function') {
|
|
6088
|
+
channel.setGroupSlashRequireMentionsResolver(async () => {
|
|
6089
|
+
return resolveEffective({ self: aid }, { cache: true }).groupSlashRequireMentions ?? true;
|
|
6090
|
+
});
|
|
6091
|
+
}
|
|
5918
6092
|
},
|
|
5919
6093
|
};
|
|
5920
6094
|
}
|
|
@@ -174,14 +174,35 @@ function sourceBuildNeedsRefresh() {
|
|
|
174
174
|
const sourceMtime = Math.max(latestSourceMtime(path.join(packageRoot, 'src')), latestSourceMtime(path.join(packageRoot, 'ecagent', 'src')));
|
|
175
175
|
return sourceMtime > fs.statSync(daemonEntry).mtimeMs;
|
|
176
176
|
}
|
|
177
|
-
function
|
|
177
|
+
function runSourceBuild() {
|
|
178
|
+
return new Promise(resolve => {
|
|
179
|
+
console.log('🔧 源码比 daemon 编译产物更新,自动重新构建...');
|
|
180
|
+
const child = spawn(platform.isWindows ? 'npm.cmd' : 'npm', ['run', 'build'], {
|
|
181
|
+
cwd: getPackageRoot(),
|
|
182
|
+
stdio: 'inherit',
|
|
183
|
+
shell: platform.isWindows,
|
|
184
|
+
});
|
|
185
|
+
child.on('error', err => {
|
|
186
|
+
console.error(`❌ 无法启动构建进程: ${err.message}`);
|
|
187
|
+
resolve(false);
|
|
188
|
+
});
|
|
189
|
+
child.on('exit', code => {
|
|
190
|
+
if (code === 0) {
|
|
191
|
+
console.log('✅ 构建完成');
|
|
192
|
+
resolve(true);
|
|
193
|
+
}
|
|
194
|
+
else {
|
|
195
|
+
console.error(`❌ 自动构建失败 (exit ${code}),服务未启动。`);
|
|
196
|
+
console.error(' 请手动执行: npm run build 排查错误');
|
|
197
|
+
resolve(false);
|
|
198
|
+
}
|
|
199
|
+
});
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
async function requireFreshSourceBuild() {
|
|
178
203
|
if (!sourceBuildNeedsRefresh())
|
|
179
204
|
return true;
|
|
180
|
-
|
|
181
|
-
console.error(' 请先执行: ec stop && npm run build');
|
|
182
|
-
console.error(' 然后执行: ec start');
|
|
183
|
-
process.exitCode = 2;
|
|
184
|
-
return false;
|
|
205
|
+
return await runSourceBuild();
|
|
185
206
|
}
|
|
186
207
|
function requireCompletedDataMigration(root) {
|
|
187
208
|
const requirement = inspectDataMigrationRequirement(root);
|
|
@@ -306,7 +327,7 @@ export async function cmdStart(opts = {}) {
|
|
|
306
327
|
return;
|
|
307
328
|
}
|
|
308
329
|
ensureDataDirs();
|
|
309
|
-
if (!requireFreshSourceBuild())
|
|
330
|
+
if (!await requireFreshSourceBuild())
|
|
310
331
|
return;
|
|
311
332
|
// 旧配置自动迁移(daemon.json → 新结构)
|
|
312
333
|
const { autoMigrateIfNeeded } = await import('../config-store.js');
|
|
@@ -681,7 +702,7 @@ export async function cmdRestart(opts = {}) {
|
|
|
681
702
|
if (initialRuntimeOrphans.length === 0 && rejectPidIsolatedLifecycle('restart', initialStatus, ping))
|
|
682
703
|
return;
|
|
683
704
|
}
|
|
684
|
-
if (!requireFreshSourceBuild())
|
|
705
|
+
if (!await requireFreshSourceBuild())
|
|
685
706
|
return;
|
|
686
707
|
// 版本检查与自动升级
|
|
687
708
|
console.log('📦 Checking for updates...');
|
package/dist/cli/task-context.js
CHANGED
|
@@ -6,6 +6,7 @@ import os from 'node:os';
|
|
|
6
6
|
import path from 'node:path';
|
|
7
7
|
export const TASK_RUNTIME_CONTEXT_ENV = 'EVOLCORE_TASK_RUNTIME_CONTEXT';
|
|
8
8
|
export const SESSION_RUNTIME_DIR_ENV = 'EVOLCORE_SESSION_RUNTIME_DIR';
|
|
9
|
+
export const DAEMON_RUNTIME_EPOCH_ENV = 'EVOLCORE_DAEMON_RUNTIME_EPOCH';
|
|
9
10
|
export { RUNTIME_LOCK_DIR_ENV };
|
|
10
11
|
const generatedProcessManagedTempDirs = new Set();
|
|
11
12
|
let processTempCleanupRegistered = false;
|
|
@@ -32,11 +33,13 @@ function normalizeTaskRuntimeContext(value) {
|
|
|
32
33
|
peerName: optionalString(value.peerName),
|
|
33
34
|
peerType: optionalString(value.peerType),
|
|
34
35
|
peerRole: optionalString(value.peerRole),
|
|
36
|
+
permissionMode: optionalString(value.permissionMode),
|
|
35
37
|
processRole: optionalProcessRole(value.processRole),
|
|
36
38
|
dataScope: optionalDataScope(value.dataScope),
|
|
37
39
|
authorizedBy: optionalString(value.authorizedBy),
|
|
38
40
|
executionSource: value.executionSource === 'fullaccess-command' || value.executionSource === 'trigger'
|
|
39
41
|
? value.executionSource : undefined,
|
|
42
|
+
daemonRuntimeEpoch: optionalString(value.daemonRuntimeEpoch),
|
|
40
43
|
threadId: optionalString(value.threadId),
|
|
41
44
|
sessionRuntimeDir: optionalAbsolutePath(value.sessionRuntimeDir),
|
|
42
45
|
runtimeLockDir: optionalAbsolutePath(value.runtimeLockDir),
|
|
@@ -336,6 +339,7 @@ export function buildTaskRuntimeEnv(ctx) {
|
|
|
336
339
|
const clean = normalizeTaskRuntimeContext(ctx);
|
|
337
340
|
return {
|
|
338
341
|
EVOLCORE_SESSION_ID: ctx.sessionId ?? '',
|
|
342
|
+
...(ctx.daemonRuntimeEpoch ? { [DAEMON_RUNTIME_EPOCH_ENV]: ctx.daemonRuntimeEpoch } : {}),
|
|
339
343
|
[TASK_RUNTIME_CONTEXT_ENV]: JSON.stringify(clean),
|
|
340
344
|
// Every managed child receives the session directory as TMPDIR. This keeps
|
|
341
345
|
// ordinary temporary files and runtime-only fallbacks out of shared agent
|
|
@@ -32,7 +32,7 @@ export async function cmdTrigger(args) {
|
|
|
32
32
|
return;
|
|
33
33
|
}
|
|
34
34
|
if (process.env.EVOLCORE_SESSION_ID
|
|
35
|
-
&& (hasOption(rest, '--file') || hasOption(rest, '--prompt-file') || hasOption(rest, '--script-path'))) {
|
|
35
|
+
&& (hasOption(rest, '--file') || hasOption(rest, '--prompt-file') || hasOption(rest, '--script-path') || hasOption(rest, '--script-file'))) {
|
|
36
36
|
throw new Error('Managed tasks cannot read trigger definitions, prompts, or scripts from local files; use inline prompt content');
|
|
37
37
|
}
|
|
38
38
|
try {
|
|
@@ -123,7 +123,7 @@ Create 参数模式支持:
|
|
|
123
123
|
|
|
124
124
|
Update 仅支持常用字段:
|
|
125
125
|
--once | --delay | --at | --cron | --every | --event [--tz]
|
|
126
|
-
--prompt <文本> | --prompt-file <路径> --name <名称>
|
|
126
|
+
--prompt <文本> | --prompt-file <路径> --script-file <路径> --name <名称>
|
|
127
127
|
--model <模型> --effort <强度> --permission <权限模式|inherit>(inherit 清除覆盖)
|
|
128
128
|
--max-runs <次数> --max-duration <时长>
|
|
129
129
|
--concurrency <forbid|replace|allow> --missed-policy <skip|run-once|run-all>
|
|
@@ -399,19 +399,28 @@ function shellQuoteArg(value) {
|
|
|
399
399
|
}
|
|
400
400
|
async function updateTrigger(args, json) {
|
|
401
401
|
const agentAid = resolveAgentAid(args);
|
|
402
|
-
const triggerId = positional(args, 0, ['--agent', '--format', '--if-revision', '--prompt-file']);
|
|
402
|
+
const triggerId = positional(args, 0, ['--agent', '--format', '--if-revision', '--prompt-file', '--script-file']);
|
|
403
403
|
const usesPromptFile = hasOption(args, '--prompt-file');
|
|
404
|
+
const usesScriptFile = hasOption(args, '--script-file');
|
|
405
|
+
const scriptFilePath = usesScriptFile ? requireFlag(args, '--script-file') : undefined;
|
|
404
406
|
const { parseTriggerUpdateArgv } = await import('../trigger/parser.js');
|
|
405
407
|
const parsed = parseTriggerUpdateArgv(triggerId, withPromptFromFile(triggerUpdateArgs(args)), { allowLongPrompt: usesPromptFile });
|
|
406
408
|
if (!parsed.ok)
|
|
407
409
|
throw new Error(parsed.error);
|
|
410
|
+
const scriptFileRequested = parsed.value.scriptFile !== undefined;
|
|
411
|
+
const { scriptFile: _scriptFile, ...patchFields } = parsed.value;
|
|
412
|
+
const patch = scriptFileRequested ? { ...patchFields, scriptFile: true } : patchFields;
|
|
408
413
|
const expectedRevision = flagValue(args, '--if-revision');
|
|
414
|
+
const scriptFileBase64 = scriptFilePath
|
|
415
|
+
? fs.readFileSync(scriptFilePath).toString('base64')
|
|
416
|
+
: undefined;
|
|
409
417
|
const res = await request({
|
|
410
418
|
type: 'trigger.update',
|
|
411
419
|
agentAid,
|
|
412
420
|
triggerId,
|
|
413
|
-
patch
|
|
421
|
+
patch,
|
|
414
422
|
...(usesPromptFile ? { promptFile: true } : {}),
|
|
423
|
+
...(scriptFileBase64 !== undefined ? { scriptFileBase64 } : {}),
|
|
415
424
|
...(expectedRevision ? { expectedRevision } : {}),
|
|
416
425
|
}, 30_000);
|
|
417
426
|
if (json)
|
|
@@ -2,6 +2,7 @@ const BEHAVIOR_TOP_FIELDS = new Set([
|
|
|
2
2
|
'active_baseagent',
|
|
3
3
|
'chatmode',
|
|
4
4
|
'mentionMode',
|
|
5
|
+
'groupSlashRequireMentions',
|
|
5
6
|
'responseMode',
|
|
6
7
|
'responseModeParams',
|
|
7
8
|
'flush_delay',
|
|
@@ -110,6 +111,8 @@ export function resolveConfigFieldRule(fieldPath) {
|
|
|
110
111
|
return scalar(field, 'nonnegative-number');
|
|
111
112
|
if (field === 'mentionMode')
|
|
112
113
|
return scalar(field, 'mention-mode');
|
|
114
|
+
if (field === 'groupSlashRequireMentions')
|
|
115
|
+
return scalar(field, 'boolean');
|
|
113
116
|
if (field === 'show_activities')
|
|
114
117
|
return scalar(field, 'show-activities');
|
|
115
118
|
if (field === 'enable_rich_content')
|
|
@@ -933,7 +933,10 @@ function acquireConfigMutationLock(file) {
|
|
|
933
933
|
return () => releaseConfigMutationLock(lockDir, ownerFile, token);
|
|
934
934
|
}
|
|
935
935
|
catch (error) {
|
|
936
|
-
|
|
936
|
+
const lockMayAlreadyExist = error?.code === 'EEXIST'
|
|
937
|
+
|| error?.code === 'ENOTEMPTY'
|
|
938
|
+
|| (process.platform === 'win32' && error?.code === 'EPERM' && fs.existsSync(lockDir));
|
|
939
|
+
if (!lockMayAlreadyExist)
|
|
937
940
|
throw error;
|
|
938
941
|
if (reclaimStaleConfigMutationLock(lockDir, ownerFile))
|
|
939
942
|
continue;
|
|
@@ -957,10 +960,10 @@ function reclaimStaleConfigMutationLock(lockDir, ownerFile) {
|
|
|
957
960
|
pid = Number(JSON.parse(raw).pid);
|
|
958
961
|
}
|
|
959
962
|
catch {
|
|
960
|
-
return
|
|
963
|
+
return reclaimCorruptConfigMutationLock(lockDir);
|
|
961
964
|
}
|
|
962
965
|
if (!Number.isInteger(pid) || pid <= 0)
|
|
963
|
-
return
|
|
966
|
+
return reclaimCorruptConfigMutationLock(lockDir);
|
|
964
967
|
try {
|
|
965
968
|
process.kill(pid, 0);
|
|
966
969
|
return false;
|
|
@@ -979,6 +982,24 @@ function reclaimStaleConfigMutationLock(lockDir, ownerFile) {
|
|
|
979
982
|
}
|
|
980
983
|
}
|
|
981
984
|
}
|
|
985
|
+
function reclaimCorruptConfigMutationLock(lockDir) {
|
|
986
|
+
try {
|
|
987
|
+
const stat = fs.statSync(lockDir);
|
|
988
|
+
if (Date.now() - stat.mtimeMs < CONFIG_MUTATION_LOCK_WAIT_MS)
|
|
989
|
+
return false;
|
|
990
|
+
const reclaimed = `${lockDir}.reclaimed-corrupt-${crypto.randomBytes(8).toString('hex')}`;
|
|
991
|
+
fs.renameSync(lockDir, reclaimed);
|
|
992
|
+
fs.rmSync(reclaimed, { recursive: true, force: true });
|
|
993
|
+
return true;
|
|
994
|
+
}
|
|
995
|
+
catch (error) {
|
|
996
|
+
if (error?.code === 'ENOENT')
|
|
997
|
+
return true;
|
|
998
|
+
if (error?.code === 'EEXIST' || error?.code === 'ENOTEMPTY' || error?.code === 'EPERM')
|
|
999
|
+
return false;
|
|
1000
|
+
throw error;
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
982
1003
|
function releaseConfigMutationLock(lockDir, ownerFile, token) {
|
|
983
1004
|
try {
|
|
984
1005
|
const owner = JSON.parse(fs.readFileSync(ownerFile, 'utf8'));
|
|
@@ -1289,9 +1310,13 @@ function stripStaticOwnerField(config) {
|
|
|
1289
1310
|
return config;
|
|
1290
1311
|
if (!Object.prototype.hasOwnProperty.call(config, 'owners')
|
|
1291
1312
|
&& !Object.prototype.hasOwnProperty.call(config, 'admins')
|
|
1292
|
-
&& !Object.prototype.hasOwnProperty.call(config, 'access')
|
|
1313
|
+
&& !Object.prototype.hasOwnProperty.call(config, 'access')
|
|
1314
|
+
&& !Object.prototype.hasOwnProperty.call(config, 'groupSlashRequireMentions'))
|
|
1293
1315
|
return config;
|
|
1294
|
-
|
|
1316
|
+
// groupSlashRequireMentions is intentionally Agent-only. Relation/defaults
|
|
1317
|
+
// files allow forward-compatible unknown properties, so strip it before the
|
|
1318
|
+
// generic merge chain can treat a manually written value as an override.
|
|
1319
|
+
const { owners: _owners, admins: _admins, access: _access, groupSlashRequireMentions: _groupSlashRequireMentions, ...rest } = config;
|
|
1295
1320
|
return rest;
|
|
1296
1321
|
}
|
|
1297
1322
|
function stripRelationRoleData(config) {
|
|
@@ -1330,6 +1355,7 @@ export function resolveEffective(sel, opts = {}) {
|
|
|
1330
1355
|
baseagents: config.baseagents,
|
|
1331
1356
|
chatmode: config.chatmode,
|
|
1332
1357
|
mentionMode: config.mentionMode,
|
|
1358
|
+
groupSlashRequireMentions: config.groupSlashRequireMentions ?? true,
|
|
1333
1359
|
model: config.model,
|
|
1334
1360
|
responseMode: config.responseMode,
|
|
1335
1361
|
responseModeParams: config.responseModeParams,
|
|
@@ -1361,6 +1387,7 @@ const EFFECTIVE_BEHAVIOR_FIELDS = [
|
|
|
1361
1387
|
'baseagents',
|
|
1362
1388
|
'chatmode',
|
|
1363
1389
|
'mentionMode',
|
|
1390
|
+
'groupSlashRequireMentions',
|
|
1364
1391
|
'flush_delay',
|
|
1365
1392
|
'debounce',
|
|
1366
1393
|
'show_activities',
|