evolcore 0.0.25 → 0.0.26
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 +607 -565
- package/LICENSE +21 -21
- package/README.md +455 -455
- package/bin/codex-managed-acl.ps1 +160 -160
- package/bin/codex-managed-hook.mjs +208 -208
- package/bin/ec-safe-output.js +327 -327
- package/bin/ec.js +40 -29
- package/bin/install-codex-managed-hooks.mjs +582 -555
- package/dist/agents/baseagent.js +96 -46
- package/dist/agents/claude-runner.js +50 -12
- package/dist/agents/codex/app-server-client.js +13 -5
- package/dist/agents/codex/catalog.js +1 -1
- package/dist/agents/codex/diagnostics.js +34 -1
- package/dist/agents/codex-runner.js +121 -38
- package/dist/agents/ecagent-runner.js +4 -9
- package/dist/agents/gemini-runner.js +2 -7
- package/dist/agents/runner-types.js +25 -17
- package/dist/aun/aid/agentmd.js +81 -8
- package/dist/aun/aid/client.js +1 -1
- package/dist/aun/aid/identity.js +1 -1
- package/dist/aun/aid/index.js +1 -1
- package/dist/aun/aid/store.js +17 -13
- package/dist/aun/menu-client.js +74 -0
- package/dist/aun/msg/group.js +1 -1
- package/dist/aun/msg/managed-operation.js +1 -1
- package/dist/aun/msg/p2p.js +6 -1
- package/dist/aun/msg/upload.js +30 -22
- package/dist/channels/aun.js +329 -94
- package/dist/cli/agent-command.js +105 -84
- package/dist/cli/agent-create.js +112 -10
- package/dist/cli/agent.js +16 -5
- package/dist/cli/aun-commands.js +297 -296
- package/dist/cli/bench.js +20 -20
- package/dist/cli/config.js +47 -28
- package/dist/cli/contact.js +70 -13
- package/dist/cli/ctl-command.js +22 -22
- package/dist/cli/daemon-commands.js +105 -40
- package/dist/cli/diag.js +31 -0
- package/dist/cli/fs-command.js +67 -67
- package/dist/cli/handoff-command.js +41 -9
- package/dist/cli/index.js +83 -77
- package/dist/cli/link-rules.js +21 -21
- package/dist/cli/managed-command-guard.js +3 -0
- package/dist/cli/menu-client.js +46 -31
- package/dist/cli/net-check.js +22 -22
- package/dist/cli/queue-command.js +29 -29
- package/dist/cli/response.js +30 -30
- package/dist/cli/restart-monitor.js +87 -40
- package/dist/cli/stats.js +51 -51
- package/dist/cli/trigger-command.js +56 -56
- package/dist/config/builtin-roles.js +1 -0
- package/dist/config/config-operation-service.js +145 -0
- package/dist/config/contact-operation-service.js +96 -7
- package/dist/config/resolved-config-op.js +27 -7
- package/dist/core/agent-application-service.js +2 -1
- package/dist/core/auth/authorization-audit.js +20 -12
- package/dist/core/auth/operation-authorizer.js +25 -4
- package/dist/core/auth/operation-catalog.js +35 -4
- package/dist/core/auth/remote-access-store.js +40 -1
- package/dist/core/bootstrap-service.js +1 -1
- package/dist/core/command/command-handler.js +83 -26
- package/dist/core/command/diag-service.js +118 -0
- package/dist/core/command/menu-catalog.js +12 -3
- package/dist/core/command/menu-handler.js +47 -26
- package/dist/core/command/menu-protocol.js +5 -0
- package/dist/core/command/menu-token-store.js +41 -8
- package/dist/core/command/slash-gate.js +34 -4
- package/dist/core/command/slash-handler.js +86 -21
- package/dist/core/handoff/runtime.js +205 -27
- package/dist/core/handoff/store.js +193 -4
- package/dist/core/message/im-renderer.js +72 -21
- package/dist/core/message/message-bridge.js +69 -9
- package/dist/core/message/message-queue.js +26 -0
- package/dist/core/message/message-utils.js +1 -0
- package/dist/core/message/response-engine.js +47 -28
- package/dist/core/model/catalog-service.js +79 -49
- package/dist/core/model/context-window-suffix.js +33 -0
- package/dist/core/model/model-catalog.js +1 -1
- package/dist/core/model/model-permission.js +12 -3
- package/dist/core/permission/ec-command-parser.js +51 -12
- package/dist/core/permission/sandbox-runtime.js +36 -14
- package/dist/core/permission/tool-policy.js +2 -1
- package/dist/index.js +280 -163
- package/dist/ipc.js +54 -2
- package/dist/response-system/engines/v1/proactive-flow.js +59 -2
- package/dist/stats/billing.js +41 -15
- package/dist/stats/db.js +159 -159
- package/dist/stats/eck-vars.js +9 -9
- package/dist/stats/price-resolver.js +28 -10
- package/dist/stats/query.js +139 -139
- package/dist/stats/role-budget.js +6 -6
- package/dist/stats/writer.js +51 -51
- package/dist/utils/cross-platform.js +30 -1
- package/dist/utils/error-dict.json +160 -160
- package/dist/utils/error-utils.js +21 -0
- package/dist/utils/evolcore-version.js +129 -0
- package/dist/utils/logger.js +3 -1
- package/dist/utils/macos-autostart.js +22 -22
- package/dist/utils/model-prices.jsonl +23 -23
- package/dist/utils/npm-ops.js +176 -35
- package/dist/utils/welcome.js +22 -22
- package/ecagent/LICENSE +21 -21
- package/ecagent/NOTICE +11 -11
- package/ecagent/README.md +61 -61
- package/ecagent/dist/harness/compaction/branch-summarization.js +30 -30
- package/ecagent/dist/harness/compaction/compaction.js +83 -83
- package/ecagent/dist/harness/messages.js +7 -7
- package/kits/docs/GUIDE.md +20 -20
- package/kits/docs/INDEX.md +66 -66
- package/kits/docs/aun/CHEATSHEET.md +19 -19
- package/kits/docs/aun/SYNC_PROTOCOL.md +15 -15
- package/kits/docs/channels/aun.md +56 -56
- package/kits/docs/channels/feishu.md +56 -56
- package/kits/docs/context-assembly.md +364 -364
- package/kits/docs/eck_templates/GUIDE.template.md +22 -22
- package/kits/docs/eck_templates/INDEX.template.md +28 -28
- package/kits/docs/eck_templates/path-registry.template.md +33 -33
- package/kits/docs/eck_templates/runtime.template.md +19 -19
- package/kits/docs/evolcore/INDEX.md +71 -71
- package/kits/docs/evolcore/agent-create.md +170 -165
- package/kits/docs/evolcore/agent.md +66 -66
- package/kits/docs/evolcore/aid.md +60 -60
- package/kits/docs/evolcore/codex-windows-sandbox.md +146 -146
- package/kits/docs/evolcore/config.md +202 -195
- package/kits/docs/evolcore/contact.md +75 -68
- package/kits/docs/evolcore/ctl.md +46 -46
- package/kits/docs/evolcore/event.md +216 -216
- package/kits/docs/evolcore/fs-architecture.md +1215 -1215
- package/kits/docs/evolcore/fs.md +110 -110
- package/kits/docs/evolcore/group-collaboration.md +243 -243
- package/kits/docs/evolcore/group-fs.md +17 -17
- package/kits/docs/evolcore/group-rules.md +251 -251
- package/kits/docs/evolcore/group.md +152 -152
- package/kits/docs/evolcore/menu-client.md +42 -34
- package/kits/docs/evolcore/model.md +50 -50
- package/kits/docs/evolcore/msg.md +166 -165
- package/kits/docs/evolcore/response.md +75 -75
- package/kits/docs/evolcore/rpc.md +37 -37
- package/kits/docs/evolcore/self-summary.md +29 -29
- package/kits/docs/evolcore/stats.md +83 -83
- package/kits/docs/evolcore/storage.md +50 -50
- package/kits/docs/evolcore/trigger.md +575 -575
- package/kits/docs/identity/AID_PROFILE_SPEC.md +26 -26
- package/kits/docs/identity/PATH_OPS.md +16 -16
- package/kits/docs/identity/ROLE_DETAIL.md +25 -25
- package/kits/docs/identity/identity-tools.md +26 -26
- package/kits/docs/path-registry.md +62 -62
- package/kits/docs/prompt-loading-architecture.md +269 -269
- package/kits/docs/venues/aun-group.md +45 -45
- package/kits/docs/venues/aun-private.md +10 -10
- package/kits/docs/venues/client-desktop.md +10 -10
- package/kits/docs/venues/client-mobile.md +10 -10
- package/kits/docs/venues/feishu-group.md +13 -13
- package/kits/docs/venues/feishu-private.md +9 -9
- package/kits/docs/venues/group.md +37 -25
- package/kits/docs/venues/private.md +10 -10
- package/kits/eck_manifest.auxiliary.json +43 -43
- package/kits/eck_manifest.json +190 -190
- package/kits/eck_message_manifest.json +74 -74
- package/kits/migrations/README-role-config-v4.md +32 -32
- package/kits/migrations/migrate-contact-book-v2.mjs +748 -748
- package/kits/migrations/migrate-role-config-v4.mjs +614 -614
- package/kits/migrations/migrate-role-config-v5.mjs +365 -365
- package/kits/migrations/rename-config-file.mjs +99 -99
- package/kits/rules/01-overview.md +162 -162
- package/kits/rules/02-navigation.md +96 -96
- package/kits/rules/03-identity.md +36 -36
- package/kits/rules/04-relation.md +75 -75
- package/kits/rules/05-venue.md +60 -60
- package/kits/rules/06-channel.md +63 -63
- package/kits/schemas/_meta.json +53 -53
- package/kits/schemas/agent-config.schema.1.json +177 -177
- package/kits/schemas/agent-config.schema.10.json +395 -395
- package/kits/schemas/agent-config.schema.11.json +421 -421
- package/kits/schemas/agent-config.schema.12.json +427 -427
- package/kits/schemas/agent-config.schema.2.json +239 -239
- package/kits/schemas/agent-config.schema.3.json +119 -119
- package/kits/schemas/agent-config.schema.4.json +208 -208
- package/kits/schemas/agent-config.schema.5.json +326 -326
- package/kits/schemas/agent-config.schema.6.json +322 -322
- package/kits/schemas/agent-config.schema.7.json +303 -303
- package/kits/schemas/agent-config.schema.8.json +304 -304
- package/kits/schemas/agent-config.schema.9.json +364 -364
- package/kits/schemas/contact-book.schema.1.json +36 -36
- package/kits/schemas/contact-book.schema.2.json +43 -43
- package/kits/schemas/contact-book.schema.3.json +67 -67
- package/kits/schemas/daemon.schema.1.json +91 -91
- package/kits/schemas/daemon.schema.2.json +101 -101
- package/kits/schemas/daemon.schema.3.json +123 -123
- package/kits/schemas/daemon.schema.4.json +136 -136
- package/kits/schemas/daemon.schema.5.json +135 -135
- package/kits/schemas/daemon.schema.6.json +131 -131
- package/kits/schemas/defaults.schema.1.json +81 -81
- package/kits/schemas/defaults.schema.2.json +85 -85
- package/kits/schemas/defaults.schema.3.json +73 -73
- package/kits/schemas/defaults.schema.4.json +107 -107
- package/kits/schemas/defaults.schema.5.json +119 -119
- package/kits/schemas/menu-exec-schema-commands.md +208 -208
- package/kits/schemas/migrations/README.md +30 -30
- package/kits/schemas/relation-config.schema.1.json +158 -158
- package/kits/schemas/relation-config.schema.2.json +73 -73
- package/kits/schemas/relation-config.schema.3.json +50 -50
- package/kits/schemas/relation-config.schema.4.json +47 -47
- package/kits/schemas/relation-config.schema.5.json +47 -47
- package/kits/schemas/relation-config.schema.6.json +46 -46
- package/kits/schemas/relation-config.schema.7.json +59 -59
- package/kits/schemas/relation-config.schema.8.json +103 -103
- package/kits/schemas/role-config.schema.1.json +201 -201
- package/kits/schemas/role-registry.schema.1.json +35 -35
- package/kits/schemas/single-session.schema.1.json +31 -31
- package/kits/schemas/single-session.schema.2.json +57 -57
- package/kits/schemas/single-session.schema.3.json +32 -32
- package/kits/templates/bootstrap-welcome.md +17 -17
- package/kits/templates/message-fragments/handoff-context-to-target.md +9 -9
- package/kits/templates/message-fragments/handoff-request-to-target.md +19 -19
- package/kits/templates/message-fragments/handoff-response-to-origin.md +9 -9
- package/kits/templates/message-fragments/inject-default.md +2 -2
- package/kits/templates/message-fragments/item.md +2 -2
- package/kits/templates/roles/admin.json +92 -92
- package/kits/templates/roles/member.json +96 -96
- package/kits/templates/roles/owner.json +9 -9
- package/kits/templates/roles/visitor.json +85 -85
- package/kits/templates/system-fragments/baseagent.md +14 -14
- package/kits/templates/system-fragments/bootstrap.md +31 -29
- package/kits/templates/system-fragments/channel.md +64 -64
- package/kits/templates/system-fragments/commands.md +28 -28
- package/kits/templates/system-fragments/identity.md +13 -13
- package/kits/templates/system-fragments/relation.md +19 -19
- package/kits/templates/system-fragments/session.md +59 -59
- package/kits/templates/system-fragments/venue.md +31 -31
- package/package.json +100 -98
package/dist/channels/aun.js
CHANGED
|
@@ -4,6 +4,7 @@ import { performance } from 'node:perf_hooks';
|
|
|
4
4
|
import fs from 'fs';
|
|
5
5
|
import path from 'path';
|
|
6
6
|
import os from 'os';
|
|
7
|
+
import { fileURLToPath, pathToFileURL } from 'url';
|
|
7
8
|
import { logger, localTimestamp } from '../utils/logger.js';
|
|
8
9
|
import { LogWriter } from '../utils/log-writer.js';
|
|
9
10
|
import { middleOutputModePolicy, resolveShowActivities, showActivitiesPolicy } from '../core/channel-loader.js';
|
|
@@ -33,6 +34,7 @@ import { deriveCausation, normalizeCausation } from '../core/causation/context.j
|
|
|
33
34
|
import { recordCausationSpan } from '../core/causation/audit.js';
|
|
34
35
|
import { refreshAgentDisplayName, resolveAgentDisplayName } from '../aun/aid/agentmd.js';
|
|
35
36
|
import { readInstalledEvolcoreVersion } from '../utils/evolcore-version.js';
|
|
37
|
+
import { ManagedMenuClient } from '../aun/menu-client.js';
|
|
36
38
|
import { toolInputForDisplay } from '../utils/tool-summary.js';
|
|
37
39
|
import { bindPostBootstrapWelcomeOutboxSession, hasPendingPostBootstrapWelcomeOutbox, pendingPostBootstrapWelcomeOwners, isPostBootstrapWelcomeOperationId, findPostBootstrapWelcomeOutbox, bootstrapInitialMessageOperationPrefix, postBootstrapWelcomeOperationId, preparePostBootstrapWelcomeOutbox, } from '../core/bootstrap-messages.js';
|
|
38
40
|
import { hasMentionAll, mentionEntryAids, mentionEntryTargets, } from '../core/message/mention-schema.js';
|
|
@@ -52,6 +54,51 @@ const AUN_SEND_LOG_TEXT_MAX_LENGTH = 60;
|
|
|
52
54
|
export function formatAunSendLogText(text) {
|
|
53
55
|
return text.replace(/\s+/gu, ' ').trim().slice(0, AUN_SEND_LOG_TEXT_MAX_LENGTH);
|
|
54
56
|
}
|
|
57
|
+
/**
|
|
58
|
+
* Echo trace 行的墙钟前缀。
|
|
59
|
+
*
|
|
60
|
+
* 格式必须保持 `HH:MM:SS.mmm` 且毫秒后紧跟空格——Evol App 的
|
|
61
|
+
* `evol/lib/utils/echo_utils.dart` 用 `^(\d{2}):(\d{2}):(\d{2})\.(\d{3})\s` 匹配这一段,
|
|
62
|
+
* 在毫秒和空格之间插任何东西(比如时区偏移)都会让旧版 App 静默失配、不再显示 RTT。
|
|
63
|
+
* 时区信息改由行尾的 {@link echoTraceOriginLabel} 提供,前缀一字不动,两端发版顺序因此解耦。
|
|
64
|
+
*/
|
|
65
|
+
export function echoTraceClock(now = Date.now()) {
|
|
66
|
+
const d = new Date(now);
|
|
67
|
+
const pad = (n, width = 2) => String(n).padStart(width, '0');
|
|
68
|
+
return `${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}.${pad(d.getMilliseconds(), 3)}`;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Echo trace 行尾的权威时间标记:`epoch=<ms> tz=<±HH:MM>`。
|
|
72
|
+
*
|
|
73
|
+
* 前缀的墙钟没有日期也没有时区,消费方只能按自己的本地时区解析,两端 TZ 不一致时算出的
|
|
74
|
+
* RTT 会整小时偏移(UTC 对 Asia/Shanghai 就是 +28800000ms),跨午夜还会得到负值。
|
|
75
|
+
* `epoch` 是绝对值,消费方应当用它相减算延迟;`tz` 仅供人工核对墙钟,不参与计算。
|
|
76
|
+
*/
|
|
77
|
+
export function echoTraceOriginLabel(now = Date.now()) {
|
|
78
|
+
const d = new Date(now);
|
|
79
|
+
const pad = (n, width = 2) => String(n).padStart(width, '0');
|
|
80
|
+
const offsetMinutes = -d.getTimezoneOffset();
|
|
81
|
+
const sign = offsetMinutes >= 0 ? '+' : '-';
|
|
82
|
+
const absOffset = Math.abs(offsetMinutes);
|
|
83
|
+
return ` epoch=${now} tz=${sign}${pad(Math.floor(absOffset / 60))}:${pad(absOffset % 60)}`;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* 入站腿延迟(对端/网关发出 → 本进程收到)。
|
|
87
|
+
*
|
|
88
|
+
* `created_at` 由对端或网关打戳,因此结果含对端时钟漂移,只能当估算值——标签用
|
|
89
|
+
* `inbound_est` 而非 `inbound` 就是为了让消费方知道这点。负值说明对端时钟快于本机,
|
|
90
|
+
* 照实输出而不夹到 0:那本身就是有用的时钟偏移信号。
|
|
91
|
+
*/
|
|
92
|
+
export function echoInboundLegLabel(createdAt, receivedAt) {
|
|
93
|
+
if (typeof createdAt !== 'number' || !Number.isFinite(createdAt) || createdAt <= 0)
|
|
94
|
+
return '';
|
|
95
|
+
// 网关历史上有按秒打戳的实现,按量级归一到毫秒。
|
|
96
|
+
const createdMs = createdAt < 1e12 ? createdAt * 1000 : createdAt;
|
|
97
|
+
const delta = receivedAt - createdMs;
|
|
98
|
+
if (!Number.isFinite(delta))
|
|
99
|
+
return '';
|
|
100
|
+
return ` inbound_est=${Math.round(delta)}ms`;
|
|
101
|
+
}
|
|
55
102
|
function attachmentDownloadHost(url) {
|
|
56
103
|
try {
|
|
57
104
|
return new URL(url).host || '<unknown>';
|
|
@@ -431,6 +478,8 @@ export function aunOptsToInbound(opts, channel, channelType) {
|
|
|
431
478
|
content: opts.content,
|
|
432
479
|
chatType: opts.chatType || 'private',
|
|
433
480
|
peerId: opts.peerId || '',
|
|
481
|
+
peerDeviceId: opts.peerDeviceId,
|
|
482
|
+
peerSlotId: opts.peerSlotId,
|
|
434
483
|
peerName: opts.peerName,
|
|
435
484
|
peerType: opts.peerType,
|
|
436
485
|
sameDevice: opts.sameDevice,
|
|
@@ -502,9 +551,26 @@ function payloadTypeForObserver(value) {
|
|
|
502
551
|
const type = payload.type;
|
|
503
552
|
return typeof type === 'string' ? type : undefined;
|
|
504
553
|
}
|
|
554
|
+
function errorCodeForLog(error) {
|
|
555
|
+
if (!error || typeof error !== 'object')
|
|
556
|
+
return '';
|
|
557
|
+
const value = error;
|
|
558
|
+
return typeof value.code === 'string' || typeof value.code === 'number' ? String(value.code) : '';
|
|
559
|
+
}
|
|
505
560
|
export class AUNChannel {
|
|
506
561
|
config;
|
|
562
|
+
managedMenuClient = new ManagedMenuClient(params => {
|
|
563
|
+
if (!this.connected || !this.client)
|
|
564
|
+
return Promise.reject(new Error('AUN channel is not connected'));
|
|
565
|
+
return this.callAndTrace('message.send', params);
|
|
566
|
+
});
|
|
567
|
+
requestRemoteMenu(options) {
|
|
568
|
+
if (options.fromAid !== (this._aid || this.config.aid))
|
|
569
|
+
throw new Error('Menu sender does not match channel identity');
|
|
570
|
+
return this.managedMenuClient.request(options);
|
|
571
|
+
}
|
|
507
572
|
client = null;
|
|
573
|
+
localSenderInstallation;
|
|
508
574
|
store = null;
|
|
509
575
|
/** 实际连接的网关 URL(来自 authenticate() 返回值 / connection.state 事件),替代旧 (client as any)._gatewayUrl。 */
|
|
510
576
|
gatewayUrl = '';
|
|
@@ -562,46 +628,88 @@ export class AUNChannel {
|
|
|
562
628
|
* 统一的 RPC 调用包装:自动记录 OUT 发送、.ok 结果、.error 错误(含 trace + daemon.log 失败日志)。
|
|
563
629
|
* 所有 client.call() 都应通过此方法调用,保证 aun-trace 里每个 OUT 调用都有"发+收/错"成对记录。
|
|
564
630
|
*/
|
|
565
|
-
callClient(method, params) {
|
|
566
|
-
return this.withOutboundSendGate(method, () => (this.client.call(method, params).then(value => value)));
|
|
631
|
+
callClient(method, params, lane = 'normal', onStart) {
|
|
632
|
+
return this.withOutboundSendGate(method, () => (this.client.call(method, params).then(value => value)), lane, onStart);
|
|
567
633
|
}
|
|
568
|
-
withOutboundSendGate(method, run) {
|
|
569
|
-
if (!AUNChannel.OUTBOUND_SEND_METHODS.has(method))
|
|
634
|
+
withOutboundSendGate(method, run, lane = 'normal', onStart) {
|
|
635
|
+
if (!AUNChannel.OUTBOUND_SEND_METHODS.has(method)) {
|
|
636
|
+
onStart?.();
|
|
570
637
|
return run();
|
|
638
|
+
}
|
|
639
|
+
const queue = lane === 'observer' ? this.observerSendQueue : this.outboundSendQueue;
|
|
640
|
+
if (lane === 'observer' && queue.length >= AUNChannel.OBSERVER_SEND_QUEUE_MAX) {
|
|
641
|
+
this.noteObserverQueueDrop();
|
|
642
|
+
const error = Object.assign(new Error('observer send queue is full'), {
|
|
643
|
+
code: 'AUN_OBSERVER_QUEUE_FULL',
|
|
644
|
+
});
|
|
645
|
+
return Promise.reject(error);
|
|
646
|
+
}
|
|
571
647
|
return new Promise((resolve, reject) => {
|
|
572
|
-
|
|
648
|
+
queue.push({
|
|
573
649
|
run: run,
|
|
574
650
|
resolve: resolve,
|
|
575
651
|
reject,
|
|
652
|
+
onStart,
|
|
576
653
|
});
|
|
577
654
|
this.pumpOutboundSendGate();
|
|
578
655
|
});
|
|
579
656
|
}
|
|
580
657
|
pumpOutboundSendGate() {
|
|
581
|
-
while (this.outboundSendActive < AUNChannel.OUTBOUND_SEND_CONCURRENCY
|
|
582
|
-
|
|
583
|
-
|
|
658
|
+
while (this.outboundSendActive + this.observerSendActive < AUNChannel.OUTBOUND_SEND_CONCURRENCY && this.outboundSendQueue.length > 0) {
|
|
659
|
+
this.startOutboundSendJob(this.outboundSendQueue.shift(), 'normal');
|
|
660
|
+
}
|
|
661
|
+
while (this.outboundSendActive + this.observerSendActive < AUNChannel.OUTBOUND_SEND_CONCURRENCY
|
|
662
|
+
&& this.observerSendActive < AUNChannel.OBSERVER_SEND_CONCURRENCY
|
|
663
|
+
&& this.outboundSendQueue.length === 0
|
|
664
|
+
&& this.observerSendQueue.length > 0) {
|
|
665
|
+
this.startOutboundSendJob(this.observerSendQueue.shift(), 'observer');
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
clearQueuedObserverSends(reason) {
|
|
669
|
+
if (this.observerSendQueue.length === 0)
|
|
670
|
+
return;
|
|
671
|
+
const error = Object.assign(new Error(`observer send discarded while ${reason}`), {
|
|
672
|
+
code: 'AUN_OBSERVER_QUEUE_DROPPED',
|
|
673
|
+
});
|
|
674
|
+
const queued = this.observerSendQueue.splice(0);
|
|
675
|
+
for (const job of queued)
|
|
676
|
+
job.reject(error);
|
|
677
|
+
this.noteObserverQueueDrop(queued.length);
|
|
678
|
+
}
|
|
679
|
+
startOutboundSendJob(job, lane) {
|
|
680
|
+
if (lane === 'observer')
|
|
681
|
+
this.observerSendActive++;
|
|
682
|
+
else
|
|
584
683
|
this.outboundSendActive++;
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
684
|
+
Promise.resolve()
|
|
685
|
+
.then(() => {
|
|
686
|
+
job.onStart?.();
|
|
687
|
+
return job.run();
|
|
688
|
+
})
|
|
689
|
+
.then(job.resolve, job.reject)
|
|
690
|
+
.finally(() => {
|
|
691
|
+
if (lane === 'observer')
|
|
692
|
+
this.observerSendActive--;
|
|
693
|
+
else
|
|
589
694
|
this.outboundSendActive--;
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
}
|
|
695
|
+
this.pumpOutboundSendGate();
|
|
696
|
+
});
|
|
593
697
|
}
|
|
594
698
|
async callAndTrace(method, params, opts) {
|
|
595
699
|
this.trace('OUT', method, params);
|
|
596
|
-
//
|
|
597
|
-
//
|
|
598
|
-
// 是 /evolhelp 等命令「回复慢/无回复」的直接可观测信号。
|
|
700
|
+
// 计时同时覆盖排队和 SDK 往返;callStartedAt 让日志能区分「本地队列堵塞」
|
|
701
|
+
// 与「网关慢」。两者对 /evolhelp 等命令的回复延迟都有直接影响。
|
|
599
702
|
const rpcStart = Date.now();
|
|
600
703
|
// SLOW_RPC_WARN_MS 需明显低于 SDK 默认 10s 超时,以便在真正超时前提前告警。
|
|
601
704
|
const SLOW_RPC_WARN_MS = 3000;
|
|
705
|
+
let callStartedAt = rpcStart;
|
|
602
706
|
try {
|
|
603
|
-
const result = await this.callClient(method, params)
|
|
707
|
+
const result = await this.callClient(method, params, opts?.lane ?? 'normal', () => {
|
|
708
|
+
callStartedAt = Date.now();
|
|
709
|
+
});
|
|
604
710
|
const durationMs = Date.now() - rpcStart;
|
|
711
|
+
const rpcDurationMs = Date.now() - callStartedAt;
|
|
712
|
+
const queueWaitMs = Math.max(0, callStartedAt - rpcStart);
|
|
605
713
|
if (!opts?.silentOk) {
|
|
606
714
|
const r = result;
|
|
607
715
|
const snap = r && typeof r === 'object'
|
|
@@ -609,25 +717,66 @@ export class AUNChannel {
|
|
|
609
717
|
: { duration_ms: durationMs };
|
|
610
718
|
this.trace('OUT', `${method}.ok`, snap);
|
|
611
719
|
}
|
|
612
|
-
if (durationMs >= SLOW_RPC_WARN_MS)
|
|
613
|
-
|
|
614
|
-
}
|
|
720
|
+
if (durationMs >= SLOW_RPC_WARN_MS)
|
|
721
|
+
this.logRpcWarning('slow', method, params, durationMs, queueWaitMs, rpcDurationMs);
|
|
615
722
|
return result;
|
|
616
723
|
}
|
|
617
724
|
catch (e) {
|
|
618
725
|
const durationMs = Date.now() - rpcStart;
|
|
726
|
+
const queueWaitMs = Math.max(0, callStartedAt - rpcStart);
|
|
727
|
+
const rpcDurationMs = Math.max(0, Date.now() - callStartedAt);
|
|
619
728
|
this.trace('OUT', `${method}.error`, {
|
|
620
729
|
error: e?.message ?? String(e),
|
|
621
730
|
code: e?.code,
|
|
622
731
|
name: e?.name,
|
|
623
732
|
duration_ms: durationMs,
|
|
624
733
|
});
|
|
625
|
-
if (!opts?.silentError) {
|
|
626
|
-
|
|
734
|
+
if (!opts?.silentError && e?.code !== 'AUN_OBSERVER_QUEUE_FULL' && e?.code !== 'AUN_OBSERVER_QUEUE_DROPPED') {
|
|
735
|
+
this.logRpcWarning('failed', method, params, durationMs, queueWaitMs, rpcDurationMs, e);
|
|
627
736
|
}
|
|
628
737
|
throw e;
|
|
629
738
|
}
|
|
630
739
|
}
|
|
740
|
+
logRpcWarning(kind, method, params, durationMs, queueWaitMs, rpcDurationMs, error) {
|
|
741
|
+
const payloadType = payloadTypeForObserver(params) ?? 'unknown';
|
|
742
|
+
const code = kind === 'failed' ? errorCodeForLog(error) : '';
|
|
743
|
+
const key = `${kind}|${method}|${payloadType}`;
|
|
744
|
+
const now = Date.now();
|
|
745
|
+
const state = this.rpcWarningStates.get(key) ?? { lastWarnAt: 0, suppressed: 0, maxDurationMs: 0 };
|
|
746
|
+
state.maxDurationMs = Math.max(state.maxDurationMs, durationMs);
|
|
747
|
+
if (state.lastWarnAt > 0 && now - state.lastWarnAt < AUNChannel.RPC_WARN_INTERVAL_MS) {
|
|
748
|
+
state.suppressed++;
|
|
749
|
+
this.rpcWarningStates.set(key, state);
|
|
750
|
+
return;
|
|
751
|
+
}
|
|
752
|
+
const suppressed = state.suppressed;
|
|
753
|
+
const maxDurationMs = state.maxDurationMs;
|
|
754
|
+
state.lastWarnAt = now;
|
|
755
|
+
state.suppressed = 0;
|
|
756
|
+
state.maxDurationMs = 0;
|
|
757
|
+
this.rpcWarningStates.set(key, state);
|
|
758
|
+
const summary = suppressed > 0 ? ` suppressed=${suppressed} max_duration=${maxDurationMs}ms` : '';
|
|
759
|
+
if (kind === 'failed') {
|
|
760
|
+
const name = error instanceof Error ? error.name : '';
|
|
761
|
+
const message = error instanceof Error ? error.message : String(error ?? '');
|
|
762
|
+
const errorLabel = `${name}${code ? `(${code})` : ''} ${message}`.trim();
|
|
763
|
+
logger.warn(`${this.logPrefix()} rpc ${method} failed after ${durationMs}ms: ${errorLabel} queue_wait=${queueWaitMs}ms rpc=${rpcDurationMs}ms payload=${payloadType}${summary}`);
|
|
764
|
+
return;
|
|
765
|
+
}
|
|
766
|
+
logger.warn(`${this.logPrefix()} rpc ${method} SLOW: duration=${durationMs}ms queue_wait=${queueWaitMs}ms rpc=${rpcDurationMs}ms payload=${payloadType}${summary} (发送管线可能饱和或链路抖动)`);
|
|
767
|
+
}
|
|
768
|
+
observerQueueDropCount = 0;
|
|
769
|
+
observerQueueDropLastWarnAt = 0;
|
|
770
|
+
noteObserverQueueDrop(count = 1) {
|
|
771
|
+
this.observerQueueDropCount += count;
|
|
772
|
+
const now = Date.now();
|
|
773
|
+
if (now - this.observerQueueDropLastWarnAt < AUNChannel.RPC_WARN_INTERVAL_MS)
|
|
774
|
+
return;
|
|
775
|
+
const dropped = this.observerQueueDropCount;
|
|
776
|
+
this.observerQueueDropCount = 0;
|
|
777
|
+
this.observerQueueDropLastWarnAt = now;
|
|
778
|
+
logger.warn(`${this.logPrefix()} observer send queue full: dropped=${dropped} (observer telemetry is being sampled while the gateway is unavailable)`);
|
|
779
|
+
}
|
|
631
780
|
/** Resolve the authenticated transport route for an ordinary outbound send.
|
|
632
781
|
* The channel id is an address only; it is intentionally never used to infer
|
|
633
782
|
* whether the address is a group.
|
|
@@ -693,7 +842,13 @@ export class AUNChannel {
|
|
|
693
842
|
context.threadId = threadId;
|
|
694
843
|
if (operatorId && delivery.chatType === 'group')
|
|
695
844
|
context.peerId = operatorId;
|
|
696
|
-
|
|
845
|
+
const payload = {
|
|
846
|
+
type: 'notice',
|
|
847
|
+
...this.buildTaskPayloadBase({}, context),
|
|
848
|
+
text,
|
|
849
|
+
severity: 'info',
|
|
850
|
+
};
|
|
851
|
+
void this.sendReliableStructured(channelId, payload, context, text).catch((err) => {
|
|
697
852
|
logger.error(`${this.logPrefix()} Failed to send card action error:`, err);
|
|
698
853
|
});
|
|
699
854
|
}
|
|
@@ -713,6 +868,12 @@ export class AUNChannel {
|
|
|
713
868
|
: typeof obj.card_message_id === 'string' ? obj.card_message_id : '';
|
|
714
869
|
const cardInfo = cardMsgId ? this.cardMessageIdMap.get(cardMsgId) : undefined;
|
|
715
870
|
const cardDelivery = cardInfo?.delivery ?? delivery;
|
|
871
|
+
const savedTopic = this.ownedCardTopics.get(cardMsgId);
|
|
872
|
+
// Keep the topic after the actionable mapping expires, including for
|
|
873
|
+
// older clients that omit thread_id in their callbacks.
|
|
874
|
+
const threadId = cardInfo?.replyContext?.threadId
|
|
875
|
+
?? (savedTopic?.channelId === channelId ? savedTopic?.threadId : undefined)
|
|
876
|
+
?? (typeof obj.thread_id === 'string' ? obj.thread_id : undefined);
|
|
716
877
|
if (cardInfo) {
|
|
717
878
|
// compose 按钮由新版客户端在卡片内采集输入;旧版客户端可继续从主编辑器
|
|
718
879
|
// 采集。两者最终都回传 action_card_reply{behavior:"compose", text}。
|
|
@@ -728,10 +889,6 @@ export class AUNChannel {
|
|
|
728
889
|
const actionValue = isComposeReply ? '_custom_input'
|
|
729
890
|
: typeof obj.value === 'string' ? obj.value
|
|
730
891
|
: typeof obj.action_value === 'string' ? obj.action_value : text;
|
|
731
|
-
// Prefer the trusted context persisted with the card. A callback's
|
|
732
|
-
// thread_id is client-controlled and may be absent or stale.
|
|
733
|
-
const threadId = cardInfo?.replyContext?.threadId
|
|
734
|
-
?? (typeof obj.thread_id === 'string' ? obj.thread_id : undefined);
|
|
735
892
|
// 卡片点击者身份:只信认证信封(senderAid 参数,由调用方从 msg.from / msg.sender_aid 提取)。
|
|
736
893
|
// payload 自报字段(from / sender_aid / user_id)不可信,可被客户端伪造,不读取。
|
|
737
894
|
// 两类卡片共用:CommandCard → 伪入站消息的 peerId,ActionInteraction → operatorId。
|
|
@@ -774,9 +931,11 @@ export class AUNChannel {
|
|
|
774
931
|
peerName: typeof obj.label === 'string' ? obj.label : typeof obj.action_label === 'string' ? obj.action_label : undefined,
|
|
775
932
|
messageId: `card-trigger-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
|
|
776
933
|
threadId,
|
|
777
|
-
replyContext:
|
|
778
|
-
|
|
779
|
-
:
|
|
934
|
+
replyContext: {
|
|
935
|
+
...(cardInfo.replyContext ?? {}),
|
|
936
|
+
...(threadId ? { threadId } : {}),
|
|
937
|
+
...(cardDelivery ? { delivery: cardDelivery } : {}),
|
|
938
|
+
},
|
|
780
939
|
source: 'card-trigger',
|
|
781
940
|
})).catch((err) => {
|
|
782
941
|
logger.error(`${this.logPrefix()} CommandCard handler failed: action=${actionValue} mid=${cardMsgId}`, err);
|
|
@@ -887,9 +1046,9 @@ export class AUNChannel {
|
|
|
887
1046
|
}
|
|
888
1047
|
}
|
|
889
1048
|
else if (this.ownedCardMsgIds.has(cardMsgId)) {
|
|
890
|
-
// 本 agent 发出的卡片,但 entry
|
|
1049
|
+
// 本 agent 发出的卡片,但 entry 已过期或被清理。
|
|
891
1050
|
logger.debug(`${this.logPrefix()} action_card_reply expired: cardMsgId=${cardMsgId}`);
|
|
892
|
-
this.notifyCardActionFailure(channelId, '⚠️ 卡片已失效,请重新发起',
|
|
1051
|
+
this.notifyCardActionFailure(channelId, '⚠️ 卡片已失效,请重新发起', senderAid, threadId, delivery);
|
|
893
1052
|
}
|
|
894
1053
|
else {
|
|
895
1054
|
// 非本 agent 发出的卡片(broadcast 模式下其他 agent 的卡)→ 静默忽略
|
|
@@ -1295,6 +1454,7 @@ export class AUNChannel {
|
|
|
1295
1454
|
cardMessageIdMap = new Map();
|
|
1296
1455
|
/** 本 agent 曾发出过的卡片 msgId(只增不删,用于区分"过期失效"vs"他人发的卡") */
|
|
1297
1456
|
ownedCardMsgIds = new Set();
|
|
1457
|
+
ownedCardTopics = new Map();
|
|
1298
1458
|
/** requestId → 已投递的卡片 message_id 集合(撤权/超时时撤回) */
|
|
1299
1459
|
interactionCardMessageIds = new Map();
|
|
1300
1460
|
/** 撤权可能先于 outbox 卡片投递完成;迟到卡片注册后立即撤回。 */
|
|
@@ -1304,7 +1464,7 @@ export class AUNChannel {
|
|
|
1304
1464
|
groupSlashRequireMentionsResolver;
|
|
1305
1465
|
static PROACTIVE_ALLOW_TYPES = new Set([
|
|
1306
1466
|
'text', 'quote', 'image', 'video', 'voice', 'file', 'json',
|
|
1307
|
-
'merge', 'link', 'location', 'personal_card',
|
|
1467
|
+
'merge', 'link', 'location', 'personal_card', 'contact.request.response',
|
|
1308
1468
|
]);
|
|
1309
1469
|
/** 观察者插话请求类型(owner → agent.AID)。详见 docs/observer-insert-design.md。 */
|
|
1310
1470
|
static INJECT_REQUEST_TYPE = 'observer.inject';
|
|
@@ -1344,7 +1504,7 @@ export class AUNChannel {
|
|
|
1344
1504
|
aidState;
|
|
1345
1505
|
aidStatsCollector;
|
|
1346
1506
|
outboxInFlight = new Set();
|
|
1347
|
-
/** Shared gateway-facing send
|
|
1507
|
+
/** Shared total gateway-facing send capacity; normal traffic has priority over observer telemetry. */
|
|
1348
1508
|
static OUTBOUND_SEND_METHODS = new Set([
|
|
1349
1509
|
'message.send',
|
|
1350
1510
|
'group.send',
|
|
@@ -1352,8 +1512,14 @@ export class AUNChannel {
|
|
|
1352
1512
|
'group.thought.put',
|
|
1353
1513
|
]);
|
|
1354
1514
|
static OUTBOUND_SEND_CONCURRENCY = 4;
|
|
1515
|
+
static OBSERVER_SEND_CONCURRENCY = 1;
|
|
1516
|
+
static OBSERVER_SEND_QUEUE_MAX = 256;
|
|
1517
|
+
static RPC_WARN_INTERVAL_MS = 60_000;
|
|
1355
1518
|
outboundSendActive = 0;
|
|
1356
1519
|
outboundSendQueue = [];
|
|
1520
|
+
observerSendActive = 0;
|
|
1521
|
+
observerSendQueue = [];
|
|
1522
|
+
rpcWarningStates = new Map();
|
|
1357
1523
|
constructor(config) {
|
|
1358
1524
|
this.config = config;
|
|
1359
1525
|
this.agentDir = agentDirPath(config.aid);
|
|
@@ -1425,6 +1591,27 @@ export class AUNChannel {
|
|
|
1425
1591
|
this.initInProgress = false;
|
|
1426
1592
|
}
|
|
1427
1593
|
}
|
|
1594
|
+
/** Optional local compatibility code is never required by a clean checkout or npm install. */
|
|
1595
|
+
async loadLocalSenderInstallation(client) {
|
|
1596
|
+
this.localSenderInstallation = undefined;
|
|
1597
|
+
const currentFile = fileURLToPath(import.meta.url);
|
|
1598
|
+
const modulePath = path.join(path.dirname(currentFile), '..', 'aun', `sender-installation${path.extname(currentFile)}`);
|
|
1599
|
+
if (!fs.existsSync(modulePath))
|
|
1600
|
+
return;
|
|
1601
|
+
try {
|
|
1602
|
+
// Use a runtime URL so TypeScript can build when the local file is absent.
|
|
1603
|
+
const extension = await import(pathToFileURL(modulePath).href);
|
|
1604
|
+
if (typeof extension.enableAunSenderInstallation !== 'function'
|
|
1605
|
+
|| typeof extension.aunSenderInstallation !== 'function') {
|
|
1606
|
+
throw new Error('Invalid local sender installation module');
|
|
1607
|
+
}
|
|
1608
|
+
extension.enableAunSenderInstallation(client);
|
|
1609
|
+
this.localSenderInstallation = extension;
|
|
1610
|
+
}
|
|
1611
|
+
catch (error) {
|
|
1612
|
+
logger.warn(`${this.logPrefix()} Optional local sender installation disabled: ${error instanceof Error ? error.message : String(error)}`);
|
|
1613
|
+
}
|
|
1614
|
+
}
|
|
1428
1615
|
async _initClientInner() {
|
|
1429
1616
|
this.observerGroupMembers.clear();
|
|
1430
1617
|
for (const groupId of new Set([
|
|
@@ -1454,6 +1641,7 @@ export class AUNChannel {
|
|
|
1454
1641
|
this.store = null;
|
|
1455
1642
|
}
|
|
1456
1643
|
this.connected = false;
|
|
1644
|
+
this.clearQueuedObserverSends('client reinitialization');
|
|
1457
1645
|
this.lastConnectionStateSignature = '';
|
|
1458
1646
|
const aunPath = this.config.keystorePath || resolveRoot();
|
|
1459
1647
|
const aidName = this.config.aid;
|
|
@@ -1473,6 +1661,7 @@ export class AUNChannel {
|
|
|
1473
1661
|
const client = await loadClient(store, aidName);
|
|
1474
1662
|
enableFullGroupPullPagination(client);
|
|
1475
1663
|
enableReliableMessageDelivery(client);
|
|
1664
|
+
await this.loadLocalSenderInstallation(client);
|
|
1476
1665
|
this.client = client;
|
|
1477
1666
|
// A reconnect replaces the SDK client. Late events from the retired
|
|
1478
1667
|
// instance must not mutate the new instance's health or message state.
|
|
@@ -2115,6 +2304,15 @@ export class AUNChannel {
|
|
|
2115
2304
|
return;
|
|
2116
2305
|
const protectedHeaders = verifiedProtectedHeaders(msg, env);
|
|
2117
2306
|
const payload = msg.payload ?? '';
|
|
2307
|
+
if (this.managedMenuClient.accept(fromAid, payload, !!env.encrypted)) {
|
|
2308
|
+
this.acknowledgeImmediately(messageId, seq);
|
|
2309
|
+
return;
|
|
2310
|
+
}
|
|
2311
|
+
if (payloadTypeForObserver(msg) === 'observer.forward') {
|
|
2312
|
+
this.acknowledgeImmediately(messageId, seq);
|
|
2313
|
+
logger.debug(`${this.logPrefix()} P2P dropped observer.forward control envelope: mid=${messageId}`);
|
|
2314
|
+
return;
|
|
2315
|
+
}
|
|
2118
2316
|
const mentions = this.parsePayloadMentionsOrReject(payload, 'p2p.inbound', messageId, seq);
|
|
2119
2317
|
if (!mentions)
|
|
2120
2318
|
return;
|
|
@@ -2191,12 +2389,17 @@ export class AUNChannel {
|
|
|
2191
2389
|
if (p2pPayloadType.startsWith('menu.')
|
|
2192
2390
|
|| p2pPayloadType === AUNChannel.CONTACT_REQUEST_QUERY_TYPE
|
|
2193
2391
|
|| p2pPayloadType === AUNChannel.CONTACT_REQUEST_SUBMIT_TYPE) {
|
|
2392
|
+
const installation = p2pPayloadType.startsWith('menu.')
|
|
2393
|
+
? this.localSenderInstallation?.aunSenderInstallation(msg) : undefined;
|
|
2194
2394
|
this.acknowledgeImmediately(messageId, seq);
|
|
2195
2395
|
this.appendInboundPayloadJsonl(chatId, fromAid, messageId, msgEncrypted, false, logDescriptor, threadId, receivedAt, seq);
|
|
2196
2396
|
this.dispatchMessage({
|
|
2197
2397
|
channelId: chatId, userId: fromAid,
|
|
2198
2398
|
text: JSON.stringify(payload),
|
|
2199
2399
|
chatType: 'private', messageId, seq, receivedAt, receivedAtMono, gatewaySeq: seq,
|
|
2400
|
+
peerDeviceId: installation?.deviceId,
|
|
2401
|
+
peerSlotId: installation?.slotId,
|
|
2402
|
+
threadId,
|
|
2200
2403
|
peerName: displayName || undefined,
|
|
2201
2404
|
peerType: peerIdentity.type,
|
|
2202
2405
|
encrypted: msgEncrypted,
|
|
@@ -2228,6 +2431,7 @@ export class AUNChannel {
|
|
|
2228
2431
|
channelId: chatId, userId: fromAid,
|
|
2229
2432
|
text: JSON.stringify(payload),
|
|
2230
2433
|
chatType: 'private', messageId, seq, receivedAt, gatewaySeq: seq,
|
|
2434
|
+
threadId,
|
|
2231
2435
|
peerName: displayName || undefined,
|
|
2232
2436
|
peerType: peerIdentity.type,
|
|
2233
2437
|
encrypted: msgEncrypted,
|
|
@@ -2260,6 +2464,7 @@ export class AUNChannel {
|
|
|
2260
2464
|
chatType: 'private',
|
|
2261
2465
|
messageId,
|
|
2262
2466
|
receivedAt,
|
|
2467
|
+
createdAt: typeof msg.created_at === 'number' ? msg.created_at : undefined,
|
|
2263
2468
|
gatewaySeq: seq,
|
|
2264
2469
|
causation,
|
|
2265
2470
|
seq,
|
|
@@ -2297,6 +2502,12 @@ export class AUNChannel {
|
|
|
2297
2502
|
const groupId = env.group_id ?? '';
|
|
2298
2503
|
const senderAid = env.from ?? '';
|
|
2299
2504
|
const payload = msg.payload ?? '';
|
|
2505
|
+
const inboundType = (payload && typeof payload === 'object') ? payload.type : undefined;
|
|
2506
|
+
if (inboundType === 'observer.forward') {
|
|
2507
|
+
this.acknowledgeImmediately(messageId, seq);
|
|
2508
|
+
logger.debug(`${this.logPrefix()} Group dropped observer.forward control envelope: mid=${messageId}`);
|
|
2509
|
+
return;
|
|
2510
|
+
}
|
|
2300
2511
|
const payloadMentionEntries = this.parsePayloadMentionsOrReject(payload, 'group.inbound', messageId, seq);
|
|
2301
2512
|
if (!payloadMentionEntries)
|
|
2302
2513
|
return;
|
|
@@ -2307,7 +2518,6 @@ export class AUNChannel {
|
|
|
2307
2518
|
const topicName = (typeof payload === 'object' && payload !== null)
|
|
2308
2519
|
? this.sanitizeTopicName(payload.topicName)
|
|
2309
2520
|
: undefined;
|
|
2310
|
-
const inboundType = (payload && typeof payload === 'object') ? payload.type : undefined;
|
|
2311
2521
|
// 终态错误只作为展示信号,不能进入 Agent,也不能被 observer 再次传播。
|
|
2312
2522
|
if (inboundType === 'error') {
|
|
2313
2523
|
this.acknowledgeImmediately(messageId, seq);
|
|
@@ -2377,27 +2587,10 @@ export class AUNChannel {
|
|
|
2377
2587
|
{
|
|
2378
2588
|
const payloadObj = (payload && typeof payload === 'object') ? payload : null;
|
|
2379
2589
|
const payloadType = typeof payloadObj?.type === 'string' ? payloadObj.type : '';
|
|
2380
|
-
//
|
|
2590
|
+
// Menu control requests are private-only, including token issuance.
|
|
2381
2591
|
if (payloadType.startsWith('menu.')) {
|
|
2382
|
-
const menuEncrypted = !!env.encrypted;
|
|
2383
2592
|
this.acknowledgeImmediately(messageId, seq);
|
|
2384
|
-
this.
|
|
2385
|
-
this.dispatchMessage({
|
|
2386
|
-
channelId: groupId, userId: senderAid,
|
|
2387
|
-
text: JSON.stringify(payload),
|
|
2388
|
-
chatType: 'group', messageId, seq, receivedAt, receivedAtMono, gatewaySeq: seq, groupId,
|
|
2389
|
-
encrypted: menuEncrypted,
|
|
2390
|
-
protectedHeaders,
|
|
2391
|
-
mentions: payloadMentionEntries,
|
|
2392
|
-
mentionAids: payloadMentionAids,
|
|
2393
|
-
isMentioned: hasMentionAll(payloadMentionEntries)
|
|
2394
|
-
|| (!!this._aid && mentionEntryTargets(payloadMentionEntries).includes(this._aid)),
|
|
2395
|
-
replyContext: this.buildGroupReplyContext(threadId, senderAid, menuEncrypted, messageId, payloadObj?.chatmode, topicName, groupId),
|
|
2396
|
-
msgType: logDescriptor.msgType,
|
|
2397
|
-
payloadType: logDescriptor.payloadType,
|
|
2398
|
-
payloadSummary: logDescriptor.payloadSummary,
|
|
2399
|
-
messageLogContent: logDescriptor.content,
|
|
2400
|
-
});
|
|
2593
|
+
logger.debug(`${this.logPrefix()} Group Menu dropped: code=MENU_PRIVATE_ONLY mid=${messageId}`);
|
|
2401
2594
|
return;
|
|
2402
2595
|
}
|
|
2403
2596
|
if (payloadType && !AUNChannel.PROACTIVE_ALLOW_TYPES.has(payloadType)) {
|
|
@@ -2437,17 +2630,15 @@ export class AUNChannel {
|
|
|
2437
2630
|
if (isEchoMsg) {
|
|
2438
2631
|
// 短 echo(≤10 字符)已在前面的快速通道命中并 return,这里只处理长 echo
|
|
2439
2632
|
// >10 字符:追加 trace,存 pending echo,跳过 mention 过滤继续走 Agent 流程
|
|
2440
|
-
const
|
|
2441
|
-
|
|
2442
|
-
return `${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')}:${String(d.getSeconds()).padStart(2, '0')}.${String(d.getMilliseconds()).padStart(3, '0')}`;
|
|
2443
|
-
};
|
|
2633
|
+
const echoCreatedAt = data.created_at;
|
|
2634
|
+
const echoReceiveTs = Date.now();
|
|
2444
2635
|
let echoText = text;
|
|
2445
|
-
echoText += `\n${
|
|
2636
|
+
echoText += `\n${echoTraceClock(echoReceiveTs)} [EvolCore.receive] from=${senderAid} mid=${messageId} chat=group self=${this._aid || 'unknown'} conn_uptime=${this.connectedAt ? Math.round((echoReceiveTs - this.connectedAt) / 1000) + 's' : 'unknown'}${echoInboundLegLabel(echoCreatedAt, receivedAt)}${echoTraceOriginLabel(echoReceiveTs)}`;
|
|
2446
2637
|
const msgChatmodeEcho = (payload && typeof payload === 'object') ? payload.chatmode : undefined;
|
|
2447
2638
|
this.pendingEchoMessages.set(messageId, {
|
|
2448
2639
|
text: echoText,
|
|
2449
2640
|
channelId: groupId,
|
|
2450
|
-
context: this.buildGroupReplyContext(
|
|
2641
|
+
context: this.buildGroupReplyContext(threadId, senderAid, msgEncrypted, messageId, msgChatmodeEcho, topicName, groupId),
|
|
2451
2642
|
receiveTs: Date.now(),
|
|
2452
2643
|
});
|
|
2453
2644
|
// 继续走正常 Agent 流程(下面的代码会 dispatch)
|
|
@@ -2536,6 +2727,7 @@ export class AUNChannel {
|
|
|
2536
2727
|
chatType: 'group',
|
|
2537
2728
|
messageId,
|
|
2538
2729
|
receivedAt,
|
|
2730
|
+
createdAt: typeof msg.created_at === 'number' ? msg.created_at : undefined,
|
|
2539
2731
|
gatewaySeq: seq,
|
|
2540
2732
|
seq,
|
|
2541
2733
|
threadId,
|
|
@@ -2564,6 +2756,7 @@ export class AUNChannel {
|
|
|
2564
2756
|
&& authenticatedDelivery.chatType === 'group'
|
|
2565
2757
|
&& suppliedDelivery.groupId === authenticatedDelivery.groupId;
|
|
2566
2758
|
const delivery = suppliedDelivery && suppliedMatches ? suppliedDelivery : authenticatedDelivery;
|
|
2759
|
+
const threadId = event.threadId ?? event.replyContext?.threadId;
|
|
2567
2760
|
if (suppliedDelivery && delivery !== suppliedDelivery) {
|
|
2568
2761
|
logger.warn(`${this.logPrefix()} Ignoring mismatched inbound reply route: channel=${event.channelId} chatType=${event.chatType}`);
|
|
2569
2762
|
}
|
|
@@ -2585,17 +2778,15 @@ export class AUNChannel {
|
|
|
2585
2778
|
}
|
|
2586
2779
|
// 长 echo(>10 字符):存 pending,继续交给 agent 处理
|
|
2587
2780
|
if (/echo/i.test(firstLine) && firstLine.trim().length > 10 && !hasEvolCoreTracePrivate) {
|
|
2588
|
-
const
|
|
2589
|
-
const d = new Date();
|
|
2590
|
-
return `${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')}:${String(d.getSeconds()).padStart(2, '0')}.${String(d.getMilliseconds()).padStart(3, '0')}`;
|
|
2591
|
-
};
|
|
2781
|
+
const echoReceiveTs = Date.now();
|
|
2592
2782
|
let echoText = event.text;
|
|
2593
|
-
echoText += `\n${
|
|
2783
|
+
echoText += `\n${echoTraceClock(echoReceiveTs)} [EvolCore.receive] from=${event.userId}${event.peerName ? `(${event.peerName})` : ''} mid=${event.messageId} chat=${event.chatType} self=${this._aid || 'unknown'} conn_uptime=${this.connectedAt ? Math.round((echoReceiveTs - this.connectedAt) / 1000) + 's' : 'unknown'}${echoInboundLegLabel(event.createdAt, event.receivedAt ?? echoReceiveTs)}${echoTraceOriginLabel(echoReceiveTs)}`;
|
|
2594
2784
|
this.pendingEchoMessages.set(event.messageId, {
|
|
2595
2785
|
text: echoText,
|
|
2596
2786
|
channelId: event.channelId,
|
|
2597
2787
|
context: {
|
|
2598
2788
|
...(event.replyContext?.metadata ? { metadata: event.replyContext.metadata } : {}),
|
|
2789
|
+
...(threadId ? { threadId } : {}),
|
|
2599
2790
|
delivery,
|
|
2600
2791
|
},
|
|
2601
2792
|
receiveTs: Date.now(),
|
|
@@ -2610,7 +2801,7 @@ export class AUNChannel {
|
|
|
2610
2801
|
const replyContext = {
|
|
2611
2802
|
...(event.replyContext ?? {}),
|
|
2612
2803
|
delivery,
|
|
2613
|
-
...(
|
|
2804
|
+
...(threadId ? { threadId } : {}),
|
|
2614
2805
|
};
|
|
2615
2806
|
this.messageHandler({
|
|
2616
2807
|
channelId: event.channelId || '',
|
|
@@ -2622,6 +2813,8 @@ export class AUNChannel {
|
|
|
2622
2813
|
peerId: event.userId || event.channelId || '',
|
|
2623
2814
|
peerName: event.peerName,
|
|
2624
2815
|
peerType: event.peerType,
|
|
2816
|
+
peerDeviceId: event.peerDeviceId,
|
|
2817
|
+
peerSlotId: event.peerSlotId,
|
|
2625
2818
|
sameDevice: event.sameDevice,
|
|
2626
2819
|
sameNetwork: event.sameNetwork,
|
|
2627
2820
|
sameEgressIp: event.sameEgressIp,
|
|
@@ -2632,7 +2825,7 @@ export class AUNChannel {
|
|
|
2632
2825
|
receivedAtMono: event.receivedAtMono,
|
|
2633
2826
|
gatewaySeq: event.gatewaySeq ?? event.seq,
|
|
2634
2827
|
causation: event.causation,
|
|
2635
|
-
threadId
|
|
2828
|
+
threadId,
|
|
2636
2829
|
mentions: event.mentions,
|
|
2637
2830
|
mentionAids: event.mentionAids,
|
|
2638
2831
|
isMentioned: event.isMentioned, // ← 新增:传递 isMentioned 标记
|
|
@@ -2797,7 +2990,10 @@ export class AUNChannel {
|
|
|
2797
2990
|
forwardInbound(data) {
|
|
2798
2991
|
if (!this.connected || !this.client)
|
|
2799
2992
|
return Promise.resolve();
|
|
2800
|
-
|
|
2993
|
+
const inboundType = payloadTypeForObserver(data);
|
|
2994
|
+
// Observer envelopes are control-plane telemetry. Mirroring one again
|
|
2995
|
+
// would amplify every activity event across observer-connected agents.
|
|
2996
|
+
if (inboundType === 'observer.forward' || inboundType?.startsWith('menu.'))
|
|
2801
2997
|
return Promise.resolve();
|
|
2802
2998
|
const { observable, owners } = this.getObserverConfig();
|
|
2803
2999
|
if (!observable || owners.length === 0)
|
|
@@ -2833,7 +3029,8 @@ export class AUNChannel {
|
|
|
2833
3029
|
forwardOutbound(result) {
|
|
2834
3030
|
if (!this.connected || !this.client)
|
|
2835
3031
|
return Promise.resolve();
|
|
2836
|
-
|
|
3032
|
+
const outboundType = payloadTypeForObserver(result);
|
|
3033
|
+
if (outboundType === 'observer.forward' || outboundType?.startsWith('menu.'))
|
|
2837
3034
|
return Promise.resolve();
|
|
2838
3035
|
const { observable, owners } = this.getObserverConfig();
|
|
2839
3036
|
if (!observable || owners.length === 0)
|
|
@@ -2874,8 +3071,8 @@ export class AUNChannel {
|
|
|
2874
3071
|
};
|
|
2875
3072
|
const encrypt = this.shouldEncrypt('observer.forward');
|
|
2876
3073
|
for (const ownerAid of owners) {
|
|
2877
|
-
this.callAndTrace('message.send', { to: ownerAid, payload: forwardPayload, encrypt })
|
|
2878
|
-
.catch(e => logger.
|
|
3074
|
+
this.callAndTrace('message.send', { to: ownerAid, payload: forwardPayload, encrypt }, { lane: 'observer' })
|
|
3075
|
+
.catch(e => logger.debug(`${this.logPrefix()} observer.forward send to ${ownerAid} failed: ${e}`));
|
|
2879
3076
|
}
|
|
2880
3077
|
}
|
|
2881
3078
|
// ── 观察者插话(Observer Insert,v0.3 待用上下文提示) ──────────────
|
|
@@ -2978,14 +3175,12 @@ export class AUNChannel {
|
|
|
2978
3175
|
}
|
|
2979
3176
|
}
|
|
2980
3177
|
handleEcho(event) {
|
|
2981
|
-
const
|
|
2982
|
-
const d = new Date();
|
|
2983
|
-
return `${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')}:${String(d.getSeconds()).padStart(2, '0')}.${String(d.getMilliseconds()).padStart(3, '0')}`;
|
|
2984
|
-
};
|
|
3178
|
+
const receiveTs = Date.now();
|
|
2985
3179
|
// 在收到的消息文本末尾追加 trace 行(只追加,不修改原文)
|
|
2986
3180
|
let echoText = event.text;
|
|
2987
|
-
echoText += `\n${
|
|
2988
|
-
|
|
3181
|
+
echoText += `\n${echoTraceClock(receiveTs)} [EvolCore.receive] from=${event.userId}${event.peerName ? `(${event.peerName})` : ''} mid=${event.messageId} chat=${event.chatType} self=${this._aid || 'unknown'} conn_uptime=${this.connectedAt ? Math.round((receiveTs - this.connectedAt) / 1000) + 's' : 'unknown'}${echoInboundLegLabel(event.createdAt, receiveTs)}${echoTraceOriginLabel(receiveTs)}`;
|
|
3182
|
+
const replyTs = Date.now();
|
|
3183
|
+
echoText += `\n${echoTraceClock(replyTs)} [EvolCore.reply] echo回声发出 conn_uptime=${this.connectedAt ? Math.round((replyTs - this.connectedAt) / 1000) + 's' : 'unknown'}${echoTraceOriginLabel(replyTs)}`;
|
|
2989
3184
|
if (Buffer.byteLength(echoText, 'utf-8') > 4096) {
|
|
2990
3185
|
echoText += `\n[TRUNCATED]`;
|
|
2991
3186
|
}
|
|
@@ -2995,7 +3190,7 @@ export class AUNChannel {
|
|
|
2995
3190
|
? { chatType: 'group', groupId: event.channelId }
|
|
2996
3191
|
: { chatType: 'private' };
|
|
2997
3192
|
const context = {
|
|
2998
|
-
threadId: event.threadId,
|
|
3193
|
+
threadId: event.threadId ?? event.replyContext?.threadId,
|
|
2999
3194
|
metadata: event.replyContext?.metadata,
|
|
3000
3195
|
delivery: authenticatedDelivery,
|
|
3001
3196
|
};
|
|
@@ -3098,6 +3293,7 @@ export class AUNChannel {
|
|
|
3098
3293
|
else if (state === 'disconnected' || state === 'reconnecting') {
|
|
3099
3294
|
const wasConnected = this.connected;
|
|
3100
3295
|
this.connected = false;
|
|
3296
|
+
this.clearQueuedObserverSends(state);
|
|
3101
3297
|
// The nine-state SDK emits standby -> retry_backoff -> reconnecting for
|
|
3102
3298
|
// one reconnect cycle. Count the connected-to-down edge once instead of
|
|
3103
3299
|
// inflating the metric for every intermediate state.
|
|
@@ -3281,6 +3477,7 @@ export class AUNChannel {
|
|
|
3281
3477
|
this.client = null;
|
|
3282
3478
|
}
|
|
3283
3479
|
this.connected = false;
|
|
3480
|
+
this.clearQueuedObserverSends(`takeover_${reason}`);
|
|
3284
3481
|
const delaySec = Math.round(delayMs / 1000);
|
|
3285
3482
|
logger.info(`${this.logPrefix()} Scheduling TS-layer reconnect (${reason}) in ${delaySec}s`);
|
|
3286
3483
|
this.trace('OUT', 'reconnect.scheduled', { reason, delayMs, generation: gen });
|
|
@@ -3498,6 +3695,9 @@ export class AUNChannel {
|
|
|
3498
3695
|
if (!current || typeof current !== 'object')
|
|
3499
3696
|
continue;
|
|
3500
3697
|
for (const key of Object.keys(current)) {
|
|
3698
|
+
// Topic routing must survive content truncation and fallback.
|
|
3699
|
+
if (current === limited && key === 'thread_id')
|
|
3700
|
+
continue;
|
|
3501
3701
|
const value = current[key];
|
|
3502
3702
|
if (typeof value === 'string') {
|
|
3503
3703
|
leaves.push({ parent: current, key, value, bytes: Buffer.byteLength(value, 'utf8') });
|
|
@@ -3544,6 +3744,8 @@ export class AUNChannel {
|
|
|
3544
3744
|
};
|
|
3545
3745
|
if (typeof payload.chatmode === 'string')
|
|
3546
3746
|
fallback.chatmode = truncateUtf8ToBytes(payload.chatmode, 32);
|
|
3747
|
+
if (typeof payload.thread_id === 'string')
|
|
3748
|
+
fallback.thread_id = payload.thread_id;
|
|
3547
3749
|
logger.info(`${this.logPrefix()} Thought payload reduced to fallback (${Buffer.byteLength(JSON.stringify(fallback), 'utf8')} bytes)`);
|
|
3548
3750
|
return fallback;
|
|
3549
3751
|
}
|
|
@@ -3611,6 +3813,9 @@ export class AUNChannel {
|
|
|
3611
3813
|
replyContext,
|
|
3612
3814
|
});
|
|
3613
3815
|
this.ownedCardMsgIds.add(messageId);
|
|
3816
|
+
if (replyContext?.threadId) {
|
|
3817
|
+
this.ownedCardTopics.set(messageId, { channelId, threadId: replyContext.threadId });
|
|
3818
|
+
}
|
|
3614
3819
|
let messageIds = this.interactionCardMessageIds.get(action.requestId);
|
|
3615
3820
|
if (!messageIds) {
|
|
3616
3821
|
messageIds = new Set();
|
|
@@ -3629,7 +3834,10 @@ export class AUNChannel {
|
|
|
3629
3834
|
this.interactionCardMessageIds.delete(action.requestId);
|
|
3630
3835
|
}, mapTtl);
|
|
3631
3836
|
mapTimer.unref?.();
|
|
3632
|
-
const ownedTimer = setTimeout(() =>
|
|
3837
|
+
const ownedTimer = setTimeout(() => {
|
|
3838
|
+
this.ownedCardMsgIds.delete(messageId);
|
|
3839
|
+
this.ownedCardTopics.delete(messageId);
|
|
3840
|
+
}, 24 * 60 * 60 * 1000);
|
|
3633
3841
|
ownedTimer.unref?.();
|
|
3634
3842
|
const invalidationReason = this.invalidatedInteractions.get(action.requestId);
|
|
3635
3843
|
if (invalidationReason) {
|
|
@@ -3889,10 +4097,11 @@ export class AUNChannel {
|
|
|
3889
4097
|
/**
|
|
3890
4098
|
* Truncate payload if it exceeds AUN protocol limits.
|
|
3891
4099
|
* AUN SDK enforces MAX_WS_PAYLOAD_SIZE = 1_000_000 bytes (1MB) for the entire JSON-RPC message.
|
|
3892
|
-
* We use a conservative
|
|
4100
|
+
* We use a conservative 512KB limit for the payload itself to leave room for
|
|
4101
|
+
* the encrypted AUN envelope and RPC framing.
|
|
3893
4102
|
*/
|
|
3894
4103
|
truncatePayloadIfNeeded(payload, label) {
|
|
3895
|
-
const MAX_PAYLOAD_SIZE =
|
|
4104
|
+
const MAX_PAYLOAD_SIZE = 512 * 1024; // 512KB conservative limit
|
|
3896
4105
|
const MAX_FIELD_SIZE = 256 * 1024; // 256KB per field
|
|
3897
4106
|
// First check total size
|
|
3898
4107
|
const payloadJson = JSON.stringify(payload);
|
|
@@ -3981,6 +4190,8 @@ export class AUNChannel {
|
|
|
3981
4190
|
throw Object.assign(new Error(result.error ?? 'AUN structured send failed'), {
|
|
3982
4191
|
code: result.code ?? 'AUN_SEND_FAILED',
|
|
3983
4192
|
outboxId: result.outboxId,
|
|
4193
|
+
permanent: true,
|
|
4194
|
+
status: 'failed',
|
|
3984
4195
|
});
|
|
3985
4196
|
}
|
|
3986
4197
|
return result;
|
|
@@ -3994,7 +4205,7 @@ export class AUNChannel {
|
|
|
3994
4205
|
const routedContext = this.withDelivery(context, delivery);
|
|
3995
4206
|
// 长 echo: agent 首次回复前先发 echo trace(echo 自身发送时跳过)
|
|
3996
4207
|
if (!this.isEchoSending) {
|
|
3997
|
-
await this.flushPendingEcho(channelId, delivery);
|
|
4208
|
+
await this.flushPendingEcho(channelId, delivery, context?.threadId);
|
|
3998
4209
|
}
|
|
3999
4210
|
let finalText = text;
|
|
4000
4211
|
this.sentCount.set(channelId, (this.sentCount.get(channelId) || 0) + 1);
|
|
@@ -4321,20 +4532,20 @@ export class AUNChannel {
|
|
|
4321
4532
|
return { ok: false, error: e?.message || String(e), code: e?.code };
|
|
4322
4533
|
}
|
|
4323
4534
|
}
|
|
4324
|
-
async flushPendingEcho(channelId, delivery) {
|
|
4535
|
+
async flushPendingEcho(channelId, delivery, threadId) {
|
|
4325
4536
|
// 查找该 channelId 是否有 pending echo(长 echo 等待 agent 首次回复)
|
|
4326
4537
|
for (const [key, echo] of this.pendingEchoMessages) {
|
|
4327
|
-
if (echo.channelId === channelId
|
|
4538
|
+
if (echo.channelId === channelId
|
|
4539
|
+
&& (echo.context?.threadId ?? '') === (threadId ?? '')
|
|
4540
|
+
&& echo.context?.delivery?.chatType === delivery.chatType) {
|
|
4328
4541
|
this.pendingEchoMessages.delete(key);
|
|
4329
4542
|
logger.info(`${this.logPrefix()} [Echo] flushPendingEcho triggered: key=${key} channelId=${channelId} pendingCount=${this.pendingEchoMessages.size}`);
|
|
4330
|
-
const
|
|
4331
|
-
|
|
4332
|
-
return `${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')}:${String(d.getSeconds()).padStart(2, '0')}.${String(d.getMilliseconds()).padStart(3, '0')}`;
|
|
4333
|
-
};
|
|
4334
|
-
const agentDuration = Date.now() - echo.receiveTs;
|
|
4543
|
+
const agentDoneTs = Date.now();
|
|
4544
|
+
const agentDuration = agentDoneTs - echo.receiveTs;
|
|
4335
4545
|
let echoText = echo.text;
|
|
4336
|
-
echoText += `\n${
|
|
4337
|
-
|
|
4546
|
+
echoText += `\n${echoTraceClock(agentDoneTs)} [EvolCore.agent] duration=${agentDuration}ms${echoTraceOriginLabel(agentDoneTs)}`;
|
|
4547
|
+
const replyTs = Date.now();
|
|
4548
|
+
echoText += `\n${echoTraceClock(replyTs)} [EvolCore.reply] echo回声发出 conn_uptime=${this.connectedAt ? Math.round((replyTs - this.connectedAt) / 1000) + 's' : 'unknown'}${echoTraceOriginLabel(replyTs)}`;
|
|
4338
4549
|
if (Buffer.byteLength(echoText, 'utf-8') > 4096) {
|
|
4339
4550
|
echoText += `\n[TRUNCATED]`;
|
|
4340
4551
|
}
|
|
@@ -4777,6 +4988,8 @@ export class AUNChannel {
|
|
|
4777
4988
|
const payloadWithChatmode = context?.metadata?.chatmode && !finalPayload.chatmode
|
|
4778
4989
|
? { ...finalPayload, chatmode: context.metadata.chatmode }
|
|
4779
4990
|
: finalPayload;
|
|
4991
|
+
if (context?.threadId && !payloadWithChatmode.thread_id)
|
|
4992
|
+
payloadWithChatmode.thread_id = context.threadId;
|
|
4780
4993
|
const params = {
|
|
4781
4994
|
context: { type: 'task', id: taskId },
|
|
4782
4995
|
payload: this.limitThoughtPayload(payloadWithChatmode),
|
|
@@ -5525,6 +5738,7 @@ export class AUNChannel {
|
|
|
5525
5738
|
await this.sendStructuredOrThrow(channelId, payloadObj, context);
|
|
5526
5739
|
}
|
|
5527
5740
|
async disconnect() {
|
|
5741
|
+
this.managedMenuClient.close();
|
|
5528
5742
|
this.intentionalDisconnect = true;
|
|
5529
5743
|
if (this.reconnectTimer) {
|
|
5530
5744
|
clearTimeout(this.reconnectTimer);
|
|
@@ -5549,6 +5763,7 @@ export class AUNChannel {
|
|
|
5549
5763
|
this.store = null;
|
|
5550
5764
|
}
|
|
5551
5765
|
this.connected = false;
|
|
5766
|
+
this.clearQueuedObserverSends('intentional disconnect');
|
|
5552
5767
|
appendAidEvent({ ts: Date.now(), iso: new Date().toISOString(), event: 'disconnected', aid: this.config.aid, reason: 'intentional' });
|
|
5553
5768
|
appendAidLifecycle({ ts: Date.now(), iso: new Date().toISOString(), event: 'disconnected', aid: this.config.aid, reason: 'intentional' });
|
|
5554
5769
|
this.setAidStatus('disabled');
|
|
@@ -5626,7 +5841,7 @@ export class AUNChannel {
|
|
|
5626
5841
|
try {
|
|
5627
5842
|
const selfAgentDir = path.join(resolvePaths().agentsDir, this.config.aid);
|
|
5628
5843
|
const identity = await PeerIdentityCache.resolve('aun', aid, selfAgentDir, this.store, false);
|
|
5629
|
-
const type = identity.type === 'human' ? 'human' : '
|
|
5844
|
+
const type = identity.type === 'human' ? 'human' : 'agent';
|
|
5630
5845
|
const name = identity.name || undefined;
|
|
5631
5846
|
const info = { type, name };
|
|
5632
5847
|
this.peerInfoCache.set(aid, info);
|
|
@@ -5777,6 +5992,26 @@ export class AUNChannelPlugin {
|
|
|
5777
5992
|
case 'result.text':
|
|
5778
5993
|
case 'command.result':
|
|
5779
5994
|
case 'command.error': {
|
|
5995
|
+
// Group command feedback is control traffic, not model input for
|
|
5996
|
+
// the other agents in the conversation.
|
|
5997
|
+
if (delivery.chatType === 'group'
|
|
5998
|
+
&& (payload.kind === 'command.result' || payload.kind === 'command.error')) {
|
|
5999
|
+
const isError = payload.kind === 'command.error';
|
|
6000
|
+
const feedback = {
|
|
6001
|
+
type: isError ? 'error' : 'notice',
|
|
6002
|
+
...taskBase(),
|
|
6003
|
+
subtype: payload.kind,
|
|
6004
|
+
text: payload.text,
|
|
6005
|
+
...(isError
|
|
6006
|
+
? { message: payload.text, user_message: payload.text, recoverable: true, terminal: false }
|
|
6007
|
+
: { severity: 'info' }),
|
|
6008
|
+
};
|
|
6009
|
+
for (const key of ['format', 'reason', 'reasonCode', 'nextStep']) {
|
|
6010
|
+
setIfDefined(feedback, key, payload[key]);
|
|
6011
|
+
}
|
|
6012
|
+
await channel.sendReliableStructured(channelId, feedback, { ...(replyCtx ?? {}), suppressPeerMention: true }, payload.text);
|
|
6013
|
+
return;
|
|
6014
|
+
}
|
|
5780
6015
|
const sendCtx = {
|
|
5781
6016
|
...(replyCtx ?? {}),
|
|
5782
6017
|
metadata: {
|