evolcore 0.0.28 → 0.0.29
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 +12 -0
- package/bin/ec-safe-output.js +270 -54
- package/dist/agents/baseagent-availability.js +37 -1
- package/dist/agents/baseagent.js +19 -9
- package/dist/agents/claude-runner.js +171 -13
- package/dist/agents/codex/app-server-client.js +6 -2
- package/dist/agents/codex/diagnostics.js +4 -3
- package/dist/agents/codex-runner.js +396 -95
- 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/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 +249 -89
- 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 +40 -10
- package/dist/cli/data-command.js +4 -4
- 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/restart-monitor.js +62 -6
- 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 +51 -23
- 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 +315 -60
- package/dist/core/command/menu-protocol.js +42 -4
- package/dist/core/command/slash-gate.js +10 -10
- package/dist/core/command/slash-handler.js +206 -64
- 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 +69 -16
- package/dist/core/message/message-bridge.js +287 -21
- 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 +567 -80
- package/dist/core/permission/ec-command-parser.js +1029 -80
- 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 +276 -23
- 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 +478 -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 +708 -18
- package/dist/ipc.js +564 -2
- package/dist/paths.js +30 -9
- 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/avatar-upload.js +59 -18
- 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/welcome.js +37 -8
- 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/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
package/dist/channels/aun.js
CHANGED
|
@@ -11,13 +11,13 @@ import { LogWriter } from '../utils/log-writer.js';
|
|
|
11
11
|
import { middleOutputModePolicy, resolveShowActivities, showActivitiesPolicy } from '../core/channel-loader.js';
|
|
12
12
|
import { DEFAULT_FLUSH_DELAY_SECONDS } from '../types.js';
|
|
13
13
|
import { resolvePaths, agentDir as agentDirPath, resolveRoot, channelStatePath } from '../paths.js';
|
|
14
|
-
import { saveToUploads, sanitizeFileName, bufferToInboundImage, safeFetch } from '../utils/media-cache.js';
|
|
14
|
+
import { saveToUploads, saveRelationAttachment, sanitizeFileName, bufferToInboundImage, safeFetch } from '../utils/media-cache.js';
|
|
15
15
|
import { appendAidEvent } from '../utils/instance-registry.js';
|
|
16
16
|
import { appendMessageLog, appendMessageLogStrict, buildOutboundEntry, buildInboundEntry, classifyAunPayloadForLog, findMessageLogOperationMessageId, hasMessageLogOperation } from '../core/message/message-log.js';
|
|
17
17
|
import { createSendFileMarkerPattern } from '../core/message/file-markers.js';
|
|
18
18
|
import { chatDirPath } from '../core/session/session-fs-store.js';
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
19
|
+
import { readTopicLabel, readTopicStatus } from '../core/session/session-mapper.js';
|
|
20
|
+
import { appendHintAddDetailed, appendHintRemoveDetailed, appendHintPromoteDetailed, parseInjectRequest, peekHints, withHintTargetLock } from '../core/message/pending-hints.js';
|
|
21
21
|
import { appendAidLifecycle } from '../aun/aid/identity.js';
|
|
22
22
|
import { enableFullGroupPullPagination, enableReliableMessageDelivery, getAidStore, loadClient, SLOT } from '../aun/aid/store.js';
|
|
23
23
|
import { MAX_AUN_ATTACHMENT_SIZE, uploadBufferAndBuildPayload, uploadFileAndBuildPayload } from '../aun/msg/upload.js';
|
|
@@ -25,7 +25,7 @@ import { loadAgent } from '../config-store.js';
|
|
|
25
25
|
import { resolveAgentLifecycle } from '../config/lifecycle.js';
|
|
26
26
|
import { resolveEffective } from '../config/config-manager.js';
|
|
27
27
|
import { isManagementRole } from '../config/builtin-roles.js';
|
|
28
|
-
import { normalizeMentionMode } from '../config/mention-mode.js';
|
|
28
|
+
import { normalizeAunMentionMode, normalizeMentionMode } from '../config/mention-mode.js';
|
|
29
29
|
import { getProcessStartTime } from '../utils/process-introspect.js';
|
|
30
30
|
import * as outbox from '../aun/outbox.js';
|
|
31
31
|
import { formatSize } from '../utils/media-cache.js';
|
|
@@ -1338,7 +1338,10 @@ export class AUNChannel {
|
|
|
1338
1338
|
// Keep the command contract consistent for mixed friendly/canonical @
|
|
1339
1339
|
// tokens: selector labels are removed above, then known AID-shaped tokens
|
|
1340
1340
|
// anywhere in the command are removed by the existing helper.
|
|
1341
|
-
|
|
1341
|
+
const stripped = this.stripAllMentions(commandText);
|
|
1342
|
+
// Keep the removed /mentionmode spelling compatible at the channel
|
|
1343
|
+
// boundary while the command handler uses /mention internally.
|
|
1344
|
+
return stripped.replace(/^\/mentionmode(?=\s|$)/, '/mention');
|
|
1342
1345
|
}
|
|
1343
1346
|
parsePayloadMentionsOrReject(payload, context, messageId, seq) {
|
|
1344
1347
|
const raw = payload && typeof payload === 'object' && !Array.isArray(payload)
|
|
@@ -1391,7 +1394,7 @@ export class AUNChannel {
|
|
|
1391
1394
|
return sanitized;
|
|
1392
1395
|
return undefined;
|
|
1393
1396
|
}
|
|
1394
|
-
buildGroupReplyContext(threadId, senderAid, encrypted, messageId, chatmode, topicName, groupId,
|
|
1397
|
+
buildGroupReplyContext(threadId, senderAid, encrypted, messageId, chatmode, topicName, groupId, label, topicStatus) {
|
|
1395
1398
|
if (!groupId)
|
|
1396
1399
|
throw new Error('buildGroupReplyContext requires groupId');
|
|
1397
1400
|
const replyContext = {
|
|
@@ -1409,11 +1412,13 @@ export class AUNChannel {
|
|
|
1409
1412
|
replyContext.metadata = {};
|
|
1410
1413
|
replyContext.metadata.topicName = topicName;
|
|
1411
1414
|
}
|
|
1412
|
-
if (
|
|
1413
|
-
replyContext.metadata.
|
|
1415
|
+
if (label && threadId)
|
|
1416
|
+
replyContext.metadata.label = label;
|
|
1417
|
+
if (topicStatus && threadId)
|
|
1418
|
+
replyContext.metadata.topicStatus = topicStatus;
|
|
1414
1419
|
return replyContext;
|
|
1415
1420
|
}
|
|
1416
|
-
buildPrivateReplyContext(threadId, senderAid, encrypted, messageId, chatmode, topicName, refMessageId,
|
|
1421
|
+
buildPrivateReplyContext(threadId, senderAid, encrypted, messageId, chatmode, topicName, refMessageId, label, topicStatus) {
|
|
1417
1422
|
const replyContext = {
|
|
1418
1423
|
metadata: { encrypted, chatmode, refMessageId },
|
|
1419
1424
|
delivery: { chatType: 'private' },
|
|
@@ -1425,8 +1430,10 @@ export class AUNChannel {
|
|
|
1425
1430
|
replyContext.replyToMessageId = messageId;
|
|
1426
1431
|
if (topicName && threadId)
|
|
1427
1432
|
replyContext.metadata.topicName = topicName;
|
|
1428
|
-
if (
|
|
1429
|
-
replyContext.metadata.
|
|
1433
|
+
if (label && threadId)
|
|
1434
|
+
replyContext.metadata.label = label;
|
|
1435
|
+
if (topicStatus && threadId)
|
|
1436
|
+
replyContext.metadata.topicStatus = topicStatus;
|
|
1430
1437
|
return replyContext;
|
|
1431
1438
|
}
|
|
1432
1439
|
claimInboundMessage(kind, messageId, seq) {
|
|
@@ -1545,7 +1552,7 @@ export class AUNChannel {
|
|
|
1545
1552
|
peerInfoCache = new Map();
|
|
1546
1553
|
peerInfoRevision = 0;
|
|
1547
1554
|
messageSeqMap = new Map(); // messageId → seq (for ack/diagnostics)
|
|
1548
|
-
sentCount = new Map(); // channelId →
|
|
1555
|
+
sentCount = new Map(); // "channelId\0threadId" → 本轮已发消息计数(seqInTurn/turnMessageCount 用)
|
|
1549
1556
|
peerE2ee = new Map();
|
|
1550
1557
|
static E2EE_PROBE_TTL = 10 * 60 * 1000; // 10min
|
|
1551
1558
|
plaintextRecv = 0;
|
|
@@ -1842,6 +1849,18 @@ export class AUNChannel {
|
|
|
1842
1849
|
const kind = (data && typeof data === 'object') ? data.kind ?? '' : '';
|
|
1843
1850
|
const keys = (data && typeof data === 'object') ? Object.keys(data).join(',') : typeof data;
|
|
1844
1851
|
logger.debug(`${this.logPrefix()}[DIAG] message.received: kind=${kind} keys=${keys}`);
|
|
1852
|
+
// 最早入站打点:SDK message.received 回调触发瞬间
|
|
1853
|
+
if (data && typeof data === 'object') {
|
|
1854
|
+
const d = data;
|
|
1855
|
+
const payloadType = d.payload?.type ?? d.envelope?.type ?? '';
|
|
1856
|
+
if (payloadType === 'text') {
|
|
1857
|
+
const msgId = d.message_id ?? d.id ?? '';
|
|
1858
|
+
const from = d.from ?? d.envelope?.from ?? '';
|
|
1859
|
+
const to = d.to ?? d.envelope?.to ?? this.config.aid ?? '';
|
|
1860
|
+
const text = d.payload?.text ?? '';
|
|
1861
|
+
import('../utils/msg-diag.js').then(m => m.diagLog('sdk_received', { aid: this.config.aid, from, to, msgId, text })).catch(() => { });
|
|
1862
|
+
}
|
|
1863
|
+
}
|
|
1845
1864
|
await this.handleIncomingPrivateMessage(data);
|
|
1846
1865
|
});
|
|
1847
1866
|
// pureIdentity(控制 AID):协议层不接群消息,不注册 group 创建监听
|
|
@@ -2264,24 +2283,39 @@ export class AUNChannel {
|
|
|
2264
2283
|
}
|
|
2265
2284
|
const fileParts = [];
|
|
2266
2285
|
for (const att of rawAttachments) {
|
|
2267
|
-
const
|
|
2268
|
-
if (!
|
|
2286
|
+
const attachmentBuffer = await this.downloadAttachment(att, channelId, delivery);
|
|
2287
|
+
if (!attachmentBuffer)
|
|
2269
2288
|
continue;
|
|
2270
2289
|
const name = sanitizeFileName(att.filename || att.object_key?.split('/').pop() || 'file');
|
|
2271
2290
|
let img = null;
|
|
2272
2291
|
try {
|
|
2273
|
-
|
|
2274
|
-
img = await bufferToInboundImage(readFileSync(filePath), {
|
|
2292
|
+
img = await bufferToInboundImage(attachmentBuffer, {
|
|
2275
2293
|
contentType: att.content_type, mimeType: att.mime_type, filename: name,
|
|
2276
2294
|
});
|
|
2277
2295
|
}
|
|
2278
2296
|
catch { /* read failed, treat as non-image file */ }
|
|
2279
2297
|
if (img) {
|
|
2298
|
+
try {
|
|
2299
|
+
const attachment = saveRelationAttachment(attachmentBuffer, this.config.aid, formatPeerKey('aun', channelId), img.mimeType);
|
|
2300
|
+
img = { ...img, attachmentId: attachment.attachmentId, localPath: attachment.filePath };
|
|
2301
|
+
}
|
|
2302
|
+
catch (error) {
|
|
2303
|
+
logger.warn(`${this.logPrefix()} Failed to persist relation attachment for ${name}: ${error instanceof Error ? error.message : String(error)}`);
|
|
2304
|
+
}
|
|
2280
2305
|
images.push(img);
|
|
2281
2306
|
// 图片已注入视觉通道,不追加 [文件: …] 文本行
|
|
2282
2307
|
}
|
|
2283
2308
|
else {
|
|
2284
|
-
|
|
2309
|
+
const projectPath = this.projectPathProvider
|
|
2310
|
+
? await this.projectPathProvider(channelId)
|
|
2311
|
+
: process.cwd();
|
|
2312
|
+
try {
|
|
2313
|
+
const saved = saveToUploads(attachmentBuffer, name, projectPath);
|
|
2314
|
+
fileParts.push(`[文件: ${name} → ${saved.filePath}]`);
|
|
2315
|
+
}
|
|
2316
|
+
catch (error) {
|
|
2317
|
+
logger.warn(`${this.logPrefix()} saveToUploads failed for ${name}: ${error}`);
|
|
2318
|
+
}
|
|
2285
2319
|
}
|
|
2286
2320
|
}
|
|
2287
2321
|
const parts = [];
|
|
@@ -2392,18 +2426,7 @@ export class AUNChannel {
|
|
|
2392
2426
|
return null;
|
|
2393
2427
|
}
|
|
2394
2428
|
}
|
|
2395
|
-
|
|
2396
|
-
? await this.projectPathProvider(channelId)
|
|
2397
|
-
: process.cwd();
|
|
2398
|
-
try {
|
|
2399
|
-
const result = saveToUploads(buffer, filename, projectPath);
|
|
2400
|
-
logger.info(`${this.logPrefix()} Saved attachment: ${result.filePath} (${result.size} bytes)`);
|
|
2401
|
-
return result.filePath;
|
|
2402
|
-
}
|
|
2403
|
-
catch (e) {
|
|
2404
|
-
logger.warn(`${this.logPrefix()} saveToUploads failed for ${filename}: ${e}`);
|
|
2405
|
-
return null;
|
|
2406
|
-
}
|
|
2429
|
+
return buffer;
|
|
2407
2430
|
}
|
|
2408
2431
|
extractAttachmentUrlToken(url) {
|
|
2409
2432
|
try {
|
|
@@ -2555,9 +2578,13 @@ export class AUNChannel {
|
|
|
2555
2578
|
// forwardInbound 内部排除 self-echo 与 from-owner。
|
|
2556
2579
|
// 显式排除 observer.inject:它是 owner 对本 agent 的控制消息,不应镜像给观察者
|
|
2557
2580
|
// (即便日后 from-owner 排除规则调整,也不会泄漏)。
|
|
2558
|
-
if (inboundType !==
|
|
2581
|
+
if (typeof inboundType !== 'string' || !inboundType.startsWith('observer.inject')) {
|
|
2559
2582
|
void this.forwardInbound(msg);
|
|
2560
2583
|
}
|
|
2584
|
+
if (inboundType === 'observer.inject.status' || inboundType === 'observer.inject.ack') {
|
|
2585
|
+
this.acknowledgeImmediately(messageId, seq);
|
|
2586
|
+
return;
|
|
2587
|
+
}
|
|
2561
2588
|
// 回声过滤:自己发出的消息会被 gateway fanout 回来,
|
|
2562
2589
|
// 只有 from_aid == self 且 chat_id 不匹配时才丢弃(说明是其它实例发的)
|
|
2563
2590
|
const msgChatId = typeof payload === 'object' && payload !== null && payload.chat_id;
|
|
@@ -2593,6 +2620,9 @@ export class AUNChannel {
|
|
|
2593
2620
|
? payload.type
|
|
2594
2621
|
: '';
|
|
2595
2622
|
logger.info(`${this.logPrefix()} P2P dispatch decision: mid=${messageId} from=${shortAid}(${displayName}) peerType=${peerIdentity.type} payloadType=${p2pPayloadType} chatId=${chatId} encrypt=${msgEncrypted} textPreview=${JSON.stringify(text.slice(0, 80))}`);
|
|
2623
|
+
if (p2pPayloadType === 'text') {
|
|
2624
|
+
import('../utils/msg-diag.js').then(m => m.diagLog('aun_in', { aid: this.config.aid, from: fromAid, to: this.config.aid, msgId: messageId, text })).catch(() => { });
|
|
2625
|
+
}
|
|
2596
2626
|
// action_card_reply 已在 extractTextPayload 中消费,不分发给 agent
|
|
2597
2627
|
if (p2pPayloadType === 'action_card_reply') {
|
|
2598
2628
|
this.appendInboundPayloadJsonl(chatId, fromAid, messageId, msgEncrypted, false, logDescriptor, threadId, receivedAt, seq);
|
|
@@ -2640,7 +2670,9 @@ export class AUNChannel {
|
|
|
2640
2670
|
// observer 插话(v0.3):只落盘到 pending-hints,不进 Agent、不回 owner。详见 docs/observer-insert-design.md。
|
|
2641
2671
|
if (p2pPayloadType === AUNChannel.INJECT_REQUEST_TYPE) {
|
|
2642
2672
|
this.acknowledgeImmediately(messageId, seq);
|
|
2643
|
-
this.handleObserverInject(fromAid, payload, displayName, peerIdentity.type)
|
|
2673
|
+
void Promise.resolve(this.handleObserverInject(fromAid, payload, displayName, peerIdentity.type)).catch(error => {
|
|
2674
|
+
logger.error(`${this.logPrefix()} observer.inject failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
2675
|
+
});
|
|
2644
2676
|
return;
|
|
2645
2677
|
}
|
|
2646
2678
|
// bind.request:Evol App 绑定请求。绕过白名单透传原始 payload JSON 给 bridge,
|
|
@@ -2678,7 +2710,7 @@ export class AUNChannel {
|
|
|
2678
2710
|
appendAidEvent({ ts: Date.now(), iso: new Date().toISOString(), event: 'message_in', aid: this.config.aid, from: fromAid, msgId: messageId, kind: 'text', len: finalText.length });
|
|
2679
2711
|
const isSystemP2P = p2pPayloadType === 'event';
|
|
2680
2712
|
this.aidStatsCollector?.recordInbound(this.config.aid, fromAid, Buffer.byteLength(finalText, 'utf-8'), finalText, isSystemP2P, msgEncrypted, msgChatmode, isSystemP2P ? 'notify' : 'send');
|
|
2681
|
-
const replyContext = this.buildPrivateReplyContext(threadId, fromAid, msgEncrypted, messageId, msgChatmode, topicName, refMessageId,
|
|
2713
|
+
const replyContext = this.buildPrivateReplyContext(threadId, fromAid, msgEncrypted, messageId, msgChatmode, topicName, refMessageId, readTopicLabel(payload), readTopicStatus(payload));
|
|
2682
2714
|
this.dispatchMessage({
|
|
2683
2715
|
channelId: chatId,
|
|
2684
2716
|
userId: fromAid,
|
|
@@ -2725,9 +2757,9 @@ export class AUNChannel {
|
|
|
2725
2757
|
const senderAid = env.from ?? '';
|
|
2726
2758
|
const payload = msg.payload ?? '';
|
|
2727
2759
|
const inboundType = (payload && typeof payload === 'object') ? payload.type : undefined;
|
|
2728
|
-
if (inboundType === 'observer.forward') {
|
|
2760
|
+
if (inboundType === 'observer.forward' || (typeof inboundType === 'string' && inboundType.startsWith('observer.inject'))) {
|
|
2729
2761
|
this.acknowledgeImmediately(messageId, seq);
|
|
2730
|
-
logger.debug(`${this.logPrefix()} Group dropped observer
|
|
2762
|
+
logger.debug(`${this.logPrefix()} Group dropped observer control envelope: mid=${messageId}`);
|
|
2731
2763
|
return;
|
|
2732
2764
|
}
|
|
2733
2765
|
const payloadMentionEntries = this.parsePayloadMentionsOrReject(payload, 'group.inbound', messageId, seq);
|
|
@@ -2790,7 +2822,7 @@ export class AUNChannel {
|
|
|
2790
2822
|
peerName: displayName,
|
|
2791
2823
|
peerType: peerInfo?.type || 'unknown',
|
|
2792
2824
|
seq,
|
|
2793
|
-
replyContext: this.buildGroupReplyContext(threadId, senderAid, msgEncryptedFast, messageId, msgChatmodeFast, topicName, groupId,
|
|
2825
|
+
replyContext: this.buildGroupReplyContext(threadId, senderAid, msgEncryptedFast, messageId, msgChatmodeFast, topicName, groupId, readTopicLabel(payload), readTopicStatus(payload)),
|
|
2794
2826
|
createdAt,
|
|
2795
2827
|
});
|
|
2796
2828
|
return;
|
|
@@ -2827,8 +2859,8 @@ export class AUNChannel {
|
|
|
2827
2859
|
if (!msgEncrypted)
|
|
2828
2860
|
this.plaintextRecv++;
|
|
2829
2861
|
// mention_mode 是唯一的服务端群 @ 策略字段;缺失/非法时使用协议内置默认 disabled。
|
|
2830
|
-
const serverMentionMode =
|
|
2831
|
-
??
|
|
2862
|
+
const serverMentionMode = normalizeAunMentionMode(msg.mention_mode)
|
|
2863
|
+
?? normalizeAunMentionMode(payload?.mention_mode)
|
|
2832
2864
|
?? 'disabled';
|
|
2833
2865
|
const localMentionMode = normalizeMentionMode(this.mentionModeResolver
|
|
2834
2866
|
? await this.mentionModeResolver(groupId).catch(() => undefined)
|
|
@@ -2846,6 +2878,13 @@ export class AUNChannel {
|
|
|
2846
2878
|
// 命令判定:剥离所有 @ 后看是否 / 开头(多 @ 场景如 @a @b /status 也能正确识别)。
|
|
2847
2879
|
// echo 消息走独立的 trace 流程,不参与命令语义判定。
|
|
2848
2880
|
const isCommandMsg = !isEchoMsg && slashCommandText.startsWith('/');
|
|
2881
|
+
const commandMentionAids = payloadMentionAids.length > 0
|
|
2882
|
+
? payloadMentionAids
|
|
2883
|
+
: this.extractMentionAidsFromText(text);
|
|
2884
|
+
const commandTargetsAnotherAgent = isCommandMsg
|
|
2885
|
+
&& commandMentionAids.length > 0
|
|
2886
|
+
&& !commandMentionAids.includes(this._aid ?? '')
|
|
2887
|
+
&& !mentionedAll;
|
|
2849
2888
|
const groupSlashRequireMentions = this.groupSlashRequireMentionsResolver
|
|
2850
2889
|
? await this.groupSlashRequireMentionsResolver().catch(() => undefined) ?? true
|
|
2851
2890
|
: true;
|
|
@@ -2860,7 +2899,7 @@ export class AUNChannel {
|
|
|
2860
2899
|
this.pendingEchoMessages.set(messageId, {
|
|
2861
2900
|
text: echoText,
|
|
2862
2901
|
channelId: groupId,
|
|
2863
|
-
context: this.buildGroupReplyContext(threadId, senderAid, msgEncrypted, messageId, msgChatmodeEcho, topicName, groupId,
|
|
2902
|
+
context: this.buildGroupReplyContext(threadId, senderAid, msgEncrypted, messageId, msgChatmodeEcho, topicName, groupId, readTopicLabel(payload), readTopicStatus(payload)),
|
|
2864
2903
|
receiveTs: Date.now(),
|
|
2865
2904
|
});
|
|
2866
2905
|
// 继续走正常 Agent 流程(下面的代码会 dispatch)
|
|
@@ -2876,15 +2915,15 @@ export class AUNChannel {
|
|
|
2876
2915
|
// slash 命令默认要求结构化 @;Agent 可通过
|
|
2877
2916
|
// groupSlashRequireMentions=false 选择仅按正文命令格式处理。
|
|
2878
2917
|
const enforceMention = (mentionMode === 'mention-only' && !isCommandMsg)
|
|
2879
|
-
|| (isCommandMsg && groupSlashRequireMentions);
|
|
2918
|
+
|| (isCommandMsg && (mentionMode === 'auto' || groupSlashRequireMentions));
|
|
2880
2919
|
const isMentioned = mentionedSelf || mentionedAll;
|
|
2881
2920
|
// 过滤逻辑下移到响应层:这里只标记,不过滤
|
|
2882
2921
|
// 但为了保持现有行为兼容(避免大量未 @ 消息涌入),暂时保留过滤
|
|
2883
2922
|
// TODO: Phase 6 实施 selective-response 模式后,移除此过滤,只保留 isMentioned 标记
|
|
2884
|
-
if (enforceMention && !isMentioned && !isOwnHandoff) {
|
|
2923
|
+
if ((commandTargetsAnotherAgent || (enforceMention && !isMentioned)) && !isOwnHandoff) {
|
|
2885
2924
|
try {
|
|
2886
2925
|
if (text.trim() || this.collectAllAttachments(payload).length) {
|
|
2887
|
-
this.appendInboundPayloadJsonl(groupId, senderAid, messageId, msgEncrypted, true, logDescriptor, threadId, receivedAt, seq, 'unmentioned', 'filtered');
|
|
2926
|
+
this.appendInboundPayloadJsonl(groupId, senderAid, messageId, msgEncrypted, true, logDescriptor, threadId, receivedAt, seq, isCommandMsg ? 'command' : 'unmentioned', 'filtered');
|
|
2888
2927
|
}
|
|
2889
2928
|
}
|
|
2890
2929
|
catch (error) {
|
|
@@ -2896,7 +2935,8 @@ export class AUNChannel {
|
|
|
2896
2935
|
return;
|
|
2897
2936
|
}
|
|
2898
2937
|
this.acknowledgeImmediately(messageId, seq);
|
|
2899
|
-
|
|
2938
|
+
const dropReason = commandTargetsAnotherAgent ? 'command-targeted-other-agent' : 'unmentioned';
|
|
2939
|
+
logger.info(`${this.logPrefix()} Group dropped: ${dropReason} (group=${groupId} sender=${senderAid} mid=${messageId} mentionMode=${mentionMode} slashRequireMentions=${groupSlashRequireMentions} isCommand=${isCommandMsg} commandMentionAids=${JSON.stringify(commandMentionAids)} textPreview=${JSON.stringify(text.slice(0, 80))})`);
|
|
2900
2940
|
return;
|
|
2901
2941
|
}
|
|
2902
2942
|
}
|
|
@@ -2970,7 +3010,7 @@ export class AUNChannel {
|
|
|
2970
3010
|
isMentioned: isOwnHandoff || mentionedSelf || mentionedAll,
|
|
2971
3011
|
source: isOwnHandoff ? 'handoff' : undefined,
|
|
2972
3012
|
encrypted: msgEncrypted,
|
|
2973
|
-
replyContext: this.buildGroupReplyContext(threadId, senderAid, msgEncrypted, messageId, msgChatmode, topicName, groupId,
|
|
3013
|
+
replyContext: this.buildGroupReplyContext(threadId, senderAid, msgEncrypted, messageId, msgChatmode, topicName, groupId, readTopicLabel(payload), readTopicStatus(payload)),
|
|
2974
3014
|
mentionMode: serverMentionMode,
|
|
2975
3015
|
images: inboundImages.length > 0 ? inboundImages : undefined,
|
|
2976
3016
|
msgType: logDescriptor.msgType,
|
|
@@ -3077,7 +3117,7 @@ export class AUNChannel {
|
|
|
3077
3117
|
}
|
|
3078
3118
|
appendInboundPayloadJsonl(channelId, fromAid, msgId, encrypt, isGroup, descriptor, threadId, receivedAt, gatewaySeq, groupAttention, groupDelivery) {
|
|
3079
3119
|
try {
|
|
3080
|
-
const chatDir = chatDirPath(resolvePaths().sessionsDir, 'aun', channelId, this.config.aid);
|
|
3120
|
+
const chatDir = chatDirPath(resolvePaths().sessionsDir, 'aun', channelId, this.config.aid, this.config.channelName);
|
|
3081
3121
|
const entry = buildInboundEntry({
|
|
3082
3122
|
from: fromAid,
|
|
3083
3123
|
to: this.config.aid,
|
|
@@ -3115,6 +3155,7 @@ export class AUNChannel {
|
|
|
3115
3155
|
// observable / owners 不在此处缓存——由 daemon 注入 resolver,从 EvolAgent 的
|
|
3116
3156
|
// in-memory merged config(启动/重启/热重载时统一更新的唯一缓存)读取,避免重复缓存。
|
|
3117
3157
|
observerConfigResolver;
|
|
3158
|
+
observerHintRuntime;
|
|
3118
3159
|
observerGroupMembers = new Map();
|
|
3119
3160
|
observerGroupMemberFetches = new Map();
|
|
3120
3161
|
observerGroupMembershipEpoch = new Map();
|
|
@@ -3122,6 +3163,15 @@ export class AUNChannel {
|
|
|
3122
3163
|
setObserverConfigResolver(fn) {
|
|
3123
3164
|
this.observerConfigResolver = fn;
|
|
3124
3165
|
}
|
|
3166
|
+
setObserverHintRuntime(runtime) {
|
|
3167
|
+
this.observerHintRuntime = runtime;
|
|
3168
|
+
}
|
|
3169
|
+
notifyObserverHintConsumed(hint, target, meta) {
|
|
3170
|
+
this.emitInjectStatus(hint.ownerAid, hint.id, target, 'consumed', 0, meta?.action ?? (hint.promoteRequestId ? 'promote' : 'add'), meta?.requestId ?? hint.promoteRequestId);
|
|
3171
|
+
}
|
|
3172
|
+
notifyObserverHintFailed(hint, target, failure) {
|
|
3173
|
+
this.emitInjectStatus(hint.ownerAid, hint.id, target, 'failed', 0, failure.action ?? (hint.promoteRequestId ? 'promote' : 'add'), failure.requestId ?? hint.promoteRequestId, failure);
|
|
3174
|
+
}
|
|
3125
3175
|
/** 读取 observable 开关 + owners;未接入 daemon 时没有 owning Agent,保持关闭。 */
|
|
3126
3176
|
getObserverConfig() {
|
|
3127
3177
|
return this.observerConfigResolver?.() ?? { observable: false, owners: [] };
|
|
@@ -3235,7 +3285,7 @@ export class AUNChannel {
|
|
|
3235
3285
|
const inboundType = payloadTypeForObserver(data);
|
|
3236
3286
|
// Observer envelopes are control-plane telemetry. Mirroring one again
|
|
3237
3287
|
// would amplify every activity event across observer-connected agents.
|
|
3238
|
-
if (inboundType === 'observer.forward' || inboundType?.startsWith('menu.'))
|
|
3288
|
+
if (inboundType === 'observer.forward' || inboundType?.startsWith('observer.inject') || inboundType?.startsWith('menu.'))
|
|
3239
3289
|
return Promise.resolve();
|
|
3240
3290
|
const { observable, owners } = this.getObserverConfig();
|
|
3241
3291
|
if (!observable || owners.length === 0)
|
|
@@ -3272,7 +3322,7 @@ export class AUNChannel {
|
|
|
3272
3322
|
if (!this.connected || !this.client)
|
|
3273
3323
|
return Promise.resolve();
|
|
3274
3324
|
const outboundType = payloadTypeForObserver(result);
|
|
3275
|
-
if (outboundType === 'observer.forward' || outboundType?.startsWith('menu.'))
|
|
3325
|
+
if (outboundType === 'observer.forward' || outboundType?.startsWith('observer.inject') || outboundType?.startsWith('menu.'))
|
|
3276
3326
|
return Promise.resolve();
|
|
3277
3327
|
const { observable, owners } = this.getObserverConfig();
|
|
3278
3328
|
if (!observable || owners.length === 0)
|
|
@@ -3317,13 +3367,7 @@ export class AUNChannel {
|
|
|
3317
3367
|
.catch(e => logger.debug(`${this.logPrefix()} observer.forward send to ${ownerAid} failed: ${e}`));
|
|
3318
3368
|
}
|
|
3319
3369
|
}
|
|
3320
|
-
// ──
|
|
3321
|
-
//
|
|
3322
|
-
// owner 经 message.send 给 agent 自身 AID 发 observer.inject(payload 为对象)。
|
|
3323
|
-
// 鉴权 from∈owners 后,把提示【只落盘】到 agent↔对端 会话的 pending-hints.jsonl
|
|
3324
|
-
// (不 dispatch、不跑 LLM、不回 owner);下一条对端消息到达时由 message-processor
|
|
3325
|
-
// 回放消费、注入渲染层。action=add 加提示 / remove 撤销。
|
|
3326
|
-
// 详见 docs/observer-insert-design.md 第一部分。
|
|
3370
|
+
// ── 观察者插话 ──────────────
|
|
3327
3371
|
/** 回 observer.inject.ack 给 owner;加密使用 daemon 的全局 AUN 默认设置。 */
|
|
3328
3372
|
emitInjectAck(ownerAid, injectId, data, error) {
|
|
3329
3373
|
if (!this.connected || !this.client)
|
|
@@ -3338,12 +3382,51 @@ export class AUNChannel {
|
|
|
3338
3382
|
this.callAndTrace('message.send', { to: ownerAid, payload: ackPayload, encrypt: this.shouldEncrypt(ownerAid) })
|
|
3339
3383
|
.catch(e => logger.warn(`${this.logPrefix()} observer.inject.ack send to ${ownerAid} failed: ${e}`));
|
|
3340
3384
|
}
|
|
3341
|
-
|
|
3342
|
-
|
|
3385
|
+
emitInjectStatus(ownerAid, hintId, target, status, attempt = 0, action, requestId, failure) {
|
|
3386
|
+
if (!this.getObserverConfig().owners.includes(ownerAid))
|
|
3387
|
+
return;
|
|
3388
|
+
const payload = {
|
|
3389
|
+
type: 'observer.inject.status', id: hintId,
|
|
3390
|
+
target: { channel_id: target.channelId, chat_type: target.chatType,
|
|
3391
|
+
...(target.threadId ? { thread_id: target.threadId } : {}) },
|
|
3392
|
+
data: {
|
|
3393
|
+
status,
|
|
3394
|
+
hint_id: hintId,
|
|
3395
|
+
...(action ? { action } : {}),
|
|
3396
|
+
...(requestId ? { request_id: requestId } : {}),
|
|
3397
|
+
...(status === 'consumed' ? { consumed_at: Date.now() } : {}),
|
|
3398
|
+
...(failure ? { error: { code: failure.code, message: failure.message } } : {}),
|
|
3399
|
+
},
|
|
3400
|
+
};
|
|
3401
|
+
const delivery = this.connected && this.client
|
|
3402
|
+
? this.callAndTrace('message.send', { to: ownerAid, payload, encrypt: this.shouldEncrypt(ownerAid) })
|
|
3403
|
+
: Promise.reject(new Error('AUN is disconnected'));
|
|
3404
|
+
void delivery.catch(error => {
|
|
3405
|
+
if (attempt < 3) {
|
|
3406
|
+
const timer = setTimeout(() => this.emitInjectStatus(ownerAid, hintId, target, status, attempt + 1, action, requestId, failure), [250, 1000, 5000][attempt]);
|
|
3407
|
+
timer.unref?.();
|
|
3408
|
+
}
|
|
3409
|
+
else
|
|
3410
|
+
logger.warn(`${this.logPrefix()} observer.inject.status delivery failed owner=${ownerAid} hint=${hintId} status=${status}: ${error}`);
|
|
3411
|
+
});
|
|
3412
|
+
}
|
|
3413
|
+
async handleObserverInject(fromAid, payload, displayName, peerType) {
|
|
3343
3414
|
void peerType;
|
|
3344
3415
|
const { owners } = this.getObserverConfig();
|
|
3345
3416
|
const ts = Date.now();
|
|
3346
3417
|
const req = parseInjectRequest(payload, fromAid, owners, ts);
|
|
3418
|
+
const requestAction = req.kind === 'reject' ? req.action : req.kind;
|
|
3419
|
+
const requestHintId = req.kind === 'add' ? req.id : 'targetId' in req ? req.targetId : undefined;
|
|
3420
|
+
const requestTarget = 'channelId' in req && req.channelId
|
|
3421
|
+
? { channel_id: req.channelId, chat_type: req.chatType ?? 'private', ...(req.threadId ? { thread_id: req.threadId } : {}) }
|
|
3422
|
+
: undefined;
|
|
3423
|
+
const ackData = (status, hintId) => ({
|
|
3424
|
+
status,
|
|
3425
|
+
action: requestAction,
|
|
3426
|
+
...(hintId || requestHintId ? { hint_id: hintId || requestHintId } : {}),
|
|
3427
|
+
...(req.injectId ? { request_id: req.injectId } : {}),
|
|
3428
|
+
...(requestTarget ? { target: requestTarget } : {}),
|
|
3429
|
+
});
|
|
3347
3430
|
if (req.kind === 'reject') {
|
|
3348
3431
|
logger.warn(`${this.logPrefix()} observer.inject rejected: ${this.getShortAid(fromAid)} ${req.code}`);
|
|
3349
3432
|
this.emitInjectAck(fromAid, req.injectId, { status: 'rejected', action: req.action }, { code: req.code, message: req.message });
|
|
@@ -3351,24 +3434,67 @@ export class AUNChannel {
|
|
|
3351
3434
|
}
|
|
3352
3435
|
const selfAID = this.config.aid;
|
|
3353
3436
|
const sessionsDir = resolvePaths().sessionsDir;
|
|
3354
|
-
|
|
3355
|
-
|
|
3356
|
-
|
|
3357
|
-
|
|
3358
|
-
|
|
3359
|
-
|
|
3437
|
+
const channelKey = this.config.channelName ?? 'aun';
|
|
3438
|
+
try {
|
|
3439
|
+
await withHintTargetLock(sessionsDir, 'aun', req.channelId, selfAID, req.chatType, req.threadId, async () => {
|
|
3440
|
+
const immediate = (req.kind === 'add' && req.mode === 'interrupt') || req.kind === 'promote';
|
|
3441
|
+
const target = { channelKey, channelId: req.channelId, chatType: req.chatType, threadId: req.threadId, selfAid: selfAID };
|
|
3442
|
+
if (immediate && (!this.observerHintRuntime || !await this.observerHintRuntime.hasSession(target))) {
|
|
3443
|
+
this.emitInjectAck(fromAid, req.injectId, { status: 'rejected', action: req.kind }, { code: 'SESSION_NOT_FOUND', message: '目标 Agent session 不存在' });
|
|
3444
|
+
return;
|
|
3445
|
+
}
|
|
3446
|
+
const result = req.kind === 'add'
|
|
3447
|
+
? appendHintAddDetailed(sessionsDir, 'aun', req.channelId, selfAID, {
|
|
3448
|
+
id: req.id, text: req.text, mode: req.mode, chatType: req.chatType,
|
|
3449
|
+
threadId: req.threadId, ownerAid: req.ownerAid, ts,
|
|
3450
|
+
}, channelKey)
|
|
3451
|
+
: req.kind === 'remove'
|
|
3452
|
+
? appendHintRemoveDetailed(sessionsDir, 'aun', req.channelId, selfAID, {
|
|
3453
|
+
targetId: req.targetId, chatType: req.chatType, threadId: req.threadId, ts,
|
|
3454
|
+
}, channelKey)
|
|
3455
|
+
: appendHintPromoteDetailed(sessionsDir, 'aun', req.channelId, selfAID, {
|
|
3456
|
+
targetId: req.targetId, chatType: req.chatType, threadId: req.threadId, requestId: req.injectId, ts,
|
|
3457
|
+
}, channelKey);
|
|
3458
|
+
if (!result.ok) {
|
|
3459
|
+
this.emitInjectAck(fromAid, req.injectId, { status: 'rejected', action: req.kind }, { code: result.code ?? 'STORE_FAILED', message: '提示状态操作失败' });
|
|
3460
|
+
return;
|
|
3461
|
+
}
|
|
3462
|
+
const hintId = req.kind === 'add' ? req.id : req.targetId;
|
|
3463
|
+
logger.info(`${this.logPrefix()} observer.inject ${req.kind} stored: from=${this.getShortAid(fromAid)}(${displayName}) target=${req.channelId} chatType=${req.chatType} thread=${req.threadId ?? 'main'} hint=${hintId ?? 'all'}`);
|
|
3464
|
+
this.emitInjectAck(fromAid, req.injectId, ackData('accepted', hintId));
|
|
3465
|
+
if (req.kind === 'remove') {
|
|
3466
|
+
for (const hint of result.removedHints ?? [])
|
|
3467
|
+
this.emitInjectStatus(hint.ownerAid, hint.id, target, 'removed', 0, 'remove', req.injectId);
|
|
3468
|
+
}
|
|
3469
|
+
else if (req.kind === 'add') {
|
|
3470
|
+
this.emitInjectStatus(req.ownerAid, req.id, target, result.state ?? 'pending', 0, 'add', req.injectId || req.id);
|
|
3471
|
+
}
|
|
3472
|
+
else {
|
|
3473
|
+
const stored = peekHints(sessionsDir, 'aun', req.channelId, selfAID, req.threadId, req.chatType, undefined, channelKey)
|
|
3474
|
+
.find(item => item.id === req.targetId);
|
|
3475
|
+
if (stored)
|
|
3476
|
+
this.emitInjectStatus(stored.ownerAid, req.targetId, target, 'pending', 0, 'promote', stored.promoteRequestId || req.injectId);
|
|
3477
|
+
}
|
|
3478
|
+
if (immediate && result.state === 'pending') {
|
|
3479
|
+
try {
|
|
3480
|
+
const scheduled = await this.observerHintRuntime.schedule({ ...target, hintId: hintId, requestId: req.injectId });
|
|
3481
|
+
if (scheduled === 'SESSION_NOT_FOUND')
|
|
3482
|
+
logger.warn(`${this.logPrefix()} observer hint session vanished before scheduling hint=${hintId}`);
|
|
3483
|
+
}
|
|
3484
|
+
catch (error) {
|
|
3485
|
+
logger.warn(`${this.logPrefix()} observer hint scheduling failed hint=${hintId}: ${error}`);
|
|
3486
|
+
}
|
|
3487
|
+
}
|
|
3488
|
+
const watchText = req.kind === 'add' ? req.text
|
|
3489
|
+
: req.kind === 'promote' ? `[立即执行提示] id=${req.targetId}`
|
|
3490
|
+
: `[撤销提示]${req.targetId ? ` id=${req.targetId}` : '(全部)'}`;
|
|
3491
|
+
this.recordInjectWatch('in', fromAid, req.channelId, req.chatType, `inject-${req.injectId || ts}`, watchText, req.threadId, channelKey);
|
|
3492
|
+
}, channelKey);
|
|
3360
3493
|
}
|
|
3361
|
-
|
|
3362
|
-
this.
|
|
3363
|
-
|
|
3494
|
+
catch (error) {
|
|
3495
|
+
logger.warn(`${this.logPrefix()} observer.inject failed before acceptance: ${error instanceof Error ? error.message : String(error)}`);
|
|
3496
|
+
this.emitInjectAck(fromAid, req.injectId, { status: 'rejected', action: req.kind }, { code: 'STORE_FAILED', message: '提示状态操作失败' });
|
|
3364
3497
|
}
|
|
3365
|
-
logger.info(`${this.logPrefix()} observer.inject ${req.kind} stored: from=${this.getShortAid(fromAid)}(${displayName}) target=${req.channelId} chatType=${req.chatType} thread=${req.threadId ?? 'main'}${req.kind === 'add' ? ` textLen=${req.text.length}` : `${req.targetId ? ` targetId=${req.targetId}` : ' (clear-all)'}`}`);
|
|
3366
|
-
this.emitInjectAck(fromAid, req.injectId, { status: 'accepted', action: req.kind });
|
|
3367
|
-
// 记录到 watch(被观察的 agent↔对端 会话),带 owner-inject 标记区分对端真实消息。
|
|
3368
|
-
// v0.3:只记"提示已添加/已撤销",不触发处理、不产生 agent→owner 回应。
|
|
3369
|
-
const watchText = req.kind === 'remove' ? `[撤销提示]${req.targetId ? ` id=${req.targetId}` : '(全部)'}` : req.text;
|
|
3370
|
-
const synthId = `inject-${req.injectId || ts}`;
|
|
3371
|
-
this.recordInjectWatch('in', fromAid, req.channelId, req.chatType, synthId, watchText, req.threadId);
|
|
3372
3498
|
}
|
|
3373
3499
|
/**
|
|
3374
3500
|
* 把 observer 插话 / 对插话的回应记录到 watch(被观察的 agent↔对端 会话),
|
|
@@ -3377,11 +3503,11 @@ export class AUNChannel {
|
|
|
3377
3503
|
* @param dir 'in'=owner→agent 插话;'out'=agent→owner 对插话的回应
|
|
3378
3504
|
* @param peerChannelId 被观察会话的对端(agent↔对端),日志落点 = sessions/aun/<self>/<peerChannelId>/
|
|
3379
3505
|
*/
|
|
3380
|
-
recordInjectWatch(dir, ownerAid, peerChannelId, chatType, msgId, text, threadId) {
|
|
3506
|
+
recordInjectWatch(dir, ownerAid, peerChannelId, chatType, msgId, text, threadId, channelKey) {
|
|
3381
3507
|
try {
|
|
3382
3508
|
const selfAID = this.config.aid;
|
|
3383
3509
|
const isGroup = chatType === 'group';
|
|
3384
|
-
const chatDir = chatDirPath(resolvePaths().sessionsDir, 'aun', peerChannelId, selfAID);
|
|
3510
|
+
const chatDir = chatDirPath(resolvePaths().sessionsDir, 'aun', peerChannelId, selfAID, channelKey);
|
|
3385
3511
|
const entry = dir === 'in'
|
|
3386
3512
|
? buildInboundEntry({
|
|
3387
3513
|
from: ownerAid, to: selfAID, chatType,
|
|
@@ -4144,6 +4270,9 @@ export class AUNChannel {
|
|
|
4144
4270
|
if (isMenuPayload && typeof context?.metadata?.menuClientSdkVersion === 'string') {
|
|
4145
4271
|
params.menu_client_sdk_version = context.metadata.menuClientSdkVersion;
|
|
4146
4272
|
}
|
|
4273
|
+
if (isMenuPayload && context?.metadata?.menuClientVolatileV1 === true) {
|
|
4274
|
+
params.menu_client_volatile_v1 = true;
|
|
4275
|
+
}
|
|
4147
4276
|
if (isMenuPayload && encrypt && context?.metadata?.protectedHeaders) {
|
|
4148
4277
|
params.protected_headers = context.metadata.protectedHeaders;
|
|
4149
4278
|
}
|
|
@@ -4388,17 +4517,24 @@ export class AUNChannel {
|
|
|
4388
4517
|
const rawItem = item && typeof item === 'object' && !Array.isArray(item)
|
|
4389
4518
|
? { ...item }
|
|
4390
4519
|
: { kind: 'unknown', text: String(item ?? '') };
|
|
4520
|
+
// Lift evolCommand out of the item and into the payload's ext dict.
|
|
4521
|
+
const evolCommand = typeof rawItem.evolCommand === 'string' ? rawItem.evolCommand : undefined;
|
|
4522
|
+
if (evolCommand)
|
|
4523
|
+
delete rawItem.evolCommand;
|
|
4391
4524
|
const displayArguments = rawItem.kind === 'tool_call'
|
|
4392
4525
|
? toolInputForDisplay(rawItem.name, rawItem.arguments)
|
|
4393
4526
|
: undefined;
|
|
4394
4527
|
const activityItem = rawItem.kind === 'tool_call' && displayArguments !== rawItem.arguments
|
|
4395
4528
|
? { ...rawItem, arguments: displayArguments }
|
|
4396
4529
|
: rawItem;
|
|
4397
|
-
|
|
4530
|
+
const base = {
|
|
4398
4531
|
...this.buildTaskPayloadBase(envelope, context),
|
|
4399
4532
|
type: 'activity',
|
|
4400
4533
|
item: this.stripUndefinedDeep(activityItem),
|
|
4401
|
-
}
|
|
4534
|
+
};
|
|
4535
|
+
if (evolCommand)
|
|
4536
|
+
base.ext = { evolCommand };
|
|
4537
|
+
return withAunAttention(base);
|
|
4402
4538
|
}
|
|
4403
4539
|
async sendReliableStructured(channelId, payload, context, logText) {
|
|
4404
4540
|
const result = await this.sendContentPayload(channelId, payload, {
|
|
@@ -4426,13 +4562,17 @@ export class AUNChannel {
|
|
|
4426
4562
|
return { status: 'failed', error: 'message text is empty', code: 'EMPTY_MESSAGE' };
|
|
4427
4563
|
}
|
|
4428
4564
|
const delivery = this.requireDelivery(channelId, context);
|
|
4429
|
-
|
|
4565
|
+
let routedContext = this.withDelivery(context, delivery);
|
|
4430
4566
|
// 长 echo: agent 首次回复前先发 echo trace(echo 自身发送时跳过)
|
|
4431
4567
|
if (!this.isEchoSending) {
|
|
4432
4568
|
await this.flushPendingEcho(channelId, delivery, context?.threadId);
|
|
4433
4569
|
}
|
|
4434
4570
|
let finalText = text;
|
|
4435
|
-
|
|
4571
|
+
const turnKey = `${channelId}\0${context?.threadId ?? ''}`;
|
|
4572
|
+
const seqInTurn = (this.sentCount.get(turnKey) || 0) + 1;
|
|
4573
|
+
this.sentCount.set(turnKey, seqInTurn);
|
|
4574
|
+
// Carry seq into context so deliverTextEntry can embed it in ext.
|
|
4575
|
+
routedContext = { ...routedContext, ext: { ...routedContext.ext, seqInTurn } };
|
|
4436
4576
|
if (delivery.chatType === 'group') {
|
|
4437
4577
|
const requestedMentions = Array.from(new Set((context?.mentionUserIds ?? [])
|
|
4438
4578
|
.filter((id) => typeof id === 'string' && id.trim().length > 0)
|
|
@@ -4846,12 +4986,23 @@ export class AUNChannel {
|
|
|
4846
4986
|
}
|
|
4847
4987
|
// 从 context.metadata.source 读取 source,默认为 'daemon'
|
|
4848
4988
|
const source = context?.metadata?.source ?? 'daemon';
|
|
4849
|
-
//
|
|
4850
|
-
//
|
|
4851
|
-
|
|
4852
|
-
|
|
4853
|
-
|
|
4854
|
-
|
|
4989
|
+
// Merge ext hints from context into the wire payload.
|
|
4990
|
+
// isFinal: written by response-engine; turnMessageCount: total messages this turn (only on final).
|
|
4991
|
+
let ext = context?.ext
|
|
4992
|
+
? { ...context.ext }
|
|
4993
|
+
: undefined;
|
|
4994
|
+
if (ext?.isFinal) {
|
|
4995
|
+
const turnKey = `${channelId}\0${context?.threadId ?? ''}`;
|
|
4996
|
+
const total = this.sentCount.get(turnKey) ?? 1;
|
|
4997
|
+
ext = { ...ext, turnMessageCount: total };
|
|
4998
|
+
}
|
|
4999
|
+
const payload = withAunAttention({ type: 'text', text: finalText }, context?.attention);
|
|
5000
|
+
if (ext && Object.keys(ext).length > 0)
|
|
5001
|
+
payload.ext = ext;
|
|
5002
|
+
// sentAt 在每次投递尝试时盖戳(outbox 重发取重发时刻)。服务端会用自己的时间覆盖
|
|
5003
|
+
// 顶层 timestamp,两者有毫秒级差异,sentAt 只代表本端时钟。
|
|
5004
|
+
if (context?.debug)
|
|
5005
|
+
payload.debug = { ...context.debug, sentAt: localTimestamp() };
|
|
4855
5006
|
if (delivery.chatType === 'group') {
|
|
4856
5007
|
const extracted = this.extractMentionEntriesFromText(finalText);
|
|
4857
5008
|
if (extracted.length > 0)
|
|
@@ -4876,7 +5027,8 @@ export class AUNChannel {
|
|
|
4876
5027
|
registerAunCausation(causationRef, this.config.aid, channelId, causation);
|
|
4877
5028
|
}
|
|
4878
5029
|
// 诊断日志:记录 payload 构造结果(含 task_id / thread_id / chatmode)
|
|
4879
|
-
logger.info(`${this.logPrefix()} deliverTextEntry: channelId=${channelId} thread_id=${payload.thread_id ?? 'none'} task_id=${payload.task_id ?? 'none'} chatmode=${payload.chatmode ?? 'none'} source=${source} textLen=${
|
|
5030
|
+
logger.info(`${this.logPrefix()} deliverTextEntry: channelId=${channelId} thread_id=${payload.thread_id ?? 'none'} task_id=${payload.task_id ?? 'none'} chatmode=${payload.chatmode ?? 'none'} source=${source} textLen=${finalText.length}`);
|
|
5031
|
+
import('../utils/msg-diag.js').then(m => m.diagLog('aun_out', { aid: this.config.aid, from: this.config.aid, to: channelId, msgId: payload.task_id ?? '', taskId: payload.task_id, text: finalText })).catch(() => { });
|
|
4880
5032
|
const isGroup = delivery.chatType === 'group';
|
|
4881
5033
|
const targetAid = channelId;
|
|
4882
5034
|
if (entry.deliveryReceipt) {
|
|
@@ -5348,6 +5500,9 @@ export class AUNChannel {
|
|
|
5348
5500
|
if (isMenuPayload && typeof context?.metadata?.menuClientSdkVersion === 'string') {
|
|
5349
5501
|
params.menu_client_sdk_version = context.metadata.menuClientSdkVersion;
|
|
5350
5502
|
}
|
|
5503
|
+
if (isMenuPayload && context?.metadata?.menuClientVolatileV1 === true) {
|
|
5504
|
+
params.menu_client_volatile_v1 = true;
|
|
5505
|
+
}
|
|
5351
5506
|
const rawHeaders = context?.metadata?.protectedHeaders;
|
|
5352
5507
|
if (encrypt && rawHeaders && typeof rawHeaders === 'object' && !Array.isArray(rawHeaders)) {
|
|
5353
5508
|
const protectedHeaders = {};
|
|
@@ -5427,8 +5582,8 @@ export class AUNChannel {
|
|
|
5427
5582
|
logger.warn(`${this.logPrefix()} sendFile: file is empty`);
|
|
5428
5583
|
return { status: 'failed', error: 'file is empty', code: 'EMPTY_FILE' };
|
|
5429
5584
|
}
|
|
5430
|
-
if (stat.size >
|
|
5431
|
-
logger.warn(`${this.logPrefix()} sendFile: file too large (${formatSize(stat.size)}, max
|
|
5585
|
+
if (stat.size > MAX_AUN_ATTACHMENT_SIZE) {
|
|
5586
|
+
logger.warn(`${this.logPrefix()} sendFile: file too large (${formatSize(stat.size)}, max ${formatSize(MAX_AUN_ATTACHMENT_SIZE)})`);
|
|
5432
5587
|
return { status: 'failed', error: `file too large (max ${formatSize(MAX_AUN_ATTACHMENT_SIZE)})`, code: 'FILE_TOO_LARGE' };
|
|
5433
5588
|
}
|
|
5434
5589
|
// Write-ahead: persist to outbox
|
|
@@ -5882,8 +6037,13 @@ export class AUNChannel {
|
|
|
5882
6037
|
return;
|
|
5883
6038
|
}
|
|
5884
6039
|
context = this.withDelivery(context, delivery);
|
|
5885
|
-
if (status === 'start')
|
|
5886
|
-
|
|
6040
|
+
if (status === 'start') {
|
|
6041
|
+
// 新任务开始,重置所有以该 channelId 开头的计数(不区分 threadId)
|
|
6042
|
+
for (const key of this.sentCount.keys()) {
|
|
6043
|
+
if (key.startsWith(`${channelId}\0`))
|
|
6044
|
+
this.sentCount.delete(key);
|
|
6045
|
+
}
|
|
6046
|
+
}
|
|
5887
6047
|
if (!this.client || !this.connected)
|
|
5888
6048
|
return;
|
|
5889
6049
|
const requestedSeverity = extraMeta?.severity;
|
|
@@ -6252,7 +6412,7 @@ export class AUNChannelPlugin {
|
|
|
6252
6412
|
if (replyCtx) {
|
|
6253
6413
|
replyCtx = { ...replyCtx };
|
|
6254
6414
|
delete replyCtx.attention;
|
|
6255
|
-
//
|
|
6415
|
+
// Do not inherit title from a previous payload's reply context.
|
|
6256
6416
|
delete replyCtx.title;
|
|
6257
6417
|
}
|
|
6258
6418
|
if (attention !== undefined)
|