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
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';
|
|
@@ -715,8 +715,9 @@ export class AUNChannel {
|
|
|
715
715
|
* 统一的 RPC 调用包装:自动记录 OUT 发送、.ok 结果、.error 错误(含 trace + daemon.log 失败日志)。
|
|
716
716
|
* 所有 client.call() 都应通过此方法调用,保证 aun-trace 里每个 OUT 调用都有"发+收/错"成对记录。
|
|
717
717
|
*/
|
|
718
|
-
callClient(method, params, lane = 'normal', onStart) {
|
|
718
|
+
callClient(method, params, lane = 'normal', onStart, onPrepared) {
|
|
719
719
|
params = menuTransportParams(method, params);
|
|
720
|
+
onPrepared?.(params);
|
|
720
721
|
return this.withOutboundSendGate(method, () => (this.client.call(method, params).then(value => value)), lane, onStart);
|
|
721
722
|
}
|
|
722
723
|
withOutboundSendGate(method, run, lane = 'normal', onStart) {
|
|
@@ -784,7 +785,6 @@ export class AUNChannel {
|
|
|
784
785
|
});
|
|
785
786
|
}
|
|
786
787
|
async callAndTrace(method, params, opts) {
|
|
787
|
-
this.trace('OUT', method, params);
|
|
788
788
|
// 计时同时覆盖排队和 SDK 往返;callStartedAt 让日志能区分「本地队列堵塞」
|
|
789
789
|
// 与「网关慢」。两者对 /evolhelp 等命令的回复延迟都有直接影响。
|
|
790
790
|
const rpcStart = Date.now();
|
|
@@ -794,6 +794,11 @@ export class AUNChannel {
|
|
|
794
794
|
try {
|
|
795
795
|
const result = await this.callClient(method, params, opts?.lane ?? 'normal', () => {
|
|
796
796
|
callStartedAt = Date.now();
|
|
797
|
+
}, preparedParams => {
|
|
798
|
+
// Apply Menu compatibility exactly once, then log the same parameters
|
|
799
|
+
// passed to the SDK (TTL and device targeting may both be removed).
|
|
800
|
+
params = preparedParams;
|
|
801
|
+
this.trace('OUT', method, params);
|
|
797
802
|
});
|
|
798
803
|
const durationMs = Date.now() - rpcStart;
|
|
799
804
|
const rpcDurationMs = Date.now() - callStartedAt;
|
|
@@ -844,14 +849,17 @@ export class AUNChannel {
|
|
|
844
849
|
state.maxDurationMs = 0;
|
|
845
850
|
this.rpcWarningStates.set(key, state);
|
|
846
851
|
const summary = suppressed > 0 ? ` suppressed=${suppressed} max_duration=${maxDurationMs}ms` : '';
|
|
852
|
+
const menuTransport = method === 'message.send' && payloadType.startsWith('menu.')
|
|
853
|
+
? ` ttl=${params.ttl ?? 'default'} to_device_id=${JSON.stringify(params.to_device_id ?? null)}`
|
|
854
|
+
: '';
|
|
847
855
|
if (kind === 'failed') {
|
|
848
856
|
const name = error instanceof Error ? error.name : '';
|
|
849
857
|
const message = error instanceof Error ? error.message : String(error ?? '');
|
|
850
858
|
const errorLabel = `${name}${code ? `(${code})` : ''} ${message}`.trim();
|
|
851
|
-
logger.warn(`${this.logPrefix()} rpc ${method} failed after ${durationMs}ms: ${errorLabel} queue_wait=${queueWaitMs}ms rpc=${rpcDurationMs}ms payload=${payloadType}${summary}`);
|
|
859
|
+
logger.warn(`${this.logPrefix()} rpc ${method} failed after ${durationMs}ms: ${errorLabel} queue_wait=${queueWaitMs}ms rpc=${rpcDurationMs}ms payload=${payloadType}${menuTransport}${summary}`);
|
|
852
860
|
return;
|
|
853
861
|
}
|
|
854
|
-
logger.warn(`${this.logPrefix()} rpc ${method} SLOW: duration=${durationMs}ms queue_wait=${queueWaitMs}ms rpc=${rpcDurationMs}ms payload=${payloadType}${summary} (发送管线可能饱和或链路抖动)`);
|
|
862
|
+
logger.warn(`${this.logPrefix()} rpc ${method} SLOW: duration=${durationMs}ms queue_wait=${queueWaitMs}ms rpc=${rpcDurationMs}ms payload=${payloadType}${menuTransport}${summary} (发送管线可能饱和或链路抖动)`);
|
|
855
863
|
}
|
|
856
864
|
observerQueueDropCount = 0;
|
|
857
865
|
observerQueueDropLastWarnAt = 0;
|
|
@@ -1330,7 +1338,10 @@ export class AUNChannel {
|
|
|
1330
1338
|
// Keep the command contract consistent for mixed friendly/canonical @
|
|
1331
1339
|
// tokens: selector labels are removed above, then known AID-shaped tokens
|
|
1332
1340
|
// anywhere in the command are removed by the existing helper.
|
|
1333
|
-
|
|
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');
|
|
1334
1345
|
}
|
|
1335
1346
|
parsePayloadMentionsOrReject(payload, context, messageId, seq) {
|
|
1336
1347
|
const raw = payload && typeof payload === 'object' && !Array.isArray(payload)
|
|
@@ -1383,7 +1394,7 @@ export class AUNChannel {
|
|
|
1383
1394
|
return sanitized;
|
|
1384
1395
|
return undefined;
|
|
1385
1396
|
}
|
|
1386
|
-
buildGroupReplyContext(threadId, senderAid, encrypted, messageId, chatmode, topicName, groupId,
|
|
1397
|
+
buildGroupReplyContext(threadId, senderAid, encrypted, messageId, chatmode, topicName, groupId, label, topicStatus) {
|
|
1387
1398
|
if (!groupId)
|
|
1388
1399
|
throw new Error('buildGroupReplyContext requires groupId');
|
|
1389
1400
|
const replyContext = {
|
|
@@ -1401,11 +1412,13 @@ export class AUNChannel {
|
|
|
1401
1412
|
replyContext.metadata = {};
|
|
1402
1413
|
replyContext.metadata.topicName = topicName;
|
|
1403
1414
|
}
|
|
1404
|
-
if (
|
|
1405
|
-
replyContext.metadata.
|
|
1415
|
+
if (label && threadId)
|
|
1416
|
+
replyContext.metadata.label = label;
|
|
1417
|
+
if (topicStatus && threadId)
|
|
1418
|
+
replyContext.metadata.topicStatus = topicStatus;
|
|
1406
1419
|
return replyContext;
|
|
1407
1420
|
}
|
|
1408
|
-
buildPrivateReplyContext(threadId, senderAid, encrypted, messageId, chatmode, topicName, refMessageId,
|
|
1421
|
+
buildPrivateReplyContext(threadId, senderAid, encrypted, messageId, chatmode, topicName, refMessageId, label, topicStatus) {
|
|
1409
1422
|
const replyContext = {
|
|
1410
1423
|
metadata: { encrypted, chatmode, refMessageId },
|
|
1411
1424
|
delivery: { chatType: 'private' },
|
|
@@ -1417,8 +1430,10 @@ export class AUNChannel {
|
|
|
1417
1430
|
replyContext.replyToMessageId = messageId;
|
|
1418
1431
|
if (topicName && threadId)
|
|
1419
1432
|
replyContext.metadata.topicName = topicName;
|
|
1420
|
-
if (
|
|
1421
|
-
replyContext.metadata.
|
|
1433
|
+
if (label && threadId)
|
|
1434
|
+
replyContext.metadata.label = label;
|
|
1435
|
+
if (topicStatus && threadId)
|
|
1436
|
+
replyContext.metadata.topicStatus = topicStatus;
|
|
1422
1437
|
return replyContext;
|
|
1423
1438
|
}
|
|
1424
1439
|
claimInboundMessage(kind, messageId, seq) {
|
|
@@ -1537,7 +1552,7 @@ export class AUNChannel {
|
|
|
1537
1552
|
peerInfoCache = new Map();
|
|
1538
1553
|
peerInfoRevision = 0;
|
|
1539
1554
|
messageSeqMap = new Map(); // messageId → seq (for ack/diagnostics)
|
|
1540
|
-
sentCount = new Map(); // channelId →
|
|
1555
|
+
sentCount = new Map(); // "channelId\0threadId" → 本轮已发消息计数(seqInTurn/turnMessageCount 用)
|
|
1541
1556
|
peerE2ee = new Map();
|
|
1542
1557
|
static E2EE_PROBE_TTL = 10 * 60 * 1000; // 10min
|
|
1543
1558
|
plaintextRecv = 0;
|
|
@@ -1834,6 +1849,18 @@ export class AUNChannel {
|
|
|
1834
1849
|
const kind = (data && typeof data === 'object') ? data.kind ?? '' : '';
|
|
1835
1850
|
const keys = (data && typeof data === 'object') ? Object.keys(data).join(',') : typeof data;
|
|
1836
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
|
+
}
|
|
1837
1864
|
await this.handleIncomingPrivateMessage(data);
|
|
1838
1865
|
});
|
|
1839
1866
|
// pureIdentity(控制 AID):协议层不接群消息,不注册 group 创建监听
|
|
@@ -2256,24 +2283,39 @@ export class AUNChannel {
|
|
|
2256
2283
|
}
|
|
2257
2284
|
const fileParts = [];
|
|
2258
2285
|
for (const att of rawAttachments) {
|
|
2259
|
-
const
|
|
2260
|
-
if (!
|
|
2286
|
+
const attachmentBuffer = await this.downloadAttachment(att, channelId, delivery);
|
|
2287
|
+
if (!attachmentBuffer)
|
|
2261
2288
|
continue;
|
|
2262
2289
|
const name = sanitizeFileName(att.filename || att.object_key?.split('/').pop() || 'file');
|
|
2263
2290
|
let img = null;
|
|
2264
2291
|
try {
|
|
2265
|
-
|
|
2266
|
-
img = await bufferToInboundImage(readFileSync(filePath), {
|
|
2292
|
+
img = await bufferToInboundImage(attachmentBuffer, {
|
|
2267
2293
|
contentType: att.content_type, mimeType: att.mime_type, filename: name,
|
|
2268
2294
|
});
|
|
2269
2295
|
}
|
|
2270
2296
|
catch { /* read failed, treat as non-image file */ }
|
|
2271
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
|
+
}
|
|
2272
2305
|
images.push(img);
|
|
2273
2306
|
// 图片已注入视觉通道,不追加 [文件: …] 文本行
|
|
2274
2307
|
}
|
|
2275
2308
|
else {
|
|
2276
|
-
|
|
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
|
+
}
|
|
2277
2319
|
}
|
|
2278
2320
|
}
|
|
2279
2321
|
const parts = [];
|
|
@@ -2384,18 +2426,7 @@ export class AUNChannel {
|
|
|
2384
2426
|
return null;
|
|
2385
2427
|
}
|
|
2386
2428
|
}
|
|
2387
|
-
|
|
2388
|
-
? await this.projectPathProvider(channelId)
|
|
2389
|
-
: process.cwd();
|
|
2390
|
-
try {
|
|
2391
|
-
const result = saveToUploads(buffer, filename, projectPath);
|
|
2392
|
-
logger.info(`${this.logPrefix()} Saved attachment: ${result.filePath} (${result.size} bytes)`);
|
|
2393
|
-
return result.filePath;
|
|
2394
|
-
}
|
|
2395
|
-
catch (e) {
|
|
2396
|
-
logger.warn(`${this.logPrefix()} saveToUploads failed for ${filename}: ${e}`);
|
|
2397
|
-
return null;
|
|
2398
|
-
}
|
|
2429
|
+
return buffer;
|
|
2399
2430
|
}
|
|
2400
2431
|
extractAttachmentUrlToken(url) {
|
|
2401
2432
|
try {
|
|
@@ -2547,9 +2578,13 @@ export class AUNChannel {
|
|
|
2547
2578
|
// forwardInbound 内部排除 self-echo 与 from-owner。
|
|
2548
2579
|
// 显式排除 observer.inject:它是 owner 对本 agent 的控制消息,不应镜像给观察者
|
|
2549
2580
|
// (即便日后 from-owner 排除规则调整,也不会泄漏)。
|
|
2550
|
-
if (inboundType !==
|
|
2581
|
+
if (typeof inboundType !== 'string' || !inboundType.startsWith('observer.inject')) {
|
|
2551
2582
|
void this.forwardInbound(msg);
|
|
2552
2583
|
}
|
|
2584
|
+
if (inboundType === 'observer.inject.status' || inboundType === 'observer.inject.ack') {
|
|
2585
|
+
this.acknowledgeImmediately(messageId, seq);
|
|
2586
|
+
return;
|
|
2587
|
+
}
|
|
2553
2588
|
// 回声过滤:自己发出的消息会被 gateway fanout 回来,
|
|
2554
2589
|
// 只有 from_aid == self 且 chat_id 不匹配时才丢弃(说明是其它实例发的)
|
|
2555
2590
|
const msgChatId = typeof payload === 'object' && payload !== null && payload.chat_id;
|
|
@@ -2585,6 +2620,9 @@ export class AUNChannel {
|
|
|
2585
2620
|
? payload.type
|
|
2586
2621
|
: '';
|
|
2587
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
|
+
}
|
|
2588
2626
|
// action_card_reply 已在 extractTextPayload 中消费,不分发给 agent
|
|
2589
2627
|
if (p2pPayloadType === 'action_card_reply') {
|
|
2590
2628
|
this.appendInboundPayloadJsonl(chatId, fromAid, messageId, msgEncrypted, false, logDescriptor, threadId, receivedAt, seq);
|
|
@@ -2632,7 +2670,9 @@ export class AUNChannel {
|
|
|
2632
2670
|
// observer 插话(v0.3):只落盘到 pending-hints,不进 Agent、不回 owner。详见 docs/observer-insert-design.md。
|
|
2633
2671
|
if (p2pPayloadType === AUNChannel.INJECT_REQUEST_TYPE) {
|
|
2634
2672
|
this.acknowledgeImmediately(messageId, seq);
|
|
2635
|
-
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
|
+
});
|
|
2636
2676
|
return;
|
|
2637
2677
|
}
|
|
2638
2678
|
// bind.request:Evol App 绑定请求。绕过白名单透传原始 payload JSON 给 bridge,
|
|
@@ -2670,7 +2710,7 @@ export class AUNChannel {
|
|
|
2670
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 });
|
|
2671
2711
|
const isSystemP2P = p2pPayloadType === 'event';
|
|
2672
2712
|
this.aidStatsCollector?.recordInbound(this.config.aid, fromAid, Buffer.byteLength(finalText, 'utf-8'), finalText, isSystemP2P, msgEncrypted, msgChatmode, isSystemP2P ? 'notify' : 'send');
|
|
2673
|
-
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));
|
|
2674
2714
|
this.dispatchMessage({
|
|
2675
2715
|
channelId: chatId,
|
|
2676
2716
|
userId: fromAid,
|
|
@@ -2717,9 +2757,9 @@ export class AUNChannel {
|
|
|
2717
2757
|
const senderAid = env.from ?? '';
|
|
2718
2758
|
const payload = msg.payload ?? '';
|
|
2719
2759
|
const inboundType = (payload && typeof payload === 'object') ? payload.type : undefined;
|
|
2720
|
-
if (inboundType === 'observer.forward') {
|
|
2760
|
+
if (inboundType === 'observer.forward' || (typeof inboundType === 'string' && inboundType.startsWith('observer.inject'))) {
|
|
2721
2761
|
this.acknowledgeImmediately(messageId, seq);
|
|
2722
|
-
logger.debug(`${this.logPrefix()} Group dropped observer
|
|
2762
|
+
logger.debug(`${this.logPrefix()} Group dropped observer control envelope: mid=${messageId}`);
|
|
2723
2763
|
return;
|
|
2724
2764
|
}
|
|
2725
2765
|
const payloadMentionEntries = this.parsePayloadMentionsOrReject(payload, 'group.inbound', messageId, seq);
|
|
@@ -2782,7 +2822,7 @@ export class AUNChannel {
|
|
|
2782
2822
|
peerName: displayName,
|
|
2783
2823
|
peerType: peerInfo?.type || 'unknown',
|
|
2784
2824
|
seq,
|
|
2785
|
-
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)),
|
|
2786
2826
|
createdAt,
|
|
2787
2827
|
});
|
|
2788
2828
|
return;
|
|
@@ -2819,8 +2859,8 @@ export class AUNChannel {
|
|
|
2819
2859
|
if (!msgEncrypted)
|
|
2820
2860
|
this.plaintextRecv++;
|
|
2821
2861
|
// mention_mode 是唯一的服务端群 @ 策略字段;缺失/非法时使用协议内置默认 disabled。
|
|
2822
|
-
const serverMentionMode =
|
|
2823
|
-
??
|
|
2862
|
+
const serverMentionMode = normalizeAunMentionMode(msg.mention_mode)
|
|
2863
|
+
?? normalizeAunMentionMode(payload?.mention_mode)
|
|
2824
2864
|
?? 'disabled';
|
|
2825
2865
|
const localMentionMode = normalizeMentionMode(this.mentionModeResolver
|
|
2826
2866
|
? await this.mentionModeResolver(groupId).catch(() => undefined)
|
|
@@ -2838,6 +2878,13 @@ export class AUNChannel {
|
|
|
2838
2878
|
// 命令判定:剥离所有 @ 后看是否 / 开头(多 @ 场景如 @a @b /status 也能正确识别)。
|
|
2839
2879
|
// echo 消息走独立的 trace 流程,不参与命令语义判定。
|
|
2840
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;
|
|
2841
2888
|
const groupSlashRequireMentions = this.groupSlashRequireMentionsResolver
|
|
2842
2889
|
? await this.groupSlashRequireMentionsResolver().catch(() => undefined) ?? true
|
|
2843
2890
|
: true;
|
|
@@ -2852,7 +2899,7 @@ export class AUNChannel {
|
|
|
2852
2899
|
this.pendingEchoMessages.set(messageId, {
|
|
2853
2900
|
text: echoText,
|
|
2854
2901
|
channelId: groupId,
|
|
2855
|
-
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)),
|
|
2856
2903
|
receiveTs: Date.now(),
|
|
2857
2904
|
});
|
|
2858
2905
|
// 继续走正常 Agent 流程(下面的代码会 dispatch)
|
|
@@ -2868,15 +2915,15 @@ export class AUNChannel {
|
|
|
2868
2915
|
// slash 命令默认要求结构化 @;Agent 可通过
|
|
2869
2916
|
// groupSlashRequireMentions=false 选择仅按正文命令格式处理。
|
|
2870
2917
|
const enforceMention = (mentionMode === 'mention-only' && !isCommandMsg)
|
|
2871
|
-
|| (isCommandMsg && groupSlashRequireMentions);
|
|
2918
|
+
|| (isCommandMsg && (mentionMode === 'auto' || groupSlashRequireMentions));
|
|
2872
2919
|
const isMentioned = mentionedSelf || mentionedAll;
|
|
2873
2920
|
// 过滤逻辑下移到响应层:这里只标记,不过滤
|
|
2874
2921
|
// 但为了保持现有行为兼容(避免大量未 @ 消息涌入),暂时保留过滤
|
|
2875
2922
|
// TODO: Phase 6 实施 selective-response 模式后,移除此过滤,只保留 isMentioned 标记
|
|
2876
|
-
if (enforceMention && !isMentioned && !isOwnHandoff) {
|
|
2923
|
+
if ((commandTargetsAnotherAgent || (enforceMention && !isMentioned)) && !isOwnHandoff) {
|
|
2877
2924
|
try {
|
|
2878
2925
|
if (text.trim() || this.collectAllAttachments(payload).length) {
|
|
2879
|
-
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');
|
|
2880
2927
|
}
|
|
2881
2928
|
}
|
|
2882
2929
|
catch (error) {
|
|
@@ -2888,7 +2935,8 @@ export class AUNChannel {
|
|
|
2888
2935
|
return;
|
|
2889
2936
|
}
|
|
2890
2937
|
this.acknowledgeImmediately(messageId, seq);
|
|
2891
|
-
|
|
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))})`);
|
|
2892
2940
|
return;
|
|
2893
2941
|
}
|
|
2894
2942
|
}
|
|
@@ -2962,7 +3010,7 @@ export class AUNChannel {
|
|
|
2962
3010
|
isMentioned: isOwnHandoff || mentionedSelf || mentionedAll,
|
|
2963
3011
|
source: isOwnHandoff ? 'handoff' : undefined,
|
|
2964
3012
|
encrypted: msgEncrypted,
|
|
2965
|
-
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)),
|
|
2966
3014
|
mentionMode: serverMentionMode,
|
|
2967
3015
|
images: inboundImages.length > 0 ? inboundImages : undefined,
|
|
2968
3016
|
msgType: logDescriptor.msgType,
|
|
@@ -3069,7 +3117,7 @@ export class AUNChannel {
|
|
|
3069
3117
|
}
|
|
3070
3118
|
appendInboundPayloadJsonl(channelId, fromAid, msgId, encrypt, isGroup, descriptor, threadId, receivedAt, gatewaySeq, groupAttention, groupDelivery) {
|
|
3071
3119
|
try {
|
|
3072
|
-
const chatDir = chatDirPath(resolvePaths().sessionsDir, 'aun', channelId, this.config.aid);
|
|
3120
|
+
const chatDir = chatDirPath(resolvePaths().sessionsDir, 'aun', channelId, this.config.aid, this.config.channelName);
|
|
3073
3121
|
const entry = buildInboundEntry({
|
|
3074
3122
|
from: fromAid,
|
|
3075
3123
|
to: this.config.aid,
|
|
@@ -3107,6 +3155,7 @@ export class AUNChannel {
|
|
|
3107
3155
|
// observable / owners 不在此处缓存——由 daemon 注入 resolver,从 EvolAgent 的
|
|
3108
3156
|
// in-memory merged config(启动/重启/热重载时统一更新的唯一缓存)读取,避免重复缓存。
|
|
3109
3157
|
observerConfigResolver;
|
|
3158
|
+
observerHintRuntime;
|
|
3110
3159
|
observerGroupMembers = new Map();
|
|
3111
3160
|
observerGroupMemberFetches = new Map();
|
|
3112
3161
|
observerGroupMembershipEpoch = new Map();
|
|
@@ -3114,6 +3163,15 @@ export class AUNChannel {
|
|
|
3114
3163
|
setObserverConfigResolver(fn) {
|
|
3115
3164
|
this.observerConfigResolver = fn;
|
|
3116
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
|
+
}
|
|
3117
3175
|
/** 读取 observable 开关 + owners;未接入 daemon 时没有 owning Agent,保持关闭。 */
|
|
3118
3176
|
getObserverConfig() {
|
|
3119
3177
|
return this.observerConfigResolver?.() ?? { observable: false, owners: [] };
|
|
@@ -3227,7 +3285,7 @@ export class AUNChannel {
|
|
|
3227
3285
|
const inboundType = payloadTypeForObserver(data);
|
|
3228
3286
|
// Observer envelopes are control-plane telemetry. Mirroring one again
|
|
3229
3287
|
// would amplify every activity event across observer-connected agents.
|
|
3230
|
-
if (inboundType === 'observer.forward' || inboundType?.startsWith('menu.'))
|
|
3288
|
+
if (inboundType === 'observer.forward' || inboundType?.startsWith('observer.inject') || inboundType?.startsWith('menu.'))
|
|
3231
3289
|
return Promise.resolve();
|
|
3232
3290
|
const { observable, owners } = this.getObserverConfig();
|
|
3233
3291
|
if (!observable || owners.length === 0)
|
|
@@ -3264,7 +3322,7 @@ export class AUNChannel {
|
|
|
3264
3322
|
if (!this.connected || !this.client)
|
|
3265
3323
|
return Promise.resolve();
|
|
3266
3324
|
const outboundType = payloadTypeForObserver(result);
|
|
3267
|
-
if (outboundType === 'observer.forward' || outboundType?.startsWith('menu.'))
|
|
3325
|
+
if (outboundType === 'observer.forward' || outboundType?.startsWith('observer.inject') || outboundType?.startsWith('menu.'))
|
|
3268
3326
|
return Promise.resolve();
|
|
3269
3327
|
const { observable, owners } = this.getObserverConfig();
|
|
3270
3328
|
if (!observable || owners.length === 0)
|
|
@@ -3309,13 +3367,7 @@ export class AUNChannel {
|
|
|
3309
3367
|
.catch(e => logger.debug(`${this.logPrefix()} observer.forward send to ${ownerAid} failed: ${e}`));
|
|
3310
3368
|
}
|
|
3311
3369
|
}
|
|
3312
|
-
// ──
|
|
3313
|
-
//
|
|
3314
|
-
// owner 经 message.send 给 agent 自身 AID 发 observer.inject(payload 为对象)。
|
|
3315
|
-
// 鉴权 from∈owners 后,把提示【只落盘】到 agent↔对端 会话的 pending-hints.jsonl
|
|
3316
|
-
// (不 dispatch、不跑 LLM、不回 owner);下一条对端消息到达时由 message-processor
|
|
3317
|
-
// 回放消费、注入渲染层。action=add 加提示 / remove 撤销。
|
|
3318
|
-
// 详见 docs/observer-insert-design.md 第一部分。
|
|
3370
|
+
// ── 观察者插话 ──────────────
|
|
3319
3371
|
/** 回 observer.inject.ack 给 owner;加密使用 daemon 的全局 AUN 默认设置。 */
|
|
3320
3372
|
emitInjectAck(ownerAid, injectId, data, error) {
|
|
3321
3373
|
if (!this.connected || !this.client)
|
|
@@ -3330,12 +3382,51 @@ export class AUNChannel {
|
|
|
3330
3382
|
this.callAndTrace('message.send', { to: ownerAid, payload: ackPayload, encrypt: this.shouldEncrypt(ownerAid) })
|
|
3331
3383
|
.catch(e => logger.warn(`${this.logPrefix()} observer.inject.ack send to ${ownerAid} failed: ${e}`));
|
|
3332
3384
|
}
|
|
3333
|
-
|
|
3334
|
-
|
|
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) {
|
|
3335
3414
|
void peerType;
|
|
3336
3415
|
const { owners } = this.getObserverConfig();
|
|
3337
3416
|
const ts = Date.now();
|
|
3338
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
|
+
});
|
|
3339
3430
|
if (req.kind === 'reject') {
|
|
3340
3431
|
logger.warn(`${this.logPrefix()} observer.inject rejected: ${this.getShortAid(fromAid)} ${req.code}`);
|
|
3341
3432
|
this.emitInjectAck(fromAid, req.injectId, { status: 'rejected', action: req.action }, { code: req.code, message: req.message });
|
|
@@ -3343,24 +3434,67 @@ export class AUNChannel {
|
|
|
3343
3434
|
}
|
|
3344
3435
|
const selfAID = this.config.aid;
|
|
3345
3436
|
const sessionsDir = resolvePaths().sessionsDir;
|
|
3346
|
-
|
|
3347
|
-
|
|
3348
|
-
|
|
3349
|
-
|
|
3350
|
-
|
|
3351
|
-
|
|
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);
|
|
3352
3493
|
}
|
|
3353
|
-
|
|
3354
|
-
this.
|
|
3355
|
-
|
|
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: '提示状态操作失败' });
|
|
3356
3497
|
}
|
|
3357
|
-
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)'}`}`);
|
|
3358
|
-
this.emitInjectAck(fromAid, req.injectId, { status: 'accepted', action: req.kind });
|
|
3359
|
-
// 记录到 watch(被观察的 agent↔对端 会话),带 owner-inject 标记区分对端真实消息。
|
|
3360
|
-
// v0.3:只记"提示已添加/已撤销",不触发处理、不产生 agent→owner 回应。
|
|
3361
|
-
const watchText = req.kind === 'remove' ? `[撤销提示]${req.targetId ? ` id=${req.targetId}` : '(全部)'}` : req.text;
|
|
3362
|
-
const synthId = `inject-${req.injectId || ts}`;
|
|
3363
|
-
this.recordInjectWatch('in', fromAid, req.channelId, req.chatType, synthId, watchText, req.threadId);
|
|
3364
3498
|
}
|
|
3365
3499
|
/**
|
|
3366
3500
|
* 把 observer 插话 / 对插话的回应记录到 watch(被观察的 agent↔对端 会话),
|
|
@@ -3369,11 +3503,11 @@ export class AUNChannel {
|
|
|
3369
3503
|
* @param dir 'in'=owner→agent 插话;'out'=agent→owner 对插话的回应
|
|
3370
3504
|
* @param peerChannelId 被观察会话的对端(agent↔对端),日志落点 = sessions/aun/<self>/<peerChannelId>/
|
|
3371
3505
|
*/
|
|
3372
|
-
recordInjectWatch(dir, ownerAid, peerChannelId, chatType, msgId, text, threadId) {
|
|
3506
|
+
recordInjectWatch(dir, ownerAid, peerChannelId, chatType, msgId, text, threadId, channelKey) {
|
|
3373
3507
|
try {
|
|
3374
3508
|
const selfAID = this.config.aid;
|
|
3375
3509
|
const isGroup = chatType === 'group';
|
|
3376
|
-
const chatDir = chatDirPath(resolvePaths().sessionsDir, 'aun', peerChannelId, selfAID);
|
|
3510
|
+
const chatDir = chatDirPath(resolvePaths().sessionsDir, 'aun', peerChannelId, selfAID, channelKey);
|
|
3377
3511
|
const entry = dir === 'in'
|
|
3378
3512
|
? buildInboundEntry({
|
|
3379
3513
|
from: ownerAid, to: selfAID, chatType,
|
|
@@ -4136,6 +4270,9 @@ export class AUNChannel {
|
|
|
4136
4270
|
if (isMenuPayload && typeof context?.metadata?.menuClientSdkVersion === 'string') {
|
|
4137
4271
|
params.menu_client_sdk_version = context.metadata.menuClientSdkVersion;
|
|
4138
4272
|
}
|
|
4273
|
+
if (isMenuPayload && context?.metadata?.menuClientVolatileV1 === true) {
|
|
4274
|
+
params.menu_client_volatile_v1 = true;
|
|
4275
|
+
}
|
|
4139
4276
|
if (isMenuPayload && encrypt && context?.metadata?.protectedHeaders) {
|
|
4140
4277
|
params.protected_headers = context.metadata.protectedHeaders;
|
|
4141
4278
|
}
|
|
@@ -4380,17 +4517,24 @@ export class AUNChannel {
|
|
|
4380
4517
|
const rawItem = item && typeof item === 'object' && !Array.isArray(item)
|
|
4381
4518
|
? { ...item }
|
|
4382
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;
|
|
4383
4524
|
const displayArguments = rawItem.kind === 'tool_call'
|
|
4384
4525
|
? toolInputForDisplay(rawItem.name, rawItem.arguments)
|
|
4385
4526
|
: undefined;
|
|
4386
4527
|
const activityItem = rawItem.kind === 'tool_call' && displayArguments !== rawItem.arguments
|
|
4387
4528
|
? { ...rawItem, arguments: displayArguments }
|
|
4388
4529
|
: rawItem;
|
|
4389
|
-
|
|
4530
|
+
const base = {
|
|
4390
4531
|
...this.buildTaskPayloadBase(envelope, context),
|
|
4391
4532
|
type: 'activity',
|
|
4392
4533
|
item: this.stripUndefinedDeep(activityItem),
|
|
4393
|
-
}
|
|
4534
|
+
};
|
|
4535
|
+
if (evolCommand)
|
|
4536
|
+
base.ext = { evolCommand };
|
|
4537
|
+
return withAunAttention(base);
|
|
4394
4538
|
}
|
|
4395
4539
|
async sendReliableStructured(channelId, payload, context, logText) {
|
|
4396
4540
|
const result = await this.sendContentPayload(channelId, payload, {
|
|
@@ -4418,13 +4562,17 @@ export class AUNChannel {
|
|
|
4418
4562
|
return { status: 'failed', error: 'message text is empty', code: 'EMPTY_MESSAGE' };
|
|
4419
4563
|
}
|
|
4420
4564
|
const delivery = this.requireDelivery(channelId, context);
|
|
4421
|
-
|
|
4565
|
+
let routedContext = this.withDelivery(context, delivery);
|
|
4422
4566
|
// 长 echo: agent 首次回复前先发 echo trace(echo 自身发送时跳过)
|
|
4423
4567
|
if (!this.isEchoSending) {
|
|
4424
4568
|
await this.flushPendingEcho(channelId, delivery, context?.threadId);
|
|
4425
4569
|
}
|
|
4426
4570
|
let finalText = text;
|
|
4427
|
-
|
|
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 } };
|
|
4428
4576
|
if (delivery.chatType === 'group') {
|
|
4429
4577
|
const requestedMentions = Array.from(new Set((context?.mentionUserIds ?? [])
|
|
4430
4578
|
.filter((id) => typeof id === 'string' && id.trim().length > 0)
|
|
@@ -4838,7 +4986,23 @@ export class AUNChannel {
|
|
|
4838
4986
|
}
|
|
4839
4987
|
// 从 context.metadata.source 读取 source,默认为 'daemon'
|
|
4840
4988
|
const source = context?.metadata?.source ?? 'daemon';
|
|
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
|
+
}
|
|
4841
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() };
|
|
4842
5006
|
if (delivery.chatType === 'group') {
|
|
4843
5007
|
const extracted = this.extractMentionEntriesFromText(finalText);
|
|
4844
5008
|
if (extracted.length > 0)
|
|
@@ -4864,6 +5028,7 @@ export class AUNChannel {
|
|
|
4864
5028
|
}
|
|
4865
5029
|
// 诊断日志:记录 payload 构造结果(含 task_id / thread_id / chatmode)
|
|
4866
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(() => { });
|
|
4867
5032
|
const isGroup = delivery.chatType === 'group';
|
|
4868
5033
|
const targetAid = channelId;
|
|
4869
5034
|
if (entry.deliveryReceipt) {
|
|
@@ -5335,6 +5500,9 @@ export class AUNChannel {
|
|
|
5335
5500
|
if (isMenuPayload && typeof context?.metadata?.menuClientSdkVersion === 'string') {
|
|
5336
5501
|
params.menu_client_sdk_version = context.metadata.menuClientSdkVersion;
|
|
5337
5502
|
}
|
|
5503
|
+
if (isMenuPayload && context?.metadata?.menuClientVolatileV1 === true) {
|
|
5504
|
+
params.menu_client_volatile_v1 = true;
|
|
5505
|
+
}
|
|
5338
5506
|
const rawHeaders = context?.metadata?.protectedHeaders;
|
|
5339
5507
|
if (encrypt && rawHeaders && typeof rawHeaders === 'object' && !Array.isArray(rawHeaders)) {
|
|
5340
5508
|
const protectedHeaders = {};
|
|
@@ -5414,8 +5582,8 @@ export class AUNChannel {
|
|
|
5414
5582
|
logger.warn(`${this.logPrefix()} sendFile: file is empty`);
|
|
5415
5583
|
return { status: 'failed', error: 'file is empty', code: 'EMPTY_FILE' };
|
|
5416
5584
|
}
|
|
5417
|
-
if (stat.size >
|
|
5418
|
-
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)})`);
|
|
5419
5587
|
return { status: 'failed', error: `file too large (max ${formatSize(MAX_AUN_ATTACHMENT_SIZE)})`, code: 'FILE_TOO_LARGE' };
|
|
5420
5588
|
}
|
|
5421
5589
|
// Write-ahead: persist to outbox
|
|
@@ -5869,8 +6037,13 @@ export class AUNChannel {
|
|
|
5869
6037
|
return;
|
|
5870
6038
|
}
|
|
5871
6039
|
context = this.withDelivery(context, delivery);
|
|
5872
|
-
if (status === 'start')
|
|
5873
|
-
|
|
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
|
+
}
|
|
5874
6047
|
if (!this.client || !this.connected)
|
|
5875
6048
|
return;
|
|
5876
6049
|
const requestedSeverity = extraMeta?.severity;
|
|
@@ -6239,6 +6412,8 @@ export class AUNChannelPlugin {
|
|
|
6239
6412
|
if (replyCtx) {
|
|
6240
6413
|
replyCtx = { ...replyCtx };
|
|
6241
6414
|
delete replyCtx.attention;
|
|
6415
|
+
// Do not inherit title from a previous payload's reply context.
|
|
6416
|
+
delete replyCtx.title;
|
|
6242
6417
|
}
|
|
6243
6418
|
if (attention !== undefined)
|
|
6244
6419
|
replyCtx = { ...(replyCtx ?? {}), attention };
|
|
@@ -6283,8 +6458,9 @@ export class AUNChannelPlugin {
|
|
|
6283
6458
|
? { forkAnchor: payload.metadata.forkAnchor } : {}),
|
|
6284
6459
|
},
|
|
6285
6460
|
};
|
|
6286
|
-
if (payload.kind === 'result.text' && payload.isFinal)
|
|
6461
|
+
if (payload.kind === 'result.text' && payload.isFinal && delivery.chatType === 'private') {
|
|
6287
6462
|
sendCtx.title = '✅ 最终回复:';
|
|
6463
|
+
}
|
|
6288
6464
|
let result;
|
|
6289
6465
|
try {
|
|
6290
6466
|
result = await channel.sendMessage(channelId, payload.text, sendCtx);
|