evolcore 0.0.8 → 0.0.9
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 +24 -0
- package/README.md +37 -3
- package/dist/agents/baseagent.js +111 -19
- package/dist/agents/claude-runner.js +30 -2
- package/dist/agents/codex-app-server-client.js +31 -0
- package/dist/agents/codex-runner.js +3 -0
- package/dist/aun/msg/group.js +9 -9
- package/dist/aun/msg/p2p.js +11 -6
- package/dist/aun/msg/upload.js +43 -29
- package/dist/aun/outbox.js +48 -5
- package/dist/channels/aun.js +199 -50
- package/dist/cli/aun-commands.js +10 -7
- package/dist/cli/daemon-commands.js +19 -4
- package/dist/cli/index.js +11 -1
- package/dist/cli/init.js +103 -4
- package/dist/cli/model.js +5 -5
- package/dist/config/builtin-role-templates.js +4 -3
- package/dist/config/config-field-policy.js +6 -2
- package/dist/config/config-manager.js +48 -11
- package/dist/config/role-schema.js +18 -3
- package/dist/config/role-service.js +3 -3
- package/dist/config/role-store.js +4 -3
- package/dist/config-store.js +4 -0
- package/dist/core/auth/operation-authorizer.js +3 -1
- package/dist/core/bootstrap-service.js +0 -12
- package/dist/core/channel-loader.js +5 -1
- package/dist/core/command/command-handler.js +1 -1
- package/dist/core/command/evol-menu-version-gate.js +3 -2
- package/dist/core/command/menu-handler.js +33 -22
- package/dist/core/command/menu-protocol.js +7 -4
- package/dist/core/command/slash-handler.js +4 -4
- package/dist/core/inference/text-inference.js +18 -5
- package/dist/core/message/message-bridge.js +17 -6
- package/dist/core/message/response-engine.js +22 -3
- package/dist/core/model/config-scope.js +3 -3
- package/dist/core/permission/tool-policy.js +18 -0
- package/dist/index.js +43 -26
- package/dist/ipc.js +1 -1
- package/dist/product.js +1 -0
- package/dist/utils/autostart.js +27 -0
- package/dist/utils/ecweb-utils.js +15 -2
- package/dist/utils/macos-autostart.js +179 -0
- package/dist/utils/windows-autostart.js +130 -0
- package/ecagent/dist/agent-loop.d.ts +1 -1
- package/ecagent/dist/agent-loop.js +1 -1
- package/ecagent/dist/agent.d.ts +1 -1
- package/ecagent/dist/agent.js +1 -1
- package/ecagent/dist/harness/agent-harness.d.ts +1 -1
- package/ecagent/dist/harness/compaction/branch-summarization.d.ts +1 -1
- package/ecagent/dist/harness/compaction/compaction.d.ts +1 -1
- package/ecagent/dist/harness/compaction/utils.d.ts +1 -1
- package/ecagent/dist/harness/messages.d.ts +1 -1
- package/ecagent/dist/harness/session/session.d.ts +1 -1
- package/ecagent/dist/harness/types.d.ts +1 -1
- package/ecagent/dist/proxy.d.ts +1 -1
- package/ecagent/dist/proxy.js +1 -1
- package/ecagent/dist/types.d.ts +1 -1
- package/kits/migrations/migrate-role-config-v5.mjs +27 -2
- package/kits/schemas/agent-config.schema.10.json +19 -1
- package/kits/schemas/daemon.schema.4.json +4 -0
- package/kits/schemas/defaults.schema.4.json +15 -1
- package/kits/schemas/relation-config.schema.8.json +22 -1
- package/kits/schemas/role-config.schema.1.json +2 -2
- package/package.json +2 -4
- package/ecagent/dist/agent-loop.d.ts.map +0 -1
- package/ecagent/dist/agent-loop.js.map +0 -1
- package/ecagent/dist/agent.d.ts.map +0 -1
- package/ecagent/dist/agent.js.map +0 -1
- package/ecagent/dist/harness/agent-harness.d.ts.map +0 -1
- package/ecagent/dist/harness/agent-harness.js.map +0 -1
- package/ecagent/dist/harness/compaction/branch-summarization.d.ts.map +0 -1
- package/ecagent/dist/harness/compaction/branch-summarization.js.map +0 -1
- package/ecagent/dist/harness/compaction/compaction.d.ts.map +0 -1
- package/ecagent/dist/harness/compaction/compaction.js.map +0 -1
- package/ecagent/dist/harness/compaction/utils.d.ts.map +0 -1
- package/ecagent/dist/harness/compaction/utils.js.map +0 -1
- package/ecagent/dist/harness/env/nodejs.d.ts.map +0 -1
- package/ecagent/dist/harness/env/nodejs.js.map +0 -1
- package/ecagent/dist/harness/messages.d.ts.map +0 -1
- package/ecagent/dist/harness/messages.js.map +0 -1
- package/ecagent/dist/harness/prompt-templates.d.ts.map +0 -1
- package/ecagent/dist/harness/prompt-templates.js.map +0 -1
- package/ecagent/dist/harness/session/jsonl-repo.d.ts.map +0 -1
- package/ecagent/dist/harness/session/jsonl-repo.js.map +0 -1
- package/ecagent/dist/harness/session/jsonl-storage.d.ts.map +0 -1
- package/ecagent/dist/harness/session/jsonl-storage.js.map +0 -1
- package/ecagent/dist/harness/session/memory-repo.d.ts.map +0 -1
- package/ecagent/dist/harness/session/memory-repo.js.map +0 -1
- package/ecagent/dist/harness/session/memory-storage.d.ts.map +0 -1
- package/ecagent/dist/harness/session/memory-storage.js.map +0 -1
- package/ecagent/dist/harness/session/repo-utils.d.ts.map +0 -1
- package/ecagent/dist/harness/session/repo-utils.js.map +0 -1
- package/ecagent/dist/harness/session/session.d.ts.map +0 -1
- package/ecagent/dist/harness/session/session.js.map +0 -1
- package/ecagent/dist/harness/session/uuid.d.ts.map +0 -1
- package/ecagent/dist/harness/session/uuid.js.map +0 -1
- package/ecagent/dist/harness/skills.d.ts.map +0 -1
- package/ecagent/dist/harness/skills.js.map +0 -1
- package/ecagent/dist/harness/system-prompt.d.ts.map +0 -1
- package/ecagent/dist/harness/system-prompt.js.map +0 -1
- package/ecagent/dist/harness/types.d.ts.map +0 -1
- package/ecagent/dist/harness/types.js.map +0 -1
- package/ecagent/dist/harness/utils/shell-output.d.ts.map +0 -1
- package/ecagent/dist/harness/utils/shell-output.js.map +0 -1
- package/ecagent/dist/harness/utils/truncate.d.ts.map +0 -1
- package/ecagent/dist/harness/utils/truncate.js.map +0 -1
- package/ecagent/dist/index.d.ts.map +0 -1
- package/ecagent/dist/index.js.map +0 -1
- package/ecagent/dist/node.d.ts.map +0 -1
- package/ecagent/dist/node.js.map +0 -1
- package/ecagent/dist/proxy.d.ts.map +0 -1
- package/ecagent/dist/proxy.js.map +0 -1
- package/ecagent/dist/runtime/api/lazy.d.ts.map +0 -1
- package/ecagent/dist/runtime/api/lazy.js.map +0 -1
- package/ecagent/dist/runtime/api/pi-messages.d.ts.map +0 -1
- package/ecagent/dist/runtime/api/pi-messages.js.map +0 -1
- package/ecagent/dist/runtime/api/pi-messages.lazy.d.ts.map +0 -1
- package/ecagent/dist/runtime/api/pi-messages.lazy.js.map +0 -1
- package/ecagent/dist/runtime/auth/context.d.ts.map +0 -1
- package/ecagent/dist/runtime/auth/context.js.map +0 -1
- package/ecagent/dist/runtime/auth/credential-store.d.ts.map +0 -1
- package/ecagent/dist/runtime/auth/credential-store.js.map +0 -1
- package/ecagent/dist/runtime/auth/helpers.d.ts.map +0 -1
- package/ecagent/dist/runtime/auth/helpers.js.map +0 -1
- package/ecagent/dist/runtime/auth/resolve.d.ts.map +0 -1
- package/ecagent/dist/runtime/auth/resolve.js.map +0 -1
- package/ecagent/dist/runtime/auth/types.d.ts.map +0 -1
- package/ecagent/dist/runtime/auth/types.js.map +0 -1
- package/ecagent/dist/runtime/compat.d.ts.map +0 -1
- package/ecagent/dist/runtime/compat.js.map +0 -1
- package/ecagent/dist/runtime/index.d.ts.map +0 -1
- package/ecagent/dist/runtime/index.js.map +0 -1
- package/ecagent/dist/runtime/models.d.ts.map +0 -1
- package/ecagent/dist/runtime/models.js.map +0 -1
- package/ecagent/dist/runtime/oauth.d.ts.map +0 -1
- package/ecagent/dist/runtime/oauth.js.map +0 -1
- package/ecagent/dist/runtime/providers/faux.d.ts.map +0 -1
- package/ecagent/dist/runtime/providers/faux.js.map +0 -1
- package/ecagent/dist/runtime/types.d.ts.map +0 -1
- package/ecagent/dist/runtime/types.js.map +0 -1
- package/ecagent/dist/runtime/utils/diagnostics.d.ts.map +0 -1
- package/ecagent/dist/runtime/utils/diagnostics.js.map +0 -1
- package/ecagent/dist/runtime/utils/event-stream.d.ts.map +0 -1
- package/ecagent/dist/runtime/utils/event-stream.js.map +0 -1
- package/ecagent/dist/runtime/utils/headers.d.ts.map +0 -1
- package/ecagent/dist/runtime/utils/headers.js.map +0 -1
- package/ecagent/dist/runtime/utils/json-parse.d.ts.map +0 -1
- package/ecagent/dist/runtime/utils/json-parse.js.map +0 -1
- package/ecagent/dist/runtime/utils/oauth/types.d.ts.map +0 -1
- package/ecagent/dist/runtime/utils/oauth/types.js.map +0 -1
- package/ecagent/dist/runtime/utils/overflow.d.ts.map +0 -1
- package/ecagent/dist/runtime/utils/overflow.js.map +0 -1
- package/ecagent/dist/runtime/utils/provider-env.d.ts.map +0 -1
- package/ecagent/dist/runtime/utils/provider-env.js.map +0 -1
- package/ecagent/dist/runtime/utils/retry.d.ts.map +0 -1
- package/ecagent/dist/runtime/utils/retry.js.map +0 -1
- package/ecagent/dist/runtime/utils/typebox-helpers.d.ts.map +0 -1
- package/ecagent/dist/runtime/utils/typebox-helpers.js.map +0 -1
- package/ecagent/dist/runtime/utils/validation.d.ts.map +0 -1
- package/ecagent/dist/runtime/utils/validation.js.map +0 -1
- package/ecagent/dist/types.d.ts.map +0 -1
- package/ecagent/dist/types.js.map +0 -1
- package/ecagent/package.json +0 -93
- package/ecagent/src/agent-loop.ts +0 -792
- package/ecagent/src/agent.ts +0 -575
- package/ecagent/src/harness/agent-harness.ts +0 -1029
- package/ecagent/src/harness/compaction/branch-summarization.ts +0 -261
- package/ecagent/src/harness/compaction/compaction.ts +0 -753
- package/ecagent/src/harness/compaction/utils.ts +0 -144
- package/ecagent/src/harness/env/nodejs.ts +0 -569
- package/ecagent/src/harness/messages.ts +0 -164
- package/ecagent/src/harness/prompt-templates.ts +0 -267
- package/ecagent/src/harness/session/jsonl-repo.ts +0 -179
- package/ecagent/src/harness/session/jsonl-storage.ts +0 -314
- package/ecagent/src/harness/session/memory-repo.ts +0 -50
- package/ecagent/src/harness/session/memory-storage.ts +0 -133
- package/ecagent/src/harness/session/repo-utils.ts +0 -51
- package/ecagent/src/harness/session/session.ts +0 -338
- package/ecagent/src/harness/session/uuid.ts +0 -54
- package/ecagent/src/harness/skills.ts +0 -375
- package/ecagent/src/harness/system-prompt.ts +0 -34
- package/ecagent/src/harness/types.ts +0 -838
- package/ecagent/src/harness/utils/shell-output.ts +0 -135
- package/ecagent/src/harness/utils/truncate.ts +0 -344
- package/ecagent/src/index.ts +0 -46
- package/ecagent/src/node.ts +0 -2
- package/ecagent/src/proxy.ts +0 -367
- package/ecagent/src/runtime/api/lazy.ts +0 -70
- package/ecagent/src/runtime/api/pi-messages.lazy.ts +0 -4
- package/ecagent/src/runtime/api/pi-messages.ts +0 -436
- package/ecagent/src/runtime/auth/context.ts +0 -45
- package/ecagent/src/runtime/auth/credential-store.ts +0 -47
- package/ecagent/src/runtime/auth/helpers.ts +0 -46
- package/ecagent/src/runtime/auth/resolve.ts +0 -141
- package/ecagent/src/runtime/auth/types.ts +0 -182
- package/ecagent/src/runtime/compat.ts +0 -158
- package/ecagent/src/runtime/index.ts +0 -31
- package/ecagent/src/runtime/models.ts +0 -452
- package/ecagent/src/runtime/oauth.ts +0 -1
- package/ecagent/src/runtime/providers/faux.ts +0 -538
- package/ecagent/src/runtime/types.ts +0 -718
- package/ecagent/src/runtime/utils/diagnostics.ts +0 -45
- package/ecagent/src/runtime/utils/event-stream.ts +0 -88
- package/ecagent/src/runtime/utils/headers.ts +0 -18
- package/ecagent/src/runtime/utils/json-parse.ts +0 -124
- package/ecagent/src/runtime/utils/oauth/types.ts +0 -79
- package/ecagent/src/runtime/utils/overflow.ts +0 -165
- package/ecagent/src/runtime/utils/provider-env.ts +0 -52
- package/ecagent/src/runtime/utils/retry.ts +0 -101
- package/ecagent/src/runtime/utils/typebox-helpers.ts +0 -24
- package/ecagent/src/runtime/utils/validation.ts +0 -310
- package/ecagent/src/types.ts +0 -430
- package/ecagent/tsconfig.build.json +0 -32
package/dist/aun/msg/upload.js
CHANGED
|
@@ -4,37 +4,30 @@ import path from 'path';
|
|
|
4
4
|
import { guessMime, formatSize } from '../../utils/media-cache.js';
|
|
5
5
|
import { inferPayloadType, isValidPayloadType } from './payload-type.js';
|
|
6
6
|
/** 单次上传最大大小(与 daemon sendFile 一致)。 */
|
|
7
|
-
const
|
|
7
|
+
export const MAX_AUN_ATTACHMENT_SIZE = 10 * 1024 * 1024;
|
|
8
8
|
/** server 端 inline 上限错误的识别特征。优先匹配错误消息里的“inline 上限”字样。 */
|
|
9
9
|
function isInlineLimitError(err) {
|
|
10
10
|
const msg = err?.message ?? '';
|
|
11
11
|
return /inline\s*上限|inline limit|create_upload_session/i.test(msg);
|
|
12
12
|
}
|
|
13
13
|
/**
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* 策略:先无脑 storage.put_object(内联 base64)。server 报“超过 inline 上限”
|
|
17
|
-
* 时降级到 storage.create_upload_session + HTTP PUT + storage.complete_upload,
|
|
18
|
-
* 此时通过 onProgress 上报字节级进度。
|
|
14
|
+
* 上传内存中的二进制内容并构造发送用的标准附件 payload。
|
|
19
15
|
*/
|
|
20
|
-
export async function
|
|
21
|
-
const
|
|
22
|
-
if (!
|
|
23
|
-
throw new Error(
|
|
16
|
+
export async function uploadBufferAndBuildPayload(conn, ownerAid, data, filename, opts) {
|
|
17
|
+
const safeFilename = path.basename(filename.trim());
|
|
18
|
+
if (!safeFilename || safeFilename === '.' || safeFilename === '..') {
|
|
19
|
+
throw new Error('文件名不能为空');
|
|
24
20
|
}
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
throw new Error(`文件为空: ${absPath}`);
|
|
21
|
+
if (data.length === 0) {
|
|
22
|
+
throw new Error(`文件为空: ${safeFilename}`);
|
|
28
23
|
}
|
|
29
|
-
if (
|
|
30
|
-
throw new Error(`文件过大 (${formatSize(
|
|
24
|
+
if (data.length > MAX_AUN_ATTACHMENT_SIZE) {
|
|
25
|
+
throw new Error(`文件过大 (${formatSize(data.length)}, 上限 ${formatSize(MAX_AUN_ATTACHMENT_SIZE)}): ${safeFilename}`);
|
|
31
26
|
}
|
|
32
|
-
const
|
|
33
|
-
const
|
|
34
|
-
const
|
|
35
|
-
const
|
|
36
|
-
const objectKey = `shared/${crypto.randomUUID()}/${filename}`;
|
|
37
|
-
const total = stat.size;
|
|
27
|
+
const sha256 = crypto.createHash('sha256').update(data).digest('hex');
|
|
28
|
+
const contentType = opts?.contentType ?? guessMime(safeFilename);
|
|
29
|
+
const objectKey = `shared/${crypto.randomUUID()}/${safeFilename}`;
|
|
30
|
+
const total = data.length;
|
|
38
31
|
const report = (phase, bytes) => opts?.onProgress?.({ phase, bytes, total });
|
|
39
32
|
// 1. 先按 inline 走
|
|
40
33
|
let inlineRejected = false;
|
|
@@ -42,7 +35,7 @@ export async function uploadFileAndBuildPayload(conn, ownerAid, filePath, opts)
|
|
|
42
35
|
report('inline', 0);
|
|
43
36
|
await conn.call('storage.put_object', {
|
|
44
37
|
object_key: objectKey,
|
|
45
|
-
content:
|
|
38
|
+
content: data.toString('base64'),
|
|
46
39
|
content_type: contentType,
|
|
47
40
|
is_private: false,
|
|
48
41
|
overwrite: true,
|
|
@@ -70,10 +63,9 @@ export async function uploadFileAndBuildPayload(conn, ownerAid, filePath, opts)
|
|
|
70
63
|
const PROGRESS_TICK_MS = 250;
|
|
71
64
|
let lastBytes = 0;
|
|
72
65
|
const tickerStart = Date.now();
|
|
73
|
-
//
|
|
66
|
+
// 实际上一次性发送;等待响应期间按 2MB/s 估算进度,且不超过 99%。
|
|
74
67
|
const ticker = setInterval(() => {
|
|
75
68
|
const elapsed = Date.now() - tickerStart;
|
|
76
|
-
// 假设 2MB/s 估算;不超过 99%
|
|
77
69
|
const estimated = Math.min(total - 1, Math.floor((elapsed / 1000) * 2 * 1024 * 1024));
|
|
78
70
|
if (estimated > lastBytes) {
|
|
79
71
|
lastBytes = estimated;
|
|
@@ -84,7 +76,7 @@ export async function uploadFileAndBuildPayload(conn, ownerAid, filePath, opts)
|
|
|
84
76
|
try {
|
|
85
77
|
uploadResp = await fetch(uploadUrl, {
|
|
86
78
|
method: 'PUT',
|
|
87
|
-
body: new Blob([new Uint8Array(
|
|
79
|
+
body: new Blob([new Uint8Array(data)], { type: contentType }),
|
|
88
80
|
headers: { 'Content-Type': contentType },
|
|
89
81
|
});
|
|
90
82
|
}
|
|
@@ -107,12 +99,11 @@ export async function uploadFileAndBuildPayload(conn, ownerAid, filePath, opts)
|
|
|
107
99
|
const attachment = {
|
|
108
100
|
owner_aid: ownerAid,
|
|
109
101
|
object_key: objectKey,
|
|
110
|
-
filename,
|
|
102
|
+
filename: safeFilename,
|
|
111
103
|
size_bytes: total,
|
|
112
104
|
sha256,
|
|
113
105
|
content_type: contentType,
|
|
114
106
|
};
|
|
115
|
-
// 确定渲染类型
|
|
116
107
|
let type;
|
|
117
108
|
if (opts?.as) {
|
|
118
109
|
if (!isValidPayloadType(opts.as)) {
|
|
@@ -121,7 +112,7 @@ export async function uploadFileAndBuildPayload(conn, ownerAid, filePath, opts)
|
|
|
121
112
|
type = opts.as;
|
|
122
113
|
}
|
|
123
114
|
else {
|
|
124
|
-
type = inferPayloadType(
|
|
115
|
+
type = inferPayloadType(safeFilename);
|
|
125
116
|
}
|
|
126
117
|
const payload = {
|
|
127
118
|
type,
|
|
@@ -130,8 +121,31 @@ export async function uploadFileAndBuildPayload(conn, ownerAid, filePath, opts)
|
|
|
130
121
|
if (opts?.text)
|
|
131
122
|
payload.text = opts.text;
|
|
132
123
|
else if (type === 'file')
|
|
133
|
-
payload.text = `📎 ${
|
|
124
|
+
payload.text = `📎 ${safeFilename} (${formatSize(total)})`;
|
|
134
125
|
if (type === 'voice' && opts?.transcript)
|
|
135
126
|
payload.transcript = opts.transcript;
|
|
136
127
|
return { payload, type, attachment };
|
|
137
128
|
}
|
|
129
|
+
/**
|
|
130
|
+
* 上传本地文件并构造发送用的 payload。
|
|
131
|
+
*
|
|
132
|
+
* 策略:先无脑 storage.put_object(内联 base64)。server 报“超过 inline 上限”
|
|
133
|
+
* 时降级到 storage.create_upload_session + HTTP PUT + storage.complete_upload,
|
|
134
|
+
* 此时通过 onProgress 上报字节级进度。
|
|
135
|
+
*/
|
|
136
|
+
export async function uploadFileAndBuildPayload(conn, ownerAid, filePath, opts) {
|
|
137
|
+
const absPath = path.resolve(filePath);
|
|
138
|
+
if (!fs.existsSync(absPath)) {
|
|
139
|
+
throw new Error(`文件不存在: ${absPath}`);
|
|
140
|
+
}
|
|
141
|
+
const stat = fs.statSync(absPath);
|
|
142
|
+
if (stat.size === 0) {
|
|
143
|
+
throw new Error(`文件为空: ${absPath}`);
|
|
144
|
+
}
|
|
145
|
+
if (stat.size > MAX_AUN_ATTACHMENT_SIZE) {
|
|
146
|
+
throw new Error(`文件过大 (${formatSize(stat.size)}, 上限 ${formatSize(MAX_AUN_ATTACHMENT_SIZE)}): ${absPath}`);
|
|
147
|
+
}
|
|
148
|
+
const filename = path.basename(absPath);
|
|
149
|
+
const fileData = fs.readFileSync(absPath);
|
|
150
|
+
return uploadBufferAndBuildPayload(conn, ownerAid, fileData, filename, opts);
|
|
151
|
+
}
|
package/dist/aun/outbox.js
CHANGED
|
@@ -59,6 +59,7 @@ export function enqueue(aid, opts) {
|
|
|
59
59
|
payload: opts.payload,
|
|
60
60
|
text: opts.text,
|
|
61
61
|
filePath: opts.filePath,
|
|
62
|
+
image: opts.image,
|
|
62
63
|
logText: opts.logText,
|
|
63
64
|
context: opts.context,
|
|
64
65
|
ttl: opts.ttl ?? DEFAULT_TTL,
|
|
@@ -81,6 +82,16 @@ export function remove(aid, id) {
|
|
|
81
82
|
const entries = readEntries(aid).filter(e => e.id !== id);
|
|
82
83
|
writeEntries(aid, entries);
|
|
83
84
|
}
|
|
85
|
+
/** Replace an existing entry without consuming another outbox slot. */
|
|
86
|
+
export function replace(aid, entry) {
|
|
87
|
+
const entries = readEntries(aid);
|
|
88
|
+
const index = entries.findIndex(candidate => candidate.id === entry.id);
|
|
89
|
+
if (index < 0)
|
|
90
|
+
return false;
|
|
91
|
+
entries[index] = entry;
|
|
92
|
+
writeEntries(aid, entries);
|
|
93
|
+
return true;
|
|
94
|
+
}
|
|
84
95
|
/** Remove durable interaction cards that were cancelled before delivery. */
|
|
85
96
|
export function removeInteractionCards(aid, requestId) {
|
|
86
97
|
const entries = readEntries(aid);
|
|
@@ -102,7 +113,11 @@ export function cleanup(aid) {
|
|
|
102
113
|
writeEntries(aid, valid);
|
|
103
114
|
return removed;
|
|
104
115
|
}
|
|
105
|
-
|
|
116
|
+
// Each caller gets its own pass over the current disk state. This prevents
|
|
117
|
+
// duplicate sends while allowing a later drain trigger to pick up entries
|
|
118
|
+
// enqueued during an active pass.
|
|
119
|
+
const activeDrainTails = new Map();
|
|
120
|
+
async function drainOnce(aid, sender) {
|
|
106
121
|
const entries = readEntries(aid);
|
|
107
122
|
if (entries.length === 0)
|
|
108
123
|
return { sent: 0, expired: 0, failed: 0 };
|
|
@@ -135,12 +150,40 @@ export async function drain(aid, sender) {
|
|
|
135
150
|
}
|
|
136
151
|
}
|
|
137
152
|
const current = readEntries(aid);
|
|
138
|
-
const
|
|
139
|
-
const
|
|
140
|
-
|
|
141
|
-
|
|
153
|
+
const failedById = new Map(remaining.map(entry => [entry.id, entry]));
|
|
154
|
+
const retained = current.flatMap(entry => {
|
|
155
|
+
if (!drainedIds.has(entry.id))
|
|
156
|
+
return [entry];
|
|
157
|
+
const failedEntry = failedById.get(entry.id);
|
|
158
|
+
if (!failedEntry)
|
|
159
|
+
return [];
|
|
160
|
+
return [{
|
|
161
|
+
...failedEntry,
|
|
162
|
+
...entry,
|
|
163
|
+
attempts: failedEntry.attempts,
|
|
164
|
+
lastError: failedEntry.lastError,
|
|
165
|
+
}];
|
|
166
|
+
});
|
|
167
|
+
writeEntries(aid, retained);
|
|
142
168
|
return { sent, expired, failed };
|
|
143
169
|
}
|
|
170
|
+
export async function drain(aid, sender) {
|
|
171
|
+
const previous = activeDrainTails.get(aid);
|
|
172
|
+
let releaseTurn;
|
|
173
|
+
const turn = new Promise(resolve => { releaseTurn = resolve; });
|
|
174
|
+
const tail = (previous ?? Promise.resolve()).then(() => turn);
|
|
175
|
+
activeDrainTails.set(aid, tail);
|
|
176
|
+
if (previous)
|
|
177
|
+
await previous;
|
|
178
|
+
try {
|
|
179
|
+
return await drainOnce(aid, sender);
|
|
180
|
+
}
|
|
181
|
+
finally {
|
|
182
|
+
releaseTurn();
|
|
183
|
+
if (activeDrainTails.get(aid) === tail)
|
|
184
|
+
activeDrainTails.delete(aid);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
144
187
|
export function hasPending(aid) {
|
|
145
188
|
const file = outboxFile(aid);
|
|
146
189
|
if (!fs.existsSync(file))
|
package/dist/channels/aun.js
CHANGED
|
@@ -16,9 +16,8 @@ import { chatDirPath } from '../core/session/session-fs-store.js';
|
|
|
16
16
|
import { appendHintAdd, appendHintRemove, parseInjectRequest } from '../core/message/pending-hints.js';
|
|
17
17
|
import { appendAidLifecycle } from '../aun/aid/identity.js';
|
|
18
18
|
import { getAidStore, loadClient, SLOT } from '../aun/aid/store.js';
|
|
19
|
-
import { uploadFileAndBuildPayload } from '../aun/msg/upload.js';
|
|
19
|
+
import { MAX_AUN_ATTACHMENT_SIZE, uploadBufferAndBuildPayload, uploadFileAndBuildPayload } from '../aun/msg/upload.js';
|
|
20
20
|
import { loadAgent, saveAgent } from '../config-store.js';
|
|
21
|
-
import { activeBaseagent } from '../core/model/config-scope.js';
|
|
22
21
|
import { resolveEffective } from '../config/config-manager.js';
|
|
23
22
|
import { isManagementRole } from '../config/builtin-roles.js';
|
|
24
23
|
import { mentionModeToDispatch } from '../config/mention-mode.js';
|
|
@@ -36,6 +35,18 @@ import { refreshAgentDisplayName, resolveAgentDisplayName } from '../aun/aid/age
|
|
|
36
35
|
import { readInstalledEvolcoreVersion } from '../utils/evolcore-version.js';
|
|
37
36
|
export const AUN_HANDOFF_MARKER_FIELD = '_evolcore_handoff_id';
|
|
38
37
|
const AUN_INTERACTION_CARD_TTL_MS = 24 * 60 * 60 * 1000;
|
|
38
|
+
const IMAGE_MIME_TYPE = /^image\/[!#$%&'*+.^_`|~0-9A-Za-z-]+$/i;
|
|
39
|
+
function normalizeImageMimeType(mimeType) {
|
|
40
|
+
const normalized = mimeType?.split(';', 1)[0].trim().toLowerCase();
|
|
41
|
+
if (!normalized)
|
|
42
|
+
return undefined;
|
|
43
|
+
if (!normalized.startsWith('image/'))
|
|
44
|
+
return undefined;
|
|
45
|
+
if (!IMAGE_MIME_TYPE.test(normalized)) {
|
|
46
|
+
throw new Error('result.image MIME type must be a valid image media type');
|
|
47
|
+
}
|
|
48
|
+
return normalized;
|
|
49
|
+
}
|
|
39
50
|
/**
|
|
40
51
|
* 构造 connect extra_info:自描述本进程身份。
|
|
41
52
|
*
|
|
@@ -360,10 +371,11 @@ export class AUNChannel {
|
|
|
360
371
|
|| (channelId && !this.isGroupId(channelId) ? channelId : '')
|
|
361
372
|
|| '';
|
|
362
373
|
if (cardInfo.isCommandCard) {
|
|
363
|
-
// CommandCard:action_value 是完整 slash
|
|
364
|
-
|
|
374
|
+
// CommandCard:action_value 是完整 slash 命令,构造伪入站消息。
|
|
375
|
+
// 先完成点击者鉴权,再消费 mapping;否则群成员的一次误点会让发起者
|
|
376
|
+
// 无法继续操作同一张卡片。
|
|
377
|
+
const chatType = channelId ? (this.isGroupId(channelId) ? 'group' : 'private') : 'private';
|
|
365
378
|
if (this.messageHandler && actionValue.startsWith('/')) {
|
|
366
|
-
const chatType = channelId ? (this.isGroupId(channelId) ? 'group' : 'private') : 'private';
|
|
367
379
|
// Initiator 校验:仅群聊需要(私聊信道一对一,点击者恒为对端 = initiator)。
|
|
368
380
|
// 身份只信认证信封提取的 cardClickerAid,非 payload 自报。
|
|
369
381
|
if (chatType === 'group' && !cardClickerAid) {
|
|
@@ -377,6 +389,9 @@ export class AUNChannel {
|
|
|
377
389
|
this.notifyCardActionFailure(channelId, '⚠️ 仅卡片发起者可操作', cardClickerAid, threadId);
|
|
378
390
|
return '';
|
|
379
391
|
}
|
|
392
|
+
// 已通过鉴权并即将进入处理流程;此时才消费 mapping,避免非发起者
|
|
393
|
+
// 或非法命令点击导致卡片提前失效。
|
|
394
|
+
this.cardMessageIdMap.delete(cardMsgId);
|
|
380
395
|
Promise.resolve(this.messageHandler({
|
|
381
396
|
channelId: channelId || '',
|
|
382
397
|
channelType: 'aun',
|
|
@@ -580,6 +595,38 @@ export class AUNChannel {
|
|
|
580
595
|
parts.push('---');
|
|
581
596
|
return parts.join('\n');
|
|
582
597
|
}
|
|
598
|
+
// 结构化 link/location 必须优先保留业务字段。部分客户端同时提供
|
|
599
|
+
// 顶层 text(仅供展示),若先返回该字段会丢失 URL、坐标和地址。
|
|
600
|
+
if (obj.type === 'link') {
|
|
601
|
+
const parts = [];
|
|
602
|
+
if (typeof obj.title === 'string' && obj.title)
|
|
603
|
+
parts.push(`标题: ${obj.title}`);
|
|
604
|
+
if (typeof obj.description === 'string' && obj.description)
|
|
605
|
+
parts.push(`描述: ${obj.description}`);
|
|
606
|
+
if (typeof obj.url === 'string' && obj.url)
|
|
607
|
+
parts.push(`URL: ${obj.url}`);
|
|
608
|
+
if (text && !parts.includes(text))
|
|
609
|
+
parts.push(text);
|
|
610
|
+
return parts.length > 0 ? parts.join('\n') : text || JSON.stringify(payload);
|
|
611
|
+
}
|
|
612
|
+
if (obj.type === 'location') {
|
|
613
|
+
const parts = [];
|
|
614
|
+
if (typeof obj.name === 'string' && obj.name)
|
|
615
|
+
parts.push(`地点: ${obj.name}`);
|
|
616
|
+
if (typeof obj.address === 'string' && obj.address)
|
|
617
|
+
parts.push(`地址: ${obj.address}`);
|
|
618
|
+
if (typeof obj.latitude === 'number' && Number.isFinite(obj.latitude))
|
|
619
|
+
parts.push(`纬度: ${obj.latitude}`);
|
|
620
|
+
if (typeof obj.longitude === 'number' && Number.isFinite(obj.longitude))
|
|
621
|
+
parts.push(`经度: ${obj.longitude}`);
|
|
622
|
+
if (typeof obj.precision_m === 'number' && Number.isFinite(obj.precision_m))
|
|
623
|
+
parts.push(`精度: ${obj.precision_m}m`);
|
|
624
|
+
if (typeof obj.map_url === 'string' && obj.map_url)
|
|
625
|
+
parts.push(`地图: ${obj.map_url}`);
|
|
626
|
+
if (text && !parts.includes(text))
|
|
627
|
+
parts.push(text);
|
|
628
|
+
return parts.length > 0 ? parts.join('\n') : text || JSON.stringify(payload);
|
|
629
|
+
}
|
|
583
630
|
if (typeof obj.text === 'string')
|
|
584
631
|
return text;
|
|
585
632
|
return JSON.stringify(payload);
|
|
@@ -714,8 +761,11 @@ export class AUNChannel {
|
|
|
714
761
|
this.messageSeqMap.delete(messageId);
|
|
715
762
|
}
|
|
716
763
|
shouldEncrypt(_peerId) {
|
|
717
|
-
|
|
718
|
-
|
|
764
|
+
return this.config.defaultEncrypt ?? true;
|
|
765
|
+
}
|
|
766
|
+
/** Effective channel default for callers that must persist a concrete transport choice. */
|
|
767
|
+
getDefaultEncrypt() {
|
|
768
|
+
return this.shouldEncrypt(this.config.aid);
|
|
719
769
|
}
|
|
720
770
|
_aid;
|
|
721
771
|
_selfName; // 本地 agent.md 中的 name,首次 connect 时读取
|
|
@@ -1143,13 +1193,10 @@ EvolCore AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
1143
1193
|
await this.callAndTrace('message.send', {
|
|
1144
1194
|
to: owner,
|
|
1145
1195
|
payload: { type: 'text', text: welcomeText },
|
|
1146
|
-
encrypt:
|
|
1196
|
+
encrypt: this.shouldEncrypt(owner),
|
|
1147
1197
|
persist_required: true,
|
|
1148
1198
|
});
|
|
1149
1199
|
logger.info(`${this.logPrefix()} Welcome message sent to owner: ${owner}`);
|
|
1150
|
-
// Send binding credential for Evol App to persist locally
|
|
1151
|
-
// active_baseagent 经 config-scope 解析。
|
|
1152
|
-
await this.sendBindingCredential(owner, agentDisplayName, activeBaseagent(aidName)).catch(e => logger.warn(`${this.logPrefix()} Binding credential failed: ${e}`));
|
|
1153
1200
|
// Mark agent as initialized in config.json (replaces old agent.md frontmatter flag)
|
|
1154
1201
|
try {
|
|
1155
1202
|
const fresh = loadAgent(aidName);
|
|
@@ -1167,17 +1214,6 @@ EvolCore AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
1167
1214
|
logger.warn(`${this.logPrefix()} Failed to send welcome message: ${e}`);
|
|
1168
1215
|
}
|
|
1169
1216
|
}
|
|
1170
|
-
async sendBindingCredential(owner, name, baseagent) {
|
|
1171
|
-
if (!this.client)
|
|
1172
|
-
return;
|
|
1173
|
-
await this.callAndTrace('message.send', {
|
|
1174
|
-
to: owner,
|
|
1175
|
-
payload: { type: 'binding', aid: this.config.aid, name, owner, baseagent },
|
|
1176
|
-
encrypt: true,
|
|
1177
|
-
persist_required: true,
|
|
1178
|
-
});
|
|
1179
|
-
logger.info(`${this.logPrefix()} Binding credential sent to owner: ${owner}`);
|
|
1180
|
-
}
|
|
1181
1217
|
// ── Event handlers ──────────────────────────────────────────
|
|
1182
1218
|
/**
|
|
1183
1219
|
* 统一处理入站附件:下载 → 图片识别+base64 注入 → 拼接文本。
|
|
@@ -1926,7 +1962,7 @@ EvolCore AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
1926
1962
|
//
|
|
1927
1963
|
// observable=true 时,Agent 收发的每条 AUN 消息(原始信封 + 解密后明文
|
|
1928
1964
|
// payload)镜像一份给 owners[]。入站在所有过滤之前转发;出站在真实发送
|
|
1929
|
-
//
|
|
1965
|
+
// 成功后转发。Observer 使用 daemon 的全局 AUN 默认加密设置,业务合同保持不变。
|
|
1930
1966
|
// observable / owners 不在此处缓存——由 daemon 注入 resolver,从 EvolAgent 的
|
|
1931
1967
|
// in-memory merged config(启动/重启/热重载时统一更新的唯一缓存)读取,避免重复缓存。
|
|
1932
1968
|
observerConfigResolver;
|
|
@@ -1989,7 +2025,7 @@ EvolCore AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
1989
2025
|
this.emitForward('outbound', result, recipientOwners);
|
|
1990
2026
|
}
|
|
1991
2027
|
/**
|
|
1992
|
-
* 实际投递 observer.forward 给每个 owner
|
|
2028
|
+
* 实际投递 observer.forward 给每个 owner,使用 daemon 的全局 AUN 默认加密设置。
|
|
1993
2029
|
* original 为 SDK 给到的原始对象(入站回调 data / 出站 SendResult),整体透传,
|
|
1994
2030
|
* 不挑字段、不改字段——SDK 加任何字段都会自动一并转发给 owner。
|
|
1995
2031
|
*/
|
|
@@ -2000,9 +2036,10 @@ EvolCore AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
2000
2036
|
agent_aid: this.config.aid,
|
|
2001
2037
|
original,
|
|
2002
2038
|
};
|
|
2039
|
+
const encrypt = this.shouldEncrypt('observer.forward');
|
|
2003
2040
|
for (const ownerAid of owners) {
|
|
2004
|
-
this.callAndTrace('message.send', { to: ownerAid, payload: forwardPayload, encrypt
|
|
2005
|
-
.catch(e => logger.
|
|
2041
|
+
this.callAndTrace('message.send', { to: ownerAid, payload: forwardPayload, encrypt })
|
|
2042
|
+
.catch(e => logger.warn(`${this.logPrefix()} observer.forward send to ${ownerAid} failed: ${e}`));
|
|
2006
2043
|
}
|
|
2007
2044
|
}
|
|
2008
2045
|
// ── 观察者插话(Observer Insert,v0.3 待用上下文提示) ──────────────
|
|
@@ -2012,7 +2049,7 @@ EvolCore AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
2012
2049
|
// (不 dispatch、不跑 LLM、不回 owner);下一条对端消息到达时由 message-processor
|
|
2013
2050
|
// 回放消费、注入渲染层。action=add 加提示 / remove 撤销。
|
|
2014
2051
|
// 详见 docs/observer-insert-design.md 第一部分。
|
|
2015
|
-
/** 回 observer.inject.ack 给 owner
|
|
2052
|
+
/** 回 observer.inject.ack 给 owner;加密使用 daemon 的全局 AUN 默认设置。 */
|
|
2016
2053
|
emitInjectAck(ownerAid, injectId, data, error) {
|
|
2017
2054
|
if (!this.connected || !this.client)
|
|
2018
2055
|
return;
|
|
@@ -2023,8 +2060,8 @@ EvolCore AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
2023
2060
|
ackPayload.data = data;
|
|
2024
2061
|
if (error)
|
|
2025
2062
|
ackPayload.error = error;
|
|
2026
|
-
this.callAndTrace('message.send', { to: ownerAid, payload: ackPayload, encrypt:
|
|
2027
|
-
.catch(e => logger.
|
|
2063
|
+
this.callAndTrace('message.send', { to: ownerAid, payload: ackPayload, encrypt: this.shouldEncrypt(ownerAid) })
|
|
2064
|
+
.catch(e => logger.warn(`${this.logPrefix()} observer.inject.ack send to ${ownerAid} failed: ${e}`));
|
|
2028
2065
|
}
|
|
2029
2066
|
/** 处理 observer.inject:鉴权 + 校验 + 只落盘到 pending-hints(不触发处理、不回 owner)。 */
|
|
2030
2067
|
handleObserverInject(fromAid, payload, displayName, peerType) {
|
|
@@ -2524,9 +2561,6 @@ EvolCore AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
2524
2561
|
}
|
|
2525
2562
|
}
|
|
2526
2563
|
payloadByteLength(payload, logText) {
|
|
2527
|
-
if (payload.type === 'image' && typeof payload.data_base64 === 'string') {
|
|
2528
|
-
return Buffer.byteLength(payload.data_base64, 'utf-8');
|
|
2529
|
-
}
|
|
2530
2564
|
return Buffer.byteLength(logText, 'utf-8');
|
|
2531
2565
|
}
|
|
2532
2566
|
applyReplyContextToPayload(payload, context) {
|
|
@@ -2546,11 +2580,16 @@ EvolCore AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
2546
2580
|
const timer = setTimeout(() => this.invalidatedInteractions.delete(interactionId), 24 * 60 * 60 * 1000);
|
|
2547
2581
|
timer.unref?.();
|
|
2548
2582
|
}
|
|
2549
|
-
async recallInteractionCard(messageId) {
|
|
2583
|
+
async recallInteractionCard(messageId, channelId) {
|
|
2550
2584
|
if (!this.client || !this.connected)
|
|
2551
2585
|
return false;
|
|
2552
2586
|
try {
|
|
2553
|
-
|
|
2587
|
+
const isGroup = channelId ? this.isGroupId(channelId) : false;
|
|
2588
|
+
const method = isGroup ? 'group.recall' : 'message.recall';
|
|
2589
|
+
const params = isGroup
|
|
2590
|
+
? { group_id: channelId, message_ids: [messageId] }
|
|
2591
|
+
: { message_ids: [messageId] };
|
|
2592
|
+
await this.callAndTrace(method, params, { silentError: true });
|
|
2554
2593
|
return true;
|
|
2555
2594
|
}
|
|
2556
2595
|
catch (error) {
|
|
@@ -2571,8 +2610,9 @@ EvolCore AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
2571
2610
|
}
|
|
2572
2611
|
const messageIds = [...(this.interactionCardMessageIds.get(interactionId) ?? [])];
|
|
2573
2612
|
for (const messageId of messageIds) {
|
|
2613
|
+
const channelId = this.cardMessageIdMap.get(messageId)?.channelId;
|
|
2574
2614
|
this.forgetInteractionCard(messageId, interactionId);
|
|
2575
|
-
await this.recallInteractionCard(messageId);
|
|
2615
|
+
await this.recallInteractionCard(messageId, channelId);
|
|
2576
2616
|
}
|
|
2577
2617
|
}
|
|
2578
2618
|
forgetInteractionCard(messageId, interactionId) {
|
|
@@ -2582,13 +2622,14 @@ EvolCore AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
2582
2622
|
if (ids && ids.size === 0)
|
|
2583
2623
|
this.interactionCardMessageIds.delete(interactionId);
|
|
2584
2624
|
}
|
|
2585
|
-
registerCardPostSend(messageId, action) {
|
|
2625
|
+
registerCardPostSend(messageId, action, channelId) {
|
|
2586
2626
|
if (action.type !== 'register_interaction_card')
|
|
2587
2627
|
return;
|
|
2588
2628
|
this.cardMessageIdMap.set(messageId, {
|
|
2589
2629
|
requestId: action.requestId,
|
|
2590
2630
|
isCommandCard: action.isCommandCard,
|
|
2591
2631
|
initiatorAid: action.initiatorAid,
|
|
2632
|
+
channelId,
|
|
2592
2633
|
});
|
|
2593
2634
|
this.ownedCardMsgIds.add(messageId);
|
|
2594
2635
|
let messageIds = this.interactionCardMessageIds.get(action.requestId);
|
|
@@ -2614,14 +2655,14 @@ EvolCore AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
2614
2655
|
const invalidationReason = this.invalidatedInteractions.get(action.requestId);
|
|
2615
2656
|
if (invalidationReason) {
|
|
2616
2657
|
this.cardMessageIdMap.delete(messageId);
|
|
2617
|
-
void this.recallInteractionCard(messageId);
|
|
2658
|
+
void this.recallInteractionCard(messageId, channelId);
|
|
2618
2659
|
}
|
|
2619
2660
|
}
|
|
2620
2661
|
runPostSend(entry, messageId) {
|
|
2621
2662
|
if (!entry.postSend)
|
|
2622
2663
|
return;
|
|
2623
2664
|
if (entry.postSend.type === 'register_interaction_card') {
|
|
2624
|
-
this.registerCardPostSend(messageId, entry.postSend);
|
|
2665
|
+
this.registerCardPostSend(messageId, entry.postSend, entry.channelId);
|
|
2625
2666
|
}
|
|
2626
2667
|
}
|
|
2627
2668
|
async sendAunPayload(channelId, payload, context, label) {
|
|
@@ -2936,7 +2977,7 @@ EvolCore AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
2936
2977
|
finalPayload._evolcore_causation_ref = causationRef;
|
|
2937
2978
|
registerAunCausation(causationRef, this.config.aid, args.to, outboundCausation);
|
|
2938
2979
|
}
|
|
2939
|
-
const encrypt = args.encrypt
|
|
2980
|
+
const encrypt = args.encrypt ?? this.shouldEncrypt(args.to);
|
|
2940
2981
|
const sendParams = {
|
|
2941
2982
|
to: args.to,
|
|
2942
2983
|
payload: this.truncatePayloadIfNeeded(finalPayload, 'msg-send-ipc'),
|
|
@@ -3041,7 +3082,7 @@ EvolCore AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
3041
3082
|
const finalPayload = this.stripUndefinedDeep({ ...args.payload });
|
|
3042
3083
|
if (args.mentions?.length)
|
|
3043
3084
|
finalPayload.mentions = args.mentions;
|
|
3044
|
-
const encrypt = args.encrypt
|
|
3085
|
+
const encrypt = args.encrypt ?? this.shouldEncrypt(args.groupId);
|
|
3045
3086
|
try {
|
|
3046
3087
|
const result = await this.callAndTrace('group.send', {
|
|
3047
3088
|
group_id: args.groupId,
|
|
@@ -3534,6 +3575,117 @@ EvolCore AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
3534
3575
|
outbox.remove(this.config.aid, entry.id);
|
|
3535
3576
|
}
|
|
3536
3577
|
}
|
|
3578
|
+
async sendImage(channelId, data, mimeType, alt, context) {
|
|
3579
|
+
if (!Buffer.isBuffer(data)) {
|
|
3580
|
+
throw new Error('result.image data must be a Buffer');
|
|
3581
|
+
}
|
|
3582
|
+
if (data.length === 0) {
|
|
3583
|
+
throw new Error('result.image data cannot be empty');
|
|
3584
|
+
}
|
|
3585
|
+
if (data.length > MAX_AUN_ATTACHMENT_SIZE) {
|
|
3586
|
+
throw new Error(`result.image is too large (${formatSize(data.length)}, max ${formatSize(MAX_AUN_ATTACHMENT_SIZE)})`);
|
|
3587
|
+
}
|
|
3588
|
+
let normalizedMimeType = normalizeImageMimeType(mimeType);
|
|
3589
|
+
if (!normalizedMimeType) {
|
|
3590
|
+
const { default: imageType } = await import('image-type');
|
|
3591
|
+
const detected = await imageType(data).catch(() => undefined);
|
|
3592
|
+
normalizedMimeType = detected?.mime;
|
|
3593
|
+
if (!normalizedMimeType) {
|
|
3594
|
+
throw new Error('result.image requires a valid image MIME type or recognizable image data');
|
|
3595
|
+
}
|
|
3596
|
+
}
|
|
3597
|
+
const logText = alt ? `[image] ${alt}` : '[image]';
|
|
3598
|
+
const entry = outbox.enqueue(this.config.aid, {
|
|
3599
|
+
channelId,
|
|
3600
|
+
type: 'image',
|
|
3601
|
+
contentKind: 'image',
|
|
3602
|
+
image: { dataBase64: data.toString('base64'), mimeType: normalizedMimeType, alt },
|
|
3603
|
+
context,
|
|
3604
|
+
logText,
|
|
3605
|
+
});
|
|
3606
|
+
logger.debug(`${this.logPrefix()} Outbox enqueued image: id=${entry.id} channel=${channelId} bytes=${data.length}`);
|
|
3607
|
+
if (!this.connected || !this.client) {
|
|
3608
|
+
logger.warn(`${this.logPrefix()} Not connected, image send queued in outbox (id=${entry.id}). Triggering reconnect.`);
|
|
3609
|
+
if (!this.reconnectTimer && !this.client) {
|
|
3610
|
+
this.initClient().catch(e => logger.error(`${this.logPrefix()} Reconnect from sendImage failed: ${e}`));
|
|
3611
|
+
}
|
|
3612
|
+
return;
|
|
3613
|
+
}
|
|
3614
|
+
const ok = await this.withOutboxInFlight(entry, () => this.deliverImageEntry(entry), false);
|
|
3615
|
+
if (ok)
|
|
3616
|
+
outbox.remove(this.config.aid, entry.id);
|
|
3617
|
+
}
|
|
3618
|
+
async buildUploadedImagePayload(data, mimeType, alt) {
|
|
3619
|
+
if (!this.connected || !this.client) {
|
|
3620
|
+
throw new Error('AUN channel is not connected; image cannot be uploaded');
|
|
3621
|
+
}
|
|
3622
|
+
let contentType = normalizeImageMimeType(mimeType);
|
|
3623
|
+
let extension;
|
|
3624
|
+
if (!contentType) {
|
|
3625
|
+
const { default: imageType } = await import('image-type');
|
|
3626
|
+
const detected = await imageType(data).catch(() => undefined);
|
|
3627
|
+
contentType = detected?.mime;
|
|
3628
|
+
extension = detected?.ext;
|
|
3629
|
+
}
|
|
3630
|
+
if (!contentType) {
|
|
3631
|
+
throw new Error('result.image requires a valid image MIME type or recognizable image data');
|
|
3632
|
+
}
|
|
3633
|
+
if (!extension) {
|
|
3634
|
+
const subtype = contentType.slice('image/'.length);
|
|
3635
|
+
extension = subtype === 'jpeg'
|
|
3636
|
+
? 'jpg'
|
|
3637
|
+
: subtype === 'svg+xml'
|
|
3638
|
+
? 'svg'
|
|
3639
|
+
: subtype.replace(/[^a-z0-9]/g, '') || 'img';
|
|
3640
|
+
}
|
|
3641
|
+
const digest = crypto.createHash('sha256').update(data).digest('hex').slice(0, 12);
|
|
3642
|
+
const filename = `image-${digest}.${extension}`;
|
|
3643
|
+
const connection = {
|
|
3644
|
+
call: (method, params) => this.callAndTrace(method, params),
|
|
3645
|
+
close: async () => { },
|
|
3646
|
+
};
|
|
3647
|
+
const built = await uploadBufferAndBuildPayload(connection, this.config.aid, data, filename, { as: 'image', contentType });
|
|
3648
|
+
const imagePayload = { ...built.payload };
|
|
3649
|
+
setIfDefined(imagePayload, 'alt', alt);
|
|
3650
|
+
return imagePayload;
|
|
3651
|
+
}
|
|
3652
|
+
async deliverImageEntry(entry) {
|
|
3653
|
+
const image = entry.image;
|
|
3654
|
+
if (!image || typeof image.dataBase64 !== 'string') {
|
|
3655
|
+
logger.warn(`${this.logPrefix()} deliverImageEntry: missing image data (outbox id=${entry.id})`);
|
|
3656
|
+
return true;
|
|
3657
|
+
}
|
|
3658
|
+
const data = Buffer.from(image.dataBase64, 'base64');
|
|
3659
|
+
if (data.length === 0) {
|
|
3660
|
+
logger.warn(`${this.logPrefix()} deliverImageEntry: invalid image data (outbox id=${entry.id})`);
|
|
3661
|
+
return true;
|
|
3662
|
+
}
|
|
3663
|
+
const imagePayload = await this.buildUploadedImagePayload(data, image.mimeType, image.alt);
|
|
3664
|
+
entry.type = 'payload';
|
|
3665
|
+
entry.contentKind = 'image';
|
|
3666
|
+
entry.payload = this.applyReplyContextToPayload(imagePayload, entry.context);
|
|
3667
|
+
entry.logText ??= image.alt ? `[image] ${image.alt}` : '[image]';
|
|
3668
|
+
delete entry.image;
|
|
3669
|
+
// Persist the attachment reference before message.send. A failed send then
|
|
3670
|
+
// retries the compact wire payload without re-uploading the same image.
|
|
3671
|
+
let payloadEntry = entry;
|
|
3672
|
+
if (!outbox.replace(this.config.aid, entry)) {
|
|
3673
|
+
payloadEntry = outbox.enqueue(this.config.aid, {
|
|
3674
|
+
channelId: entry.channelId,
|
|
3675
|
+
type: 'payload',
|
|
3676
|
+
contentKind: 'image',
|
|
3677
|
+
payload: entry.payload,
|
|
3678
|
+
context: entry.context,
|
|
3679
|
+
logText: entry.logText,
|
|
3680
|
+
ttl: entry.ttl,
|
|
3681
|
+
});
|
|
3682
|
+
}
|
|
3683
|
+
const sent = await this.deliverPayloadEntry(payloadEntry);
|
|
3684
|
+
if (sent.ok && payloadEntry.id !== entry.id) {
|
|
3685
|
+
outbox.remove(this.config.aid, payloadEntry.id);
|
|
3686
|
+
}
|
|
3687
|
+
return sent.ok;
|
|
3688
|
+
}
|
|
3537
3689
|
async deliverFileEntry(entry) {
|
|
3538
3690
|
const channelId = entry.channelId;
|
|
3539
3691
|
const absPath = entry.filePath;
|
|
@@ -3720,6 +3872,9 @@ EvolCore AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
3720
3872
|
else if (entry.type === 'file') {
|
|
3721
3873
|
return this.withOutboxInFlight(entry, () => this.deliverFileEntry(entry), false);
|
|
3722
3874
|
}
|
|
3875
|
+
else if (entry.type === 'image') {
|
|
3876
|
+
return this.withOutboxInFlight(entry, () => this.deliverImageEntry(entry), false);
|
|
3877
|
+
}
|
|
3723
3878
|
else if (entry.type === 'payload') {
|
|
3724
3879
|
const sent = await this.withOutboxInFlight(entry, () => this.deliverPayloadEntry(entry), { ok: false });
|
|
3725
3880
|
return sent.ok;
|
|
@@ -4026,6 +4181,7 @@ export class AUNChannelPlugin {
|
|
|
4026
4181
|
aid,
|
|
4027
4182
|
keystorePath: inst.keystorePath,
|
|
4028
4183
|
gatewayUrl: inst.gatewayUrl,
|
|
4184
|
+
defaultEncrypt: ctx.aunDefaultEncrypt,
|
|
4029
4185
|
accessToken: inst.accessToken,
|
|
4030
4186
|
flushDelay: inst.flushDelay,
|
|
4031
4187
|
agentName: ctx.agentName,
|
|
@@ -4092,16 +4248,7 @@ export class AUNChannelPlugin {
|
|
|
4092
4248
|
return;
|
|
4093
4249
|
}
|
|
4094
4250
|
case 'result.image': {
|
|
4095
|
-
|
|
4096
|
-
const b64 = buf.toString('base64');
|
|
4097
|
-
const imagePayload = { type: 'image', data_base64: b64 };
|
|
4098
|
-
setIfDefined(imagePayload, 'alt', payload.alt);
|
|
4099
|
-
setIfDefined(imagePayload, 'mime_type', payload.mimeType);
|
|
4100
|
-
await channel.sendContentPayload(channelId, imagePayload, {
|
|
4101
|
-
contentKind: 'image',
|
|
4102
|
-
context: replyCtx,
|
|
4103
|
-
logText: payload.alt ? `[image] ${payload.alt}` : '[image]',
|
|
4104
|
-
});
|
|
4251
|
+
await channel.sendImage(channelId, payload.data, payload.mimeType, payload.alt, replyCtx);
|
|
4105
4252
|
return;
|
|
4106
4253
|
}
|
|
4107
4254
|
case 'activity.batch': {
|
|
@@ -4209,6 +4356,8 @@ export class AUNChannelPlugin {
|
|
|
4209
4356
|
aunCard.text = card.body;
|
|
4210
4357
|
setIfDefined(aunCard, 'format', card.bodyFormat);
|
|
4211
4358
|
}
|
|
4359
|
+
if (req.initiatorId && channel.isGroupId(channelId))
|
|
4360
|
+
aunCard.initiator = req.initiatorId;
|
|
4212
4361
|
if (replyCtx?.threadId)
|
|
4213
4362
|
aunCard.thread_id = replyCtx.threadId;
|
|
4214
4363
|
await channel.sendContentPayload(channelId, aunCard, {
|