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.
Files changed (112) hide show
  1. package/CHANGELOG.md +59 -0
  2. package/bin/ec.js +12 -1
  3. package/bin/install-codex-managed-hooks.mjs +28 -1
  4. package/dist/agents/baseagent.js +96 -46
  5. package/dist/agents/claude-runner.js +54 -13
  6. package/dist/agents/codex/app-server-client.js +13 -5
  7. package/dist/agents/codex/catalog.js +1 -1
  8. package/dist/agents/codex/diagnostics.js +34 -1
  9. package/dist/agents/codex-runner.js +128 -42
  10. package/dist/agents/ecagent-runner.js +14 -18
  11. package/dist/agents/gemini-runner.js +24 -9
  12. package/dist/agents/runner-types.js +25 -17
  13. package/dist/aun/aid/agentmd.js +135 -10
  14. package/dist/aun/aid/client.js +1 -1
  15. package/dist/aun/aid/identity.js +1 -1
  16. package/dist/aun/aid/index.js +2 -1
  17. package/dist/aun/aid/managed-operation.js +53 -1
  18. package/dist/aun/aid/store.js +17 -13
  19. package/dist/aun/menu-client.js +74 -0
  20. package/dist/aun/msg/group.js +1 -1
  21. package/dist/aun/msg/managed-operation.js +1 -1
  22. package/dist/aun/msg/p2p.js +6 -1
  23. package/dist/aun/msg/upload.js +30 -22
  24. package/dist/aun/storage/client.js +4 -1
  25. package/dist/aun/storage/index.js +1 -1
  26. package/dist/channels/aun.js +329 -94
  27. package/dist/cli/agent-command.js +125 -5
  28. package/dist/cli/agent-create.js +151 -11
  29. package/dist/cli/agent.js +119 -6
  30. package/dist/cli/aun-commands.js +74 -7
  31. package/dist/cli/config.js +27 -8
  32. package/dist/cli/contact.js +63 -6
  33. package/dist/cli/daemon-commands.js +90 -25
  34. package/dist/cli/diag.js +31 -0
  35. package/dist/cli/handoff-command.js +35 -3
  36. package/dist/cli/index.js +7 -1
  37. package/dist/cli/managed-command-guard.js +3 -0
  38. package/dist/cli/menu-client.js +38 -23
  39. package/dist/cli/restart-monitor.js +57 -10
  40. package/dist/config/builtin-roles.js +2 -0
  41. package/dist/config/config-manager.js +15 -0
  42. package/dist/config/config-operation-service.js +165 -3
  43. package/dist/config/contact-operation-service.js +96 -7
  44. package/dist/config/resolved-config-op.js +66 -8
  45. package/dist/core/agent-application-service.js +2 -1
  46. package/dist/core/auth/authorization-audit.js +20 -12
  47. package/dist/core/auth/operation-authorizer.js +87 -8
  48. package/dist/core/auth/operation-catalog.js +51 -4
  49. package/dist/core/auth/remote-access-store.js +40 -1
  50. package/dist/core/bootstrap-service.js +1 -1
  51. package/dist/core/command/agent-control.js +54 -25
  52. package/dist/core/command/cli-intent-parser.js +18 -0
  53. package/dist/core/command/command-handler.js +152 -20
  54. package/dist/core/command/diag-service.js +118 -0
  55. package/dist/core/command/menu-catalog.js +13 -3
  56. package/dist/core/command/menu-handler.js +88 -31
  57. package/dist/core/command/menu-protocol.js +6 -0
  58. package/dist/core/command/menu-token-store.js +41 -8
  59. package/dist/core/command/slash-gate.js +34 -4
  60. package/dist/core/command/slash-handler.js +77 -12
  61. package/dist/core/event-catalog.js +5 -0
  62. package/dist/core/evolagent.js +3 -3
  63. package/dist/core/handoff/runtime.js +205 -27
  64. package/dist/core/handoff/store.js +193 -4
  65. package/dist/core/message/im-renderer.js +72 -21
  66. package/dist/core/message/message-bridge.js +69 -9
  67. package/dist/core/message/message-queue.js +26 -0
  68. package/dist/core/message/message-utils.js +1 -0
  69. package/dist/core/message/response-engine.js +48 -23
  70. package/dist/core/model/catalog-service.js +79 -49
  71. package/dist/core/model/context-window-suffix.js +33 -0
  72. package/dist/core/model/model-catalog.js +1 -1
  73. package/dist/core/model/model-permission.js +12 -3
  74. package/dist/core/permission/ec-command-parser.js +51 -12
  75. package/dist/core/permission/sandbox-runtime.js +36 -14
  76. package/dist/core/permission/tool-policy.js +47 -7
  77. package/dist/index.js +195 -31
  78. package/dist/ipc.js +54 -2
  79. package/dist/response-system/engines/v1/proactive-flow.js +59 -2
  80. package/dist/stats/billing.js +41 -15
  81. package/dist/stats/price-resolver.js +28 -10
  82. package/dist/utils/avatar-upload.js +282 -23
  83. package/dist/utils/cross-platform.js +30 -1
  84. package/dist/utils/error-utils.js +21 -0
  85. package/dist/utils/evolcore-version.js +129 -0
  86. package/dist/utils/logger.js +3 -1
  87. package/dist/utils/model-prices.jsonl +3 -3
  88. package/dist/utils/npm-ops.js +176 -35
  89. package/kits/docs/context-assembly.md +1 -0
  90. package/kits/docs/evolcore/INDEX.md +3 -1
  91. package/kits/docs/evolcore/agent-create.md +30 -11
  92. package/kits/docs/evolcore/agent.md +18 -8
  93. package/kits/docs/evolcore/aid.md +6 -4
  94. package/kits/docs/evolcore/codex-windows-sandbox.md +146 -0
  95. package/kits/docs/evolcore/config.md +7 -0
  96. package/kits/docs/evolcore/contact.md +12 -5
  97. package/kits/docs/evolcore/menu-client.md +11 -3
  98. package/kits/docs/evolcore/msg.md +1 -0
  99. package/kits/docs/prompt-loading-architecture.md +1 -0
  100. package/kits/docs/venues/group.md +12 -0
  101. package/kits/eck_manifest.json +12 -0
  102. package/kits/rules/03-identity.md +3 -1
  103. package/kits/templates/message-fragments/handoff-request-to-target.md +3 -3
  104. package/kits/templates/roles/admin.json +14 -2
  105. package/kits/templates/roles/member.json +3 -2
  106. package/kits/templates/roles/visitor.json +4 -3
  107. package/kits/templates/system-fragments/bootstrap.md +16 -5
  108. package/kits/templates/system-fragments/channel.md +1 -1
  109. package/kits/templates/system-fragments/session.md +1 -1
  110. package/package.json +6 -4
  111. package/skills/eclink/SKILL.md +0 -84
  112. package/skills/eclink/agents/openai.yaml +0 -4
@@ -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
- const agentType = opts.type || 'ai';
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
- return `---\naid: ${yamlDoubleQuote(opts.aid)}\nname: ${yamlDoubleQuote(agentName)}\ntype: ${yamlDoubleQuote(agentType)}\nversion: "1.0.0"\ndescription: ${yamlDoubleQuote(description)}\ntags:\n - evolcore\n---\n`;
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
- if (Buffer.byteLength(payload, 'utf8') > 4 * 1024) {
109
- errors.push('agent.md must not exceed 4KB before the signature block');
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
- const description = requiredString('description');
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
- if (description && description.length > 100) {
146
- errors.push('agent.md frontmatter description must be at most 100 characters');
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 && (!Array.isArray(values.tags)
149
- || values.tags.some(tag => typeof tag !== 'string' || !tag.trim()))) {
150
- errors.push('agent.md frontmatter tags must be an array of non-empty strings');
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
  }
@@ -39,7 +39,7 @@ export function suppressSdkLogs() {
39
39
  };
40
40
  }
41
41
  // ==================== SDK package ====================
42
- export const MIN_AUN_CORE_SDK = [0, 5, 6];
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));
@@ -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: "ai"\nversion: "1.0.0"\ndescription: "Local signing self-check"\ntags:\n - evolcore\n---\n`;
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)}` };
@@ -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, or agentmd put commands with an optional --format json suffix',
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
  }
@@ -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
- * The SDK's background realtime recovery path intentionally passes
84
- * `maxPages=1`; for a daemon this can strand a backlog after a tail sync.
85
- * This adapter only expands that sentinel value and leaves all other pull
86
- * options unchanged. It is isolated to the client that explicitly opts in.
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 0.5.13 publishes message events with dispatcher.enqueue(), then
114
- * advances its sequence tracker immediately. That is a transport watermark,
115
- * so a daemon restart can acknowledge a message before the application has
116
- * durably claimed it. The SDK keeps _publishAppEvent private, but the public
117
- * client is intentionally adapted here in one place: preserve the SDK's
118
- * normal publication path and wait for its dispatcher queue for pulled
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
- if (typeof publish !== 'function' || !dispatcher || typeof dispatcher.flush !== 'function')
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 dispatcher.flush();
142
+ await flushApplication();
139
143
  return result;
140
144
  };
141
145
  Object.defineProperty(target, RELIABLE_MESSAGE_DELIVERY_PATCH, {
@@ -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);
@@ -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 && typeof payload.thread_id !== 'string')
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 !== 'ai')
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;
@@ -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 && !payload.thread_id)
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
@@ -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
- // 1. 先按 inline 走
48
- let inlineRejected = false;
49
- try {
50
- report('inline', 0);
51
- await conn.call('storage.put_object', {
52
- object_key: objectKey,
53
- content: data.toString('base64'),
54
- content_type: contentType,
55
- is_private: false,
56
- overwrite: true,
57
- });
58
- report('inline', total);
59
- }
60
- catch (e) {
61
- if (!isInlineLimitError(e))
62
- throw e;
63
- inlineRejected = true;
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
- // 2. inline 被拒:降级到 session + HTTP PUT
66
- if (inlineRejected) {
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
- * 策略:先无脑 storage.put_object(内联 base64)。server 报“超过 inline 上限”
148
- * 时降级到 storage.create_upload_session + HTTP PUT + storage.complete_upload,
149
- * 此时通过 onProgress 上报字节级进度。
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';