evolcore 0.0.27 → 0.0.29-test.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/CHANGELOG.md +38 -15
- package/bin/ec-safe-output.js +270 -54
- package/dist/agents/baseagent-availability.js +37 -1
- package/dist/agents/baseagent.js +29 -14
- package/dist/agents/claude-runner.js +171 -13
- package/dist/agents/codex/app-server-client.js +13 -2
- package/dist/agents/codex/catalog.js +7 -1
- package/dist/agents/codex/diagnostics.js +4 -3
- package/dist/agents/codex-runner.js +444 -106
- package/dist/agents/gemini-runner.js +35 -17
- package/dist/aun/group/history.js +130 -90
- package/dist/aun/group/operations.js +1 -3
- package/dist/aun/menu/client.js +1 -0
- package/dist/aun/menu/transport.js +5 -3
- package/dist/aun/msg/managed-operation.js +70 -0
- package/dist/aun/msg/p2p.js +1 -3
- package/dist/aun/msg/upload.js +2 -2
- package/dist/aun/outbox.js +1 -37
- package/dist/channels/aun.js +262 -86
- package/dist/channels/daemon.js +18 -1
- package/dist/channels/feishu.js +1 -2
- package/dist/cli/agent-command.js +23 -3
- package/dist/cli/agent-create.js +68 -2
- package/dist/cli/agent.js +12 -2
- package/dist/cli/aun-commands.js +87 -13
- package/dist/cli/cli-argv.js +26 -0
- package/dist/cli/daemon-commands.js +116 -149
- package/dist/cli/data-command.js +4 -4
- package/dist/cli/diag.js +1 -1
- package/dist/cli/handoff-command.js +2 -0
- package/dist/cli/index.js +18 -1
- package/dist/cli/managed-command-guard.js +10 -0
- package/dist/cli/memory-command.js +441 -0
- package/dist/cli/menu-client.js +4 -0
- package/dist/cli/restart-monitor.js +127 -23
- package/dist/cli/skill-command.js +41 -0
- package/dist/cli/task-context.js +2 -23
- package/dist/cli/trigger-command.js +9 -1
- package/dist/cli/watch-msg-diag.js +209 -0
- package/dist/config/config-field-policy.js +52 -3
- package/dist/config/config-manager.js +17 -4
- package/dist/config/contact-book-store.js +2 -2
- package/dist/config/mention-mode.js +7 -2
- package/dist/config/merge.js +28 -9
- package/dist/config/resolved-config-op.js +1 -1
- package/dist/config/runtime-env.js +137 -0
- package/dist/config/schema-registry.js +11 -1
- package/dist/config-store.js +10 -10
- package/dist/core/audit/event-key.js +3 -1
- package/dist/core/audit/log-integrity.js +4 -0
- package/dist/core/auth/agent-delegation.js +2 -1
- package/dist/core/auth/auth-gateway.js +13 -6
- package/dist/core/auth/authorization-audit.js +54 -19
- package/dist/core/auth/operation-authorizer.js +17 -0
- package/dist/core/auth/operation-catalog.js +9 -0
- package/dist/core/causation/context.js +5 -1
- package/dist/core/command/agent-control.js +1 -2
- package/dist/core/command/command-handler.js +80 -24
- package/dist/core/command/diag-service.js +292 -27
- package/dist/core/command/memory-slash.js +270 -0
- package/dist/core/command/menu-catalog.js +1 -0
- package/dist/core/command/menu-handler.js +331 -79
- package/dist/core/command/menu-protocol.js +45 -4
- package/dist/core/command/slash-gate.js +10 -10
- package/dist/core/command/slash-handler.js +230 -79
- package/dist/core/data-migration.js +282 -505
- package/dist/core/event-catalog.js +26 -0
- package/dist/core/evolagent-registry.js +6 -0
- package/dist/core/evolagent.js +0 -10
- package/dist/core/handoff/store.js +1 -20
- package/dist/core/inference/text-inference.js +57 -1
- package/dist/core/memory/consolidation-context.js +505 -0
- package/dist/core/memory/consolidation-ops-records.js +124 -0
- package/dist/core/memory/consolidation-trigger.js +394 -0
- package/dist/core/memory/index-manager.js +308 -0
- package/dist/core/memory/launch-consolidation.js +395 -0
- package/dist/core/memory/memory-admin.js +234 -0
- package/dist/core/memory/memory-document.js +100 -0
- package/dist/core/memory/memory-session-policy.js +41 -0
- package/dist/core/memory/memory-signature.js +257 -0
- package/dist/core/memory/memory-writer.js +432 -0
- package/dist/core/memory/staleness.js +117 -0
- package/dist/core/message/auto-mention-gate.js +454 -0
- package/dist/core/message/im-renderer.js +90 -20
- package/dist/core/message/message-bridge.js +333 -23
- package/dist/core/message/message-log.js +1 -0
- package/dist/core/message/message-queue.js +114 -27
- package/dist/core/message/pending-hints.js +329 -161
- package/dist/core/message/response-engine.js +596 -90
- package/dist/core/permission/ec-command-parser.js +1044 -87
- package/dist/core/permission/protected-paths.js +376 -126
- package/dist/core/permission/sandbox-runtime.js +40 -12
- package/dist/core/permission/tool-error-code.js +96 -13
- package/dist/core/permission/tool-policy.js +277 -24
- package/dist/core/role/runtime-policy.js +3 -7
- package/dist/core/session/{runner-task-history.js → runner-event-summary.js} +1 -1
- package/dist/core/session/session-fs-store.js +0 -76
- package/dist/core/session/session-idle-monitor.js +355 -0
- package/dist/core/session/session-manager.js +482 -17
- package/dist/core/session/session-mapper.js +33 -16
- package/dist/core/session/session-renew.js +217 -155
- package/dist/core/session/{session-turns.js → session-transcript.js} +35 -6
- package/dist/core/skill/global-watcher.js +282 -0
- package/dist/core/skill/index-generator.js +84 -0
- package/dist/core/skill/startup.js +73 -0
- package/dist/core/skill/switch-resolver.js +115 -0
- package/dist/core/task/task-activity-store.js +219 -0
- package/dist/eck/kit-renderer.js +30 -3
- package/dist/eck/manifest-engine.js +26 -4
- package/dist/index.js +722 -171
- package/dist/ipc.js +564 -2
- package/dist/paths.js +30 -9
- package/dist/product.js +1 -0
- package/dist/response-system/engines/v1/proactive-flow.js +116 -15
- package/dist/response-system/modes/single-session/index.js +3 -0
- package/dist/trigger/feedback.js +1 -0
- package/dist/trigger/manager.js +0 -10
- package/dist/trigger/scheduler.js +11 -0
- package/dist/trigger/validation.js +22 -0
- package/dist/utils/atomic-write.js +98 -0
- package/dist/utils/avatar-upload.js +59 -18
- package/dist/utils/cross-platform.js +87 -16
- package/dist/utils/error-dict.json +6 -3
- package/dist/utils/error-utils.js +27 -0
- package/dist/utils/logger.js +32 -10
- package/dist/utils/media-cache.js +84 -0
- package/dist/utils/msg-diag.js +97 -0
- package/dist/utils/npm-ops.js +119 -4
- package/dist/utils/package-version-info.js +244 -0
- package/dist/utils/welcome.js +37 -8
- package/dist/utils/windows-shell-trust.js +37 -0
- package/kits/docs/AUTHORING.md +314 -0
- package/kits/docs/GUIDE.md +2 -0
- package/kits/docs/INDEX.md +2 -0
- package/kits/docs/aun/CHEATSHEET.md +4 -0
- package/kits/docs/aun/SDK.md +60 -0
- package/kits/docs/channels/aun.md +8 -0
- package/kits/docs/context-assembly.md +10 -6
- package/kits/docs/evolcore/INDEX.md +17 -3
- package/kits/docs/evolcore/agent-create.md +24 -0
- package/kits/docs/evolcore/agent.md +5 -2
- package/kits/docs/evolcore/config.md +1 -1
- package/kits/docs/evolcore/ec-capabilities.md +364 -0
- package/kits/docs/evolcore/group.md +34 -18
- package/kits/docs/evolcore/memory-architecture.md +78 -0
- package/kits/docs/evolcore/memory-consolidation-batches.md +133 -0
- package/kits/docs/evolcore/memory-consolidation.md +263 -0
- package/kits/docs/evolcore/memory-tags.md +63 -0
- package/kits/docs/evolcore/memory.md +110 -0
- package/kits/docs/evolcore/menu-client.md +27 -11
- package/kits/{schemas → docs/evolcore}/menu-exec-schema-commands.md +12 -6
- package/kits/docs/evolcore/menu-protocol.md +213 -0
- package/kits/docs/evolcore/msg.md +2 -0
- package/kits/docs/evolcore/trigger.md +7 -5
- package/kits/docs/path-registry.md +6 -8
- package/kits/docs/prompt-loading-architecture.md +5 -3
- package/kits/docs/venues/group.md +5 -4
- package/kits/eck_manifest.json +294 -88
- package/kits/eck_manifest.memory.json +257 -0
- package/kits/rules/03-identity.md +8 -8
- package/kits/rules/05-venue.md +4 -2
- package/kits/rules/06-channel.md +1 -1
- package/kits/schemas/_meta.json +276 -47
- package/kits/schemas/agent-config.schema.12.json +38 -2
- package/kits/schemas/agent-config.schema.13.json +591 -0
- package/kits/schemas/daemon.schema.6.json +9 -9
- package/kits/schemas/defaults.schema.5.json +29 -0
- package/kits/schemas/relation-config.schema.8.json +14 -1
- package/kits/schemas/relation-config.schema.9.json +129 -0
- package/kits/schemas/role-config.schema.1.json +3 -3
- package/kits/skills/.switches.json +3 -0
- package/kits/skills/ec-skill/SKILL.md +217 -0
- package/kits/skills/ec-skill/scripts/cli.mjs +128 -0
- package/kits/skills/ec-skill/scripts/deps.mjs +107 -0
- package/kits/skills/ec-skill/scripts/frontmatter.mjs +104 -0
- package/kits/skills/ec-skill/scripts/skill.mjs +629 -0
- package/kits/skills/ec-skill/scripts/template/SKILL.md.tmpl +30 -0
- package/kits/templates/decision/auto-mention.md +25 -0
- package/kits/templates/decision/session-renew.md +22 -0
- package/kits/templates/memory-consolidation/forget.md +104 -0
- package/kits/templates/memory-consolidation/generalize.md +76 -0
- package/kits/templates/memory-consolidation/notes.md +133 -0
- package/kits/templates/memory-consolidation/profile.md +58 -0
- package/kits/templates/memory-consolidation/promote.md +72 -0
- package/kits/templates/memory-consolidation/workflow.md +75 -0
- package/kits/templates/system-fragments/bootstrap.md +14 -5
- package/kits/templates/system-fragments/commands.md +1 -0
- package/kits/templates/system-fragments/group-history.md +6 -0
- package/kits/templates/system-fragments/identity.md +0 -3
- package/kits/templates/system-fragments/memory.md +79 -0
- package/kits/templates/system-fragments/reply.md +1 -0
- package/kits/templates/system-fragments/venue.md +1 -1
- package/package.json +4 -6
- package/dist/trigger/legacy-session-history.js +0 -19
- package/kits/templates/system-fragments/group-unread-history.md +0 -7
|
@@ -1,20 +1,26 @@
|
|
|
1
1
|
import { loadAgent } from '../../config-store.js';
|
|
2
2
|
import { initializationBlocksTasks } from '../../cli/agent-create.js';
|
|
3
3
|
import { createHash, randomBytes } from 'crypto';
|
|
4
|
+
import fs from 'fs';
|
|
5
|
+
import path from 'path';
|
|
4
6
|
import { agentStorageMenuService, isAgentStorageMenuRequest } from '../command/agent-storage-menu.js';
|
|
5
7
|
import { logger } from '../../utils/logger.js';
|
|
6
8
|
import { StreamDebouncer } from './stream-debouncer.js';
|
|
7
9
|
import { appendMessageLog, appendMessageLogStrict, buildInboundEntry, isTransientProtocolMessage } from './message-log.js';
|
|
8
10
|
import { buildEnvelope, isInteractionSendAccepted, sendInteractionPayload } from './message-utils.js';
|
|
9
11
|
import { chatDirPath } from '../session/session-fs-store.js';
|
|
10
|
-
import {
|
|
12
|
+
import { readTopicLabel, readTopicStatus } from '../session/session-mapper.js';
|
|
11
13
|
import { formatChannelKey, tryParseChannelKey } from '../channel-loader.js';
|
|
12
14
|
import { agentDir, resolvePaths } from '../../paths.js';
|
|
13
15
|
import { listStaticAgentAdmins, listStaticAgentOwners, resolvePeerRoleDetail, } from '../../config/peer-role-resolver.js';
|
|
14
16
|
import { resolveContactView, resolvePeerDisplayLabel, resolvePrincipal } from '../../config/contact-book.js';
|
|
15
17
|
import { findContactRequestNote, expirePendingContactRequests, reviewContactRequest, submitContactRequest, contactRequestSubmissionProtocolResult, queryOwnContactRequestStatus, } from '../../config/contact-request-service.js';
|
|
16
18
|
import { isValidAid } from '../../aun/aid/validation.js';
|
|
17
|
-
import { PeerIdentityCache } from '../relation/peer-identity.js';
|
|
19
|
+
import { PeerIdentityCache, formatPeerKey } from '../relation/peer-identity.js';
|
|
20
|
+
import { resolveEffective } from '../../config/config-manager.js';
|
|
21
|
+
import { createDecisionInferenceProvider } from '../inference/text-inference.js';
|
|
22
|
+
import { AutoMentionGate } from './auto-mention-gate.js';
|
|
23
|
+
import { syncGroupRulesContext } from '../../eck/group-rules-sync.js';
|
|
18
24
|
import { handlePendingDingtalkContactBindMessage, registerPendingDingtalkContactBind } from '../../channels/dingtalk.js';
|
|
19
25
|
import { handlePendingFeishuContactBindMessage, registerPendingFeishuContactBind } from '../../channels/feishu.js';
|
|
20
26
|
import { handlePendingQQBotContactBindMessage, registerPendingQQBotContactBind } from '../../channels/qqbot.js';
|
|
@@ -30,9 +36,9 @@ import { recordCausationSpan } from '../causation/audit.js';
|
|
|
30
36
|
import { renderActionAsText } from '../interaction-router.js';
|
|
31
37
|
import { planApprovalRoute } from '../permission/approval-gateway.js';
|
|
32
38
|
import { guardGroupSlashCommand, parseFullAccessCommand } from '../command/slash-gate.js';
|
|
33
|
-
import { isFullAccessEnabled } from '../../config-store.js';
|
|
34
39
|
import { createAgentPolicyAuthorization } from '../role/runtime-policy.js';
|
|
35
40
|
import { auditFullAccessEvent } from '../auth/authorization-audit.js';
|
|
41
|
+
import { DIAGNOSTIC_AGENT_AID, DiagnosticSessionRouter, parseDiagnosticEvent, isDiagnosticSessionActive } from '../command/diag-service.js';
|
|
36
42
|
import { consumeAdmissionReplyBudget, clearAdmissionReplyBudgets, evaluateInboundAdmissionPreflight, evaluateOrdinaryInboundAdmission, } from './message-admission.js';
|
|
37
43
|
const CONTACT_REJECTION_NOTICE = '[rejection] Not accepting messages from you.';
|
|
38
44
|
const REJECTABLE_PEER_TYPES = new Set(['ai', 'bot', 'agent', 'service']);
|
|
@@ -104,6 +110,79 @@ export class MessageBridge {
|
|
|
104
110
|
cmdHandler;
|
|
105
111
|
eventBus;
|
|
106
112
|
processOwnersProvider;
|
|
113
|
+
autoMentionGate = new AutoMentionGate({
|
|
114
|
+
resolveInference: item => {
|
|
115
|
+
const config = resolveEffective({
|
|
116
|
+
self: item.selfAid,
|
|
117
|
+
peerKey: formatPeerKey('aun', item.groupId),
|
|
118
|
+
}, { cache: true, expand: true });
|
|
119
|
+
const model = config.decision_model?.model?.trim();
|
|
120
|
+
const provider = createDecisionInferenceProvider(config.decision_model);
|
|
121
|
+
return model && provider ? { model, provider } : undefined;
|
|
122
|
+
},
|
|
123
|
+
resolveResources: async (item) => {
|
|
124
|
+
const config = resolveEffective({
|
|
125
|
+
self: item.selfAid,
|
|
126
|
+
peerKey: formatPeerKey('aun', item.groupId),
|
|
127
|
+
}, { cache: true, expand: true });
|
|
128
|
+
const resources = {};
|
|
129
|
+
if (config.groupRules?.mode !== 'ignore') {
|
|
130
|
+
try {
|
|
131
|
+
const synced = await syncGroupRulesContext({ selfAid: item.selfAid, groupId: item.groupId, channel: 'aun' });
|
|
132
|
+
if (synced.groupRulesPath && fs.existsSync(synced.groupRulesPath)) {
|
|
133
|
+
resources.groupRules = fs.readFileSync(synced.groupRulesPath, 'utf8');
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
catch (error) {
|
|
137
|
+
logger.debug(`[MessageBridge] Decision context group rules unavailable: ${String(error)}`);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
const personalPath = path.join(agentDir(item.selfAid), 'personal', 'personal.md');
|
|
141
|
+
if (fs.existsSync(personalPath))
|
|
142
|
+
resources.personal = fs.readFileSync(personalPath, 'utf8');
|
|
143
|
+
if (item.projectPath) {
|
|
144
|
+
const files = [];
|
|
145
|
+
let current = path.resolve(item.projectPath);
|
|
146
|
+
while (true) {
|
|
147
|
+
const candidate = path.join(current, 'AGENTS.md');
|
|
148
|
+
if (fs.existsSync(candidate))
|
|
149
|
+
files.unshift(candidate);
|
|
150
|
+
const parent = path.dirname(current);
|
|
151
|
+
if (parent === current)
|
|
152
|
+
break;
|
|
153
|
+
current = parent;
|
|
154
|
+
}
|
|
155
|
+
if (files.length > 0) {
|
|
156
|
+
resources.projectInstructions = files.map(file => (`# ${path.relative(item.projectPath, file) || 'AGENTS.md'}\n${fs.readFileSync(file, 'utf8')}`)).join('\n\n');
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return resources;
|
|
160
|
+
},
|
|
161
|
+
resolveQuietWindowMs: item => resolveEffective({
|
|
162
|
+
self: item.selfAid,
|
|
163
|
+
peerKey: formatPeerKey('aun', item.groupId),
|
|
164
|
+
}, { cache: true, expand: true }).autoMentionQuietWindowMs,
|
|
165
|
+
resolveMessageState: item => {
|
|
166
|
+
if (!item.messageId)
|
|
167
|
+
return 'missing';
|
|
168
|
+
if (item.sessionId) {
|
|
169
|
+
const state = this.messageQueue.getMessageState?.(item.sessionId, item.messageId, item.selfAid)?.state ?? 'missing';
|
|
170
|
+
if (state !== 'missing')
|
|
171
|
+
return state;
|
|
172
|
+
}
|
|
173
|
+
return this.messageQueue.getMessageStateByMessageId?.(item.messageId, item.selfAid, {
|
|
174
|
+
channelId: item.groupId,
|
|
175
|
+
groupId: item.groupId,
|
|
176
|
+
threadId: item.threadId || '',
|
|
177
|
+
})?.state ?? 'missing';
|
|
178
|
+
},
|
|
179
|
+
resolveProcessing: item => {
|
|
180
|
+
if (!item.sessionId)
|
|
181
|
+
return !!item.mainSessionBusy;
|
|
182
|
+
return (this.messageQueue.isProcessing?.(item.sessionId) ?? false)
|
|
183
|
+
|| (this.messageQueue.getQueueLength?.(item.sessionId) ?? 0) > 0;
|
|
184
|
+
},
|
|
185
|
+
});
|
|
107
186
|
debouncers = new Map();
|
|
108
187
|
defaultDebounce;
|
|
109
188
|
/** Serializes session selection/renewal with inbound log admission per route. */
|
|
@@ -115,6 +194,7 @@ export class MessageBridge {
|
|
|
115
194
|
menuTokenStore = new MenuTokenStore();
|
|
116
195
|
handoffRuntime;
|
|
117
196
|
sessionRenewService;
|
|
197
|
+
diagnosticRouter;
|
|
118
198
|
aidStatsCollector;
|
|
119
199
|
contactBindRuntimeChecker;
|
|
120
200
|
interactionRouter;
|
|
@@ -129,6 +209,13 @@ export class MessageBridge {
|
|
|
129
209
|
this.processOwnersProvider = processOwnersProvider;
|
|
130
210
|
this.defaultDebounce = defaultDebounce ?? 0;
|
|
131
211
|
this.sessionRenewService = new SessionRenewService(sessionManager, processor, { eventBus });
|
|
212
|
+
this.diagnosticRouter = new DiagnosticSessionRouter(sessionManager, async (sessionId) => {
|
|
213
|
+
// Revoke in the router first, then clear pending work before awaiting the
|
|
214
|
+
// runner interruption. Delayed debounce and recovery are fenced below.
|
|
215
|
+
this.messageQueue.clearBySession(sessionId);
|
|
216
|
+
this.messageQueue.markActiveTerminal(sessionId, 'stop');
|
|
217
|
+
await this.processor.interruptSession(sessionId, 'stop');
|
|
218
|
+
});
|
|
132
219
|
}
|
|
133
220
|
/** Inject EvolAgentRegistry so owner lookups/writes route to agent.json for agent-owned channels. */
|
|
134
221
|
setAgentRegistry(registry) {
|
|
@@ -520,6 +607,12 @@ export class MessageBridge {
|
|
|
520
607
|
}
|
|
521
608
|
return true;
|
|
522
609
|
}
|
|
610
|
+
async sendAdmissionMenuResponse(context, adapter, channel, msg, response) {
|
|
611
|
+
if (!consumeAdmissionReplyBudget(context))
|
|
612
|
+
return false;
|
|
613
|
+
await this.sendMenuResponse(adapter, channel, msg, response);
|
|
614
|
+
return true;
|
|
615
|
+
}
|
|
523
616
|
/** Controls handled before the normal slash handler still need structured group feedback. */
|
|
524
617
|
async sendSlashControlReply(channel, msg, sendReply, payload, adapter = this.processor.getChannelInfo?.(channel)?.adapter) {
|
|
525
618
|
if (msg.chatType === 'group' && adapter?.send) {
|
|
@@ -757,6 +850,12 @@ export class MessageBridge {
|
|
|
757
850
|
const conversationId = chatType === 'group' ? (msg.groupId || msg.channelId) : msg.peerId;
|
|
758
851
|
const selfAid = msg.selfAID || owningAgent?.aid || parsedChannelKey?.selfAID;
|
|
759
852
|
const resolvedChannelType = msg.channelType || parsedChannelKey?.type || effectiveChannelType;
|
|
853
|
+
const menuControl = resolvedChannelType === 'aun'
|
|
854
|
+
? parseMenuControl(content)
|
|
855
|
+
: { isMenu: false };
|
|
856
|
+
const isPrivateMenuTokenRequest = menuControl.isMenu
|
|
857
|
+
&& menuControl.type === 'menu.token.request'
|
|
858
|
+
&& chatType === 'private';
|
|
760
859
|
const admissionRoute = [
|
|
761
860
|
resolvedChannelType,
|
|
762
861
|
selfAid || '',
|
|
@@ -793,7 +892,8 @@ export class MessageBridge {
|
|
|
793
892
|
if (chatType === 'private' && !peerType) {
|
|
794
893
|
peerType = PeerIdentityCache.get('aun', primaryId, agentDir(selfAid))?.type;
|
|
795
894
|
}
|
|
796
|
-
if (
|
|
895
|
+
if (!menuControl.isMenu
|
|
896
|
+
&& chatType === 'private'
|
|
797
897
|
&& REJECTABLE_PEER_TYPES.has(String(peerType || '').trim().toLowerCase())) {
|
|
798
898
|
await this.sendAdmissionControlReply(admissionPreflight.context, msg, sendReply, CONTACT_REJECTION_NOTICE);
|
|
799
899
|
}
|
|
@@ -831,11 +931,23 @@ export class MessageBridge {
|
|
|
831
931
|
return;
|
|
832
932
|
}
|
|
833
933
|
const admissionDecision = evaluateOrdinaryInboundAdmission(admissionPreflight.context);
|
|
834
|
-
if (!admissionDecision.allow) {
|
|
934
|
+
if (!admissionDecision.allow && !isPrivateMenuTokenRequest) {
|
|
835
935
|
if (chatType === 'private') {
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
936
|
+
if (menuControl.isMenu) {
|
|
937
|
+
if (hasValidMenuId(menuControl)) {
|
|
938
|
+
const header = { id: menuControl.id, ...(menuControl.name?.trim() ? { name: menuControl.name } : {}) };
|
|
939
|
+
await this.sendAdmissionMenuResponse(admissionDecision.context, adapter, channelName, msg, menuFailure(header, {
|
|
940
|
+
code: 'ROLE_ACCESS_DENIED',
|
|
941
|
+
message: this.admissionDeniedNotice(admissionDecision.context, resolvedChannelType)
|
|
942
|
+
?? '当前不接受陌生人消息。',
|
|
943
|
+
}));
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
else {
|
|
947
|
+
const notice = this.admissionDeniedNotice(admissionDecision.context, resolvedChannelType);
|
|
948
|
+
if (notice) {
|
|
949
|
+
await this.sendAdmissionControlReply(admissionDecision.context, msg, sendReply, notice);
|
|
950
|
+
}
|
|
839
951
|
}
|
|
840
952
|
}
|
|
841
953
|
logger.info(`[MessageBridge] Inbound admission denied before business routing: self=${selfAid} actor=${actorId ?? '<none>'} channel=${channelKey} reason=${admissionDecision.reason}`);
|
|
@@ -844,9 +956,6 @@ export class MessageBridge {
|
|
|
844
956
|
// Menu is a protocol control plane. Its parsing and response path must
|
|
845
957
|
// remain available throughout the agent lifecycle; lifecycle gates
|
|
846
958
|
// ordinary model execution, not protocol handling.
|
|
847
|
-
const menuControl = resolvedChannelType === 'aun'
|
|
848
|
-
? parseMenuControl(content)
|
|
849
|
-
: { isMenu: false };
|
|
850
959
|
let menuResponseWarning;
|
|
851
960
|
if (menuControl.isMenu) {
|
|
852
961
|
// Menu is an AUN private control plane. Group messages must never
|
|
@@ -971,6 +1080,37 @@ export class MessageBridge {
|
|
|
971
1080
|
await this.sendAccessDenied(adapter, channelName, msg, sendReply);
|
|
972
1081
|
return;
|
|
973
1082
|
}
|
|
1083
|
+
// Diagnostic invitations carry their own execution boundary. Resolve
|
|
1084
|
+
// that boundary before ordinary command/session routing so a new case
|
|
1085
|
+
// cannot inherit an older Codex thread.
|
|
1086
|
+
let diagnosticSession;
|
|
1087
|
+
if (selfAid === DIAGNOSTIC_AGENT_AID && resolvedChannelType === 'aun'
|
|
1088
|
+
&& chatType === 'private' && msg.source !== 'handoff') {
|
|
1089
|
+
try {
|
|
1090
|
+
const diagnosticEvent = parseDiagnosticEvent(content);
|
|
1091
|
+
if (diagnosticEvent) {
|
|
1092
|
+
releaseAdmission = await this.acquireAdmission(admissionRoute);
|
|
1093
|
+
const routed = await this.diagnosticRouter.route({
|
|
1094
|
+
event: diagnosticEvent, message: msg, channel: channelName,
|
|
1095
|
+
channelType: resolvedChannelType, selfAid,
|
|
1096
|
+
chatDir: chatDirPath(resolvePaths().sessionsDir, resolvedChannelType, msg.channelId, selfAid, channelName),
|
|
1097
|
+
projectPath: owningAgent?.projectPath ?? this.defaultProjectPath,
|
|
1098
|
+
baseagent: owningAgent?.baseagent,
|
|
1099
|
+
});
|
|
1100
|
+
if ('notice' in routed) {
|
|
1101
|
+
await sendReply(msg.channelId, routed.notice, msg.replyContext);
|
|
1102
|
+
return;
|
|
1103
|
+
}
|
|
1104
|
+
diagnosticSession = routed.session;
|
|
1105
|
+
diagnosticSession.identity = identity;
|
|
1106
|
+
}
|
|
1107
|
+
}
|
|
1108
|
+
catch (error) {
|
|
1109
|
+
logger.warn(`[MessageBridge] Diagnostic admission failed: ${error instanceof Error ? error.message : 'unknown error'}`);
|
|
1110
|
+
await sendReply(msg.channelId, '诊断会话隔离未完成,本次未执行诊断。请检查诊断端日志后重试。', msg.replyContext);
|
|
1111
|
+
return;
|
|
1112
|
+
}
|
|
1113
|
+
}
|
|
974
1114
|
// /fa is an execution prefix, not a session-mode switch or a normal
|
|
975
1115
|
// quick command. Only this trusted bridge path can construct the
|
|
976
1116
|
// internal per-call authorization carried into ResponseEngine.
|
|
@@ -1003,10 +1143,6 @@ export class MessageBridge {
|
|
|
1003
1143
|
await reject('用法:/fa <提示词> 或 /fullaccess <提示词>');
|
|
1004
1144
|
return;
|
|
1005
1145
|
}
|
|
1006
|
-
if (!isFullAccessEnabled()) {
|
|
1007
|
-
await reject('fullaccess 功能未启用,请先设置 daemon.json.fullaccess.enabled=true');
|
|
1008
|
-
return;
|
|
1009
|
-
}
|
|
1010
1146
|
if (!authSubject.isDaemonOwner || !authSubject.principalId) {
|
|
1011
1147
|
await reject('仅可信 daemon-owner 可以使用 fullaccess');
|
|
1012
1148
|
return;
|
|
@@ -1073,6 +1209,27 @@ export class MessageBridge {
|
|
|
1073
1209
|
// 2. 命令快速路径(去除引用前缀后检查,兼容话题中引用上文的情况)
|
|
1074
1210
|
const contentForCmd = content.replace(/^(>[^\n]*\n)+\n?/, '').trim();
|
|
1075
1211
|
const cmdContent = contentForCmd || content;
|
|
1212
|
+
let autoMode = false;
|
|
1213
|
+
if (resolvedChannelType === 'aun' && chatType === 'group' && selfAid && msg.source !== 'handoff') {
|
|
1214
|
+
try {
|
|
1215
|
+
const config = resolveEffective({ self: selfAid, peerKey: formatPeerKey('aun', msg.groupId || msg.channelId) }, { cache: true, expand: true });
|
|
1216
|
+
autoMode = config.mentionMode === 'auto';
|
|
1217
|
+
}
|
|
1218
|
+
catch (error) {
|
|
1219
|
+
logger.warn(`[MessageBridge] Auto mention configuration unavailable: ${String(error)}`);
|
|
1220
|
+
}
|
|
1221
|
+
}
|
|
1222
|
+
if (autoMode && !msg.isMentioned && /^\//.test(cmdContent)) {
|
|
1223
|
+
const chatDir = chatDirPath(resolvePaths().sessionsDir, 'aun', msg.channelId, selfAid || '', channelKey);
|
|
1224
|
+
appendMessageLog(chatDir, buildInboundEntry({
|
|
1225
|
+
from: msg.peerId, to: selfAid || '', chatType: 'group', groupId: msg.groupId,
|
|
1226
|
+
threadId: msg.threadId, msgId: msg.messageId, content: msg.messageLogContent ?? content,
|
|
1227
|
+
timestamp: receivedAt, msgType: 'command', groupAttention: 'command', groupDelivery: 'filtered',
|
|
1228
|
+
}));
|
|
1229
|
+
if (msg.messageId)
|
|
1230
|
+
adapter?.acknowledge?.(msg.messageId).catch(() => { });
|
|
1231
|
+
return;
|
|
1232
|
+
}
|
|
1076
1233
|
if (msg.source !== 'handoff' && !executionPermissionOverride) {
|
|
1077
1234
|
const groupGuard = guardGroupSlashCommand(cmdContent, chatType);
|
|
1078
1235
|
if (groupGuard?.kind === 'command.error') {
|
|
@@ -1154,6 +1311,72 @@ export class MessageBridge {
|
|
|
1154
1311
|
logger.info(`[MessageBridge] Thread creation denied (silent drop): channel=${channelName} channelId=${msg.channelId} thread=${msg.threadId} sender=${msg.peerId}`);
|
|
1155
1312
|
return;
|
|
1156
1313
|
}
|
|
1314
|
+
let autoResult;
|
|
1315
|
+
if (autoMode && selfAid && msg.groupId && !executionPermissionOverride
|
|
1316
|
+
&& !isCmd && !/\[EvolCore\.(receive|reply|agent)\]/.test(content)) {
|
|
1317
|
+
const attentionItem = {
|
|
1318
|
+
value: msg, selfAid, groupId: msg.groupId, threadId: msg.threadId || '', channelKey,
|
|
1319
|
+
sessionId: msg.replyContext?.sessionId
|
|
1320
|
+
?? this.sessionManager.getActiveSessionSync?.(channelName, msg.channelId, resolvedChannelType, selfAid)?.id,
|
|
1321
|
+
projectPath: owningAgent?.projectPath ?? this.defaultProjectPath,
|
|
1322
|
+
messageId: msg.messageId, sender: msg.peerId, role: identity.role,
|
|
1323
|
+
mentions: (msg.mentions ?? []).flatMap(mention => {
|
|
1324
|
+
if (typeof mention.aid === 'string')
|
|
1325
|
+
return [mention.aid];
|
|
1326
|
+
return mention.scope === 'all' ? ['all'] : [];
|
|
1327
|
+
}),
|
|
1328
|
+
content, receivedAt,
|
|
1329
|
+
mainSessionBusy: (() => {
|
|
1330
|
+
try {
|
|
1331
|
+
const active = this.sessionManager.getActiveSessionSync?.(channelName, msg.channelId, resolvedChannelType, selfAid);
|
|
1332
|
+
return !!active && ((this.messageQueue.isProcessing?.(active.id) ?? false)
|
|
1333
|
+
|| (this.messageQueue.getQueueLength?.(active.id) ?? 0) > 0);
|
|
1334
|
+
}
|
|
1335
|
+
catch {
|
|
1336
|
+
return false;
|
|
1337
|
+
}
|
|
1338
|
+
})(),
|
|
1339
|
+
};
|
|
1340
|
+
if (msg.isMentioned) {
|
|
1341
|
+
autoResult = {
|
|
1342
|
+
decision: 'interrupt', source: 'mention', leader: true, batch: [attentionItem], background: [],
|
|
1343
|
+
current: [{ source: 'current', message_id: msg.messageId ?? null, sender: msg.peerId,
|
|
1344
|
+
role: identity.role, mentions: attentionItem.mentions,
|
|
1345
|
+
timestamp: new Date(receivedAt).toISOString(), content }],
|
|
1346
|
+
};
|
|
1347
|
+
}
|
|
1348
|
+
else {
|
|
1349
|
+
if (msg.messageId)
|
|
1350
|
+
adapter?.acknowledge?.(msg.messageId).catch(() => { });
|
|
1351
|
+
autoResult = await this.autoMentionGate.offer(attentionItem);
|
|
1352
|
+
if (autoResult.decision === 'ignore') {
|
|
1353
|
+
try {
|
|
1354
|
+
const chatDir = chatDirPath(resolvePaths().sessionsDir, 'aun', msg.channelId, selfAid, channelKey);
|
|
1355
|
+
appendMessageLogStrict(chatDir, buildInboundEntry({
|
|
1356
|
+
from: msg.peerId, to: selfAid, chatType: 'group', groupId: msg.groupId,
|
|
1357
|
+
threadId: msg.threadId, msgId: msg.messageId, content: msg.messageLogContent ?? content,
|
|
1358
|
+
permMode: identity.role, timestamp: receivedAt, receivedAt: msg.receivedAt,
|
|
1359
|
+
gatewaySeq: msg.gatewaySeq, encrypt: msg.encrypted, peerName: msg.peerName,
|
|
1360
|
+
peerType: msg.peerType, msgType: msg.msgType, payloadType: msg.payloadType,
|
|
1361
|
+
payloadSummary: msg.payloadSummary,
|
|
1362
|
+
groupAttention: autoResult.source === 'fallback' ? 'auto-fallback' : 'auto-ignore',
|
|
1363
|
+
groupDelivery: 'filtered',
|
|
1364
|
+
attentionDecision: { ...autoResult.details, decision: 'ignore', source: autoResult.source },
|
|
1365
|
+
}), true);
|
|
1366
|
+
return;
|
|
1367
|
+
}
|
|
1368
|
+
catch (error) {
|
|
1369
|
+
logger.warn(`[MessageBridge] Auto ignore history append failed, responding: ${String(error)}`);
|
|
1370
|
+
autoResult = { ...autoResult, decision: 'interrupt', source: 'fallback', leader: true, batch: [attentionItem] };
|
|
1371
|
+
}
|
|
1372
|
+
}
|
|
1373
|
+
if (!autoResult.leader)
|
|
1374
|
+
return;
|
|
1375
|
+
}
|
|
1376
|
+
}
|
|
1377
|
+
const attentionDecision = autoResult
|
|
1378
|
+
? { ...autoResult.details, decision: autoResult.decision, source: autoResult.source }
|
|
1379
|
+
: undefined;
|
|
1157
1380
|
const metadata = {};
|
|
1158
1381
|
// 话题会话创建时写入 replyContext(用于 threadId 路由);主会话不写(避免群聊覆盖)
|
|
1159
1382
|
if (msg.threadId && msg.replyContext)
|
|
@@ -1186,9 +1409,10 @@ export class MessageBridge {
|
|
|
1186
1409
|
// transaction. Without this per-route single-flight, a second message
|
|
1187
1410
|
// can observe the just-renewed empty session before the first message
|
|
1188
1411
|
// has been appended and create another renewed session.
|
|
1189
|
-
|
|
1412
|
+
if (!releaseAdmission)
|
|
1413
|
+
releaseAdmission = await this.acquireAdmission(admissionRoute);
|
|
1190
1414
|
const hadMainSession = msg.threadId ? true : this.sessionManager.hasMainSession(channelName, msg.channelId, msg.channelType || effectiveChannelType, msg.selfAID || selfAid);
|
|
1191
|
-
let session = await this.sessionManager.getOrCreateSession(channelName, msg.channelId, effectiveProjectPath, msg.threadId, Object.keys(metadata).length ? metadata : undefined, this.extractTopicName(msg), msg.peerId, chatType, owningAgent?.baseagent, msg.selfAID, msg.channelType || effectiveChannelType, msg.peerType, identity,
|
|
1415
|
+
let session = diagnosticSession ?? await this.sessionManager.getOrCreateSession(channelName, msg.channelId, effectiveProjectPath, msg.threadId, Object.keys(metadata).length ? metadata : undefined, this.extractTopicName(msg), msg.peerId, chatType, owningAgent?.baseagent, msg.selfAID, msg.channelType || effectiveChannelType, msg.peerType, identity, readTopicLabel(msg) ?? readTopicLabel(msg.replyContext?.metadata), readTopicStatus(msg) ?? readTopicStatus(msg.replyContext?.metadata));
|
|
1192
1416
|
if (session.threadId && typeof this.messageQueue.reserveSessionIngress === 'function') {
|
|
1193
1417
|
releaseSessionIngress = this.messageQueue.reserveSessionIngress(session.id);
|
|
1194
1418
|
}
|
|
@@ -1201,7 +1425,7 @@ export class MessageBridge {
|
|
|
1201
1425
|
const inboundCausation = restoredCausation
|
|
1202
1426
|
? deriveCausation(restoredCausation)
|
|
1203
1427
|
: createRootCausation();
|
|
1204
|
-
const renewResult = await this.sessionRenewService.resolve({
|
|
1428
|
+
const renewResult = session.metadata?.diagnostic ? { session } : await this.sessionRenewService.resolve({
|
|
1205
1429
|
session,
|
|
1206
1430
|
channelName,
|
|
1207
1431
|
channelType: msg.channelType || effectiveChannelType,
|
|
@@ -1218,6 +1442,32 @@ export class MessageBridge {
|
|
|
1218
1442
|
causation: inboundCausation,
|
|
1219
1443
|
});
|
|
1220
1444
|
session = renewResult.session;
|
|
1445
|
+
if ('renewed' in renewResult && renewResult.renewed) {
|
|
1446
|
+
const notice = '已自动切换到新会话';
|
|
1447
|
+
try {
|
|
1448
|
+
if (adapter?.send) {
|
|
1449
|
+
await adapter.send(buildEnvelope({
|
|
1450
|
+
taskId: `session-renew-${randomBytes(5).toString('hex')}`,
|
|
1451
|
+
sessionId: session.id,
|
|
1452
|
+
channel: channelName,
|
|
1453
|
+
channelId: msg.channelId,
|
|
1454
|
+
agentName: owningAgent?.name ?? selfAid ?? '<unknown>',
|
|
1455
|
+
chatmode: 'interactive',
|
|
1456
|
+
replyContext: msg.replyContext,
|
|
1457
|
+
}), {
|
|
1458
|
+
kind: 'system.notice',
|
|
1459
|
+
text: notice,
|
|
1460
|
+
subtype: 'session.auto-renewed',
|
|
1461
|
+
});
|
|
1462
|
+
}
|
|
1463
|
+
else {
|
|
1464
|
+
await sendReply(msg.channelId, notice, msg.replyContext);
|
|
1465
|
+
}
|
|
1466
|
+
}
|
|
1467
|
+
catch (error) {
|
|
1468
|
+
logger.warn(`[MessageBridge] Failed to send automatic session-renew notice: ${error instanceof Error ? error.message : String(error)}`);
|
|
1469
|
+
}
|
|
1470
|
+
}
|
|
1221
1471
|
// 4. 群聊发送者标注由消息渲染层(message-renderer)逐条承担,不再在此硬编码前缀,
|
|
1222
1472
|
// 消息日志因此保存干净原文。policy.messagePrefix 暂保留(未来清理)。
|
|
1223
1473
|
// 5. 构造完整消息(channel 字段存实例名,用于 session 精确匹配)
|
|
@@ -1244,15 +1494,45 @@ export class MessageBridge {
|
|
|
1244
1494
|
mentions: msg.mentions, mentionAids: msg.mentionAids, threadId: msg.threadId,
|
|
1245
1495
|
isMentioned: msg.isMentioned,
|
|
1246
1496
|
topicName: this.extractTopicName(msg),
|
|
1247
|
-
|
|
1497
|
+
label: session.label,
|
|
1498
|
+
topicStatus: session.topicStatus,
|
|
1248
1499
|
replyContext: msg.replyContext,
|
|
1249
1500
|
source: msg.source,
|
|
1250
1501
|
...(executionPermissionOverride ? { executionPermissionOverride } : {}),
|
|
1251
1502
|
boundSessionId: session.id,
|
|
1252
1503
|
resolvedIdentity: identity,
|
|
1253
1504
|
mentionMode: msg.mentionMode,
|
|
1505
|
+
...(autoResult && autoResult.decision !== 'ignore' ? { attentionPolicy: autoResult.decision } : {}),
|
|
1506
|
+
...(autoResult ? { autoMentionBatch: true } : {}),
|
|
1254
1507
|
causation: inboundCausation,
|
|
1255
1508
|
};
|
|
1509
|
+
if (autoResult && autoResult.batch.length > 1) {
|
|
1510
|
+
fullMessage.content = autoResult.batch.map(item => item.content).join('\n');
|
|
1511
|
+
fullMessage.sourceMessageIds = autoResult.batch.flatMap(item => item.messageId ? [item.messageId] : []);
|
|
1512
|
+
fullMessage.items = autoResult.batch.map(item => ({
|
|
1513
|
+
peerId: item.sender, peerName: item.value.peerName, peerType: item.value.peerType,
|
|
1514
|
+
peerRole: item.role, content: item.content,
|
|
1515
|
+
mentions: item.value.mentions, mentionAids: item.value.mentionAids,
|
|
1516
|
+
images: item.value.images, timestamp: item.receivedAt,
|
|
1517
|
+
receivedAt: item.value.receivedAt, gatewaySeq: item.value.gatewaySeq,
|
|
1518
|
+
encrypted: item.value.encrypted,
|
|
1519
|
+
sameDevice: item.value.sameDevice, sameNetwork: item.value.sameNetwork,
|
|
1520
|
+
sameEgressIp: item.value.sameEgressIp,
|
|
1521
|
+
}));
|
|
1522
|
+
fullMessage.images = autoResult.batch.flatMap(item => item.value.images ?? []);
|
|
1523
|
+
fullMessage.mentions = autoResult.batch.flatMap(item => item.value.mentions ?? []);
|
|
1524
|
+
fullMessage.timestamp = autoResult.batch[0].receivedAt;
|
|
1525
|
+
fullMessage.receivedAt = autoResult.batch[0].receivedAt;
|
|
1526
|
+
fullMessage.encrypted = autoResult.batch.some(item => item.value.encrypted === true) ? true : fullMessage.encrypted;
|
|
1527
|
+
if (fullMessage.encrypted && fullMessage.replyContext) {
|
|
1528
|
+
fullMessage.replyContext = {
|
|
1529
|
+
...fullMessage.replyContext,
|
|
1530
|
+
metadata: { ...fullMessage.replyContext.metadata, encrypted: true },
|
|
1531
|
+
};
|
|
1532
|
+
}
|
|
1533
|
+
if (!autoResult.batch.every(item => item.role === identity.role))
|
|
1534
|
+
fullMessage.batchRole = 'visitor';
|
|
1535
|
+
}
|
|
1256
1536
|
recordCausationSpan(inboundCausation, 'message.inbound', {
|
|
1257
1537
|
status: 'completed',
|
|
1258
1538
|
refs: { messageId: msg.messageId, sessionId: session.id },
|
|
@@ -1285,6 +1565,11 @@ export class MessageBridge {
|
|
|
1285
1565
|
msgType: msg.msgType,
|
|
1286
1566
|
payloadType: msg.payloadType,
|
|
1287
1567
|
payloadSummary: msg.payloadSummary,
|
|
1568
|
+
groupAttention: autoResult?.source === 'mention' ? 'auto-mention'
|
|
1569
|
+
: autoResult?.source === 'fallback' ? 'auto-fallback'
|
|
1570
|
+
: autoResult?.decision === 'defer' ? 'auto-defer'
|
|
1571
|
+
: autoResult ? 'auto-interrupt' : undefined,
|
|
1572
|
+
attentionDecision,
|
|
1288
1573
|
}) };
|
|
1289
1574
|
})();
|
|
1290
1575
|
let handoffCandidate = false;
|
|
@@ -1317,6 +1602,23 @@ export class MessageBridge {
|
|
|
1317
1602
|
}
|
|
1318
1603
|
if (!handoffCandidate)
|
|
1319
1604
|
appendMessageLog(inboundEntry.chatDir, inboundEntry.entry);
|
|
1605
|
+
if (autoResult && autoResult.batch.length > 1) {
|
|
1606
|
+
for (const item of autoResult.batch.slice(0, -1)) {
|
|
1607
|
+
const member = item.value;
|
|
1608
|
+
appendMessageLog(inboundEntry.chatDir, buildInboundEntry({
|
|
1609
|
+
from: item.sender, to: selfAid || '', sessionId: session.id,
|
|
1610
|
+
threadId: session.threadId || null, chatType: 'group', groupId: msg.groupId,
|
|
1611
|
+
msgId: item.messageId, content: member.messageLogContent ?? item.content,
|
|
1612
|
+
permMode: item.role, timestamp: item.receivedAt,
|
|
1613
|
+
receivedAt: member.receivedAt, gatewaySeq: member.gatewaySeq,
|
|
1614
|
+
encrypt: member.encrypted, peerName: member.peerName, peerType: member.peerType,
|
|
1615
|
+
msgType: member.msgType, payloadType: member.payloadType,
|
|
1616
|
+
payloadSummary: member.payloadSummary,
|
|
1617
|
+
groupAttention: autoResult?.decision === 'defer' ? 'auto-defer' : 'auto-interrupt',
|
|
1618
|
+
attentionDecision,
|
|
1619
|
+
}));
|
|
1620
|
+
}
|
|
1621
|
+
}
|
|
1320
1622
|
// The authoritative session choice is now paired with its first
|
|
1321
1623
|
// conversation record. Release before waiting for queue completion so
|
|
1322
1624
|
// later group messages can still exercise the normal interrupt/FIFO
|
|
@@ -1335,16 +1637,24 @@ export class MessageBridge {
|
|
|
1335
1637
|
adapter?.acknowledge?.(fullMessage.messageId).catch(() => { });
|
|
1336
1638
|
}
|
|
1337
1639
|
const isInterrupt = chatType !== 'group' && !handoffCandidate;
|
|
1640
|
+
const attentionPolicy = fullMessage.attentionPolicy;
|
|
1641
|
+
const shouldInterrupt = isInterrupt || attentionPolicy === 'interrupt';
|
|
1338
1642
|
const enqueueAgentName = owningAgent?.name ?? '<unknown>';
|
|
1339
1643
|
const selfAID = session.selfAID;
|
|
1340
1644
|
const doEnqueue = async (m) => {
|
|
1645
|
+
// A cancellation can arrive while this message is being debounced.
|
|
1646
|
+
if (!isDiagnosticSessionActive(session, this.sessionManager.getChatDir(session)))
|
|
1647
|
+
return;
|
|
1341
1648
|
const enqueue = handoffCandidate ? this.messageQueue.enqueuePersisted.bind(this.messageQueue) : this.messageQueue.enqueue.bind(this.messageQueue);
|
|
1342
1649
|
try {
|
|
1343
1650
|
return await enqueue(session.id, m, session.projectPath, {
|
|
1344
|
-
interruptible:
|
|
1345
|
-
|
|
1651
|
+
interruptible: shouldInterrupt,
|
|
1652
|
+
attentionPolicy,
|
|
1653
|
+
interruptSamePeer: !shouldInterrupt && !attentionPolicy, // 旧群聊路径保留同人连发打断
|
|
1654
|
+
priority: attentionPolicy === 'interrupt',
|
|
1346
1655
|
agentName: enqueueAgentName,
|
|
1347
|
-
role:
|
|
1656
|
+
role: chatType === 'group' ? (session.identity?.role ?? 'none')
|
|
1657
|
+
: (!shouldInterrupt ? (session.identity?.role ?? 'none') : undefined),
|
|
1348
1658
|
sessionKeyField: session.sessionKey,
|
|
1349
1659
|
selfAID, // ← 传递 selfAID,用于队列隔离
|
|
1350
1660
|
onPersisted: releaseIngress,
|
|
@@ -1704,7 +2014,7 @@ export class MessageBridge {
|
|
|
1704
2014
|
...(msg.peerDeviceId ? { toDeviceId: msg.peerDeviceId } : {}),
|
|
1705
2015
|
metadata: {
|
|
1706
2016
|
...(msg.replyContext?.metadata ?? {}),
|
|
1707
|
-
...evolMenuResponseTransportMetadata(msg.protectedHeaders?.sdk_version),
|
|
2017
|
+
...evolMenuResponseTransportMetadata(msg.protectedHeaders?.sdk_version, msg.protectedHeaders?.menu_volatile_v1),
|
|
1708
2018
|
...(isAgentStorageMenuRequest(request) ? { encrypted: true } : {}),
|
|
1709
2019
|
},
|
|
1710
2020
|
};
|
|
@@ -345,6 +345,7 @@ export function buildInboundEntry(opts) {
|
|
|
345
345
|
payloadSummary: opts.payloadSummary,
|
|
346
346
|
groupAttention: opts.groupAttention,
|
|
347
347
|
groupDelivery: opts.groupDelivery,
|
|
348
|
+
attentionDecision: opts.attentionDecision,
|
|
348
349
|
content: opts.content,
|
|
349
350
|
replyTo: opts.replyTo ?? null,
|
|
350
351
|
agent: null,
|