evolcore 0.0.24 → 0.0.26
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +59 -0
- package/bin/ec.js +12 -1
- package/bin/install-codex-managed-hooks.mjs +28 -1
- package/dist/agents/baseagent.js +96 -46
- package/dist/agents/claude-runner.js +54 -13
- package/dist/agents/codex/app-server-client.js +13 -5
- package/dist/agents/codex/catalog.js +1 -1
- package/dist/agents/codex/diagnostics.js +34 -1
- package/dist/agents/codex-runner.js +128 -42
- package/dist/agents/ecagent-runner.js +14 -18
- package/dist/agents/gemini-runner.js +24 -9
- package/dist/agents/runner-types.js +25 -17
- package/dist/aun/aid/agentmd.js +135 -10
- package/dist/aun/aid/client.js +1 -1
- package/dist/aun/aid/identity.js +1 -1
- package/dist/aun/aid/index.js +2 -1
- package/dist/aun/aid/managed-operation.js +53 -1
- package/dist/aun/aid/store.js +17 -13
- package/dist/aun/menu-client.js +74 -0
- package/dist/aun/msg/group.js +1 -1
- package/dist/aun/msg/managed-operation.js +1 -1
- package/dist/aun/msg/p2p.js +6 -1
- package/dist/aun/msg/upload.js +30 -22
- package/dist/aun/storage/client.js +4 -1
- package/dist/aun/storage/index.js +1 -1
- package/dist/channels/aun.js +329 -94
- package/dist/cli/agent-command.js +125 -5
- package/dist/cli/agent-create.js +151 -11
- package/dist/cli/agent.js +119 -6
- package/dist/cli/aun-commands.js +74 -7
- package/dist/cli/config.js +27 -8
- package/dist/cli/contact.js +63 -6
- package/dist/cli/daemon-commands.js +90 -25
- package/dist/cli/diag.js +31 -0
- package/dist/cli/handoff-command.js +35 -3
- package/dist/cli/index.js +7 -1
- package/dist/cli/managed-command-guard.js +3 -0
- package/dist/cli/menu-client.js +38 -23
- package/dist/cli/restart-monitor.js +57 -10
- package/dist/config/builtin-roles.js +2 -0
- package/dist/config/config-manager.js +15 -0
- package/dist/config/config-operation-service.js +165 -3
- package/dist/config/contact-operation-service.js +96 -7
- package/dist/config/resolved-config-op.js +66 -8
- package/dist/core/agent-application-service.js +2 -1
- package/dist/core/auth/authorization-audit.js +20 -12
- package/dist/core/auth/operation-authorizer.js +87 -8
- package/dist/core/auth/operation-catalog.js +51 -4
- package/dist/core/auth/remote-access-store.js +40 -1
- package/dist/core/bootstrap-service.js +1 -1
- 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 +152 -20
- package/dist/core/command/diag-service.js +118 -0
- package/dist/core/command/menu-catalog.js +13 -3
- package/dist/core/command/menu-handler.js +88 -31
- package/dist/core/command/menu-protocol.js +6 -0
- package/dist/core/command/menu-token-store.js +41 -8
- package/dist/core/command/slash-gate.js +34 -4
- package/dist/core/command/slash-handler.js +77 -12
- package/dist/core/event-catalog.js +5 -0
- package/dist/core/evolagent.js +3 -3
- package/dist/core/handoff/runtime.js +205 -27
- package/dist/core/handoff/store.js +193 -4
- package/dist/core/message/im-renderer.js +72 -21
- package/dist/core/message/message-bridge.js +69 -9
- package/dist/core/message/message-queue.js +26 -0
- package/dist/core/message/message-utils.js +1 -0
- package/dist/core/message/response-engine.js +48 -23
- package/dist/core/model/catalog-service.js +79 -49
- package/dist/core/model/context-window-suffix.js +33 -0
- package/dist/core/model/model-catalog.js +1 -1
- package/dist/core/model/model-permission.js +12 -3
- package/dist/core/permission/ec-command-parser.js +51 -12
- package/dist/core/permission/sandbox-runtime.js +36 -14
- package/dist/core/permission/tool-policy.js +47 -7
- package/dist/index.js +195 -31
- package/dist/ipc.js +54 -2
- package/dist/response-system/engines/v1/proactive-flow.js +59 -2
- package/dist/stats/billing.js +41 -15
- package/dist/stats/price-resolver.js +28 -10
- package/dist/utils/avatar-upload.js +282 -23
- package/dist/utils/cross-platform.js +30 -1
- package/dist/utils/error-utils.js +21 -0
- package/dist/utils/evolcore-version.js +129 -0
- package/dist/utils/logger.js +3 -1
- package/dist/utils/model-prices.jsonl +3 -3
- package/dist/utils/npm-ops.js +176 -35
- package/kits/docs/context-assembly.md +1 -0
- package/kits/docs/evolcore/INDEX.md +3 -1
- package/kits/docs/evolcore/agent-create.md +30 -11
- package/kits/docs/evolcore/agent.md +18 -8
- package/kits/docs/evolcore/aid.md +6 -4
- package/kits/docs/evolcore/codex-windows-sandbox.md +146 -0
- package/kits/docs/evolcore/config.md +7 -0
- package/kits/docs/evolcore/contact.md +12 -5
- package/kits/docs/evolcore/menu-client.md +11 -3
- package/kits/docs/evolcore/msg.md +1 -0
- package/kits/docs/prompt-loading-architecture.md +1 -0
- package/kits/docs/venues/group.md +12 -0
- package/kits/eck_manifest.json +12 -0
- package/kits/rules/03-identity.md +3 -1
- package/kits/templates/message-fragments/handoff-request-to-target.md +3 -3
- package/kits/templates/roles/admin.json +14 -2
- package/kits/templates/roles/member.json +3 -2
- package/kits/templates/roles/visitor.json +4 -3
- package/kits/templates/system-fragments/bootstrap.md +16 -5
- package/kits/templates/system-fragments/channel.md +1 -1
- package/kits/templates/system-fragments/session.md +1 -1
- package/package.json +6 -4
- package/skills/eclink/SKILL.md +0 -84
- package/skills/eclink/agents/openai.yaml +0 -4
package/dist/aun/aid/agentmd.js
CHANGED
|
@@ -4,6 +4,88 @@ import { parseDocument } from 'yaml';
|
|
|
4
4
|
import { getAidStore, loadAid, SLOT } from './store.js';
|
|
5
5
|
import { agentMdPath, aidLocalDir, resolveRoot } from '../../paths.js';
|
|
6
6
|
export const DEFAULT_AGENT_DESCRIPTION = 'EvolCore AI agent.';
|
|
7
|
+
/**
|
|
8
|
+
* AUN 主体类型白名单。没有 `ai` / `codeagent` 这类取值:使用大模型的服务程序
|
|
9
|
+
* 一律归为 `agent`。新建 AID 未指定类型时使用 DEFAULT_AGENT_TYPE。
|
|
10
|
+
*/
|
|
11
|
+
export const AGENT_TYPES = ['human', 'agent', 'group', 'service', 'device', 'org'];
|
|
12
|
+
export const DEFAULT_AGENT_TYPE = 'agent';
|
|
13
|
+
/**
|
|
14
|
+
* 归一化主体类型:大小写不敏感,统一收敛为白名单里的小写形式。
|
|
15
|
+
* `--type AGENT` / `Agent` 都接受,最终写进 agent.md 的一律是小写 `agent`。
|
|
16
|
+
* 返回 undefined 表示输入不是可识别的类型(交由 validateAgentType 报错)。
|
|
17
|
+
*/
|
|
18
|
+
export function normalizeAgentType(type) {
|
|
19
|
+
if (typeof type !== 'string')
|
|
20
|
+
return undefined;
|
|
21
|
+
const lowered = type.trim().toLowerCase();
|
|
22
|
+
return AGENT_TYPES.includes(lowered) ? lowered : undefined;
|
|
23
|
+
}
|
|
24
|
+
/** Return a human-readable validation error for a type value, or null when valid. */
|
|
25
|
+
export function validateAgentType(type) {
|
|
26
|
+
if (typeof type !== 'string' || !type.trim())
|
|
27
|
+
return 'must be a non-empty string';
|
|
28
|
+
if (!normalizeAgentType(type))
|
|
29
|
+
return `must be one of ${AGENT_TYPES.join(', ')}`;
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* agent.md 的尺寸与字段上限,取值必须与 fastaun 的 agent-md-schema 保持一致,
|
|
34
|
+
* 否则本地校验通过、上传时才被 SDK 拒绝(失败点后移且提示不同)。
|
|
35
|
+
* 参考 @agentunion/fastaun dist/agent-md-schema.js。
|
|
36
|
+
*/
|
|
37
|
+
export const AGENT_MD_MAX_PAYLOAD_BYTES = 4096;
|
|
38
|
+
export const AGENT_DESCRIPTION_MAX_LENGTH = 100;
|
|
39
|
+
export const AGENT_AVATAR_MAX_LENGTH = 256;
|
|
40
|
+
/** 按 Unicode 码点计数——与 SDK 的 Array.from(value).length 对齐(代理对算一个字符)。 */
|
|
41
|
+
function codePointLength(value) {
|
|
42
|
+
return Array.from(value).length;
|
|
43
|
+
}
|
|
44
|
+
export const AGENT_TAG_MIN_COUNT = 3;
|
|
45
|
+
export const AGENT_TAG_MAX_COUNT = 6;
|
|
46
|
+
export const AGENT_TAG_MAX_LENGTH = 50;
|
|
47
|
+
const AGENT_TAG_PATTERN = /^[\p{L}\p{N}-]+$/u;
|
|
48
|
+
/** Return a human-readable validation error for one tag, or null when valid. */
|
|
49
|
+
export function validateAgentTag(tag) {
|
|
50
|
+
if (typeof tag !== 'string' || !tag.trim())
|
|
51
|
+
return 'must be a non-empty string';
|
|
52
|
+
const normalized = tag.trim();
|
|
53
|
+
if (Array.from(normalized).length > AGENT_TAG_MAX_LENGTH) {
|
|
54
|
+
return `must be at most ${AGENT_TAG_MAX_LENGTH} characters`;
|
|
55
|
+
}
|
|
56
|
+
if (!AGENT_TAG_PATTERN.test(normalized)) {
|
|
57
|
+
return 'must contain only Unicode letters, numbers, and hyphens';
|
|
58
|
+
}
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Validate an agent.md tags value. Count constraints are optional because
|
|
63
|
+
* existing published agent.md files may contain fewer than three tags.
|
|
64
|
+
*/
|
|
65
|
+
export function validateAgentTags(tags, options = {}) {
|
|
66
|
+
if (!Array.isArray(tags))
|
|
67
|
+
return ['must be an array of strings'];
|
|
68
|
+
const errors = [];
|
|
69
|
+
const normalized = new Set();
|
|
70
|
+
tags.forEach((tag, index) => {
|
|
71
|
+
const error = validateAgentTag(tag);
|
|
72
|
+
if (error) {
|
|
73
|
+
errors.push(`tag ${index + 1} ${error}`);
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
const value = tag.trim();
|
|
77
|
+
normalized.add(value);
|
|
78
|
+
});
|
|
79
|
+
const minCount = options.minCount;
|
|
80
|
+
const maxCount = options.maxCount;
|
|
81
|
+
if (minCount !== undefined && normalized.size < minCount) {
|
|
82
|
+
errors.push(`must contain at least ${minCount} unique tags`);
|
|
83
|
+
}
|
|
84
|
+
if (maxCount !== undefined && normalized.size > maxCount) {
|
|
85
|
+
errors.push(`must contain at most ${maxCount} unique tags`);
|
|
86
|
+
}
|
|
87
|
+
return errors;
|
|
88
|
+
}
|
|
7
89
|
const displayNameCache = new Map();
|
|
8
90
|
const displayNamePending = new Map();
|
|
9
91
|
const displayNameRevision = new Map();
|
|
@@ -85,9 +167,20 @@ export function invalidateAgentDisplayName(aid) {
|
|
|
85
167
|
}
|
|
86
168
|
export function buildInitialAgentMd(opts) {
|
|
87
169
|
const agentName = opts.name?.trim() || opts.aid.split('.')[0];
|
|
88
|
-
|
|
170
|
+
// 未指定类型时用 DEFAULT_AGENT_TYPE(agent);指定了就必须落在 AGENT_TYPES 白名单内。
|
|
171
|
+
// 大小写不敏感,写进 agent.md 的一律是归一化后的小写形式。
|
|
172
|
+
const requestedType = opts.type?.trim() || DEFAULT_AGENT_TYPE;
|
|
173
|
+
const typeError = validateAgentType(requestedType);
|
|
174
|
+
if (typeError)
|
|
175
|
+
throw new Error(`Invalid agent.md type: ${typeError}`);
|
|
176
|
+
const agentType = normalizeAgentType(requestedType) ?? DEFAULT_AGENT_TYPE;
|
|
89
177
|
const description = opts.description?.trim() || DEFAULT_AGENT_DESCRIPTION;
|
|
90
|
-
|
|
178
|
+
const rawTags = opts.tags?.length ? opts.tags : ['evolcore'];
|
|
179
|
+
const tagErrors = validateAgentTags(rawTags);
|
|
180
|
+
if (tagErrors.length > 0)
|
|
181
|
+
throw new Error(`Invalid agent.md tags: ${tagErrors.join('; ')}`);
|
|
182
|
+
const tags = [...new Set(rawTags.map(tag => tag.trim()))];
|
|
183
|
+
return `---\naid: ${yamlDoubleQuote(opts.aid)}\nname: ${yamlDoubleQuote(agentName)}\ntype: ${yamlDoubleQuote(agentType)}\nversion: "1.0.0"\ndescription: ${yamlDoubleQuote(description)}\ntags:\n${tags.map(tag => ` - ${yamlDoubleQuote(tag)}\n`).join('')}---\n`;
|
|
91
184
|
}
|
|
92
185
|
function ensureTrailingNewline(content) {
|
|
93
186
|
return content.endsWith('\n') ? content : `${content}\n`;
|
|
@@ -105,8 +198,9 @@ function yamlDoubleQuote(value) {
|
|
|
105
198
|
export function validateAgentMdFrontmatter(content, expectedAid) {
|
|
106
199
|
const errors = [];
|
|
107
200
|
const payload = stripAgentMdSignature(content);
|
|
108
|
-
|
|
109
|
-
|
|
201
|
+
const payloadBytes = Buffer.byteLength(payload, 'utf8');
|
|
202
|
+
if (payloadBytes > AGENT_MD_MAX_PAYLOAD_BYTES) {
|
|
203
|
+
errors.push(`agent.md frontmatter and body must not exceed ${AGENT_MD_MAX_PAYLOAD_BYTES} bytes before the signature block (current: ${payloadBytes} bytes)`);
|
|
110
204
|
}
|
|
111
205
|
const frontmatter = payload.match(/^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/)?.[1];
|
|
112
206
|
if (!frontmatter)
|
|
@@ -135,19 +229,50 @@ export function validateAgentMdFrontmatter(content, expectedAid) {
|
|
|
135
229
|
requiredString('name');
|
|
136
230
|
requiredString('type');
|
|
137
231
|
const version = requiredString('version');
|
|
138
|
-
|
|
232
|
+
// description 是可选字段(fastaun ≥0.5.13 起不再要求非空):缺失或空串都合法,
|
|
233
|
+
// 仅在实际提供时校验类型与长度,与 SDK 的 agent-md-schema 保持一致。
|
|
234
|
+
const rawDescription = values.description;
|
|
235
|
+
let description;
|
|
236
|
+
if (rawDescription !== undefined && rawDescription !== null) {
|
|
237
|
+
if (typeof rawDescription !== 'string') {
|
|
238
|
+
errors.push('agent.md frontmatter description must be a string');
|
|
239
|
+
}
|
|
240
|
+
else {
|
|
241
|
+
description = rawDescription.trim();
|
|
242
|
+
}
|
|
243
|
+
}
|
|
139
244
|
if (expectedAid && aid && aid !== expectedAid) {
|
|
140
245
|
errors.push(`agent.md frontmatter aid must match upload target ${expectedAid}`);
|
|
141
246
|
}
|
|
142
247
|
if (version && !/^\d+\.\d+\.\d+$/.test(version)) {
|
|
143
248
|
errors.push('agent.md frontmatter version must use major.minor.patch format');
|
|
144
249
|
}
|
|
145
|
-
|
|
146
|
-
|
|
250
|
+
// 长度按 Unicode 码点计、且对未 trim 的原值计数——与 SDK 完全一致,
|
|
251
|
+
// 否则含 emoji 或尾随空格的名片会本地放行、上传才被拒。
|
|
252
|
+
if (typeof rawDescription === 'string') {
|
|
253
|
+
const length = codePointLength(rawDescription);
|
|
254
|
+
if (length > AGENT_DESCRIPTION_MAX_LENGTH) {
|
|
255
|
+
errors.push(`agent.md frontmatter description must be at most ${AGENT_DESCRIPTION_MAX_LENGTH} characters (current: ${length})`);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
// avatar:SDK 有 256 字符上限,本地此前完全没校验。
|
|
259
|
+
const rawAvatar = values.avatar;
|
|
260
|
+
if (rawAvatar !== undefined && rawAvatar !== null) {
|
|
261
|
+
if (typeof rawAvatar !== 'string') {
|
|
262
|
+
errors.push('agent.md frontmatter avatar must be a string');
|
|
263
|
+
}
|
|
264
|
+
else {
|
|
265
|
+
const length = codePointLength(rawAvatar);
|
|
266
|
+
if (length > AGENT_AVATAR_MAX_LENGTH) {
|
|
267
|
+
errors.push(`agent.md frontmatter avatar must be at most ${AGENT_AVATAR_MAX_LENGTH} characters (current: ${length})`);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
147
270
|
}
|
|
148
|
-
if (values.tags !== undefined
|
|
149
|
-
|
|
150
|
-
|
|
271
|
+
if (values.tags !== undefined) {
|
|
272
|
+
const tagErrors = validateAgentTags(values.tags);
|
|
273
|
+
if (tagErrors.length > 0) {
|
|
274
|
+
errors.push(`agent.md frontmatter tags ${tagErrors.join('; ')}`);
|
|
275
|
+
}
|
|
151
276
|
}
|
|
152
277
|
return { valid: errors.length === 0, errors };
|
|
153
278
|
}
|
package/dist/aun/aid/client.js
CHANGED
|
@@ -39,7 +39,7 @@ export function suppressSdkLogs() {
|
|
|
39
39
|
};
|
|
40
40
|
}
|
|
41
41
|
// ==================== SDK package ====================
|
|
42
|
-
export const MIN_AUN_CORE_SDK = [0, 5,
|
|
42
|
+
export const MIN_AUN_CORE_SDK = [0, 5, 16];
|
|
43
43
|
export const AUN_CORE_SDK_PKG = '@agentunion/fastaun';
|
|
44
44
|
function compareVersion(a, min) {
|
|
45
45
|
const parts = a.split('.').map(n => parseInt(n, 10));
|
package/dist/aun/aid/identity.js
CHANGED
|
@@ -114,7 +114,7 @@ export async function verifySignAbility(aid, opts) {
|
|
|
114
114
|
// signAgentMd validates the same frontmatter contract as a published agent.md.
|
|
115
115
|
// Keep the probe minimal, but make it a valid document so self-checks do not
|
|
116
116
|
// reject otherwise healthy identities before exercising sign + verify.
|
|
117
|
-
const probe = `---\naid: "${aid}"\nname: "probe"\ntype: "
|
|
117
|
+
const probe = `---\naid: "${aid}"\nname: "probe"\ntype: "agent"\nversion: "1.0.0"\ndescription: "Local signing self-check"\ntags:\n - evolcore\n---\n`;
|
|
118
118
|
const signRes = aidObj.signAgentMd(probe);
|
|
119
119
|
if (!signRes.ok) {
|
|
120
120
|
return { ok: false, reason: `sign failed: ${String(signRes.error?.message || signRes.error?.code).slice(0, 120)}` };
|
package/dist/aun/aid/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
+
export { AGENT_TAG_MIN_COUNT, AGENT_TAG_MAX_COUNT, AGENT_TAG_MAX_LENGTH, validateAgentTag, validateAgentTags, } from './agentmd.js';
|
|
1
2
|
export { isValidAid, aidList, aidListVerified, aidCreate, aidShow, aidDelete, aidLookup, verifySignAbility, probePkiRecoverability, appendAidLifecycle, readAidLifecycle } from './identity.js';
|
|
2
|
-
export { DEFAULT_AGENT_DESCRIPTION, buildInitialAgentMd, validateAgentMdFrontmatter, agentmdGet, agentmdPut, agentmdSync, invalidateAgentDisplayName, parseAgentDisplayName, refreshAgentDisplayName, resolveAgentDisplayName, stripAgentMdSignature, updateAgentMdFrontmatterAvatar, updateAgentMdFrontmatterName, } from './agentmd.js';
|
|
3
|
+
export { AGENT_TYPES, DEFAULT_AGENT_DESCRIPTION, DEFAULT_AGENT_TYPE, normalizeAgentType, validateAgentType, buildInitialAgentMd, validateAgentMdFrontmatter, agentmdGet, agentmdPut, agentmdSync, invalidateAgentDisplayName, parseAgentDisplayName, refreshAgentDisplayName, resolveAgentDisplayName, stripAgentMdSignature, updateAgentMdFrontmatterAvatar, updateAgentMdFrontmatterName, } from './agentmd.js';
|
|
3
4
|
export { MIN_AUN_CORE_SDK, AUN_CORE_SDK_PKG, isAunSdkVersionOk, resolveAunCoreSdkPkg, ensureAunSdk, isAunSdkReady, downloadCaRoot, suppressSdkLogs, } from './client.js';
|
|
4
5
|
export { getAidStore, loadClient, loadAid, AidLoadError, SLOT } from './store.js';
|
|
@@ -2,6 +2,7 @@ import fs from 'fs';
|
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import { resolveRoot } from '../../paths.js';
|
|
4
4
|
import { agentmdGet, agentmdPut } from './agentmd.js';
|
|
5
|
+
import { uploadAvatarBuffer, uploadAvatarUrl } from '../../utils/avatar-upload.js';
|
|
5
6
|
import { aidLookup, aidShow } from './identity.js';
|
|
6
7
|
import { isValidAid } from './validation.js';
|
|
7
8
|
export class ManagedAidOperationError extends Error {
|
|
@@ -28,6 +29,32 @@ function parseAid(value) {
|
|
|
28
29
|
return { ok: false, code: 'INVALID_AID', reason: `invalid AID: ${aid}` };
|
|
29
30
|
return { ok: true, aid };
|
|
30
31
|
}
|
|
32
|
+
function parseAvatar(args) {
|
|
33
|
+
const aidResult = parseAid(args[1]);
|
|
34
|
+
if (!aidResult.ok)
|
|
35
|
+
return aidResult;
|
|
36
|
+
const sourceFlag = args[2];
|
|
37
|
+
const sourceValue = args[3];
|
|
38
|
+
if (sourceFlag !== '--file' && sourceFlag !== '--url') {
|
|
39
|
+
return { ok: false, code: 'INVALID_ARGUMENT', reason: 'avatar requires exactly one of --file <path> or --url <https-url>' };
|
|
40
|
+
}
|
|
41
|
+
if (!sourceValue || sourceValue.startsWith('-')) {
|
|
42
|
+
return { ok: false, code: 'INVALID_ARGUMENT', reason: `${sourceFlag} requires a non-empty value` };
|
|
43
|
+
}
|
|
44
|
+
if (args.length !== 4 && !(args.length === 6 && args[4] === '--format' && args[5] === 'json')) {
|
|
45
|
+
return { ok: false, code: 'NOT_ALLOWED', reason: 'avatar only supports an optional --format json suffix' };
|
|
46
|
+
}
|
|
47
|
+
return {
|
|
48
|
+
ok: true,
|
|
49
|
+
command: {
|
|
50
|
+
kind: 'avatar',
|
|
51
|
+
aid: aidResult.aid,
|
|
52
|
+
...(sourceFlag === '--file' ? { filePath: sourceValue } : { url: sourceValue }),
|
|
53
|
+
operationId: 'ec.aid.avatar',
|
|
54
|
+
canonicalArgv: ['aid', 'avatar', aidResult.aid, sourceFlag, sourceValue],
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
}
|
|
31
58
|
/**
|
|
32
59
|
* The daemon accepts only the narrowly scoped AID commands exposed to managed
|
|
33
60
|
* tasks. Public lookup/get may name any valid AID; local show/publish remain
|
|
@@ -36,6 +63,8 @@ function parseAid(value) {
|
|
|
36
63
|
export function resolveManagedAidOperation(argv) {
|
|
37
64
|
const args = argv[0] === 'aid' ? argv.slice(1) : argv;
|
|
38
65
|
const subcommand = args[0];
|
|
66
|
+
if (subcommand === 'avatar')
|
|
67
|
+
return parseAvatar(args);
|
|
39
68
|
if ((subcommand === 'show' || subcommand === 'lookup') && hasOnlyOptionalJsonFormat(args, 1)) {
|
|
40
69
|
const parsedAid = parseAid(args[1]);
|
|
41
70
|
if (!parsedAid.ok)
|
|
@@ -68,7 +97,7 @@ export function resolveManagedAidOperation(argv) {
|
|
|
68
97
|
return {
|
|
69
98
|
ok: false,
|
|
70
99
|
code: 'NOT_ALLOWED',
|
|
71
|
-
reason: 'Managed AID operations only allow exact show, lookup, agentmd get,
|
|
100
|
+
reason: 'Managed AID operations only allow exact show, lookup, agentmd get, agentmd put, or avatar commands with supported options',
|
|
72
101
|
};
|
|
73
102
|
}
|
|
74
103
|
export function managedAidShowViewForRole(role) {
|
|
@@ -93,6 +122,8 @@ const defaultDependencies = () => ({
|
|
|
93
122
|
agentmdPut,
|
|
94
123
|
existsSync: fs.existsSync,
|
|
95
124
|
readFileSync: fs.readFileSync,
|
|
125
|
+
uploadAvatarBuffer,
|
|
126
|
+
uploadAvatarUrl,
|
|
96
127
|
});
|
|
97
128
|
/** Execute a validated AID operation from the daemon-owned host filesystem. */
|
|
98
129
|
export async function executeManagedAidOperation(command, options = {}) {
|
|
@@ -125,5 +156,26 @@ export async function executeManagedAidOperation(command, options = {}) {
|
|
|
125
156
|
await deps.agentmdPut(content, { aid: command.aid, aunPath });
|
|
126
157
|
return { kind: command.kind, aid: command.aid, path: localPath };
|
|
127
158
|
}
|
|
159
|
+
case 'avatar': {
|
|
160
|
+
const result = command.url
|
|
161
|
+
? await deps.uploadAvatarUrl(command.aid, command.url, { aunPath })
|
|
162
|
+
: await deps.uploadAvatarBuffer(command.aid, options.avatarBuffer ?? deps.readFileSync(command.filePath), { aunPath });
|
|
163
|
+
if (!result.ok) {
|
|
164
|
+
throw new ManagedAidOperationError(result.code ?? 'UPLOAD_FAILED', result.error ?? 'avatar upload failed');
|
|
165
|
+
}
|
|
166
|
+
return {
|
|
167
|
+
kind: 'avatar',
|
|
168
|
+
aid: command.aid,
|
|
169
|
+
avatar: result.publicUrl,
|
|
170
|
+
objectKey: result.objectKey,
|
|
171
|
+
sha256: result.sha256,
|
|
172
|
+
mime: result.mime,
|
|
173
|
+
width: result.width,
|
|
174
|
+
height: result.height,
|
|
175
|
+
aspectRatio: result.aspectRatio,
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
default:
|
|
179
|
+
throw new ManagedAidOperationError('NOT_ALLOWED', `Unsupported managed AID operation: ${command.kind}`);
|
|
128
180
|
}
|
|
129
181
|
}
|
package/dist/aun/aid/store.js
CHANGED
|
@@ -80,10 +80,10 @@ export async function loadClient(store, aid) {
|
|
|
80
80
|
/**
|
|
81
81
|
* Enable bounded multi-page group pulls for the long-lived daemon client.
|
|
82
82
|
*
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
-
*
|
|
86
|
-
*
|
|
83
|
+
* Older SDK recovery paths used `maxPages=1`; for a daemon that can strand a
|
|
84
|
+
* backlog after a tail sync. SDK 0.5.15 normally chooses a larger recovery
|
|
85
|
+
* window, but this adapter still expands an explicit single-page request and
|
|
86
|
+
* keeps older clients safe. It is isolated to the client that opts in.
|
|
87
87
|
*/
|
|
88
88
|
export function enableFullGroupPullPagination(client) {
|
|
89
89
|
const target = client;
|
|
@@ -110,13 +110,12 @@ export function enableFullGroupPullPagination(client) {
|
|
|
110
110
|
/**
|
|
111
111
|
* Make SDK pull delivery wait until EvolCore's application handlers finish.
|
|
112
112
|
*
|
|
113
|
-
* fastaun
|
|
114
|
-
*
|
|
115
|
-
*
|
|
116
|
-
*
|
|
117
|
-
*
|
|
118
|
-
*
|
|
119
|
-
* message events before the pull operation commits the sequence.
|
|
113
|
+
* fastaun publishes message events asynchronously, while the sequence tracker
|
|
114
|
+
* is a transport watermark. A daemon restart can therefore acknowledge a
|
|
115
|
+
* pulled message before the application has durably claimed it. SDK 0.5.15
|
|
116
|
+
* split protocol and application queues; prefer its application-only flush so
|
|
117
|
+
* an unrelated protocol event cannot delay the pull commit. Older SDKs fall
|
|
118
|
+
* back to the combined flush method.
|
|
120
119
|
*/
|
|
121
120
|
export function enableReliableMessageDelivery(client) {
|
|
122
121
|
const target = client;
|
|
@@ -124,7 +123,12 @@ export function enableReliableMessageDelivery(client) {
|
|
|
124
123
|
return false;
|
|
125
124
|
const publish = target._publishAppEvent;
|
|
126
125
|
const dispatcher = target._dispatcher;
|
|
127
|
-
|
|
126
|
+
const flushApplication = typeof dispatcher?.flushApplication === 'function'
|
|
127
|
+
? dispatcher.flushApplication.bind(dispatcher)
|
|
128
|
+
: typeof dispatcher?.flush === 'function'
|
|
129
|
+
? dispatcher.flush.bind(dispatcher)
|
|
130
|
+
: undefined;
|
|
131
|
+
if (typeof publish !== 'function' || !flushApplication)
|
|
128
132
|
return false;
|
|
129
133
|
const wrappedPublish = async function (event, payload, source, ...rest) {
|
|
130
134
|
const result = await publish.call(this, event, payload, source, ...rest);
|
|
@@ -135,7 +139,7 @@ export function enableReliableMessageDelivery(client) {
|
|
|
135
139
|
&& source !== 'push'
|
|
136
140
|
&& source !== 'inline-push';
|
|
137
141
|
if (reliable)
|
|
138
|
-
await
|
|
142
|
+
await flushApplication();
|
|
139
143
|
return result;
|
|
140
144
|
};
|
|
141
145
|
Object.defineProperty(target, RELIABLE_MESSAGE_DELIVERY_PATCH, {
|
package/dist/aun/menu-client.js
CHANGED
|
@@ -22,6 +22,80 @@ export class MenuClientError extends Error {
|
|
|
22
22
|
this.name = 'MenuClientError';
|
|
23
23
|
}
|
|
24
24
|
}
|
|
25
|
+
/** Uses the daemon's existing authenticated AUN channel and its normal ACK path. */
|
|
26
|
+
export class ManagedMenuClient {
|
|
27
|
+
send;
|
|
28
|
+
pending = new Map();
|
|
29
|
+
constructor(send) {
|
|
30
|
+
this.send = send;
|
|
31
|
+
}
|
|
32
|
+
accept(fromAid, payload, encrypted) {
|
|
33
|
+
if (!payload || typeof payload !== 'object' || payload.type !== 'menu.response')
|
|
34
|
+
return false;
|
|
35
|
+
if (!encrypted)
|
|
36
|
+
return true;
|
|
37
|
+
try {
|
|
38
|
+
const response = parseMenuResponse(payload);
|
|
39
|
+
this.pending.get(this.key(fromAid, response.id))?.resolve(response);
|
|
40
|
+
}
|
|
41
|
+
catch { /* Invalid responses cannot complete a pending request. */ }
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
close() {
|
|
45
|
+
for (const pending of this.pending.values())
|
|
46
|
+
pending.reject(new MenuClientError('AUN_DISCONNECTED', 'AUN channel disconnected'));
|
|
47
|
+
this.pending.clear();
|
|
48
|
+
}
|
|
49
|
+
async request(options) {
|
|
50
|
+
if (!isValidAid(options.fromAid) || !isValidAid(options.targetAid))
|
|
51
|
+
throw new MenuClientError('INVALID_ARGUMENT', 'Valid sender and target AIDs are required');
|
|
52
|
+
if (options.aunPath !== undefined || options.slotId !== undefined || options.encrypt === false)
|
|
53
|
+
throw new MenuClientError('INVALID_ARGUMENT', 'Managed Menu uses the daemon connection with E2EE; connection overrides are forbidden');
|
|
54
|
+
const request = { ...options.request, id: options.request.id || crypto.randomUUID() };
|
|
55
|
+
const validation = validateMenuRequest(request);
|
|
56
|
+
if (validation)
|
|
57
|
+
throw new MenuClientError(validation.code, validation.message);
|
|
58
|
+
const timeout = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
59
|
+
if (!Number.isFinite(timeout) || timeout < 1000 || timeout > MAX_TIMEOUT_MS)
|
|
60
|
+
throw new MenuClientError('INVALID_ARGUMENT', 'timeout must be between 1s and 120s');
|
|
61
|
+
const key = this.key(options.targetAid, request.id);
|
|
62
|
+
if (this.pending.has(key))
|
|
63
|
+
throw new MenuClientError('CLIENT_BUSY', 'Request ID is already in flight for this daemon');
|
|
64
|
+
const headers = buildMenuClientProtectedHeaders({ clientName: 'ec-menu-client', clientVersion: readInstalledEvolcoreVersion(), remoteAccessToken: options.remoteAccessToken ?? options.ecwebToken });
|
|
65
|
+
const attempts = request.type === 'menu.query' || request.type === 'menu.options' ? 2 : 1;
|
|
66
|
+
let timer;
|
|
67
|
+
try {
|
|
68
|
+
for (let attempt = 0; attempt < attempts; attempt++) {
|
|
69
|
+
const response = new Promise((resolve, reject) => {
|
|
70
|
+
this.pending.set(key, { resolve, reject });
|
|
71
|
+
timer = setTimeout(() => reject(new MenuClientError('TIMEOUT', 'Remote Menu response timed out')), timeout);
|
|
72
|
+
});
|
|
73
|
+
const sent = this.send({ to: options.targetAid, payload: request, encrypt: true, protected_headers: headers })
|
|
74
|
+
.then(result => {
|
|
75
|
+
if (!(result?.message_id ?? result?.message?.message_id))
|
|
76
|
+
throw new MenuClientError('SEND_FAILED', 'message.send returned no message_id');
|
|
77
|
+
});
|
|
78
|
+
try {
|
|
79
|
+
const [, value] = await Promise.all([sent, response]);
|
|
80
|
+
return value;
|
|
81
|
+
}
|
|
82
|
+
catch (error) {
|
|
83
|
+
if (!(error instanceof MenuClientError) || error.code !== 'TIMEOUT' || attempt + 1 === attempts)
|
|
84
|
+
throw error;
|
|
85
|
+
}
|
|
86
|
+
finally {
|
|
87
|
+
clearTimeout(timer);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
throw new MenuClientError('TIMEOUT', 'Remote Menu response timed out');
|
|
91
|
+
}
|
|
92
|
+
finally {
|
|
93
|
+
clearTimeout(timer);
|
|
94
|
+
this.pending.delete(key);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
key(target, id) { return JSON.stringify([target, id]); }
|
|
98
|
+
}
|
|
25
99
|
async function callClient(conn, method, params) {
|
|
26
100
|
try {
|
|
27
101
|
return await conn.call(method, params);
|
package/dist/aun/msg/group.js
CHANGED
|
@@ -52,7 +52,7 @@ function groupThreadId(args, payload, runtimeContext) {
|
|
|
52
52
|
}
|
|
53
53
|
function applyGroupRoutingPayloadFields(payload, args, runtimeContext) {
|
|
54
54
|
const threadId = groupThreadId(args, payload, runtimeContext);
|
|
55
|
-
if (threadId
|
|
55
|
+
if (threadId)
|
|
56
56
|
payload.thread_id = threadId;
|
|
57
57
|
}
|
|
58
58
|
function groupSendLogMetadata(args, payload, runtimeContext) {
|
|
@@ -248,7 +248,7 @@ export function resolveManagedGroupOperation(argv) {
|
|
|
248
248
|
return checked;
|
|
249
249
|
}
|
|
250
250
|
const memberType = values.get('--member-type');
|
|
251
|
-
if (memberType !== undefined && memberType !== 'human' && memberType !== '
|
|
251
|
+
if (memberType !== undefined && memberType !== 'human' && memberType !== 'agent')
|
|
252
252
|
return failGroup('INVALID_ARGUMENT', 'invalid --member-type');
|
|
253
253
|
extra.members = members;
|
|
254
254
|
extra.memberType = memberType;
|
package/dist/aun/msg/p2p.js
CHANGED
|
@@ -67,7 +67,7 @@ function messageLogContent(body) {
|
|
|
67
67
|
}
|
|
68
68
|
function applyRoutingPayloadFields(payload, args, runtimeContext, includeRuntimeRef = true) {
|
|
69
69
|
const threadId = msgSendThreadId(args, payload, runtimeContext);
|
|
70
|
-
if (threadId
|
|
70
|
+
if (threadId)
|
|
71
71
|
payload.thread_id = threadId;
|
|
72
72
|
if (!includeRuntimeRef)
|
|
73
73
|
return;
|
|
@@ -135,6 +135,7 @@ async function tryDaemonMsgSend(args, payload, runtimeContext, file) {
|
|
|
135
135
|
encrypt: args.encrypt,
|
|
136
136
|
thread,
|
|
137
137
|
returnPolicy: args.returnPolicy,
|
|
138
|
+
handoffContext: args.handoffContext,
|
|
138
139
|
originSessionId: runtimeContext.sessionId,
|
|
139
140
|
originMessageId: runtimeContext.messageId,
|
|
140
141
|
delegationToken: process.env[AGENT_DELEGATION_TOKEN_ENV],
|
|
@@ -213,6 +214,10 @@ async function appendMsgSendOutboundLog(args, result, opts) {
|
|
|
213
214
|
catch { /* daemon 不在或 IPC 失败都忽略 */ }
|
|
214
215
|
}
|
|
215
216
|
export async function msgSend(args) {
|
|
217
|
+
if (args.handoffContext !== undefined && (!process.env.EVOLCORE_SESSION_ID
|
|
218
|
+
|| !args.handoffContext.trim() || args.handoffContext.length > 64 * 1024)) {
|
|
219
|
+
return { ok: false, code: 'INVALID_HANDOFF_CONTEXT', error: 'handoff context requires a managed session and 1–65536 characters' };
|
|
220
|
+
}
|
|
216
221
|
const runtimeContext = await readBestTaskRuntimeContext();
|
|
217
222
|
// Contact admission is a daemon-owned policy for managed Agent tasks. A
|
|
218
223
|
// non-managed CLI caller may use any valid AUN sender identity and must not
|
package/dist/aun/msg/upload.js
CHANGED
|
@@ -20,6 +20,11 @@ export function isValidPayloadType(value) {
|
|
|
20
20
|
}
|
|
21
21
|
/** 单次上传最大大小(与 daemon sendFile 一致)。 */
|
|
22
22
|
export const MAX_AUN_ATTACHMENT_SIZE = 10 * 1024 * 1024;
|
|
23
|
+
/**
|
|
24
|
+
* Inline JSON-RPC 上传阈值。超过该大小直接使用上传会话,避免把文件
|
|
25
|
+
* Base64 编码后放进 storage.put_object 的 RPC 参数。
|
|
26
|
+
*/
|
|
27
|
+
export const INLINE_AUN_ATTACHMENT_SIZE = 64 * 1024;
|
|
23
28
|
/** server 端 inline 上限错误的识别特征。优先匹配错误消息里的“inline 上限”字样。 */
|
|
24
29
|
function isInlineLimitError(err) {
|
|
25
30
|
const msg = err?.message ?? '';
|
|
@@ -44,26 +49,29 @@ export async function uploadBufferAndBuildPayload(conn, ownerAid, data, filename
|
|
|
44
49
|
const objectKey = `shared/${crypto.randomUUID()}/${safeFilename}`;
|
|
45
50
|
const total = data.length;
|
|
46
51
|
const report = (phase, bytes) => opts?.onProgress?.({ phase, bytes, total });
|
|
47
|
-
//
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
52
|
+
// 小文件优先走 inline,兼容不支持上传会话的服务端;大文件直接走
|
|
53
|
+
// session,避免先构造一份可能超过 JSON-RPC 限制的 Base64 请求。
|
|
54
|
+
let useUploadSession = total > INLINE_AUN_ATTACHMENT_SIZE;
|
|
55
|
+
if (!useUploadSession) {
|
|
56
|
+
try {
|
|
57
|
+
report('inline', 0);
|
|
58
|
+
await conn.call('storage.put_object', {
|
|
59
|
+
object_key: objectKey,
|
|
60
|
+
content: data.toString('base64'),
|
|
61
|
+
content_type: contentType,
|
|
62
|
+
is_private: false,
|
|
63
|
+
overwrite: true,
|
|
64
|
+
});
|
|
65
|
+
report('inline', total);
|
|
66
|
+
}
|
|
67
|
+
catch (e) {
|
|
68
|
+
if (!isInlineLimitError(e))
|
|
69
|
+
throw e;
|
|
70
|
+
useUploadSession = true;
|
|
71
|
+
}
|
|
64
72
|
}
|
|
65
|
-
//
|
|
66
|
-
if (
|
|
73
|
+
// inline 被拒或文件超过 inline 阈值:session + HTTP PUT
|
|
74
|
+
if (useUploadSession) {
|
|
67
75
|
report('session-create', 0);
|
|
68
76
|
const session = await conn.call('storage.create_upload_session', {
|
|
69
77
|
object_key: objectKey,
|
|
@@ -144,9 +152,9 @@ export async function uploadBufferAndBuildPayload(conn, ownerAid, data, filename
|
|
|
144
152
|
/**
|
|
145
153
|
* 上传本地文件并构造发送用的 payload。
|
|
146
154
|
*
|
|
147
|
-
*
|
|
148
|
-
*
|
|
149
|
-
*
|
|
155
|
+
* 策略:小文件使用 storage.put_object(内联 base64);大文件直接使用
|
|
156
|
+
* storage.create_upload_session + HTTP PUT + storage.complete_upload,避免
|
|
157
|
+
* 将大文件编码后放入 JSON-RPC。此时通过 onProgress 上报字节级进度。
|
|
150
158
|
*/
|
|
151
159
|
export async function uploadFileAndBuildPayload(conn, ownerAid, filePath, opts) {
|
|
152
160
|
const absPath = path.resolve(filePath);
|
|
@@ -17,7 +17,10 @@ function pickUrl(value) {
|
|
|
17
17
|
return typeof value === 'string' && value.trim() ? value : undefined;
|
|
18
18
|
}
|
|
19
19
|
export async function storageUpload(aid, localFile, remotePath, opts) {
|
|
20
|
-
const fileBuffer = fs.readFileSync(localFile);
|
|
20
|
+
const fileBuffer = Buffer.isBuffer(localFile) ? localFile : fs.readFileSync(localFile);
|
|
21
|
+
return storageUploadBuffer(aid, fileBuffer, remotePath, opts);
|
|
22
|
+
}
|
|
23
|
+
export async function storageUploadBuffer(aid, fileBuffer, remotePath, opts) {
|
|
21
24
|
const contentType = opts?.contentType ?? 'application/octet-stream';
|
|
22
25
|
const sha256 = crypto.createHash('sha256').update(fileBuffer).digest('hex');
|
|
23
26
|
const createResult = await rpcCall(aid, 'storage.create_upload_session', {
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { storageUpload, storageLs, storageRm, storageQuota } from './client.js';
|
|
1
|
+
export { storageUpload, storageUploadBuffer, storageLs, storageRm, storageQuota } from './client.js';
|
|
2
2
|
export { storageDownload } from './download.js';
|