evolcore 0.0.24 → 0.0.25
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 +578 -561
- package/LICENSE +21 -21
- package/README.md +455 -455
- package/bin/codex-managed-acl.ps1 +160 -160
- package/bin/codex-managed-hook.mjs +208 -208
- package/bin/ec-safe-output.js +327 -327
- package/bin/ec.js +29 -29
- package/bin/install-codex-managed-hooks.mjs +555 -555
- package/dist/agents/claude-runner.js +4 -1
- package/dist/agents/codex-runner.js +7 -4
- package/dist/agents/ecagent-runner.js +14 -13
- package/dist/agents/gemini-runner.js +22 -2
- package/dist/aun/aid/agentmd.js +56 -4
- package/dist/aun/aid/index.js +1 -0
- package/dist/aun/aid/managed-operation.js +53 -1
- package/dist/aun/storage/client.js +4 -1
- package/dist/aun/storage/index.js +1 -1
- package/dist/cli/agent-command.js +186 -87
- package/dist/cli/agent-create.js +42 -4
- package/dist/cli/agent.js +103 -1
- package/dist/cli/aun-commands.js +360 -294
- package/dist/cli/bench.js +20 -20
- package/dist/cli/config.js +20 -20
- package/dist/cli/contact.js +10 -10
- package/dist/cli/ctl-command.js +22 -22
- package/dist/cli/daemon-commands.js +15 -15
- package/dist/cli/fs-command.js +67 -67
- package/dist/cli/handoff-command.js +7 -7
- package/dist/cli/index.js +77 -77
- package/dist/cli/link-rules.js +21 -21
- package/dist/cli/menu-client.js +11 -11
- package/dist/cli/net-check.js +22 -22
- package/dist/cli/queue-command.js +29 -29
- package/dist/cli/response.js +30 -30
- package/dist/cli/restart-monitor.js +30 -30
- package/dist/cli/stats.js +51 -51
- package/dist/cli/trigger-command.js +56 -56
- package/dist/config/builtin-roles.js +1 -0
- package/dist/config/config-manager.js +15 -0
- package/dist/config/config-operation-service.js +20 -3
- package/dist/config/resolved-config-op.js +39 -1
- package/dist/core/auth/operation-authorizer.js +62 -4
- package/dist/core/auth/operation-catalog.js +16 -0
- package/dist/core/command/agent-control.js +54 -25
- package/dist/core/command/cli-intent-parser.js +18 -0
- package/dist/core/command/command-handler.js +91 -16
- package/dist/core/command/menu-catalog.js +1 -0
- package/dist/core/command/menu-handler.js +41 -5
- package/dist/core/command/menu-protocol.js +1 -0
- package/dist/core/command/slash-handler.js +9 -9
- package/dist/core/event-catalog.js +5 -0
- package/dist/core/evolagent.js +3 -3
- package/dist/core/message/response-engine.js +12 -6
- package/dist/core/permission/tool-policy.js +45 -6
- package/dist/index.js +209 -162
- package/dist/stats/db.js +159 -159
- package/dist/stats/eck-vars.js +9 -9
- package/dist/stats/query.js +139 -139
- package/dist/stats/role-budget.js +6 -6
- package/dist/stats/writer.js +51 -51
- package/dist/utils/avatar-upload.js +282 -23
- package/dist/utils/error-dict.json +160 -160
- package/dist/utils/macos-autostart.js +22 -22
- package/dist/utils/model-prices.jsonl +23 -23
- package/dist/utils/welcome.js +22 -22
- package/ecagent/LICENSE +21 -21
- package/ecagent/NOTICE +11 -11
- package/ecagent/README.md +61 -61
- package/ecagent/dist/harness/compaction/branch-summarization.js +30 -30
- package/ecagent/dist/harness/compaction/compaction.js +83 -83
- package/ecagent/dist/harness/messages.js +7 -7
- package/kits/docs/GUIDE.md +20 -20
- package/kits/docs/INDEX.md +66 -66
- package/kits/docs/aun/CHEATSHEET.md +19 -19
- package/kits/docs/aun/SYNC_PROTOCOL.md +15 -15
- package/kits/docs/channels/aun.md +56 -56
- package/kits/docs/channels/feishu.md +56 -56
- package/kits/docs/context-assembly.md +365 -364
- package/kits/docs/eck_templates/GUIDE.template.md +22 -22
- package/kits/docs/eck_templates/INDEX.template.md +28 -28
- package/kits/docs/eck_templates/path-registry.template.md +33 -33
- package/kits/docs/eck_templates/runtime.template.md +19 -19
- package/kits/docs/evolcore/INDEX.md +71 -69
- package/kits/docs/evolcore/agent-create.md +170 -156
- package/kits/docs/evolcore/agent.md +83 -73
- package/kits/docs/evolcore/aid.md +64 -62
- package/kits/docs/evolcore/codex-windows-sandbox.md +146 -0
- package/kits/docs/evolcore/config.md +195 -195
- package/kits/docs/evolcore/contact.md +68 -68
- package/kits/docs/evolcore/ctl.md +46 -46
- package/kits/docs/evolcore/event.md +216 -216
- package/kits/docs/evolcore/fs-architecture.md +1215 -1215
- package/kits/docs/evolcore/fs.md +110 -110
- package/kits/docs/evolcore/group-collaboration.md +243 -243
- package/kits/docs/evolcore/group-fs.md +17 -17
- package/kits/docs/evolcore/group-rules.md +251 -251
- package/kits/docs/evolcore/group.md +152 -152
- package/kits/docs/evolcore/menu-client.md +34 -34
- package/kits/docs/evolcore/model.md +50 -50
- package/kits/docs/evolcore/msg.md +165 -165
- package/kits/docs/evolcore/response.md +75 -75
- package/kits/docs/evolcore/rpc.md +37 -37
- package/kits/docs/evolcore/self-summary.md +29 -29
- package/kits/docs/evolcore/stats.md +83 -83
- package/kits/docs/evolcore/storage.md +50 -50
- package/kits/docs/evolcore/trigger.md +575 -575
- package/kits/docs/identity/AID_PROFILE_SPEC.md +26 -26
- package/kits/docs/identity/PATH_OPS.md +16 -16
- package/kits/docs/identity/ROLE_DETAIL.md +25 -25
- package/kits/docs/identity/identity-tools.md +26 -26
- package/kits/docs/path-registry.md +62 -62
- package/kits/docs/prompt-loading-architecture.md +270 -269
- package/kits/docs/venues/aun-group.md +45 -45
- package/kits/docs/venues/aun-private.md +10 -10
- package/kits/docs/venues/client-desktop.md +10 -10
- package/kits/docs/venues/client-mobile.md +10 -10
- package/kits/docs/venues/feishu-group.md +13 -13
- package/kits/docs/venues/feishu-private.md +9 -9
- package/kits/docs/venues/group.md +25 -25
- package/kits/docs/venues/private.md +10 -10
- package/kits/eck_manifest.auxiliary.json +43 -43
- package/kits/eck_manifest.json +195 -183
- package/kits/eck_message_manifest.json +74 -74
- package/kits/migrations/README-role-config-v4.md +32 -32
- package/kits/migrations/migrate-contact-book-v2.mjs +748 -748
- package/kits/migrations/migrate-role-config-v4.mjs +614 -614
- package/kits/migrations/migrate-role-config-v5.mjs +365 -365
- package/kits/migrations/rename-config-file.mjs +99 -99
- package/kits/rules/01-overview.md +162 -162
- package/kits/rules/02-navigation.md +96 -96
- package/kits/rules/03-identity.md +39 -37
- package/kits/rules/04-relation.md +75 -75
- package/kits/rules/05-venue.md +60 -60
- package/kits/rules/06-channel.md +63 -63
- package/kits/schemas/_meta.json +53 -53
- package/kits/schemas/agent-config.schema.1.json +177 -177
- package/kits/schemas/agent-config.schema.10.json +395 -395
- package/kits/schemas/agent-config.schema.11.json +421 -421
- package/kits/schemas/agent-config.schema.12.json +427 -427
- package/kits/schemas/agent-config.schema.2.json +239 -239
- package/kits/schemas/agent-config.schema.3.json +119 -119
- package/kits/schemas/agent-config.schema.4.json +208 -208
- package/kits/schemas/agent-config.schema.5.json +326 -326
- package/kits/schemas/agent-config.schema.6.json +322 -322
- package/kits/schemas/agent-config.schema.7.json +303 -303
- package/kits/schemas/agent-config.schema.8.json +304 -304
- package/kits/schemas/agent-config.schema.9.json +364 -364
- package/kits/schemas/contact-book.schema.1.json +36 -36
- package/kits/schemas/contact-book.schema.2.json +43 -43
- package/kits/schemas/contact-book.schema.3.json +67 -67
- package/kits/schemas/daemon.schema.1.json +91 -91
- package/kits/schemas/daemon.schema.2.json +101 -101
- package/kits/schemas/daemon.schema.3.json +123 -123
- package/kits/schemas/daemon.schema.4.json +136 -136
- package/kits/schemas/daemon.schema.5.json +135 -135
- package/kits/schemas/daemon.schema.6.json +131 -131
- package/kits/schemas/defaults.schema.1.json +81 -81
- package/kits/schemas/defaults.schema.2.json +85 -85
- package/kits/schemas/defaults.schema.3.json +73 -73
- package/kits/schemas/defaults.schema.4.json +107 -107
- package/kits/schemas/defaults.schema.5.json +119 -119
- package/kits/schemas/menu-exec-schema-commands.md +208 -208
- package/kits/schemas/migrations/README.md +30 -30
- package/kits/schemas/relation-config.schema.1.json +158 -158
- package/kits/schemas/relation-config.schema.2.json +73 -73
- package/kits/schemas/relation-config.schema.3.json +50 -50
- package/kits/schemas/relation-config.schema.4.json +47 -47
- package/kits/schemas/relation-config.schema.5.json +47 -47
- package/kits/schemas/relation-config.schema.6.json +46 -46
- package/kits/schemas/relation-config.schema.7.json +59 -59
- package/kits/schemas/relation-config.schema.8.json +103 -103
- package/kits/schemas/role-config.schema.1.json +201 -201
- package/kits/schemas/role-registry.schema.1.json +35 -35
- package/kits/schemas/single-session.schema.1.json +31 -31
- package/kits/schemas/single-session.schema.2.json +57 -57
- package/kits/schemas/single-session.schema.3.json +32 -32
- package/kits/templates/bootstrap-welcome.md +17 -17
- package/kits/templates/message-fragments/handoff-context-to-target.md +9 -9
- package/kits/templates/message-fragments/handoff-request-to-target.md +19 -19
- package/kits/templates/message-fragments/handoff-response-to-origin.md +9 -9
- package/kits/templates/message-fragments/inject-default.md +2 -2
- package/kits/templates/message-fragments/item.md +2 -2
- package/kits/templates/roles/admin.json +96 -84
- package/kits/templates/roles/member.json +97 -96
- package/kits/templates/roles/owner.json +9 -9
- package/kits/templates/roles/visitor.json +86 -85
- package/kits/templates/system-fragments/baseagent.md +14 -14
- package/kits/templates/system-fragments/bootstrap.md +38 -29
- package/kits/templates/system-fragments/channel.md +64 -64
- package/kits/templates/system-fragments/commands.md +28 -28
- package/kits/templates/system-fragments/identity.md +13 -13
- package/kits/templates/system-fragments/relation.md +19 -19
- package/kits/templates/system-fragments/session.md +59 -59
- package/kits/templates/system-fragments/venue.md +31 -31
- package/package.json +98 -98
- package/skills/eclink/SKILL.md +0 -84
- package/skills/eclink/agents/openai.yaml +0 -4
|
@@ -1,12 +1,19 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
|
-
import os from 'os';
|
|
3
|
-
import path from 'path';
|
|
4
2
|
import crypto from 'crypto';
|
|
3
|
+
import dns from 'dns/promises';
|
|
4
|
+
import net from 'net';
|
|
5
5
|
import { agentmdGet, agentmdPut, updateAgentMdFrontmatterAvatar } from '../aun/aid/agentmd.js';
|
|
6
6
|
import { storageRm, storageUpload } from '../aun/storage/index.js';
|
|
7
7
|
import { logger } from './logger.js';
|
|
8
8
|
import { validateImage } from './media-cache.js';
|
|
9
9
|
const MAX_AVATAR_BYTES = 500 * 1024;
|
|
10
|
+
const MIN_AVATAR_DIMENSION = 128;
|
|
11
|
+
const MAX_AVATAR_DIMENSION = 2048;
|
|
12
|
+
const MIN_AVATAR_RATIO = 0.8;
|
|
13
|
+
const MAX_AVATAR_RATIO = 1.25;
|
|
14
|
+
const MAX_AVATAR_REDIRECTS = 3;
|
|
15
|
+
const AVATAR_DOWNLOAD_TIMEOUT_MS = 10_000;
|
|
16
|
+
const AVATAR_DNS_TIMEOUT_MS = 2_000;
|
|
10
17
|
const ALLOWED_AVATAR_MIMES = new Set(['image/png', 'image/jpeg', 'image/webp']);
|
|
11
18
|
const MIME_EXT = {
|
|
12
19
|
'image/png': 'png',
|
|
@@ -64,16 +71,230 @@ export async function uploadAvatar(aid, dataUrl, opts) {
|
|
|
64
71
|
const parsed = parseAvatarDataUrl(dataUrl);
|
|
65
72
|
if ('ok' in parsed)
|
|
66
73
|
return parsed;
|
|
67
|
-
const validated = await
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
});
|
|
71
|
-
if (validated.mime === null) {
|
|
72
|
-
return fail(`头像格式验证失败:${validated.reason}`, 'INVALID_ARGS');
|
|
73
|
-
}
|
|
74
|
+
const validated = await validateAvatarBuffer(parsed.buffer);
|
|
75
|
+
if (!('dimensions' in validated))
|
|
76
|
+
return validated;
|
|
74
77
|
if (validated.mime !== parsed.mime) {
|
|
75
78
|
return fail(`头像格式验证失败:声明 ${parsed.mime},实际 ${validated.mime}`, 'INVALID_ARGS');
|
|
76
79
|
}
|
|
80
|
+
return uploadValidatedAvatar(aid, parsed.buffer, validated.mime, validated.dimensions, opts);
|
|
81
|
+
}
|
|
82
|
+
function parseJpegDimensions(buffer) {
|
|
83
|
+
let offset = 2;
|
|
84
|
+
while (offset + 3 < buffer.length) {
|
|
85
|
+
if (buffer[offset] !== 0xff) {
|
|
86
|
+
offset++;
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
while (offset < buffer.length && buffer[offset] === 0xff)
|
|
90
|
+
offset++;
|
|
91
|
+
if (offset >= buffer.length)
|
|
92
|
+
return undefined;
|
|
93
|
+
const marker = buffer[offset++];
|
|
94
|
+
if (marker === 0xd8 || marker === 0xd9 || (marker >= 0xd0 && marker <= 0xd7))
|
|
95
|
+
continue;
|
|
96
|
+
if (offset + 1 >= buffer.length)
|
|
97
|
+
return undefined;
|
|
98
|
+
const segmentLength = buffer.readUInt16BE(offset);
|
|
99
|
+
if (segmentLength < 2 || offset + segmentLength > buffer.length)
|
|
100
|
+
return undefined;
|
|
101
|
+
const isSof = (marker >= 0xc0 && marker <= 0xc3)
|
|
102
|
+
|| (marker >= 0xc5 && marker <= 0xc7)
|
|
103
|
+
|| (marker >= 0xc9 && marker <= 0xcb)
|
|
104
|
+
|| (marker >= 0xcd && marker <= 0xcf);
|
|
105
|
+
if (isSof && segmentLength >= 7) {
|
|
106
|
+
const height = buffer.readUInt16BE(offset + 3);
|
|
107
|
+
const width = buffer.readUInt16BE(offset + 5);
|
|
108
|
+
return { width, height };
|
|
109
|
+
}
|
|
110
|
+
offset += segmentLength;
|
|
111
|
+
}
|
|
112
|
+
return undefined;
|
|
113
|
+
}
|
|
114
|
+
function parseWebpDimensions(buffer) {
|
|
115
|
+
if (buffer.length < 16 || buffer.toString('ascii', 0, 4) !== 'RIFF' || buffer.toString('ascii', 8, 12) !== 'WEBP')
|
|
116
|
+
return undefined;
|
|
117
|
+
const chunk = buffer.toString('ascii', 12, 16);
|
|
118
|
+
if (chunk === 'VP8X' && buffer.length >= 30) {
|
|
119
|
+
const width = 1 + buffer[24] + (buffer[25] << 8) + (buffer[26] << 16);
|
|
120
|
+
const height = 1 + buffer[27] + (buffer[28] << 8) + (buffer[29] << 16);
|
|
121
|
+
return { width, height };
|
|
122
|
+
}
|
|
123
|
+
if (chunk === 'VP8L' && buffer.length >= 25 && buffer[20] === 0x2f) {
|
|
124
|
+
const bits = buffer[21] | (buffer[22] << 8) | (buffer[23] << 16) | (buffer[24] << 24);
|
|
125
|
+
const width = 1 + (bits & 0x3fff);
|
|
126
|
+
const height = 1 + ((bits >>> 14) & 0x3fff);
|
|
127
|
+
return { width, height };
|
|
128
|
+
}
|
|
129
|
+
if (chunk === 'VP8 ' && buffer.length >= 30) {
|
|
130
|
+
const frame = buffer.indexOf(Buffer.from([0x9d, 0x01, 0x2a]), 20);
|
|
131
|
+
if (frame >= 0 && frame + 7 < buffer.length) {
|
|
132
|
+
return { width: buffer.readUInt16LE(frame + 3) & 0x3fff, height: buffer.readUInt16LE(frame + 5) & 0x3fff };
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return undefined;
|
|
136
|
+
}
|
|
137
|
+
function parseAvatarDimensions(buffer, mime) {
|
|
138
|
+
try {
|
|
139
|
+
if (mime === 'image/png' && buffer.length >= 24
|
|
140
|
+
&& buffer.subarray(0, 8).equals(Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]))) {
|
|
141
|
+
return { width: buffer.readUInt32BE(16), height: buffer.readUInt32BE(20) };
|
|
142
|
+
}
|
|
143
|
+
if (mime === 'image/jpeg' && buffer.length >= 4 && buffer[0] === 0xff && buffer[1] === 0xd8)
|
|
144
|
+
return parseJpegDimensions(buffer);
|
|
145
|
+
if (mime === 'image/webp')
|
|
146
|
+
return parseWebpDimensions(buffer);
|
|
147
|
+
}
|
|
148
|
+
catch {
|
|
149
|
+
return undefined;
|
|
150
|
+
}
|
|
151
|
+
return undefined;
|
|
152
|
+
}
|
|
153
|
+
function validateAvatarDimensions(buffer, mime) {
|
|
154
|
+
const dimensions = parseAvatarDimensions(buffer, mime);
|
|
155
|
+
if (!dimensions || !Number.isSafeInteger(dimensions.width) || !Number.isSafeInteger(dimensions.height)
|
|
156
|
+
|| dimensions.width < MIN_AVATAR_DIMENSION || dimensions.height < MIN_AVATAR_DIMENSION
|
|
157
|
+
|| dimensions.width > MAX_AVATAR_DIMENSION || dimensions.height > MAX_AVATAR_DIMENSION) {
|
|
158
|
+
return fail(`头像分辨率必须在 ${MIN_AVATAR_DIMENSION}x${MIN_AVATAR_DIMENSION} 至 ${MAX_AVATAR_DIMENSION}x${MAX_AVATAR_DIMENSION} 之间`, 'INVALID_ARGS');
|
|
159
|
+
}
|
|
160
|
+
const aspectRatio = dimensions.width / dimensions.height;
|
|
161
|
+
if (aspectRatio < MIN_AVATAR_RATIO || aspectRatio > MAX_AVATAR_RATIO) {
|
|
162
|
+
return fail(`头像宽高比必须在 ${MIN_AVATAR_RATIO} 至 ${MAX_AVATAR_RATIO} 之间(当前: ${aspectRatio.toFixed(3)})`, 'INVALID_ARGS');
|
|
163
|
+
}
|
|
164
|
+
return dimensions;
|
|
165
|
+
}
|
|
166
|
+
function isPrivateAddress(address) {
|
|
167
|
+
if (net.isIPv4(address)) {
|
|
168
|
+
const [a, b] = address.split('.').map(Number);
|
|
169
|
+
return a === 0 || a === 10 || a === 127 || (a === 169 && b === 254)
|
|
170
|
+
|| (a === 100 && b >= 64 && b <= 127) || (a === 172 && b >= 16 && b <= 31)
|
|
171
|
+
|| (a === 192 && (b === 0 || b === 168)) || (a === 198 && (b === 18 || b === 19))
|
|
172
|
+
|| a >= 224;
|
|
173
|
+
}
|
|
174
|
+
if (net.isIPv6(address)) {
|
|
175
|
+
const normalized = address.toLowerCase();
|
|
176
|
+
const mapped = normalized.match(/^::ffff:(\d+\.\d+\.\d+\.\d+)$/)?.[1];
|
|
177
|
+
if (mapped)
|
|
178
|
+
return isPrivateAddress(mapped);
|
|
179
|
+
return normalized === '::1' || normalized === '::' || normalized.startsWith('fc')
|
|
180
|
+
|| normalized.startsWith('fd') || normalized.startsWith('fe8') || normalized.startsWith('fe9')
|
|
181
|
+
|| normalized.startsWith('fea') || normalized.startsWith('feb') || normalized.startsWith('ff');
|
|
182
|
+
}
|
|
183
|
+
return false;
|
|
184
|
+
}
|
|
185
|
+
async function validateAvatarUrl(url) {
|
|
186
|
+
let parsed;
|
|
187
|
+
try {
|
|
188
|
+
parsed = new URL(url);
|
|
189
|
+
}
|
|
190
|
+
catch {
|
|
191
|
+
return { ok: false, reason: '头像 URL 无效' };
|
|
192
|
+
}
|
|
193
|
+
if (parsed.protocol !== 'https:')
|
|
194
|
+
return { ok: false, reason: '头像 URL 仅支持 HTTPS' };
|
|
195
|
+
if (!parsed.hostname)
|
|
196
|
+
return { ok: false, reason: '头像 URL 缺少主机名' };
|
|
197
|
+
if (parsed.username || parsed.password)
|
|
198
|
+
return { ok: false, reason: '头像 URL 不允许携带认证信息' };
|
|
199
|
+
if (parsed.port && parsed.port !== '443')
|
|
200
|
+
return { ok: false, reason: '头像 URL 仅支持 HTTPS 默认端口' };
|
|
201
|
+
const hostname = parsed.hostname.replace(/^\[|\]$/g, '');
|
|
202
|
+
if (isPrivateAddress(hostname))
|
|
203
|
+
return { ok: false, reason: '头像 URL 指向受限地址' };
|
|
204
|
+
let dnsTimer;
|
|
205
|
+
try {
|
|
206
|
+
const addresses = await Promise.race([
|
|
207
|
+
dns.lookup(hostname, { all: true, verbatim: true }),
|
|
208
|
+
new Promise((_, reject) => { dnsTimer = setTimeout(() => reject(new Error('DNS lookup timeout')), AVATAR_DNS_TIMEOUT_MS); }),
|
|
209
|
+
]);
|
|
210
|
+
if (dnsTimer)
|
|
211
|
+
clearTimeout(dnsTimer);
|
|
212
|
+
if (!addresses.length || addresses.some(({ address }) => isPrivateAddress(address))) {
|
|
213
|
+
return { ok: false, reason: '头像 URL 指向受限地址' };
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
catch {
|
|
217
|
+
if (dnsTimer)
|
|
218
|
+
clearTimeout(dnsTimer);
|
|
219
|
+
return { ok: false, reason: '头像 URL 主机名无法解析' };
|
|
220
|
+
}
|
|
221
|
+
return { ok: true, url: parsed };
|
|
222
|
+
}
|
|
223
|
+
async function downloadAvatarBuffer(sourceUrl) {
|
|
224
|
+
let current = sourceUrl;
|
|
225
|
+
for (let redirect = 0; redirect <= MAX_AVATAR_REDIRECTS; redirect++) {
|
|
226
|
+
const checked = await validateAvatarUrl(current);
|
|
227
|
+
if (!checked.ok)
|
|
228
|
+
return fail(checked.reason, 'INVALID_ARGS');
|
|
229
|
+
const controller = new AbortController();
|
|
230
|
+
const timeout = setTimeout(() => controller.abort(), AVATAR_DOWNLOAD_TIMEOUT_MS);
|
|
231
|
+
let response;
|
|
232
|
+
try {
|
|
233
|
+
response = await fetch(checked.url, {
|
|
234
|
+
redirect: 'manual',
|
|
235
|
+
headers: { accept: 'image/png,image/jpeg,image/webp', 'accept-encoding': 'identity' },
|
|
236
|
+
signal: controller.signal,
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
catch (e) {
|
|
240
|
+
clearTimeout(timeout);
|
|
241
|
+
return fail(`头像 URL 下载失败: ${messageOf(e)}`, 'UPLOAD_FAILED');
|
|
242
|
+
}
|
|
243
|
+
if (response.status >= 300 && response.status < 400) {
|
|
244
|
+
const location = response.headers.get('location');
|
|
245
|
+
clearTimeout(timeout);
|
|
246
|
+
if (!location || redirect === MAX_AVATAR_REDIRECTS)
|
|
247
|
+
return fail('头像 URL 重定向次数过多或缺少目标', 'INVALID_ARGS');
|
|
248
|
+
try {
|
|
249
|
+
current = new URL(location, checked.url).toString();
|
|
250
|
+
}
|
|
251
|
+
catch {
|
|
252
|
+
return fail('头像 URL 重定向目标无效', 'INVALID_ARGS');
|
|
253
|
+
}
|
|
254
|
+
continue;
|
|
255
|
+
}
|
|
256
|
+
if (!response.ok) {
|
|
257
|
+
clearTimeout(timeout);
|
|
258
|
+
return fail(`头像 URL 返回 HTTP ${response.status}`, 'UPLOAD_FAILED');
|
|
259
|
+
}
|
|
260
|
+
const length = Number(response.headers.get('content-length') || 0);
|
|
261
|
+
if (length > MAX_AVATAR_BYTES) {
|
|
262
|
+
clearTimeout(timeout);
|
|
263
|
+
return fail('头像大小超过 500KB 限制', 'INVALID_ARGS');
|
|
264
|
+
}
|
|
265
|
+
if (!response.body) {
|
|
266
|
+
clearTimeout(timeout);
|
|
267
|
+
return fail('头像 URL 返回空响应', 'INVALID_ARGS');
|
|
268
|
+
}
|
|
269
|
+
const reader = response.body.getReader();
|
|
270
|
+
const chunks = [];
|
|
271
|
+
let total = 0;
|
|
272
|
+
try {
|
|
273
|
+
while (true) {
|
|
274
|
+
const part = await reader.read();
|
|
275
|
+
if (part.done)
|
|
276
|
+
break;
|
|
277
|
+
const chunk = Buffer.from(part.value);
|
|
278
|
+
total += chunk.length;
|
|
279
|
+
if (total > MAX_AVATAR_BYTES) {
|
|
280
|
+
await reader.cancel();
|
|
281
|
+
return fail('头像大小超过 500KB 限制', 'INVALID_ARGS');
|
|
282
|
+
}
|
|
283
|
+
chunks.push(chunk);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
catch (e) {
|
|
287
|
+
return fail(`头像 URL 下载失败: ${messageOf(e)}`, 'UPLOAD_FAILED');
|
|
288
|
+
}
|
|
289
|
+
finally {
|
|
290
|
+
clearTimeout(timeout);
|
|
291
|
+
reader.releaseLock();
|
|
292
|
+
}
|
|
293
|
+
return Buffer.concat(chunks, total);
|
|
294
|
+
}
|
|
295
|
+
return fail('头像 URL 重定向次数过多', 'INVALID_ARGS');
|
|
296
|
+
}
|
|
297
|
+
async function uploadValidatedAvatar(aid, buffer, mime, dimensions, opts) {
|
|
77
298
|
let agentMd;
|
|
78
299
|
try {
|
|
79
300
|
agentMd = await agentmdGet(aid, { aunPath: opts?.aunPath });
|
|
@@ -81,21 +302,18 @@ export async function uploadAvatar(aid, dataUrl, opts) {
|
|
|
81
302
|
catch (e) {
|
|
82
303
|
return fail(`agent.md 不存在或无法读取: ${messageOf(e)}`, classifyAgentMdReadError(e));
|
|
83
304
|
}
|
|
84
|
-
const ext = MIME_EXT[
|
|
305
|
+
const ext = MIME_EXT[mime] ?? 'img';
|
|
85
306
|
const objectKey = `shared/avatars/${crypto.randomUUID()}.${ext}`;
|
|
86
|
-
const
|
|
87
|
-
const tmpPath = path.join(tmpDir, `avatar.${ext}`);
|
|
307
|
+
const sha256 = crypto.createHash('sha256').update(buffer).digest('hex');
|
|
88
308
|
let uploaded = false;
|
|
89
309
|
try {
|
|
90
|
-
|
|
91
|
-
const uploadedAvatar = await storageUpload(aid, tmpPath, objectKey, {
|
|
310
|
+
const uploadedAvatar = await storageUpload(aid, buffer, objectKey, {
|
|
92
311
|
isPublic: true,
|
|
93
|
-
contentType:
|
|
312
|
+
contentType: mime,
|
|
94
313
|
aunPath: opts?.aunPath,
|
|
95
314
|
});
|
|
96
|
-
if (!uploadedAvatar.ok)
|
|
315
|
+
if (!uploadedAvatar.ok)
|
|
97
316
|
return fail(`上传到 AUN storage 失败: ${uploadedAvatar.error || 'unknown error'}`, 'UPLOAD_FAILED');
|
|
98
|
-
}
|
|
99
317
|
uploaded = true;
|
|
100
318
|
const publicUrl = uploadedAvatar.publicUrl ?? `https://${aid}/${uploadedAvatar.objectKey}`;
|
|
101
319
|
const updated = updateAgentMdFrontmatterAvatar(agentMd, publicUrl);
|
|
@@ -107,17 +325,58 @@ export async function uploadAvatar(aid, dataUrl, opts) {
|
|
|
107
325
|
return fail(`更新 agent.md 失败: ${messageOf(e)}`, 'UPLOAD_FAILED');
|
|
108
326
|
}
|
|
109
327
|
logger.info(`[avatar-upload] uploaded ${uploadedAvatar.objectKey} -> ${publicUrl}`);
|
|
110
|
-
return {
|
|
328
|
+
return {
|
|
329
|
+
ok: true,
|
|
330
|
+
publicUrl,
|
|
331
|
+
objectKey: uploadedAvatar.objectKey,
|
|
332
|
+
sha256,
|
|
333
|
+
mime,
|
|
334
|
+
width: dimensions.width,
|
|
335
|
+
height: dimensions.height,
|
|
336
|
+
aspectRatio: dimensions.width / dimensions.height,
|
|
337
|
+
};
|
|
111
338
|
}
|
|
112
339
|
catch (e) {
|
|
113
340
|
if (uploaded)
|
|
114
341
|
await cleanupUploadedAvatar(aid, objectKey, opts?.aunPath);
|
|
115
342
|
return fail(messageOf(e), 'UPLOAD_FAILED');
|
|
116
343
|
}
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
344
|
+
}
|
|
345
|
+
async function validateAvatarBuffer(buffer) {
|
|
346
|
+
const validated = await validateImage(buffer, {
|
|
347
|
+
maxSize: MAX_AVATAR_BYTES,
|
|
348
|
+
allowedMimes: ALLOWED_AVATAR_MIMES,
|
|
349
|
+
});
|
|
350
|
+
if (validated.mime === null)
|
|
351
|
+
return fail(`头像格式验证失败:${validated.reason}`, 'INVALID_ARGS');
|
|
352
|
+
const dimensions = validateAvatarDimensions(buffer, validated.mime);
|
|
353
|
+
if ('ok' in dimensions)
|
|
354
|
+
return dimensions;
|
|
355
|
+
return { ok: true, mime: validated.mime, dimensions };
|
|
356
|
+
}
|
|
357
|
+
/** Upload an image file after validating its bytes and real MIME type. */
|
|
358
|
+
export async function uploadAvatarFile(aid, filePath, opts) {
|
|
359
|
+
try {
|
|
360
|
+
const buffer = fs.readFileSync(filePath);
|
|
361
|
+
return await uploadAvatarBuffer(aid, buffer, opts);
|
|
122
362
|
}
|
|
363
|
+
catch (e) {
|
|
364
|
+
return fail(`头像文件读取失败: ${messageOf(e)}`, 'NOT_FOUND');
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
/** Upload already-read image bytes; callers can use this after secure fd validation. */
|
|
368
|
+
export async function uploadAvatarBuffer(aid, buffer, opts) {
|
|
369
|
+
const validated = await validateAvatarBuffer(buffer);
|
|
370
|
+
if (!('dimensions' in validated))
|
|
371
|
+
return validated;
|
|
372
|
+
return uploadValidatedAvatar(aid, buffer, validated.mime, validated.dimensions, opts);
|
|
373
|
+
}
|
|
374
|
+
/** Download an HTTPS image with redirect/SSRF/size guards, then upload it. */
|
|
375
|
+
export async function uploadAvatarUrl(aid, url, opts) {
|
|
376
|
+
if (typeof url !== 'string' || !url.trim())
|
|
377
|
+
return fail('头像 URL 不能为空', 'INVALID_ARGS');
|
|
378
|
+
const downloaded = await downloadAvatarBuffer(url.trim());
|
|
379
|
+
if (!Buffer.isBuffer(downloaded))
|
|
380
|
+
return downloaded;
|
|
381
|
+
return uploadAvatarBuffer(aid, downloaded, opts);
|
|
123
382
|
}
|
|
@@ -1,160 +1,160 @@
|
|
|
1
|
-
{
|
|
2
|
-
"rules": [
|
|
3
|
-
{
|
|
4
|
-
"id": "context-too-long-cn",
|
|
5
|
-
"match": "上下文过长",
|
|
6
|
-
"action": "stop",
|
|
7
|
-
"type": "context_too_long",
|
|
8
|
-
"message": "⚠️ 上下文过长,请手动输入 /compact 压缩上下文"
|
|
9
|
-
},
|
|
10
|
-
{
|
|
11
|
-
"id": "context-too-long-en",
|
|
12
|
-
"match": "context too long",
|
|
13
|
-
"action": "stop",
|
|
14
|
-
"type": "context_too_long",
|
|
15
|
-
"message": "⚠️ 上下文过长,请手动输入 /compact 压缩上下文"
|
|
16
|
-
},
|
|
17
|
-
{
|
|
18
|
-
"id": "prompt-too-long",
|
|
19
|
-
"match": "prompt is too long",
|
|
20
|
-
"action": "stop",
|
|
21
|
-
"type": "context_too_long",
|
|
22
|
-
"message": "⚠️ 输入过长,请精简提问或使用 /compact 压缩上下文"
|
|
23
|
-
},
|
|
24
|
-
{
|
|
25
|
-
"id": "invalid-api-key",
|
|
26
|
-
"match": "invalid api key",
|
|
27
|
-
"action": "stop",
|
|
28
|
-
"type": "auth_error",
|
|
29
|
-
"message": "❌ API Key 无效,请检查密钥配置。使用 /status 查看当前配置"
|
|
30
|
-
},
|
|
31
|
-
{
|
|
32
|
-
"id": "key-not-found",
|
|
33
|
-
"match": "key_not_found",
|
|
34
|
-
"action": "stop",
|
|
35
|
-
"type": "auth_error",
|
|
36
|
-
"message": "❌ API Key 未找到,请检查密钥配置"
|
|
37
|
-
},
|
|
38
|
-
{
|
|
39
|
-
"id": "api-key-verifier-too-many-connections",
|
|
40
|
-
"match": "error 1040 (08004): too many connections",
|
|
41
|
-
"action": "retry",
|
|
42
|
-
"type": "api_error",
|
|
43
|
-
"message": "⚠️ API 服务繁忙,请稍后重试"
|
|
44
|
-
},
|
|
45
|
-
{
|
|
46
|
-
"id": "credit-query-timeout",
|
|
47
|
-
"match": "积分查询超时",
|
|
48
|
-
"action": "retry",
|
|
49
|
-
"type": "api_error",
|
|
50
|
-
"message": "⚠️ 积分查询超时,请稍后重试"
|
|
51
|
-
},
|
|
52
|
-
{
|
|
53
|
-
"id": "capacity-pool-switching-cn",
|
|
54
|
-
"match": "算力池切换",
|
|
55
|
-
"action": "retry",
|
|
56
|
-
"type": "api_error",
|
|
57
|
-
"message": "⚠️ 当前算力池切换中,请稍后重试"
|
|
58
|
-
},
|
|
59
|
-
{
|
|
60
|
-
"id": "retry-attempt-stalled",
|
|
61
|
-
"match": "retry_attempt_stalled",
|
|
62
|
-
"action": "retry",
|
|
63
|
-
"type": "api_error",
|
|
64
|
-
"message": "⚠️ API 重试调用长时间无响应"
|
|
65
|
-
},
|
|
66
|
-
{
|
|
67
|
-
"id": "selected-model-at-capacity",
|
|
68
|
-
"match": "selected model is at capacity",
|
|
69
|
-
"action": "retry",
|
|
70
|
-
"type": "api_error",
|
|
71
|
-
"message": "⚠️ 当前模型繁忙,请稍后重试"
|
|
72
|
-
},
|
|
73
|
-
{
|
|
74
|
-
"id": "api-error-400",
|
|
75
|
-
"match": "api error: 400",
|
|
76
|
-
"action": "stop",
|
|
77
|
-
"type": "api_error",
|
|
78
|
-
"message": "❌ 请求格式错误,请检查输入内容"
|
|
79
|
-
},
|
|
80
|
-
{
|
|
81
|
-
"id": "model-not-supported-403",
|
|
82
|
-
"match": "api error: 403 不支持该模型",
|
|
83
|
-
"action": "stop",
|
|
84
|
-
"type": "model_unavailable",
|
|
85
|
-
"message": "❌ 当前模型不受支持,请正确配置模型后重试"
|
|
86
|
-
},
|
|
87
|
-
{
|
|
88
|
-
"id": "api-error-403",
|
|
89
|
-
"match": "api error: 403",
|
|
90
|
-
"action": "retry",
|
|
91
|
-
"type": "api_error",
|
|
92
|
-
"message": "⚠️ API 访问受限,请稍后重试"
|
|
93
|
-
},
|
|
94
|
-
{
|
|
95
|
-
"id": "api-error-429",
|
|
96
|
-
"match": "api error: 429",
|
|
97
|
-
"action": "retry",
|
|
98
|
-
"type": "api_error",
|
|
99
|
-
"message": "⚠️ 请求过于频繁,请稍后重试"
|
|
100
|
-
},
|
|
101
|
-
{
|
|
102
|
-
"id": "api-error-500",
|
|
103
|
-
"match": "api error: 500",
|
|
104
|
-
"action": "retry",
|
|
105
|
-
"type": "api_error",
|
|
106
|
-
"message": "❌ API 服务暂时不可用,请稍后重试"
|
|
107
|
-
},
|
|
108
|
-
{
|
|
109
|
-
"id": "api-error-502",
|
|
110
|
-
"match": "api error: 502",
|
|
111
|
-
"action": "retry",
|
|
112
|
-
"type": "api_error"
|
|
113
|
-
},
|
|
114
|
-
{
|
|
115
|
-
"id": "api-error-503",
|
|
116
|
-
"match": "api error: 503",
|
|
117
|
-
"action": "retry",
|
|
118
|
-
"type": "api_error"
|
|
119
|
-
},
|
|
120
|
-
{
|
|
121
|
-
"id": "api-error-504",
|
|
122
|
-
"match": "api error: 504",
|
|
123
|
-
"action": "retry",
|
|
124
|
-
"type": "api_error"
|
|
125
|
-
},
|
|
126
|
-
{
|
|
127
|
-
"id": "not-valid-json",
|
|
128
|
-
"match": "is not valid json",
|
|
129
|
-
"action": "retry",
|
|
130
|
-
"type": "api_error",
|
|
131
|
-
"message": "⚠️ API 返回异常响应,请稍后重试"
|
|
132
|
-
},
|
|
133
|
-
{
|
|
134
|
-
"id": "json-parse-error",
|
|
135
|
-
"match": "json parse error",
|
|
136
|
-
"action": "retry",
|
|
137
|
-
"type": "api_error",
|
|
138
|
-
"message": "⚠️ API 返回 JSON 解析异常,请稍后重试"
|
|
139
|
-
},
|
|
140
|
-
{
|
|
141
|
-
"id": "feishu-permission",
|
|
142
|
-
"match": "im:resource",
|
|
143
|
-
"action": "stop",
|
|
144
|
-
"type": "unknown",
|
|
145
|
-
"message": "❌ 权限不足,请联系管理员配置应用权限"
|
|
146
|
-
},
|
|
147
|
-
{
|
|
148
|
-
"id": "stream-error",
|
|
149
|
-
"match": "stream",
|
|
150
|
-
"action": "retry",
|
|
151
|
-
"type": "stream_error"
|
|
152
|
-
},
|
|
153
|
-
{
|
|
154
|
-
"id": "request-aborted",
|
|
155
|
-
"match": "request was aborted",
|
|
156
|
-
"action": "ignore",
|
|
157
|
-
"type": "stream_error"
|
|
158
|
-
}
|
|
159
|
-
]
|
|
160
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"rules": [
|
|
3
|
+
{
|
|
4
|
+
"id": "context-too-long-cn",
|
|
5
|
+
"match": "上下文过长",
|
|
6
|
+
"action": "stop",
|
|
7
|
+
"type": "context_too_long",
|
|
8
|
+
"message": "⚠️ 上下文过长,请手动输入 /compact 压缩上下文"
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
"id": "context-too-long-en",
|
|
12
|
+
"match": "context too long",
|
|
13
|
+
"action": "stop",
|
|
14
|
+
"type": "context_too_long",
|
|
15
|
+
"message": "⚠️ 上下文过长,请手动输入 /compact 压缩上下文"
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"id": "prompt-too-long",
|
|
19
|
+
"match": "prompt is too long",
|
|
20
|
+
"action": "stop",
|
|
21
|
+
"type": "context_too_long",
|
|
22
|
+
"message": "⚠️ 输入过长,请精简提问或使用 /compact 压缩上下文"
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"id": "invalid-api-key",
|
|
26
|
+
"match": "invalid api key",
|
|
27
|
+
"action": "stop",
|
|
28
|
+
"type": "auth_error",
|
|
29
|
+
"message": "❌ API Key 无效,请检查密钥配置。使用 /status 查看当前配置"
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
"id": "key-not-found",
|
|
33
|
+
"match": "key_not_found",
|
|
34
|
+
"action": "stop",
|
|
35
|
+
"type": "auth_error",
|
|
36
|
+
"message": "❌ API Key 未找到,请检查密钥配置"
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
"id": "api-key-verifier-too-many-connections",
|
|
40
|
+
"match": "error 1040 (08004): too many connections",
|
|
41
|
+
"action": "retry",
|
|
42
|
+
"type": "api_error",
|
|
43
|
+
"message": "⚠️ API 服务繁忙,请稍后重试"
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
"id": "credit-query-timeout",
|
|
47
|
+
"match": "积分查询超时",
|
|
48
|
+
"action": "retry",
|
|
49
|
+
"type": "api_error",
|
|
50
|
+
"message": "⚠️ 积分查询超时,请稍后重试"
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
"id": "capacity-pool-switching-cn",
|
|
54
|
+
"match": "算力池切换",
|
|
55
|
+
"action": "retry",
|
|
56
|
+
"type": "api_error",
|
|
57
|
+
"message": "⚠️ 当前算力池切换中,请稍后重试"
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
"id": "retry-attempt-stalled",
|
|
61
|
+
"match": "retry_attempt_stalled",
|
|
62
|
+
"action": "retry",
|
|
63
|
+
"type": "api_error",
|
|
64
|
+
"message": "⚠️ API 重试调用长时间无响应"
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
"id": "selected-model-at-capacity",
|
|
68
|
+
"match": "selected model is at capacity",
|
|
69
|
+
"action": "retry",
|
|
70
|
+
"type": "api_error",
|
|
71
|
+
"message": "⚠️ 当前模型繁忙,请稍后重试"
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
"id": "api-error-400",
|
|
75
|
+
"match": "api error: 400",
|
|
76
|
+
"action": "stop",
|
|
77
|
+
"type": "api_error",
|
|
78
|
+
"message": "❌ 请求格式错误,请检查输入内容"
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
"id": "model-not-supported-403",
|
|
82
|
+
"match": "api error: 403 不支持该模型",
|
|
83
|
+
"action": "stop",
|
|
84
|
+
"type": "model_unavailable",
|
|
85
|
+
"message": "❌ 当前模型不受支持,请正确配置模型后重试"
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
"id": "api-error-403",
|
|
89
|
+
"match": "api error: 403",
|
|
90
|
+
"action": "retry",
|
|
91
|
+
"type": "api_error",
|
|
92
|
+
"message": "⚠️ API 访问受限,请稍后重试"
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
"id": "api-error-429",
|
|
96
|
+
"match": "api error: 429",
|
|
97
|
+
"action": "retry",
|
|
98
|
+
"type": "api_error",
|
|
99
|
+
"message": "⚠️ 请求过于频繁,请稍后重试"
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
"id": "api-error-500",
|
|
103
|
+
"match": "api error: 500",
|
|
104
|
+
"action": "retry",
|
|
105
|
+
"type": "api_error",
|
|
106
|
+
"message": "❌ API 服务暂时不可用,请稍后重试"
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
"id": "api-error-502",
|
|
110
|
+
"match": "api error: 502",
|
|
111
|
+
"action": "retry",
|
|
112
|
+
"type": "api_error"
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
"id": "api-error-503",
|
|
116
|
+
"match": "api error: 503",
|
|
117
|
+
"action": "retry",
|
|
118
|
+
"type": "api_error"
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
"id": "api-error-504",
|
|
122
|
+
"match": "api error: 504",
|
|
123
|
+
"action": "retry",
|
|
124
|
+
"type": "api_error"
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
"id": "not-valid-json",
|
|
128
|
+
"match": "is not valid json",
|
|
129
|
+
"action": "retry",
|
|
130
|
+
"type": "api_error",
|
|
131
|
+
"message": "⚠️ API 返回异常响应,请稍后重试"
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
"id": "json-parse-error",
|
|
135
|
+
"match": "json parse error",
|
|
136
|
+
"action": "retry",
|
|
137
|
+
"type": "api_error",
|
|
138
|
+
"message": "⚠️ API 返回 JSON 解析异常,请稍后重试"
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
"id": "feishu-permission",
|
|
142
|
+
"match": "im:resource",
|
|
143
|
+
"action": "stop",
|
|
144
|
+
"type": "unknown",
|
|
145
|
+
"message": "❌ 权限不足,请联系管理员配置应用权限"
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
"id": "stream-error",
|
|
149
|
+
"match": "stream",
|
|
150
|
+
"action": "retry",
|
|
151
|
+
"type": "stream_error"
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
"id": "request-aborted",
|
|
155
|
+
"match": "request was aborted",
|
|
156
|
+
"action": "ignore",
|
|
157
|
+
"type": "stream_error"
|
|
158
|
+
}
|
|
159
|
+
]
|
|
160
|
+
}
|