evolclaw 3.1.1 → 3.1.3
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 +428 -0
- package/README.md +3 -7
- package/SKILLS.md +311 -0
- package/dist/agents/claude-runner.js +1 -1
- package/dist/agents/codex-runner.js +75 -19
- package/dist/agents/gemini-runner.js +0 -2
- package/dist/agents/kit-renderer.js +59 -10
- package/dist/aun/aid/agentmd.js +50 -27
- package/dist/aun/aid/client.js +5 -11
- package/dist/aun/aid/identity.js +32 -13
- package/dist/aun/aid/index.js +1 -1
- package/dist/aun/msg/group.js +1 -0
- package/dist/aun/msg/p2p.js +15 -2
- package/dist/aun/msg/upload.js +57 -18
- package/dist/aun/rpc/connection.js +3 -0
- package/dist/channels/aun.js +122 -48
- package/dist/channels/dingtalk.js +1 -0
- package/dist/channels/feishu.js +5 -4
- package/dist/channels/qqbot.js +1 -0
- package/dist/channels/wechat.js +1 -0
- package/dist/channels/wecom.js +1 -0
- package/dist/cli/agent.js +142 -40
- package/dist/cli/index.js +103 -58
- package/dist/cli/init-channel.js +4 -2
- package/dist/cli/init.js +55 -26
- package/dist/cli/watch-msg.js +3 -1
- package/dist/config-store.js +22 -1
- package/dist/core/channel-loader.js +4 -4
- package/dist/core/command-handler.js +626 -538
- package/dist/core/evolagent-registry.js +45 -9
- package/dist/core/evolagent.js +35 -4
- package/dist/core/message/im-renderer.js +14 -4
- package/dist/core/message/message-bridge.js +149 -25
- package/dist/core/message/message-processor.js +45 -38
- package/dist/core/session/session-fs-store.js +23 -0
- package/dist/core/session/session-manager.js +188 -42
- package/dist/index.js +15 -17
- package/dist/paths.js +35 -0
- package/dist/utils/cross-platform.js +2 -1
- package/kits/docs/INDEX.md +6 -0
- package/kits/eck_manifest.json +3 -3
- package/kits/rules/02-navigation.md +1 -0
- package/kits/rules/06-channel.md +2 -18
- package/kits/templates/system-fragments/baseagent.md +2 -2
- package/kits/templates/system-fragments/channel.md +18 -9
- package/kits/templates/system-fragments/eckruntime.md +14 -0
- package/kits/templates/system-fragments/identity.md +5 -6
- package/kits/templates/system-fragments/relation.md +7 -5
- package/kits/templates/system-fragments/venue.md +2 -3
- package/package.json +5 -2
- package/kits/templates/system-fragments/runtime.md +0 -19
package/dist/aun/aid/agentmd.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import path from 'path';
|
|
3
|
-
import os from 'os';
|
|
4
3
|
import { getAunClient } from './client.js';
|
|
4
|
+
import { agentMdPath, aidLocalDir, resolveRoot } from '../../paths.js';
|
|
5
5
|
export function buildInitialAgentMd(opts) {
|
|
6
6
|
const agentName = opts.aid.split('.')[0];
|
|
7
7
|
const agentType = opts.type || 'ai';
|
|
@@ -82,16 +82,17 @@ async function verifyContent(content, aid, certPem, client) {
|
|
|
82
82
|
* Create a bare AUNClient (no createAid) for read-only operations.
|
|
83
83
|
*/
|
|
84
84
|
async function createBareClient(aunPath) {
|
|
85
|
+
const p = aunPath ?? resolveRoot();
|
|
85
86
|
const { AUNClient } = await import('@agentunion/fastaun');
|
|
86
|
-
const caCertPath = path.join(
|
|
87
|
-
const clientOpts = { aun_path:
|
|
87
|
+
const caCertPath = path.join(p, 'CA', 'root', 'root.crt');
|
|
88
|
+
const clientOpts = { aun_path: p, debug: false };
|
|
88
89
|
if (fs.existsSync(caCertPath))
|
|
89
90
|
clientOpts.root_ca_path = caCertPath;
|
|
90
91
|
return new AUNClient(clientOpts);
|
|
91
92
|
}
|
|
92
93
|
export async function agentmdGet(aid, opts) {
|
|
93
|
-
const aunPath = opts?.aunPath ??
|
|
94
|
-
const localPath =
|
|
94
|
+
const aunPath = opts?.aunPath ?? resolveRoot();
|
|
95
|
+
const localPath = agentMdPath(aid);
|
|
95
96
|
// === Path A: local agent.md exists ===
|
|
96
97
|
if (fs.existsSync(localPath)) {
|
|
97
98
|
const content = fs.readFileSync(localPath, 'utf-8');
|
|
@@ -108,7 +109,8 @@ export async function agentmdGet(aid, opts) {
|
|
|
108
109
|
}
|
|
109
110
|
// Fallback: local invalid → try remote
|
|
110
111
|
try {
|
|
111
|
-
const
|
|
112
|
+
const info = await client.fetchAgentMd(aid);
|
|
113
|
+
const remote = info.content;
|
|
112
114
|
if (remote) {
|
|
113
115
|
const remoteVerification = await verifyContent(remote, aid, certPem, client);
|
|
114
116
|
if (remoteVerification.status === 'verified') {
|
|
@@ -132,20 +134,13 @@ export async function agentmdGet(aid, opts) {
|
|
|
132
134
|
const client = opts?.client ?? await createBareClient(aunPath);
|
|
133
135
|
const ownClient = !opts?.client;
|
|
134
136
|
try {
|
|
135
|
-
const
|
|
137
|
+
const info = await client.fetchAgentMd(aid);
|
|
138
|
+
const raw = info.content;
|
|
136
139
|
if (!opts?.withVerification) {
|
|
137
|
-
// Persist without verification
|
|
138
|
-
const aidDir = path.join(aunPath, 'AIDs', aid);
|
|
139
|
-
fs.mkdirSync(aidDir, { recursive: true });
|
|
140
|
-
fs.writeFileSync(path.join(aidDir, 'agent.md'), raw, 'utf-8');
|
|
141
140
|
return raw;
|
|
142
141
|
}
|
|
143
142
|
const certPem = await obtainCertPem(aid, aunPath, client);
|
|
144
143
|
const verification = await verifyContent(raw, aid, certPem, client);
|
|
145
|
-
// Persist to local
|
|
146
|
-
const aidDir = path.join(aunPath, 'AIDs', aid);
|
|
147
|
-
fs.mkdirSync(aidDir, { recursive: true });
|
|
148
|
-
fs.writeFileSync(path.join(aidDir, 'agent.md'), raw, 'utf-8');
|
|
149
144
|
return { content: raw, verification };
|
|
150
145
|
}
|
|
151
146
|
finally {
|
|
@@ -157,24 +152,52 @@ export async function agentmdGet(aid, opts) {
|
|
|
157
152
|
}
|
|
158
153
|
}
|
|
159
154
|
/**
|
|
160
|
-
* Upload agent.md: auto-sign + upload
|
|
155
|
+
* Upload agent.md: write to local file → publishAgentMd (auto-sign + upload).
|
|
161
156
|
*/
|
|
162
157
|
export async function agentmdPut(content, opts) {
|
|
163
|
-
const aunPath = opts.aunPath ??
|
|
158
|
+
const aunPath = opts.aunPath ?? resolveRoot();
|
|
164
159
|
const client = opts.client ?? await getAunClient(opts.aid, { aunPath });
|
|
165
160
|
const ownClient = !opts.client;
|
|
161
|
+
const dir = aidLocalDir(opts.aid);
|
|
162
|
+
const filePath = path.join(dir, 'agent.md');
|
|
163
|
+
const existed = fs.existsSync(filePath);
|
|
166
164
|
try {
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
165
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
166
|
+
fs.writeFileSync(filePath, content, 'utf-8');
|
|
167
|
+
await client.publishAgentMd();
|
|
168
|
+
}
|
|
169
|
+
catch (e) {
|
|
170
|
+
if (!existed)
|
|
171
|
+
try {
|
|
172
|
+
fs.unlinkSync(filePath);
|
|
173
|
+
}
|
|
174
|
+
catch { /* ignore */ }
|
|
175
|
+
throw e;
|
|
176
|
+
}
|
|
177
|
+
finally {
|
|
178
|
+
if (ownClient)
|
|
179
|
+
try {
|
|
180
|
+
await client.close();
|
|
181
|
+
}
|
|
182
|
+
catch { /* ignore */ }
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Check if agent.md is up-to-date (30-day cache), fetch if changed.
|
|
187
|
+
* Returns changed=true + content when a new version was downloaded.
|
|
188
|
+
*/
|
|
189
|
+
export async function agentmdSync(aid, opts) {
|
|
190
|
+
const client = opts?.client ?? await createBareClient();
|
|
191
|
+
const ownClient = !opts?.client;
|
|
192
|
+
try {
|
|
193
|
+
const state = await client.checkAgentMd(aid, 30);
|
|
194
|
+
if (!state.in_sync || !state.local_found) {
|
|
195
|
+
const info = await client.fetchAgentMd(aid);
|
|
196
|
+
return { changed: true, content: info.content };
|
|
173
197
|
}
|
|
174
|
-
|
|
175
|
-
const
|
|
176
|
-
|
|
177
|
-
fs.writeFileSync(path.join(aidDir, 'agent.md'), signed, 'utf-8');
|
|
198
|
+
const localPath = agentMdPath(aid);
|
|
199
|
+
const content = fs.existsSync(localPath) ? fs.readFileSync(localPath, 'utf-8') : undefined;
|
|
200
|
+
return { changed: false, content };
|
|
178
201
|
}
|
|
179
202
|
finally {
|
|
180
203
|
if (ownClient)
|
package/dist/aun/aid/client.js
CHANGED
|
@@ -14,21 +14,15 @@ export function suppressSdkLogs() {
|
|
|
14
14
|
const _origLog = console.log;
|
|
15
15
|
const _origInfo = console.info;
|
|
16
16
|
const _origWarn = console.warn;
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
console.log = (...args) => { if (typeof args[0] === 'string') {
|
|
21
|
-
if (SDK_LOG_RE.test(args[0]))
|
|
22
|
-
return;
|
|
23
|
-
if (SDK_ERROR_RE.test(args[0])) {
|
|
24
|
-
_origError(...args);
|
|
25
|
-
return;
|
|
26
|
-
}
|
|
27
|
-
} _origLog(...args); };
|
|
17
|
+
const SDK_LOG_RE = /^\[\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d+\]\[(?:DEBUG|INFO|WARN|ERROR)\]/;
|
|
18
|
+
console.log = (...args) => { if (typeof args[0] === 'string' && SDK_LOG_RE.test(args[0]))
|
|
19
|
+
return; _origLog(...args); };
|
|
28
20
|
console.info = (...args) => { if (typeof args[0] === 'string' && SDK_LOG_RE.test(args[0]))
|
|
29
21
|
return; _origInfo(...args); };
|
|
30
22
|
console.warn = (...args) => { if (typeof args[0] === 'string' && SDK_LOG_RE.test(args[0]))
|
|
31
23
|
return; _origWarn(...args); };
|
|
24
|
+
console.error = (...args) => { if (typeof args[0] === 'string' && SDK_LOG_RE.test(args[0]))
|
|
25
|
+
return; process.stderr.write(args.map(String).join(' ') + '\n'); };
|
|
32
26
|
}
|
|
33
27
|
// ==================== Constants ====================
|
|
34
28
|
export const MIN_AUN_CORE_SDK = [0, 2, 17];
|
package/dist/aun/aid/identity.js
CHANGED
|
@@ -3,7 +3,7 @@ import path from 'path';
|
|
|
3
3
|
import os from 'os';
|
|
4
4
|
import crypto from 'crypto';
|
|
5
5
|
import { getAunClient, downloadCaRoot } from './client.js';
|
|
6
|
-
import { resolvePaths } from '../../paths.js';
|
|
6
|
+
import { resolvePaths, aidsDir as evolclawAidsDir, agentMdPath } from '../../paths.js';
|
|
7
7
|
// ==================== Validation ====================
|
|
8
8
|
export function isValidAid(name) {
|
|
9
9
|
const labels = name.split('.');
|
|
@@ -11,17 +11,36 @@ export function isValidAid(name) {
|
|
|
11
11
|
}
|
|
12
12
|
// ==================== AID Operations ====================
|
|
13
13
|
export function aidList(aunPath) {
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
14
|
+
const aunAidsDir = path.join(aunPath ?? path.join(os.homedir(), '.aun'), 'AIDs');
|
|
15
|
+
const ecAidsDir = evolclawAidsDir();
|
|
16
|
+
const seen = new Map();
|
|
17
|
+
// Scan ~/.aun/AIDs (private keys live here)
|
|
18
|
+
if (fs.existsSync(aunAidsDir)) {
|
|
19
|
+
for (const e of fs.readdirSync(aunAidsDir, { withFileTypes: true })) {
|
|
20
|
+
if (!e.isDirectory())
|
|
21
|
+
continue;
|
|
22
|
+
seen.set(e.name, {
|
|
23
|
+
aid: e.name,
|
|
24
|
+
hasPrivateKey: fs.existsSync(path.join(aunAidsDir, e.name, 'private')),
|
|
25
|
+
hasAgentMd: fs.existsSync(agentMdPath(e.name)),
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
// Scan $EVOLCLAW_HOME/AIDs (agent.md lives here)
|
|
30
|
+
if (fs.existsSync(ecAidsDir) && ecAidsDir !== aunAidsDir) {
|
|
31
|
+
for (const e of fs.readdirSync(ecAidsDir, { withFileTypes: true })) {
|
|
32
|
+
if (!e.isDirectory())
|
|
33
|
+
continue;
|
|
34
|
+
if (seen.has(e.name))
|
|
35
|
+
continue;
|
|
36
|
+
seen.set(e.name, {
|
|
37
|
+
aid: e.name,
|
|
38
|
+
hasPrivateKey: fs.existsSync(path.join(aunAidsDir, e.name, 'private')),
|
|
39
|
+
hasAgentMd: fs.existsSync(agentMdPath(e.name)),
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return [...seen.values()];
|
|
25
44
|
}
|
|
26
45
|
export async function aidCreate(aid, opts) {
|
|
27
46
|
const aunPath = opts?.aunPath ?? path.join(os.homedir(), '.aun');
|
|
@@ -71,7 +90,7 @@ export function aidShow(aid, opts) {
|
|
|
71
90
|
const aunPath = opts?.aunPath ?? path.join(os.homedir(), '.aun');
|
|
72
91
|
const aidDir = path.join(aunPath, 'AIDs', aid);
|
|
73
92
|
const hasPrivateKey = fs.existsSync(path.join(aidDir, 'private'));
|
|
74
|
-
const hasAgentMd = fs.existsSync(
|
|
93
|
+
const hasAgentMd = fs.existsSync(agentMdPath(aid));
|
|
75
94
|
let certExpiresAt = null;
|
|
76
95
|
let certSubject = null;
|
|
77
96
|
const certPath = path.join(aidDir, 'public', 'cert.pem');
|
package/dist/aun/aid/index.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
export { isValidAid, aidList, aidCreate, aidShow, aidDelete, aidLookup, appendAidLifecycle, readAidLifecycle } from './identity.js';
|
|
2
|
-
export { buildInitialAgentMd, agentmdGet, agentmdPut } from './agentmd.js';
|
|
2
|
+
export { buildInitialAgentMd, agentmdGet, agentmdPut, agentmdSync } from './agentmd.js';
|
|
3
3
|
export { MIN_AUN_CORE_SDK, AUN_CORE_SDK_PKG, isAunSdkVersionOk, resolveAunCoreSdkPkg, ensureAunSdk, isAunSdkReady, downloadCaRoot, getAunClient, suppressSdkLogs, } from './client.js';
|
package/dist/aun/msg/group.js
CHANGED
package/dist/aun/msg/p2p.js
CHANGED
|
@@ -37,6 +37,7 @@ export async function msgSend(args) {
|
|
|
37
37
|
contentType: args.body.contentType,
|
|
38
38
|
text: args.body.text,
|
|
39
39
|
transcript: args.body.transcript,
|
|
40
|
+
onProgress: args.onUploadProgress,
|
|
40
41
|
});
|
|
41
42
|
payload = built.payload;
|
|
42
43
|
break;
|
|
@@ -44,11 +45,18 @@ export async function msgSend(args) {
|
|
|
44
45
|
}
|
|
45
46
|
// 4. 写入 payload.chatmode
|
|
46
47
|
payload.chatmode = chatmode;
|
|
48
|
+
// 5. 写入 payload.thread_id(如果指定)
|
|
49
|
+
if (args.thread) {
|
|
50
|
+
payload.thread_id = args.thread;
|
|
51
|
+
}
|
|
47
52
|
const sendParams = { to: args.to, payload };
|
|
48
53
|
// Default: plaintext. Set encrypt: true to enable E2EE.
|
|
49
54
|
sendParams.encrypt = args.encrypt === true;
|
|
50
55
|
const result = await conn.call('message.send', sendParams);
|
|
51
|
-
// 5. 写出方向 jsonl(与 daemon 一致格式,标记 source
|
|
56
|
+
// 5. 写出方向 jsonl(与 daemon 一致格式,标记 source)
|
|
57
|
+
// source 标记:
|
|
58
|
+
// - 'cli': 用户手动调用 ec msg send
|
|
59
|
+
// - 'msg': agent 在会话中调用 ec msg send
|
|
52
60
|
if (result?.message_id) {
|
|
53
61
|
try {
|
|
54
62
|
const sessionsDir = resolvePaths().sessionsDir;
|
|
@@ -57,6 +65,11 @@ export async function msgSend(args) {
|
|
|
57
65
|
: args.body.mode === 'link' ? `[link] ${args.body.url}`
|
|
58
66
|
: args.body.mode === 'file' ? `[file] ${args.body.filePath}`
|
|
59
67
|
: `[payload]`;
|
|
68
|
+
// 判断是 agent 调用还是用户手动调用
|
|
69
|
+
// 优先通过 --thread 参数判断(有 thread → msg,无 → cli)
|
|
70
|
+
// 兜底通过环境变量判断(向后兼容)
|
|
71
|
+
const isInSession = !!process.env.EVOLCLAW_SESSION_ID;
|
|
72
|
+
const source = args.thread ? 'msg' : (isInSession ? 'msg' : 'cli');
|
|
60
73
|
appendMessageLog(chatDir, buildOutboundEntry({
|
|
61
74
|
from: args.from,
|
|
62
75
|
to: args.to,
|
|
@@ -66,7 +79,7 @@ export async function msgSend(args) {
|
|
|
66
79
|
encrypt: args.encrypt === true,
|
|
67
80
|
chatmode, // 使用解析出的 chatmode
|
|
68
81
|
msgType: 'text',
|
|
69
|
-
source
|
|
82
|
+
source,
|
|
70
83
|
}));
|
|
71
84
|
}
|
|
72
85
|
catch { }
|
package/dist/aun/msg/upload.js
CHANGED
|
@@ -3,21 +3,19 @@ import fs from 'fs';
|
|
|
3
3
|
import path from 'path';
|
|
4
4
|
import { guessMime, formatSize } from '../../utils/media-cache.js';
|
|
5
5
|
import { inferPayloadType, isValidPayloadType } from './payload-type.js';
|
|
6
|
-
/** 小文件阈值:≤64KB 走 storage.put_object 内联 base64;>64KB 走 create_upload_session + HTTP PUT。 */
|
|
7
|
-
const INLINE_UPLOAD_LIMIT = 64 * 1024;
|
|
8
6
|
/** 单次上传最大大小(与 daemon sendFile 一致)。 */
|
|
9
7
|
const MAX_FILE_SIZE = 10 * 1024 * 1024;
|
|
8
|
+
/** server 端 inline 上限错误的识别特征。优先匹配错误消息里的“inline 上限”字样。 */
|
|
9
|
+
function isInlineLimitError(err) {
|
|
10
|
+
const msg = err?.message ?? '';
|
|
11
|
+
return /inline\s*上限|inline limit|create_upload_session/i.test(msg);
|
|
12
|
+
}
|
|
10
13
|
/**
|
|
11
14
|
* 上传本地文件并构造发送用的 payload。
|
|
12
15
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* 3. 按 as / 扩展名 确定 payload.type
|
|
17
|
-
* 4. 构造 payload(含 attachments 引用)
|
|
18
|
-
*
|
|
19
|
-
* 不做 outbox 持久化、不做 E2EE 加密兜底——这些是 daemon 的职责。
|
|
20
|
-
* CLI 短连接场景假定网络稳定,失败抛异常给调用方处理。
|
|
16
|
+
* 策略:先无脑 storage.put_object(内联 base64)。server 报“超过 inline 上限”
|
|
17
|
+
* 时降级到 storage.create_upload_session + HTTP PUT + storage.complete_upload,
|
|
18
|
+
* 此时通过 onProgress 上报字节级进度。
|
|
21
19
|
*/
|
|
22
20
|
export async function uploadFileAndBuildPayload(conn, ownerAid, filePath, opts) {
|
|
23
21
|
const absPath = path.resolve(filePath);
|
|
@@ -29,14 +27,19 @@ export async function uploadFileAndBuildPayload(conn, ownerAid, filePath, opts)
|
|
|
29
27
|
throw new Error(`文件为空: ${absPath}`);
|
|
30
28
|
}
|
|
31
29
|
if (stat.size > MAX_FILE_SIZE) {
|
|
32
|
-
throw new Error(`文件过大 (${formatSize(stat.size)},
|
|
30
|
+
throw new Error(`文件过大 (${formatSize(stat.size)}, 上限 ${formatSize(MAX_FILE_SIZE)}): ${absPath}`);
|
|
33
31
|
}
|
|
34
32
|
const filename = path.basename(absPath);
|
|
35
33
|
const fileData = fs.readFileSync(absPath);
|
|
36
34
|
const sha256 = crypto.createHash('sha256').update(fileData).digest('hex');
|
|
37
35
|
const contentType = opts?.contentType ?? guessMime(filename);
|
|
38
36
|
const objectKey = `shared/${crypto.randomUUID()}/${filename}`;
|
|
39
|
-
|
|
37
|
+
const total = stat.size;
|
|
38
|
+
const report = (phase, bytes) => opts?.onProgress?.({ phase, bytes, total });
|
|
39
|
+
// 1. 先按 inline 走
|
|
40
|
+
let inlineRejected = false;
|
|
41
|
+
try {
|
|
42
|
+
report('inline', 0);
|
|
40
43
|
await conn.call('storage.put_object', {
|
|
41
44
|
object_key: objectKey,
|
|
42
45
|
content: fileData.toString('base64'),
|
|
@@ -44,32 +47,68 @@ export async function uploadFileAndBuildPayload(conn, ownerAid, filePath, opts)
|
|
|
44
47
|
is_private: false,
|
|
45
48
|
overwrite: true,
|
|
46
49
|
});
|
|
50
|
+
report('inline', total);
|
|
47
51
|
}
|
|
48
|
-
|
|
52
|
+
catch (e) {
|
|
53
|
+
if (!isInlineLimitError(e))
|
|
54
|
+
throw e;
|
|
55
|
+
inlineRejected = true;
|
|
56
|
+
}
|
|
57
|
+
// 2. inline 被拒:降级到 session + HTTP PUT
|
|
58
|
+
if (inlineRejected) {
|
|
59
|
+
report('session-create', 0);
|
|
49
60
|
const session = await conn.call('storage.create_upload_session', {
|
|
50
61
|
object_key: objectKey,
|
|
51
|
-
size_bytes:
|
|
62
|
+
size_bytes: total,
|
|
52
63
|
content_type: contentType,
|
|
53
64
|
});
|
|
54
65
|
const uploadUrl = session?.upload_url;
|
|
55
66
|
if (!uploadUrl)
|
|
56
67
|
throw new Error('storage.create_upload_session 未返回 upload_url');
|
|
57
|
-
|
|
68
|
+
// 简单 Buffer 上传 + 周期性进度(不用流,避免某些 storage 网关对 chunked / duplex 不友好)
|
|
69
|
+
report('http-put', 0);
|
|
70
|
+
const PROGRESS_TICK_MS = 250;
|
|
71
|
+
let lastBytes = 0;
|
|
72
|
+
const tickerStart = Date.now();
|
|
73
|
+
// 简单的“估算”进度:实际上一次性发,但在等待响应期间按 elapsed 模拟字节数,让用户看到在跑
|
|
74
|
+
const ticker = setInterval(() => {
|
|
75
|
+
const elapsed = Date.now() - tickerStart;
|
|
76
|
+
// 假设 2MB/s 估算;不超过 99%
|
|
77
|
+
const estimated = Math.min(total - 1, Math.floor((elapsed / 1000) * 2 * 1024 * 1024));
|
|
78
|
+
if (estimated > lastBytes) {
|
|
79
|
+
lastBytes = estimated;
|
|
80
|
+
report('http-put', estimated);
|
|
81
|
+
}
|
|
82
|
+
}, PROGRESS_TICK_MS);
|
|
83
|
+
let uploadResp;
|
|
84
|
+
try {
|
|
85
|
+
uploadResp = await fetch(uploadUrl, {
|
|
86
|
+
method: 'PUT',
|
|
87
|
+
body: new Blob([new Uint8Array(fileData)], { type: contentType }),
|
|
88
|
+
headers: { 'Content-Type': contentType },
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
finally {
|
|
92
|
+
clearInterval(ticker);
|
|
93
|
+
}
|
|
58
94
|
if (!uploadResp.ok)
|
|
59
95
|
throw new Error(`HTTP 上传失败: ${uploadResp.status}`);
|
|
96
|
+
report('http-put', total);
|
|
97
|
+
report('session-complete', total);
|
|
60
98
|
await conn.call('storage.complete_upload', {
|
|
61
99
|
object_key: objectKey,
|
|
62
100
|
sha256,
|
|
63
101
|
content_type: contentType,
|
|
64
102
|
is_private: false,
|
|
65
|
-
size_bytes:
|
|
103
|
+
size_bytes: total,
|
|
66
104
|
});
|
|
67
105
|
}
|
|
106
|
+
report('done', total);
|
|
68
107
|
const attachment = {
|
|
69
108
|
owner_aid: ownerAid,
|
|
70
109
|
object_key: objectKey,
|
|
71
110
|
filename,
|
|
72
|
-
size_bytes:
|
|
111
|
+
size_bytes: total,
|
|
73
112
|
sha256,
|
|
74
113
|
content_type: contentType,
|
|
75
114
|
};
|
|
@@ -91,7 +130,7 @@ export async function uploadFileAndBuildPayload(conn, ownerAid, filePath, opts)
|
|
|
91
130
|
if (opts?.text)
|
|
92
131
|
payload.text = opts.text;
|
|
93
132
|
else if (type === 'file')
|
|
94
|
-
payload.text = `📎 ${filename} (${formatSize(
|
|
133
|
+
payload.text = `📎 ${filename} (${formatSize(total)})`;
|
|
95
134
|
if (type === 'voice' && opts?.transcript)
|
|
96
135
|
payload.transcript = opts.transcript;
|
|
97
136
|
return { payload, type, attachment };
|
|
@@ -6,9 +6,12 @@ export async function createShortConnection(aid, opts) {
|
|
|
6
6
|
const slotId = opts?.slotId ?? '';
|
|
7
7
|
const caCertPath = path.join(aunPath, 'CA', 'root', 'root.crt');
|
|
8
8
|
const { AUNClient } = await import('@agentunion/fastaun');
|
|
9
|
+
const encryptionSeed = process.env.AUN_ENCRYPTION_SEED || undefined;
|
|
9
10
|
const clientOpts = { aun_path: aunPath, debug: false };
|
|
10
11
|
if (fs.existsSync(caCertPath))
|
|
11
12
|
clientOpts.root_ca_path = caCertPath;
|
|
13
|
+
if (encryptionSeed)
|
|
14
|
+
clientOpts.encryption_seed = encryptionSeed;
|
|
12
15
|
const client = new AUNClient(clientOpts);
|
|
13
16
|
await client.auth.createAid({ aid });
|
|
14
17
|
const authResult = await client.auth.authenticate({ aid });
|