evolcore 0.0.2 → 0.0.4
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 +93 -778
- package/README.md +45 -10
- package/bin/ec-safe-output.js +89 -24
- package/dist/agents/baseagent.js +46 -0
- package/dist/agents/claude-runner.js +198 -48
- package/dist/agents/codex-app-server-client.js +68 -0
- package/dist/agents/codex-runner.js +203 -8
- package/dist/agents/runner-types.js +2 -2
- package/dist/aun/aid/agentmd.js +79 -0
- package/dist/aun/aid/encryption-seed-policy.js +29 -0
- package/dist/aun/aid/index.js +1 -1
- package/dist/aun/aid/store.js +2 -5
- package/dist/aun/outbox.js +8 -0
- package/dist/channels/aun.js +241 -116
- package/dist/channels/daemon.js +18 -4
- package/dist/channels/dingtalk.js +1005 -260
- package/dist/channels/feishu.js +184 -56
- package/dist/channels/qqbot.js +23 -1
- package/dist/channels/wechat.js +303 -155
- package/dist/channels/wecom.js +1132 -164
- package/dist/cli/agent-command.js +2 -1
- package/dist/cli/aun-commands.js +98 -43
- package/dist/cli/bench.js +2 -2
- package/dist/cli/contact.js +71 -0
- package/dist/cli/ctl-command.js +2 -2
- package/dist/cli/daemon-commands.js +77 -214
- package/dist/cli/handoff-command.js +4 -3
- package/dist/cli/help.js +9 -5
- package/dist/cli/index.js +132 -116
- package/dist/cli/init-channel.js +242 -129
- package/dist/cli/init.js +70 -31
- package/dist/cli/model.js +2 -1
- package/dist/cli/net-check.js +2 -2
- package/dist/cli/queue-command.js +30 -6
- package/dist/cli/raw-key-input.js +25 -0
- package/dist/cli/response.js +5 -6
- package/dist/cli/restart-monitor.js +25 -1
- package/dist/cli/stats.js +6 -4
- package/dist/cli/trigger-command.js +146 -25
- package/dist/cli/version.js +6 -1
- package/dist/config/access-policy-domain.js +38 -0
- package/dist/config/access-policy.js +134 -0
- package/dist/config/builtin-role-templates.js +42 -17
- package/dist/config/builtin-roles.js +31 -6
- package/dist/config/config-field-policy.js +17 -5
- package/dist/config/config-manager.js +409 -29
- package/dist/config/config-operation-service.js +35 -38
- package/dist/config/contact-alias.js +68 -0
- package/dist/config/contact-bind-code.js +274 -0
- package/dist/config/contact-book-store.js +622 -0
- package/dist/config/contact-book-v2-startup.js +35 -0
- package/dist/config/contact-book.js +185 -300
- package/dist/config/contact-operation-service.js +116 -0
- package/dist/config/contact-request-service.js +331 -0
- package/dist/config/peer-role-resolver.js +136 -55
- package/dist/config/role-ranks.js +18 -0
- package/dist/config/role-service.js +16 -19
- package/dist/config/role-store.js +16 -5
- package/dist/config/roles.js +10 -1
- package/dist/config/schema-registry.js +49 -24
- package/dist/config-store.js +14 -4
- package/dist/core/auth/agent-delegation.js +105 -11
- package/dist/core/auth/authorization-audit.js +16 -1
- package/dist/core/auth/operation-authorizer.js +10 -0
- package/dist/core/auth/operation-catalog.js +105 -1
- package/dist/core/auth/trigger-authorization.js +15 -0
- package/dist/core/bootstrap-service.js +2 -9
- package/dist/core/channel-loader.js +6 -2
- package/dist/core/command/command-handler.js +113 -21
- package/dist/core/command/connect-menu.js +599 -0
- package/dist/core/command/evol-menu-version-gate.js +38 -0
- package/dist/core/command/group-menu.js +421 -0
- package/dist/core/command/menu-handler.js +382 -100
- package/dist/core/command/menu-protocol.js +8 -2
- package/dist/core/command/menu-token-store.js +102 -0
- package/dist/core/command/role-menu.js +128 -29
- package/dist/core/command/slash-gate.js +1 -1
- package/dist/core/command/slash-handler.js +186 -49
- package/dist/core/daemon-file-cache.js +40 -6
- package/dist/core/event-catalog.js +99 -0
- package/dist/core/evolagent-registry.js +4 -40
- package/dist/core/evolagent.js +20 -9
- package/dist/core/handoff/runtime.js +162 -37
- package/dist/core/handoff/store.js +46 -2
- package/dist/core/handoff/types.js +1 -0
- package/dist/core/inference/text-inference.js +16 -74
- package/dist/core/message/file-markers.js +7 -0
- package/dist/core/message/im-renderer.js +20 -14
- package/dist/core/message/inbound-admission.js +124 -0
- package/dist/core/message/message-bridge.js +670 -57
- package/dist/core/message/message-log.js +1 -0
- package/dist/core/message/message-queue.js +195 -10
- package/dist/core/message/message-utils.js +8 -2
- package/dist/core/message/peer-mode.js +7 -8
- package/dist/core/message/response-engine.js +642 -96
- package/dist/core/message/send-receipt.js +24 -0
- package/dist/core/message/stream-debouncer.js +11 -2
- package/dist/core/permission/approval-gateway.js +17 -10
- package/dist/core/permission/ec-command-parser.js +101 -46
- package/dist/core/permission/tool-policy.js +115 -38
- package/dist/core/protected-paths.js +38 -11
- package/dist/core/session/session-fs-store.js +44 -3
- package/dist/core/session/session-manager.js +86 -8
- package/dist/core/session/session-mapper.js +2 -0
- package/dist/core/session/session-renew.js +125 -69
- package/dist/core/session/session-turn-coordinator.js +5 -1
- package/dist/eck/kit-renderer.js +12 -1
- package/dist/eck/message-renderer.js +79 -1
- package/dist/index.js +285 -94
- package/dist/ipc.js +114 -8
- package/dist/paths.js +3 -0
- package/dist/response-system/config-resolver.js +29 -0
- package/dist/response-system/coordinator.js +8 -32
- package/dist/response-system/engines/v1/proactive-flow.js +1 -1
- package/dist/response-system/index.js +1 -0
- package/dist/response-system/modes/single-session/index.js +7 -4
- package/dist/stats/billing.js +20 -8
- package/dist/trigger/manager.js +3 -0
- package/dist/trigger/parser.js +126 -11
- package/dist/trigger/patch.js +12 -2
- package/dist/trigger/scheduler.js +444 -40
- package/dist/trigger/validation.js +13 -0
- package/dist/utils/aid-bind.js +43 -29
- package/dist/utils/error-dict.json +7 -0
- package/dist/utils/evolcore-version.js +21 -0
- package/dist/utils/instance-registry.js +14 -7
- package/dist/utils/log-writer.js +46 -0
- package/dist/utils/logger.js +2 -2
- package/dist/utils/media-cache.js +4 -1
- package/dist/utils/model-prices.jsonl +6 -3
- package/dist/utils/restart-safety.js +31 -0
- package/dist/utils/stable-semver.js +21 -0
- package/dist/utils/stats.js +33 -9
- package/dist/utils/system-memory.js +62 -0
- package/kits/docs/INDEX.md +2 -1
- package/kits/docs/channels/aun.md +4 -13
- package/kits/docs/evolcore/INDEX.md +5 -3
- package/kits/docs/evolcore/agent.md +9 -1
- package/kits/docs/evolcore/aid.md +5 -2
- package/kits/docs/evolcore/config.md +47 -2
- package/kits/docs/evolcore/contact.md +61 -0
- package/kits/docs/evolcore/fs.md +9 -0
- package/kits/docs/evolcore/group-rules.md +46 -4
- package/kits/docs/evolcore/group.md +15 -6
- package/kits/docs/evolcore/model.md +4 -1
- package/kits/docs/evolcore/msg.md +12 -6
- package/kits/docs/evolcore/response.md +16 -21
- package/kits/docs/evolcore/rpc.md +2 -0
- package/kits/docs/evolcore/stats.md +15 -2
- package/kits/docs/evolcore/storage.md +1 -0
- package/kits/docs/evolcore/trigger.md +42 -10
- package/kits/docs/path-registry.md +36 -17
- package/kits/eck_manifest.json +12 -0
- package/kits/eck_message_manifest.json +12 -1
- package/kits/migrations/migrate-contact-book-v2.mjs +754 -0
- package/kits/rules/01-overview.md +17 -7
- package/kits/rules/02-navigation.md +38 -18
- package/kits/rules/03-identity.md +28 -24
- package/kits/rules/04-relation.md +44 -28
- package/kits/rules/05-venue.md +31 -15
- package/kits/rules/06-channel.md +11 -7
- package/kits/schemas/_meta.json +21 -7
- package/kits/schemas/agent-config.schema.6.json +322 -0
- package/kits/schemas/agent-config.schema.7.json +303 -0
- package/kits/schemas/agent-config.schema.8.json +304 -0
- package/kits/schemas/agent-config.schema.9.json +374 -0
- package/kits/schemas/contact-book.schema.2.json +43 -0
- package/kits/schemas/contact-book.schema.3.json +67 -0
- package/kits/schemas/daemon.schema.1.json +3 -2
- package/kits/schemas/daemon.schema.2.json +101 -0
- package/kits/schemas/daemon.schema.3.json +123 -0
- package/kits/schemas/defaults.schema.2.json +85 -0
- package/kits/schemas/defaults.schema.3.json +73 -0
- package/kits/schemas/relation-config.schema.5.json +47 -0
- package/kits/schemas/relation-config.schema.6.json +46 -0
- package/kits/schemas/relation-config.schema.7.json +59 -0
- package/kits/schemas/role-config.schema.1.json +1 -0
- package/kits/schemas/role-registry.schema.1.json +2 -2
- package/kits/schemas/single-session.schema.2.json +57 -0
- package/kits/templates/message-fragments/handoff-context-to-target.md +9 -0
- package/kits/templates/message-fragments/handoff-request-to-target.md +14 -8
- package/kits/templates/message-fragments/handoff-response-to-origin.md +5 -6
- package/kits/templates/roles/admin.json +47 -0
- package/kits/templates/roles/member.json +5 -0
- package/kits/templates/roles/owner.json +1 -0
- package/kits/templates/roles/visitor.json +5 -0
- package/kits/templates/system-fragments/channel.md +12 -2
- package/kits/templates/system-fragments/commands.md +3 -1
- package/kits/templates/system-fragments/identity.md +3 -1
- package/kits/templates/system-fragments/relation.md +1 -1
- package/kits/templates/system-fragments/session.md +6 -2
- package/package.json +7 -5
- package/MIGRATION-0.5.0.md +0 -378
- package/ROLE_ACCESS_CONTROL.md +0 -174
- package/assets/brand/evolcore/README.md +0 -19
- package/assets/brand/evolcore/evolcore-app-icon.png +0 -0
- package/assets/brand/evolcore/evolcore-app-icon.svg +0 -13
- package/assets/brand/evolcore/evolcore-brand-board.png +0 -0
- package/assets/brand/evolcore/evolcore-brand-board.svg +0 -126
- package/assets/brand/evolcore/evolcore-logo-kit.zip +0 -0
- package/assets/brand/evolcore/evolcore-logo-reverse.png +0 -0
- package/assets/brand/evolcore/evolcore-logo-reverse.svg +0 -14
- package/assets/brand/evolcore/evolcore-logo.png +0 -0
- package/assets/brand/evolcore/evolcore-logo.svg +0 -14
- package/assets/brand/evolcore/evolcore-mark.png +0 -0
- package/assets/brand/evolcore/evolcore-mark.svg +0 -10
package/dist/channels/aun.js
CHANGED
|
@@ -7,10 +7,11 @@ import { logger, localTimestamp } from '../utils/logger.js';
|
|
|
7
7
|
import { LogWriter } from '../utils/log-writer.js';
|
|
8
8
|
import { middleOutputModePolicy, resolveShowActivities, showActivitiesPolicy } from '../core/channel-loader.js';
|
|
9
9
|
import { DEFAULT_FLUSH_DELAY_SECONDS } from '../types.js';
|
|
10
|
-
import { resolvePaths,
|
|
10
|
+
import { resolvePaths, agentMdPath as agentMdPathFn, agentDir as agentDirPath, resolveRoot } from '../paths.js';
|
|
11
11
|
import { saveToUploads, sanitizeFileName, bufferToInboundImage, safeFetch } from '../utils/media-cache.js';
|
|
12
12
|
import { appendAidEvent } from '../utils/instance-registry.js';
|
|
13
13
|
import { appendMessageLog, buildOutboundEntry, buildInboundEntry, classifyAunPayloadForLog } from '../core/message/message-log.js';
|
|
14
|
+
import { createSendFileMarkerPattern } from '../core/message/file-markers.js';
|
|
14
15
|
import { chatDirPath } from '../core/session/session-fs-store.js';
|
|
15
16
|
import { appendHintAdd, appendHintRemove, parseInjectRequest } from '../core/message/pending-hints.js';
|
|
16
17
|
import { appendAidLifecycle } from '../aun/aid/identity.js';
|
|
@@ -31,6 +32,8 @@ import { consumeAunCausation, registerAunCausation } from '../core/causation/aun
|
|
|
31
32
|
import { deriveCausation, normalizeCausation } from '../core/causation/context.js';
|
|
32
33
|
import { recordCausationSpan } from '../core/causation/audit.js';
|
|
33
34
|
import { isExplicitGroupId } from '../aun/group-identity.js';
|
|
35
|
+
import { refreshAgentDisplayName, resolveAgentDisplayName } from '../aun/aid/agentmd.js';
|
|
36
|
+
import { readInstalledEvolcoreVersion } from '../utils/evolcore-version.js';
|
|
34
37
|
export const AUN_HANDOFF_MARKER_FIELD = '_evolcore_handoff_id';
|
|
35
38
|
/**
|
|
36
39
|
* 构造 connect extra_info:自描述本进程身份。
|
|
@@ -45,7 +48,7 @@ function buildConnectExtraInfo(opts) {
|
|
|
45
48
|
const startedAt = getProcessStartTime(process.pid) ?? Date.now();
|
|
46
49
|
return {
|
|
47
50
|
app: 'evolcore',
|
|
48
|
-
version:
|
|
51
|
+
version: readInstalledEvolcoreVersion(),
|
|
49
52
|
pid: process.pid,
|
|
50
53
|
started_at: startedAt,
|
|
51
54
|
started_at_iso: new Date(startedAt).toISOString(),
|
|
@@ -59,23 +62,14 @@ function buildConnectExtraInfo(opts) {
|
|
|
59
62
|
channel_name: opts.channelName ?? '',
|
|
60
63
|
};
|
|
61
64
|
}
|
|
62
|
-
let _cachedVersion = null;
|
|
63
|
-
function getEvolcoreVersion() {
|
|
64
|
-
if (_cachedVersion !== null)
|
|
65
|
-
return _cachedVersion;
|
|
66
|
-
try {
|
|
67
|
-
const pkg = JSON.parse(fs.readFileSync(path.join(getPackageRoot(), 'package.json'), 'utf-8'));
|
|
68
|
-
_cachedVersion = String(pkg.version ?? '');
|
|
69
|
-
}
|
|
70
|
-
catch {
|
|
71
|
-
_cachedVersion = '';
|
|
72
|
-
}
|
|
73
|
-
return _cachedVersion;
|
|
74
|
-
}
|
|
75
65
|
export function buildAunFilePayload(params) {
|
|
76
66
|
const correlationId = params.context?.metadata?.correlationId;
|
|
77
67
|
const payload = {
|
|
78
|
-
|
|
68
|
+
// `result.file` is EvolCore's internal outbound kind. On the AUN wire this
|
|
69
|
+
// must remain the standard `file` payload type so clients route it through
|
|
70
|
+
// their attachment/download pipeline; correlation_id carries the control
|
|
71
|
+
// request association without changing the media type.
|
|
72
|
+
type: 'file',
|
|
79
73
|
text: `📎 ${params.filename} (${formatSize(params.size)})`,
|
|
80
74
|
attachments: [params.attachment],
|
|
81
75
|
};
|
|
@@ -121,6 +115,7 @@ export function aunOptsToInbound(opts, channel, channelType) {
|
|
|
121
115
|
sameNetwork: opts.sameNetwork,
|
|
122
116
|
sameEgressIp: opts.sameEgressIp,
|
|
123
117
|
encrypted: opts.encrypted,
|
|
118
|
+
protectedHeaders: opts.protectedHeaders,
|
|
124
119
|
messageId: opts.messageId,
|
|
125
120
|
causation: opts.causation,
|
|
126
121
|
mentions: opts.mentions,
|
|
@@ -133,6 +128,54 @@ export function aunOptsToInbound(opts, channel, channelType) {
|
|
|
133
128
|
dispatchMode: opts.dispatchMode,
|
|
134
129
|
};
|
|
135
130
|
}
|
|
131
|
+
function verifiedProtectedHeaders(msg, env) {
|
|
132
|
+
const candidates = [
|
|
133
|
+
msg.e2ee?.protected_headers,
|
|
134
|
+
env.protected_headers,
|
|
135
|
+
msg.protected_headers,
|
|
136
|
+
];
|
|
137
|
+
for (const candidate of candidates) {
|
|
138
|
+
if (!candidate || typeof candidate !== 'object' || Array.isArray(candidate))
|
|
139
|
+
continue;
|
|
140
|
+
const headers = {};
|
|
141
|
+
for (const [key, value] of Object.entries(candidate)) {
|
|
142
|
+
const name = key.trim().toLowerCase();
|
|
143
|
+
if (!/^[a-z0-9_-]+$/.test(name) || typeof value !== 'string')
|
|
144
|
+
continue;
|
|
145
|
+
headers[name] = value;
|
|
146
|
+
}
|
|
147
|
+
return Object.keys(headers).length > 0 ? headers : undefined;
|
|
148
|
+
}
|
|
149
|
+
return undefined;
|
|
150
|
+
}
|
|
151
|
+
function redactMenuTokensForTrace(value, seen = new WeakSet()) {
|
|
152
|
+
if (!value || typeof value !== 'object')
|
|
153
|
+
return value;
|
|
154
|
+
if (seen.has(value))
|
|
155
|
+
return '[Circular]';
|
|
156
|
+
seen.add(value);
|
|
157
|
+
if (Array.isArray(value))
|
|
158
|
+
return value.map(item => redactMenuTokensForTrace(item, seen));
|
|
159
|
+
const redacted = {};
|
|
160
|
+
for (const [key, item] of Object.entries(value)) {
|
|
161
|
+
redacted[key] = key.toLowerCase() === 'menu_token'
|
|
162
|
+
? '[REDACTED]'
|
|
163
|
+
: redactMenuTokensForTrace(item, seen);
|
|
164
|
+
}
|
|
165
|
+
return redacted;
|
|
166
|
+
}
|
|
167
|
+
function payloadTypeForObserver(value) {
|
|
168
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
169
|
+
return undefined;
|
|
170
|
+
const record = value;
|
|
171
|
+
const payload = record.payload
|
|
172
|
+
?? (record.message && typeof record.message === 'object' ? record.message.payload : undefined)
|
|
173
|
+
?? (record.envelope && typeof record.envelope === 'object' ? record.envelope.payload : undefined);
|
|
174
|
+
if (!payload || typeof payload !== 'object' || Array.isArray(payload))
|
|
175
|
+
return undefined;
|
|
176
|
+
const type = payload.type;
|
|
177
|
+
return typeof type === 'string' ? type : undefined;
|
|
178
|
+
}
|
|
136
179
|
export class AUNChannel {
|
|
137
180
|
config;
|
|
138
181
|
client = null;
|
|
@@ -157,7 +200,8 @@ export class AUNChannel {
|
|
|
157
200
|
return;
|
|
158
201
|
// 自动从 data 推断顶层字段(self_aid / peer_aid / group_id / task_id / chatmode),
|
|
159
202
|
// 便于 jq 过滤:`jq 'select(.task_id == "task-xxx")'`
|
|
160
|
-
const
|
|
203
|
+
const safeData = redactMenuTokensForTrace(data);
|
|
204
|
+
const d = (safeData && typeof safeData === 'object') ? safeData : {};
|
|
161
205
|
const payload = d.payload ?? {};
|
|
162
206
|
// 入站事件(IN)路由字段在 d.envelope.*(SDK 0.5.*);出站 params(OUT)字段在顶层。两者兼顾。
|
|
163
207
|
const env = (d.envelope && typeof d.envelope === 'object') ? d.envelope : {};
|
|
@@ -178,7 +222,7 @@ export class AUNChannel {
|
|
|
178
222
|
const chatmode = payload.chatmode;
|
|
179
223
|
if (chatmode)
|
|
180
224
|
topContext.chatmode = chatmode;
|
|
181
|
-
const line = JSON.stringify({ ts: localTimestamp(), dir, event, ...topContext, data });
|
|
225
|
+
const line = JSON.stringify({ ts: localTimestamp(), dir, event, ...topContext, data: safeData });
|
|
182
226
|
this.traceWriter.write(line);
|
|
183
227
|
}
|
|
184
228
|
/** 日志前缀(含 self aid 简称,多实例可识别) */
|
|
@@ -195,25 +239,37 @@ export class AUNChannel {
|
|
|
195
239
|
*/
|
|
196
240
|
async callAndTrace(method, params, opts) {
|
|
197
241
|
this.trace('OUT', method, params);
|
|
242
|
+
// RPC 往返计时:区分「网关慢」与「本地队列堵塞」。message.send/group.send 的
|
|
243
|
+
// duration 若普遍逼近 SDK 的 rpc 超时(默认 10s),说明发送管线已饱和或链路抖动,
|
|
244
|
+
// 是 /evolhelp 等命令「回复慢/无回复」的直接可观测信号。
|
|
245
|
+
const rpcStart = Date.now();
|
|
246
|
+
// SLOW_RPC_WARN_MS 需明显低于 SDK 默认 10s 超时,以便在真正超时前提前告警。
|
|
247
|
+
const SLOW_RPC_WARN_MS = 3000;
|
|
198
248
|
try {
|
|
199
249
|
const result = await this.client.call(method, params);
|
|
250
|
+
const durationMs = Date.now() - rpcStart;
|
|
200
251
|
if (!opts?.silentOk) {
|
|
201
252
|
const r = result;
|
|
202
253
|
const snap = r && typeof r === 'object'
|
|
203
|
-
? { message_id: r.message_id, ok: r.ok, thought_id: r.thought_id }
|
|
204
|
-
:
|
|
205
|
-
this.trace('OUT', `${method}.ok`, snap
|
|
254
|
+
? { message_id: r.message_id, ok: r.ok, thought_id: r.thought_id, duration_ms: durationMs }
|
|
255
|
+
: { duration_ms: durationMs };
|
|
256
|
+
this.trace('OUT', `${method}.ok`, snap);
|
|
257
|
+
}
|
|
258
|
+
if (durationMs >= SLOW_RPC_WARN_MS) {
|
|
259
|
+
logger.warn(`${this.logPrefix()} rpc ${method} SLOW: duration=${durationMs}ms (发送管线可能饱和或链路抖动)`);
|
|
206
260
|
}
|
|
207
261
|
return result;
|
|
208
262
|
}
|
|
209
263
|
catch (e) {
|
|
264
|
+
const durationMs = Date.now() - rpcStart;
|
|
210
265
|
this.trace('OUT', `${method}.error`, {
|
|
211
266
|
error: e?.message ?? String(e),
|
|
212
267
|
code: e?.code,
|
|
213
268
|
name: e?.name,
|
|
269
|
+
duration_ms: durationMs,
|
|
214
270
|
});
|
|
215
271
|
if (!opts?.silentError) {
|
|
216
|
-
logger.warn(`${this.logPrefix()} rpc ${method} failed: ${e?.name ?? ''}(${e?.code ?? ''}) ${e?.message ?? e}`);
|
|
272
|
+
logger.warn(`${this.logPrefix()} rpc ${method} failed after ${durationMs}ms: ${e?.name ?? ''}(${e?.code ?? ''}) ${e?.message ?? e}`);
|
|
217
273
|
}
|
|
218
274
|
throw e;
|
|
219
275
|
}
|
|
@@ -273,8 +329,20 @@ export class AUNChannel {
|
|
|
273
329
|
: typeof obj.card_message_id === 'string' ? obj.card_message_id : '';
|
|
274
330
|
const cardInfo = cardMsgId ? this.cardMessageIdMap.get(cardMsgId) : undefined;
|
|
275
331
|
if (cardInfo) {
|
|
276
|
-
|
|
277
|
-
|
|
332
|
+
// compose 按钮点击后客户端收卡聚焦输入框,用户随后发送的自由文本由客户端结构化
|
|
333
|
+
// 回传为 action_card_reply{behavior:"compose", text},没有按钮 value/action_value。
|
|
334
|
+
// 映射为 _custom_input + values.custom_text,复用 claude-runner 已有的自定义输入分支
|
|
335
|
+
// (原为多选表单提交而设),不需要新增 action 类型,也不必拦截消息队列。
|
|
336
|
+
const behaviorRaw = typeof obj.behavior === 'string' ? obj.behavior : undefined;
|
|
337
|
+
if (behaviorRaw && behaviorRaw !== 'reply' && behaviorRaw !== 'compose' && behaviorRaw !== 'dismiss') {
|
|
338
|
+
// 非法 behavior 值:按协议兜底为 reply 处理,但记录下来便于排查不合规客户端
|
|
339
|
+
// (例如本该是 compose 手动输入却被误判为普通按钮点击)。
|
|
340
|
+
logger.warn(`${this.logPrefix()} action_card_reply with unrecognized behavior="${behaviorRaw}" mid=${cardMsgId}, treating as reply`);
|
|
341
|
+
}
|
|
342
|
+
const isComposeReply = behaviorRaw === 'compose';
|
|
343
|
+
const actionValue = isComposeReply ? '_custom_input'
|
|
344
|
+
: typeof obj.value === 'string' ? obj.value
|
|
345
|
+
: typeof obj.action_value === 'string' ? obj.action_value : text;
|
|
278
346
|
const threadId = typeof obj.thread_id === 'string' ? obj.thread_id : undefined;
|
|
279
347
|
// 卡片点击者身份:只信认证信封(senderAid 参数,由调用方从 msg.from / msg.sender_aid 提取)。
|
|
280
348
|
// payload 自报字段(from / sender_aid / user_id)不可信,可被客户端伪造,不读取。
|
|
@@ -349,7 +417,13 @@ export class AUNChannel {
|
|
|
349
417
|
type: 'interaction.response',
|
|
350
418
|
id: cardInfo.requestId,
|
|
351
419
|
action: actionValue,
|
|
352
|
-
values: {
|
|
420
|
+
values: {
|
|
421
|
+
text,
|
|
422
|
+
action_label: obj.label ?? obj.action_label,
|
|
423
|
+
behavior: obj.behavior,
|
|
424
|
+
card_message_id: cardMsgId,
|
|
425
|
+
...(isComposeReply ? { custom_text: text } : {}),
|
|
426
|
+
},
|
|
353
427
|
operatorId: cardClickerAid || undefined,
|
|
354
428
|
};
|
|
355
429
|
const settle = (accepted) => {
|
|
@@ -641,10 +715,6 @@ export class AUNChannel {
|
|
|
641
715
|
'text', 'quote', 'image', 'video', 'voice', 'file', 'json',
|
|
642
716
|
'merge', 'link', 'location', 'personal_card',
|
|
643
717
|
]);
|
|
644
|
-
/** Menu protocol 请求类型:自定义消息快速路径,绕过白名单直接分发到 bridge */
|
|
645
|
-
static MENU_REQUEST_TYPES = new Set([
|
|
646
|
-
'menu.list', 'menu.query', 'menu.options', 'menu.update', 'menu.action',
|
|
647
|
-
]);
|
|
648
718
|
/** 观察者插话请求类型(owner → agent.AID)。详见 docs/observer-insert-design.md。 */
|
|
649
719
|
static INJECT_REQUEST_TYPE = 'observer.inject';
|
|
650
720
|
/** 绑定请求类型(Evol App → 控制 AID):快速路径,绕过白名单透传原始 payload JSON 给 bridge。 */
|
|
@@ -1304,6 +1374,7 @@ EvolCore AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
1304
1374
|
// SDK 0.5.* 移除了顶层 from/to/group_id/encrypted 等别名,统一从 msg.envelope.* 读取。
|
|
1305
1375
|
// message_id / seq / payload / same_* 等仍是顶层独立字段,不在 envelope 内。
|
|
1306
1376
|
const env = (msg.envelope && typeof msg.envelope === 'object') ? msg.envelope : {};
|
|
1377
|
+
const protectedHeaders = verifiedProtectedHeaders(msg, env);
|
|
1307
1378
|
const fromAid = env.from ?? '';
|
|
1308
1379
|
const payload = msg.payload ?? '';
|
|
1309
1380
|
const causationRef = payload && typeof payload === 'object' && typeof payload._evolcore_causation_ref === 'string'
|
|
@@ -1359,7 +1430,9 @@ EvolCore AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
1359
1430
|
const shortAid = this.getShortAid(fromAid);
|
|
1360
1431
|
const displayName = peerIdentity.name || shortAid;
|
|
1361
1432
|
// 详细 dispatch 决策日志:记录消息为何被路由到 agent
|
|
1362
|
-
const p2pPayloadType = (payload && typeof payload === 'object'
|
|
1433
|
+
const p2pPayloadType = (payload && typeof payload === 'object' && typeof payload.type === 'string')
|
|
1434
|
+
? payload.type
|
|
1435
|
+
: '';
|
|
1363
1436
|
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))}`);
|
|
1364
1437
|
// action_card_reply 已在 extractTextPayload 中消费,不分发给 agent
|
|
1365
1438
|
if (p2pPayloadType === 'action_card_reply') {
|
|
@@ -1367,7 +1440,7 @@ EvolCore AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
1367
1440
|
return;
|
|
1368
1441
|
}
|
|
1369
1442
|
// menu.* 协议:自定义消息快速路径,需要原始 payload JSON 传递给 bridge
|
|
1370
|
-
if (
|
|
1443
|
+
if (p2pPayloadType.startsWith('menu.')) {
|
|
1371
1444
|
this.acknowledgeImmediately(messageId, seq);
|
|
1372
1445
|
this.appendInboundPayloadJsonl(chatId, fromAid, messageId, msgEncrypted, false, logDescriptor, threadId);
|
|
1373
1446
|
this.dispatchMessage({
|
|
@@ -1376,6 +1449,9 @@ EvolCore AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
1376
1449
|
chatType: 'private', messageId, seq,
|
|
1377
1450
|
peerName: displayName || undefined,
|
|
1378
1451
|
peerType: peerIdentity.type,
|
|
1452
|
+
encrypted: msgEncrypted,
|
|
1453
|
+
protectedHeaders,
|
|
1454
|
+
replyContext: this.buildGroupReplyContext(threadId, fromAid, msgEncrypted, messageId),
|
|
1379
1455
|
msgType: logDescriptor.msgType,
|
|
1380
1456
|
payloadType: logDescriptor.payloadType,
|
|
1381
1457
|
payloadSummary: logDescriptor.payloadSummary,
|
|
@@ -1465,6 +1541,7 @@ EvolCore AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
1465
1541
|
// SDK 0.5.* 移除了顶层 from/sender_aid/group_id/encrypted 等别名,统一从 msg.envelope.* 读取。
|
|
1466
1542
|
// message_id / seq / payload / same_* / dispatch_mode 等仍是顶层独立字段,不在 envelope 内。
|
|
1467
1543
|
const env = (msg.envelope && typeof msg.envelope === 'object') ? msg.envelope : {};
|
|
1544
|
+
const protectedHeaders = verifiedProtectedHeaders(msg, env);
|
|
1468
1545
|
const groupId = env.group_id ?? '';
|
|
1469
1546
|
const senderAid = env.from ?? '';
|
|
1470
1547
|
const payload = msg.payload ?? '';
|
|
@@ -1542,15 +1619,19 @@ EvolCore AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
1542
1619
|
// 信号类消息(status / event / thought / task.update 等)不进 Agent
|
|
1543
1620
|
{
|
|
1544
1621
|
const payloadObj = (payload && typeof payload === 'object') ? payload : null;
|
|
1545
|
-
const payloadType = payloadObj?.type
|
|
1622
|
+
const payloadType = typeof payloadObj?.type === 'string' ? payloadObj.type : '';
|
|
1546
1623
|
// menu.* 协议:自定义消息快速路径
|
|
1547
|
-
if (
|
|
1624
|
+
if (payloadType.startsWith('menu.')) {
|
|
1625
|
+
const menuEncrypted = !!env.encrypted;
|
|
1548
1626
|
this.acknowledgeImmediately(messageId, seq);
|
|
1549
|
-
this.appendInboundPayloadJsonl(groupId, senderAid, messageId,
|
|
1627
|
+
this.appendInboundPayloadJsonl(groupId, senderAid, messageId, menuEncrypted, true, logDescriptor, threadId);
|
|
1550
1628
|
this.dispatchMessage({
|
|
1551
1629
|
channelId: groupId, userId: senderAid,
|
|
1552
1630
|
text: JSON.stringify(payload),
|
|
1553
1631
|
chatType: 'group', messageId, seq, groupId,
|
|
1632
|
+
encrypted: menuEncrypted,
|
|
1633
|
+
protectedHeaders,
|
|
1634
|
+
replyContext: this.buildGroupReplyContext(threadId, senderAid, menuEncrypted, messageId, payloadObj?.chatmode, topicName),
|
|
1554
1635
|
msgType: logDescriptor.msgType,
|
|
1555
1636
|
payloadType: logDescriptor.payloadType,
|
|
1556
1637
|
payloadSummary: logDescriptor.payloadSummary,
|
|
@@ -1769,6 +1850,7 @@ EvolCore AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
1769
1850
|
sameNetwork: event.sameNetwork,
|
|
1770
1851
|
sameEgressIp: event.sameEgressIp,
|
|
1771
1852
|
encrypted: event.encrypted,
|
|
1853
|
+
protectedHeaders: event.protectedHeaders,
|
|
1772
1854
|
messageId: event.messageId,
|
|
1773
1855
|
causation: event.causation,
|
|
1774
1856
|
threadId: event.threadId,
|
|
@@ -1837,6 +1919,8 @@ EvolCore AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
1837
1919
|
forwardInbound(data) {
|
|
1838
1920
|
if (!this.connected || !this.client)
|
|
1839
1921
|
return;
|
|
1922
|
+
if (payloadTypeForObserver(data)?.startsWith('menu.'))
|
|
1923
|
+
return;
|
|
1840
1924
|
const { observable, owners } = this.getObserverConfig();
|
|
1841
1925
|
if (!observable || owners.length === 0)
|
|
1842
1926
|
return;
|
|
@@ -1858,6 +1942,8 @@ EvolCore AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
1858
1942
|
forwardOutbound(result) {
|
|
1859
1943
|
if (!this.connected || !this.client)
|
|
1860
1944
|
return;
|
|
1945
|
+
if (payloadTypeForObserver(result)?.startsWith('menu.'))
|
|
1946
|
+
return;
|
|
1861
1947
|
const { observable, owners } = this.getObserverConfig();
|
|
1862
1948
|
if (!observable || owners.length === 0)
|
|
1863
1949
|
return;
|
|
@@ -2350,6 +2436,30 @@ EvolCore AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
2350
2436
|
messageIdFromSendResult(result) {
|
|
2351
2437
|
return result?.message?.message_id ?? result?.message_id ?? null;
|
|
2352
2438
|
}
|
|
2439
|
+
/**
|
|
2440
|
+
* An AUN send result is only a gateway transport result. It is not a
|
|
2441
|
+
* receipt that the peer client has pulled, decrypted, stored, or rendered it.
|
|
2442
|
+
* Preserve the compact fields for later correlation with the receiving side.
|
|
2443
|
+
*/
|
|
2444
|
+
sendReceiptFromResult(result) {
|
|
2445
|
+
if (!result || typeof result !== 'object')
|
|
2446
|
+
return {};
|
|
2447
|
+
const response = result;
|
|
2448
|
+
return {
|
|
2449
|
+
status: typeof response.status === 'string' ? response.status : undefined,
|
|
2450
|
+
seq: typeof response.seq === 'number' ? response.seq : undefined,
|
|
2451
|
+
timestamp: typeof response.timestamp === 'number' ? response.timestamp : undefined,
|
|
2452
|
+
deliveryMode: typeof response.delivery_mode === 'string' ? response.delivery_mode : undefined,
|
|
2453
|
+
};
|
|
2454
|
+
}
|
|
2455
|
+
receiptLogFields(result) {
|
|
2456
|
+
const receipt = this.sendReceiptFromResult(result);
|
|
2457
|
+
return `aun_status=${receipt.status ?? 'unknown'} seq=${receipt.seq ?? 'unknown'} delivery_mode=${receipt.deliveryMode ?? 'unknown'} gateway_timestamp=${receipt.timestamp ?? 'unknown'}`;
|
|
2458
|
+
}
|
|
2459
|
+
logAunSendAccepted(method, target, messageId, encrypt, result, text) {
|
|
2460
|
+
const preview = text === undefined ? '' : ` text=${text.slice(0, 60)}`;
|
|
2461
|
+
logger.info(`${this.logPrefix()} ${method} accepted by AUN: target=${target} mid=${messageId} encrypt=${encrypt} ${this.receiptLogFields(result)}${preview}`);
|
|
2462
|
+
}
|
|
2353
2463
|
stripUndefinedDeep(value) {
|
|
2354
2464
|
if (Array.isArray(value)) {
|
|
2355
2465
|
return value
|
|
@@ -2542,7 +2652,7 @@ EvolCore AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
2542
2652
|
return { ok: false };
|
|
2543
2653
|
}
|
|
2544
2654
|
}
|
|
2545
|
-
recordDurableOutbound(channelId, payload, messageId, encrypt, context, isGroup, contentKind, logText, result) {
|
|
2655
|
+
recordDurableOutbound(channelId, payload, messageId, encrypt, context, isGroup, contentKind, logText, result, statsContext) {
|
|
2546
2656
|
const causation = normalizeCausation(context?.metadata?.causation);
|
|
2547
2657
|
if (!isGroup && causation)
|
|
2548
2658
|
registerAunCausation(messageId, this.config.aid, channelId, causation);
|
|
@@ -2564,10 +2674,11 @@ EvolCore AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
2564
2674
|
len: Buffer.byteLength(logText, 'utf-8'),
|
|
2565
2675
|
...(isGroup && { groupId: channelId }),
|
|
2566
2676
|
});
|
|
2567
|
-
this.aidStatsCollector?.recordOutbound(this.config.aid, channelId, this.payloadByteLength(payload, logText), logText, false, encrypt, context?.metadata?.chatmode);
|
|
2677
|
+
this.aidStatsCollector?.recordOutbound(this.config.aid, channelId, this.payloadByteLength(payload, logText), logText, false, encrypt, context?.metadata?.chatmode, 'send', statsContext);
|
|
2568
2678
|
const source = context?.metadata?.source ?? 'daemon';
|
|
2569
2679
|
this.appendOutboundJsonl(channelId, {
|
|
2570
2680
|
...classifyAunPayloadForLog(payload), msgId: messageId, encrypt, context, isGroup, source,
|
|
2681
|
+
transport: this.sendReceiptFromResult(result),
|
|
2571
2682
|
});
|
|
2572
2683
|
this.forwardOutbound(result);
|
|
2573
2684
|
}
|
|
@@ -2752,6 +2863,11 @@ EvolCore AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
2752
2863
|
context,
|
|
2753
2864
|
});
|
|
2754
2865
|
logger.debug(`${this.logPrefix()} Outbox enqueued: id=${entry.id} channel=${channelId} text=${finalText.slice(0, 40)}`);
|
|
2866
|
+
// 积压深度告警:outbox 待发条目累积说明发送速度跟不上,回复将出现明显延迟。
|
|
2867
|
+
const backlog = outbox.pendingCount(this.config.aid);
|
|
2868
|
+
if (backlog >= 5) {
|
|
2869
|
+
logger.warn(`${this.logPrefix()} Outbox backlog=${backlog} channel=${channelId} (发送管线堵塞,回复将延迟)`);
|
|
2870
|
+
}
|
|
2755
2871
|
if (!this.connected || !this.client) {
|
|
2756
2872
|
logger.warn(`${this.logPrefix()} Not connected, message queued in outbox (id=${entry.id}). Triggering reconnect.`);
|
|
2757
2873
|
if (!this.reconnectTimer && !this.client) {
|
|
@@ -2800,7 +2916,7 @@ EvolCore AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
2800
2916
|
if (!messageId) {
|
|
2801
2917
|
return { ok: false, error: `message.send returned no message_id: ${JSON.stringify(result)}`, code: 'MISSING_MESSAGE_ID' };
|
|
2802
2918
|
}
|
|
2803
|
-
|
|
2919
|
+
this.logAunSendAccepted('message.send', this.peerLabel(args.to), messageId, encrypt, result);
|
|
2804
2920
|
if (outboundCausation) {
|
|
2805
2921
|
registerAunCausation(messageId, this.config.aid, args.to, outboundCausation);
|
|
2806
2922
|
recordCausationSpan(outboundCausation, 'message.outbound', {
|
|
@@ -2829,6 +2945,7 @@ EvolCore AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
2829
2945
|
payloadSummary: classified.payloadSummary ?? args.log.payloadSummary,
|
|
2830
2946
|
source: args.log.source ?? 'msg',
|
|
2831
2947
|
handoff_trace: args.log.handoffTrace,
|
|
2948
|
+
transport: this.sendReceiptFromResult(result),
|
|
2832
2949
|
}));
|
|
2833
2950
|
this.aidStatsCollector?.recordOutbound(this.config.aid, args.to, Buffer.byteLength(classified.content || args.log.content, 'utf-8'), classified.content || args.log.content, false, encrypt, chatmode, 'send');
|
|
2834
2951
|
logWritten = true;
|
|
@@ -2836,10 +2953,10 @@ EvolCore AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
2836
2953
|
return {
|
|
2837
2954
|
ok: true,
|
|
2838
2955
|
message_id: messageId,
|
|
2839
|
-
seq: result
|
|
2840
|
-
timestamp: result
|
|
2841
|
-
status: result
|
|
2842
|
-
delivery_mode: result
|
|
2956
|
+
seq: this.sendReceiptFromResult(result).seq,
|
|
2957
|
+
timestamp: this.sendReceiptFromResult(result).timestamp,
|
|
2958
|
+
status: this.sendReceiptFromResult(result).status,
|
|
2959
|
+
delivery_mode: this.sendReceiptFromResult(result).deliveryMode,
|
|
2843
2960
|
encrypt,
|
|
2844
2961
|
chatmode,
|
|
2845
2962
|
log_written: logWritten,
|
|
@@ -2903,7 +3020,7 @@ EvolCore AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
2903
3020
|
if (!messageId) {
|
|
2904
3021
|
return { ok: false, error: `group.send returned no message_id: ${JSON.stringify(result)}`, code: 'MISSING_MESSAGE_ID' };
|
|
2905
3022
|
}
|
|
2906
|
-
|
|
3023
|
+
this.logAunSendAccepted('group.send', args.groupId, messageId, encrypt, result);
|
|
2907
3024
|
this.forwardOutbound(result);
|
|
2908
3025
|
let logWritten = false;
|
|
2909
3026
|
if (args.log?.content !== undefined) {
|
|
@@ -2927,8 +3044,10 @@ EvolCore AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
2927
3044
|
payloadSummary: classified.payloadSummary ?? args.log.payloadSummary,
|
|
2928
3045
|
source: args.log.source ?? 'msg',
|
|
2929
3046
|
handoff_trace: args.log.handoffTrace,
|
|
3047
|
+
transport: this.sendReceiptFromResult(result),
|
|
2930
3048
|
}));
|
|
2931
|
-
this.
|
|
3049
|
+
const statsContext = await this.groupStatsContext(args.groupId);
|
|
3050
|
+
this.aidStatsCollector?.recordOutbound(this.config.aid, args.groupId, Buffer.byteLength(content, 'utf-8'), content, false, encrypt, typeof finalPayload.chatmode === 'string' ? finalPayload.chatmode : undefined, 'send', statsContext);
|
|
2932
3051
|
logWritten = true;
|
|
2933
3052
|
}
|
|
2934
3053
|
return {
|
|
@@ -2937,6 +3056,10 @@ EvolCore AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
2937
3056
|
group_id: result?.group_id ?? args.groupId,
|
|
2938
3057
|
message: result?.message,
|
|
2939
3058
|
event: result?.event,
|
|
3059
|
+
seq: this.sendReceiptFromResult(result).seq,
|
|
3060
|
+
timestamp: this.sendReceiptFromResult(result).timestamp,
|
|
3061
|
+
status: this.sendReceiptFromResult(result).status,
|
|
3062
|
+
delivery_mode: this.sendReceiptFromResult(result).deliveryMode,
|
|
2940
3063
|
encrypt,
|
|
2941
3064
|
log_written: logWritten,
|
|
2942
3065
|
};
|
|
@@ -3040,11 +3163,13 @@ EvolCore AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
3040
3163
|
return false;
|
|
3041
3164
|
}
|
|
3042
3165
|
else {
|
|
3043
|
-
|
|
3166
|
+
this.logAunSendAccepted('group.send', channelId, mid, encrypt, result, finalText);
|
|
3044
3167
|
appendAidEvent({ ts: Date.now(), iso: new Date().toISOString(), event: 'message_out', aid: this.config.aid, to: channelId, msgId: mid, kind: 'text', len: finalText.length, groupId: channelId });
|
|
3045
|
-
this.
|
|
3168
|
+
const statsContext = await this.groupStatsContext(channelId);
|
|
3169
|
+
this.aidStatsCollector?.recordOutbound(this.config.aid, channelId, Buffer.byteLength(finalText, 'utf-8'), finalText, false, encrypt, context?.metadata?.chatmode, 'send', statsContext);
|
|
3046
3170
|
this.appendOutboundJsonl(channelId, {
|
|
3047
3171
|
...classifyAunPayloadForLog(payload), msgId: mid, encrypt, context, isGroup: true, source,
|
|
3172
|
+
transport: this.sendReceiptFromResult(result),
|
|
3048
3173
|
});
|
|
3049
3174
|
// Observer forward: outbound (group) — 原样转发 SDK SendResult(含 envelope + payload)
|
|
3050
3175
|
this.forwardOutbound(result);
|
|
@@ -3058,7 +3183,7 @@ EvolCore AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
3058
3183
|
return false;
|
|
3059
3184
|
}
|
|
3060
3185
|
else {
|
|
3061
|
-
|
|
3186
|
+
this.logAunSendAccepted('message.send', this.peerLabel(targetAid), result.message_id, encrypt, result, finalText);
|
|
3062
3187
|
const causation = normalizeCausation(context?.metadata?.causation);
|
|
3063
3188
|
if (causation) {
|
|
3064
3189
|
registerAunCausation(result.message_id, this.config.aid, targetAid, causation);
|
|
@@ -3071,6 +3196,7 @@ EvolCore AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
3071
3196
|
this.aidStatsCollector?.recordOutbound(this.config.aid, targetAid, Buffer.byteLength(finalText, 'utf-8'), finalText, false, encrypt, context?.metadata?.chatmode, 'send');
|
|
3072
3197
|
this.appendOutboundJsonl(targetAid, {
|
|
3073
3198
|
...classifyAunPayloadForLog(payload), msgId: result.message_id, encrypt, context, isGroup: false, source,
|
|
3199
|
+
transport: this.sendReceiptFromResult(result),
|
|
3074
3200
|
});
|
|
3075
3201
|
// Observer forward: outbound (private) — 原样转发 SDK SendResult(含 envelope + payload)
|
|
3076
3202
|
this.forwardOutbound(result);
|
|
@@ -3094,9 +3220,11 @@ EvolCore AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
3094
3220
|
return false;
|
|
3095
3221
|
}
|
|
3096
3222
|
appendAidEvent({ ts: Date.now(), iso: new Date().toISOString(), event: 'message_out', aid: this.config.aid, to: channelId, msgId: mid, kind: 'text', len: finalText.length, groupId: channelId });
|
|
3097
|
-
this.
|
|
3223
|
+
const statsContext = await this.groupStatsContext(channelId);
|
|
3224
|
+
this.aidStatsCollector?.recordOutbound(this.config.aid, channelId, Buffer.byteLength(finalText, 'utf-8'), finalText, false, false, context?.metadata?.chatmode, 'send', statsContext);
|
|
3098
3225
|
this.appendOutboundJsonl(channelId, {
|
|
3099
3226
|
...classifyAunPayloadForLog(payload), msgId: mid, encrypt: false, context, isGroup: true, source,
|
|
3227
|
+
transport: this.sendReceiptFromResult(result),
|
|
3100
3228
|
});
|
|
3101
3229
|
this.forwardOutbound(result);
|
|
3102
3230
|
}
|
|
@@ -3121,6 +3249,7 @@ EvolCore AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
3121
3249
|
this.aidStatsCollector?.recordOutbound(this.config.aid, targetAid, Buffer.byteLength(finalText, 'utf-8'), finalText, false, false, context?.metadata?.chatmode);
|
|
3122
3250
|
this.appendOutboundJsonl(targetAid, {
|
|
3123
3251
|
...classifyAunPayloadForLog(payload), msgId: mid, encrypt: false, context, isGroup: false, source,
|
|
3252
|
+
transport: this.sendReceiptFromResult(result),
|
|
3124
3253
|
});
|
|
3125
3254
|
this.forwardOutbound(result);
|
|
3126
3255
|
}
|
|
@@ -3161,15 +3290,16 @@ EvolCore AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
3161
3290
|
if (!sent.ok || !sent.messageId)
|
|
3162
3291
|
return sent;
|
|
3163
3292
|
const isGroup = this.isGroupId(channelId);
|
|
3164
|
-
|
|
3165
|
-
this.
|
|
3293
|
+
const statsContext = isGroup ? await this.groupStatsContext(channelId) : undefined;
|
|
3294
|
+
this.logAunSendAccepted(isGroup ? 'group.send' : 'message.send', isGroup ? channelId : this.peerLabel(channelId), sent.messageId, !!sent.encrypt, sent.result, logText);
|
|
3295
|
+
this.recordDurableOutbound(channelId, payload, sent.messageId, !!sent.encrypt, context, isGroup, contentKind, logText, sent.result, statsContext);
|
|
3166
3296
|
this.runPostSend(entry, sent.messageId);
|
|
3167
3297
|
return sent;
|
|
3168
3298
|
}
|
|
3169
3299
|
/** 有效会话正文写入 messages.jsonl(message.send/group.send 成功后调用)。 */
|
|
3170
3300
|
appendOutboundJsonl(channelId, descriptor) {
|
|
3171
3301
|
try {
|
|
3172
|
-
const { content, msgType, payloadType, payloadSummary, msgId, encrypt, context, isGroup, source = 'daemon' } = descriptor;
|
|
3302
|
+
const { content, msgType, payloadType, payloadSummary, msgId, encrypt, transport, context, isGroup, source = 'daemon' } = descriptor;
|
|
3173
3303
|
const sessionsDir = resolvePaths().sessionsDir;
|
|
3174
3304
|
const selfAID = this.config.aid;
|
|
3175
3305
|
const chatDir = chatDirPath(sessionsDir, 'aun', channelId, selfAID);
|
|
@@ -3189,6 +3319,7 @@ EvolCore AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
3189
3319
|
durationMs: null,
|
|
3190
3320
|
encrypt,
|
|
3191
3321
|
chatmode,
|
|
3322
|
+
transport,
|
|
3192
3323
|
msgType,
|
|
3193
3324
|
payloadType,
|
|
3194
3325
|
payloadSummary,
|
|
@@ -3261,7 +3392,8 @@ EvolCore AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
3261
3392
|
this.eventBus?.publish?.({ type: 'message:thought-put', agentName: this.config.aid, channelId, taskId, text: thoughtText });
|
|
3262
3393
|
this.forwardOutbound(putRes);
|
|
3263
3394
|
if (thoughtText) {
|
|
3264
|
-
this.
|
|
3395
|
+
const statsContext = await this.groupStatsContext(channelId);
|
|
3396
|
+
this.aidStatsCollector?.recordOutbound(this.config.aid, channelId, Buffer.byteLength(thoughtText, 'utf-8'), thoughtText, false, encrypt, context?.metadata?.chatmode ?? 'proactive', 'thought', statsContext);
|
|
3265
3397
|
}
|
|
3266
3398
|
}
|
|
3267
3399
|
else {
|
|
@@ -3296,21 +3428,36 @@ EvolCore AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
3296
3428
|
finalPayload.thread_id = context.threadId;
|
|
3297
3429
|
if (context?.replyToMessageId && !finalPayload.ref_message_id)
|
|
3298
3430
|
finalPayload.ref_message_id = context.replyToMessageId;
|
|
3431
|
+
const isMenuPayload = typeof finalPayload.type === 'string' && finalPayload.type.startsWith('menu.');
|
|
3299
3432
|
const params = { payload: finalPayload, encrypt };
|
|
3433
|
+
const rawHeaders = context?.metadata?.protectedHeaders;
|
|
3434
|
+
if (encrypt && rawHeaders && typeof rawHeaders === 'object' && !Array.isArray(rawHeaders)) {
|
|
3435
|
+
const protectedHeaders = {};
|
|
3436
|
+
for (const [key, value] of Object.entries(rawHeaders)) {
|
|
3437
|
+
const name = key.trim().toLowerCase();
|
|
3438
|
+
if (!/^[a-z0-9_-]+$/.test(name) || typeof value !== 'string')
|
|
3439
|
+
continue;
|
|
3440
|
+
protectedHeaders[name] = value;
|
|
3441
|
+
}
|
|
3442
|
+
if (Object.keys(protectedHeaders).length > 0)
|
|
3443
|
+
params.protected_headers = protectedHeaders;
|
|
3444
|
+
}
|
|
3300
3445
|
try {
|
|
3301
3446
|
if (isGroup) {
|
|
3302
3447
|
params.group_id = channelId;
|
|
3303
3448
|
const result = await this.callAndTrace('group.send', params);
|
|
3304
3449
|
const mid = result?.message?.message_id ?? result?.message_id ?? null;
|
|
3305
3450
|
logger.info(`${this.logPrefix()} group.send (${payload.type}) ok: group=${channelId} mid=${mid} encrypt=${encrypt}`);
|
|
3306
|
-
|
|
3451
|
+
if (!isMenuPayload)
|
|
3452
|
+
this.forwardOutbound(result);
|
|
3307
3453
|
return mid;
|
|
3308
3454
|
}
|
|
3309
3455
|
else {
|
|
3310
3456
|
params.to = targetAid;
|
|
3311
3457
|
const result = await this.callAndTrace('message.send', params);
|
|
3312
3458
|
logger.info(`${this.logPrefix()} message.send (${payload.type}) ok: to=${this.peerLabel(targetAid)} mid=${result?.message_id} encrypt=${encrypt}`);
|
|
3313
|
-
|
|
3459
|
+
if (!isMenuPayload)
|
|
3460
|
+
this.forwardOutbound(result);
|
|
3314
3461
|
return result?.message_id ?? null;
|
|
3315
3462
|
}
|
|
3316
3463
|
}
|
|
@@ -3493,7 +3640,8 @@ EvolCore AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
3493
3640
|
if (sentMid) {
|
|
3494
3641
|
const fileText = filePayload.text;
|
|
3495
3642
|
appendAidEvent({ ts: Date.now(), iso: new Date().toISOString(), event: 'message_out', aid: this.config.aid, to: channelId, msgId: sentMid, kind: 'file', len: fileText.length, ...(isGroup && { groupId: channelId }) });
|
|
3496
|
-
|
|
3643
|
+
const statsContext = isGroup ? await this.groupStatsContext(channelId) : undefined;
|
|
3644
|
+
this.aidStatsCollector?.recordOutbound(this.config.aid, channelId, Buffer.byteLength(fileText, 'utf-8'), fileText, false, !!params.encrypt, context?.metadata?.chatmode, 'send', statsContext);
|
|
3497
3645
|
const source = context?.metadata?.source ?? 'daemon';
|
|
3498
3646
|
this.appendOutboundJsonl(channelId, {
|
|
3499
3647
|
...classifyAunPayloadForLog(filePayload), msgId: sentMid, encrypt: !!params.encrypt,
|
|
@@ -3618,9 +3766,13 @@ EvolCore AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
3618
3766
|
const initiator = statusPayload.initiator ?? '';
|
|
3619
3767
|
const refMsgId = statusPayload.ref_message_id ?? '';
|
|
3620
3768
|
const metaStr = statusPayload.metadata ? ` meta=${JSON.stringify(statusPayload.metadata)}` : '';
|
|
3621
|
-
|
|
3769
|
+
const logTaskStatus = `${this.logPrefix()} task.${status} task=${taskId} session=${sessionId} chatmode=${chatmode} target=${targetLabel} initiator=${initiator} ref_msg=${refMsgId}${metaStr}`;
|
|
3770
|
+
if (status === 'error' || status === 'timeout')
|
|
3771
|
+
logger.error(logTaskStatus);
|
|
3772
|
+
else
|
|
3773
|
+
logger.info(logTaskStatus);
|
|
3622
3774
|
}
|
|
3623
|
-
sendCustomPayload(channelId, payload) {
|
|
3775
|
+
async sendCustomPayload(channelId, payload, context) {
|
|
3624
3776
|
if (!this.client || !this.connected)
|
|
3625
3777
|
return;
|
|
3626
3778
|
// SDK 0.3.0 E2EE requires payload to be an object
|
|
@@ -3633,19 +3785,7 @@ EvolCore AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
3633
3785
|
catch {
|
|
3634
3786
|
payloadObj = { text: payload };
|
|
3635
3787
|
}
|
|
3636
|
-
|
|
3637
|
-
const customTargetAid = channelId;
|
|
3638
|
-
const sendParams = {
|
|
3639
|
-
to: customTargetAid, payload: payloadObj,
|
|
3640
|
-
encrypt: true,
|
|
3641
|
-
};
|
|
3642
|
-
this.trace('OUT', 'message.send.custom', sendParams);
|
|
3643
|
-
this.client.call('message.send', sendParams).then((result) => {
|
|
3644
|
-
this.trace('OUT', 'message.send.custom.ok', { message_id: result?.message_id });
|
|
3645
|
-
}).catch(e => {
|
|
3646
|
-
this.trace('OUT', 'message.send.custom.error', { error: String(e) });
|
|
3647
|
-
logger.warn(`${this.logPrefix()} Custom payload failed: ${e}`);
|
|
3648
|
-
});
|
|
3788
|
+
await this.sendStructured(channelId, payloadObj, context);
|
|
3649
3789
|
}
|
|
3650
3790
|
async disconnect() {
|
|
3651
3791
|
this.intentionalDisconnect = true;
|
|
@@ -3721,44 +3861,18 @@ EvolCore AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
3721
3861
|
}
|
|
3722
3862
|
/** 读取本地 agent.md 中的 name(用于身份上下文展示),若本地不存在则尝试远程拉取 */
|
|
3723
3863
|
loadSelfName(aid) {
|
|
3724
|
-
|
|
3725
|
-
|
|
3726
|
-
|
|
3727
|
-
|
|
3728
|
-
|
|
3729
|
-
this.fetchAndCacheSelfName(aidName);
|
|
3730
|
-
return undefined;
|
|
3731
|
-
}
|
|
3732
|
-
const content = fs.readFileSync(mdPath, 'utf-8');
|
|
3733
|
-
const fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
|
|
3734
|
-
if (!fmMatch)
|
|
3735
|
-
return undefined;
|
|
3736
|
-
const nameMatch = fmMatch[1].match(/^name:\s*["']?(.+?)["']?\s*$/m);
|
|
3737
|
-
return nameMatch?.[1]?.trim() || undefined;
|
|
3738
|
-
}
|
|
3739
|
-
catch {
|
|
3740
|
-
return undefined;
|
|
3741
|
-
}
|
|
3864
|
+
const aidName = aid.startsWith('@') ? aid.slice(1) : aid;
|
|
3865
|
+
const name = resolveAgentDisplayName(aidName);
|
|
3866
|
+
if (!name)
|
|
3867
|
+
void this.fetchAndCacheSelfName(aidName);
|
|
3868
|
+
return name;
|
|
3742
3869
|
}
|
|
3743
3870
|
async fetchAndCacheSelfName(aidName) {
|
|
3744
|
-
|
|
3745
|
-
|
|
3746
|
-
|
|
3747
|
-
if (
|
|
3748
|
-
|
|
3749
|
-
if (fmMatch) {
|
|
3750
|
-
const nameMatch = fmMatch[1].match(/^name:\s*["']?(.+?)["']?\s*$/m);
|
|
3751
|
-
const name = nameMatch?.[1]?.trim();
|
|
3752
|
-
if (name) {
|
|
3753
|
-
this._selfName = name;
|
|
3754
|
-
if (this.aidStatsCollector)
|
|
3755
|
-
this.aidStatsCollector.setSelfName(this.config.aid, name);
|
|
3756
|
-
}
|
|
3757
|
-
}
|
|
3758
|
-
}
|
|
3759
|
-
}
|
|
3760
|
-
catch {
|
|
3761
|
-
// ignore — name will remain undefined
|
|
3871
|
+
const name = await refreshAgentDisplayName(aidName);
|
|
3872
|
+
if (name) {
|
|
3873
|
+
this._selfName = name;
|
|
3874
|
+
if (this.aidStatsCollector)
|
|
3875
|
+
this.aidStatsCollector.setSelfName(this.config.aid, name);
|
|
3762
3876
|
}
|
|
3763
3877
|
}
|
|
3764
3878
|
getSelfName() {
|
|
@@ -3809,7 +3923,7 @@ EvolCore AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
3809
3923
|
return result.content ?? '';
|
|
3810
3924
|
}
|
|
3811
3925
|
/**
|
|
3812
|
-
* 取群显示名(group.
|
|
3926
|
+
* 取群显示名(group.get_info → group.name),进程内缓存。
|
|
3813
3927
|
* 走长连接 callAndTrace,失败/未连接返回 undefined —— 绝不抛出阻塞消息处理。
|
|
3814
3928
|
*/
|
|
3815
3929
|
async getGroupName(groupId) {
|
|
@@ -3821,9 +3935,10 @@ EvolCore AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
3821
3935
|
if (!this.client)
|
|
3822
3936
|
return undefined;
|
|
3823
3937
|
try {
|
|
3824
|
-
const result = await this.callAndTrace('group.
|
|
3825
|
-
const
|
|
3826
|
-
|
|
3938
|
+
const result = await this.callAndTrace('group.get_info', { group_id: groupId });
|
|
3939
|
+
const group = result?.group ?? result;
|
|
3940
|
+
const name = typeof group?.name === 'string' ? group.name.trim() : '';
|
|
3941
|
+
if (name) {
|
|
3827
3942
|
this.groupNameCache.set(groupId, name);
|
|
3828
3943
|
return name;
|
|
3829
3944
|
}
|
|
@@ -3834,6 +3949,13 @@ EvolCore AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
3834
3949
|
return undefined; // 不写缓存,下次仍可重试
|
|
3835
3950
|
}
|
|
3836
3951
|
}
|
|
3952
|
+
/** 统计关系键继续使用群 ID,仅为智能体预览补充可读群名。 */
|
|
3953
|
+
async groupStatsContext(groupId) {
|
|
3954
|
+
return {
|
|
3955
|
+
channelType: 'aun',
|
|
3956
|
+
peerLabel: await this.getGroupName(groupId) ?? groupId,
|
|
3957
|
+
};
|
|
3958
|
+
}
|
|
3837
3959
|
/**
|
|
3838
3960
|
* 查询某成员在群里的角色(经 group.get_admins)。
|
|
3839
3961
|
* 仅用于话题创建权限校验(稀有事件),故不缓存:每次查权威源,结果天然最新。
|
|
@@ -3998,6 +4120,7 @@ export class AUNChannelPlugin {
|
|
|
3998
4120
|
return;
|
|
3999
4121
|
case 'interaction': {
|
|
4000
4122
|
const req = payload.interaction;
|
|
4123
|
+
const cardTtlMs = 20 * 60 * 1000;
|
|
4001
4124
|
if (req.kind.kind === 'action') {
|
|
4002
4125
|
const action = req.kind;
|
|
4003
4126
|
const aunCard = {
|
|
@@ -4007,10 +4130,12 @@ export class AUNChannelPlugin {
|
|
|
4007
4130
|
label: btn.label, value: btn.key, style: btn.style ?? 'default', behavior: 'reply',
|
|
4008
4131
|
})),
|
|
4009
4132
|
};
|
|
4010
|
-
// AUN action_card
|
|
4011
|
-
//
|
|
4133
|
+
// AUN action_card 无法内嵌输入框(只有按钮),手动输入用协议原生的 compose
|
|
4134
|
+
// behavior:客户端收到点击后自行收卡+聚焦输入框,用户在 timeout 内的下一条
|
|
4135
|
+
// 消息由客户端结构化回传为 action_card_reply{behavior:"compose"}(见
|
|
4136
|
+
// extractTextPayload 的 compose 分支),不依赖 interceptNextMessage 拦截消息队列。
|
|
4012
4137
|
if (action.allowCustomInput) {
|
|
4013
|
-
aunCard.actions.push({ label: '✏️ 手动输入',
|
|
4138
|
+
aunCard.actions.push({ label: '✏️ 手动输入', style: 'default', behavior: 'compose', compose_timeout_ms: cardTtlMs });
|
|
4014
4139
|
}
|
|
4015
4140
|
if (action.body) {
|
|
4016
4141
|
aunCard.text = action.body;
|
|
@@ -4029,7 +4154,7 @@ export class AUNChannelPlugin {
|
|
|
4029
4154
|
requestId: req.id,
|
|
4030
4155
|
isCommandCard: false,
|
|
4031
4156
|
initiatorAid: req.initiatorId,
|
|
4032
|
-
expiresAt: Date.now() +
|
|
4157
|
+
expiresAt: Date.now() + cardTtlMs,
|
|
4033
4158
|
},
|
|
4034
4159
|
});
|
|
4035
4160
|
}
|
|
@@ -4064,7 +4189,7 @@ export class AUNChannelPlugin {
|
|
|
4064
4189
|
requestId: req.id,
|
|
4065
4190
|
isCommandCard: true,
|
|
4066
4191
|
initiatorAid: req.initiatorId,
|
|
4067
|
-
expiresAt: Date.now() +
|
|
4192
|
+
expiresAt: Date.now() + cardTtlMs,
|
|
4068
4193
|
},
|
|
4069
4194
|
});
|
|
4070
4195
|
}
|
|
@@ -4075,7 +4200,7 @@ export class AUNChannelPlugin {
|
|
|
4075
4200
|
}
|
|
4076
4201
|
case 'custom': {
|
|
4077
4202
|
const text = typeof payload.payload === 'string' ? payload.payload : JSON.stringify(payload.payload);
|
|
4078
|
-
channel.sendCustomPayload(channelId, text);
|
|
4203
|
+
await channel.sendCustomPayload(channelId, text, replyCtx);
|
|
4079
4204
|
return;
|
|
4080
4205
|
}
|
|
4081
4206
|
default:
|
|
@@ -4107,7 +4232,7 @@ export class AUNChannelPlugin {
|
|
|
4107
4232
|
return {
|
|
4108
4233
|
channelType: 'aun', adapter, channel,
|
|
4109
4234
|
policy,
|
|
4110
|
-
options: { flushDelay: inst.flushDelay ?? DEFAULT_FLUSH_DELAY_SECONDS, fileMarkerPattern:
|
|
4235
|
+
options: { flushDelay: inst.flushDelay ?? DEFAULT_FLUSH_DELAY_SECONDS, fileMarkerPattern: createSendFileMarkerPattern() },
|
|
4111
4236
|
connect: () => channel.connect(),
|
|
4112
4237
|
disconnect: () => channel.disconnect(),
|
|
4113
4238
|
onProjectPathRequest: () => Promise.resolve(ctx.defaultProjectPath),
|