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.
Files changed (197) hide show
  1. package/CHANGELOG.md +578 -561
  2. package/LICENSE +21 -21
  3. package/README.md +455 -455
  4. package/bin/codex-managed-acl.ps1 +160 -160
  5. package/bin/codex-managed-hook.mjs +208 -208
  6. package/bin/ec-safe-output.js +327 -327
  7. package/bin/ec.js +29 -29
  8. package/bin/install-codex-managed-hooks.mjs +555 -555
  9. package/dist/agents/claude-runner.js +4 -1
  10. package/dist/agents/codex-runner.js +7 -4
  11. package/dist/agents/ecagent-runner.js +14 -13
  12. package/dist/agents/gemini-runner.js +22 -2
  13. package/dist/aun/aid/agentmd.js +56 -4
  14. package/dist/aun/aid/index.js +1 -0
  15. package/dist/aun/aid/managed-operation.js +53 -1
  16. package/dist/aun/storage/client.js +4 -1
  17. package/dist/aun/storage/index.js +1 -1
  18. package/dist/cli/agent-command.js +186 -87
  19. package/dist/cli/agent-create.js +42 -4
  20. package/dist/cli/agent.js +103 -1
  21. package/dist/cli/aun-commands.js +360 -294
  22. package/dist/cli/bench.js +20 -20
  23. package/dist/cli/config.js +20 -20
  24. package/dist/cli/contact.js +10 -10
  25. package/dist/cli/ctl-command.js +22 -22
  26. package/dist/cli/daemon-commands.js +15 -15
  27. package/dist/cli/fs-command.js +67 -67
  28. package/dist/cli/handoff-command.js +7 -7
  29. package/dist/cli/index.js +77 -77
  30. package/dist/cli/link-rules.js +21 -21
  31. package/dist/cli/menu-client.js +11 -11
  32. package/dist/cli/net-check.js +22 -22
  33. package/dist/cli/queue-command.js +29 -29
  34. package/dist/cli/response.js +30 -30
  35. package/dist/cli/restart-monitor.js +30 -30
  36. package/dist/cli/stats.js +51 -51
  37. package/dist/cli/trigger-command.js +56 -56
  38. package/dist/config/builtin-roles.js +1 -0
  39. package/dist/config/config-manager.js +15 -0
  40. package/dist/config/config-operation-service.js +20 -3
  41. package/dist/config/resolved-config-op.js +39 -1
  42. package/dist/core/auth/operation-authorizer.js +62 -4
  43. package/dist/core/auth/operation-catalog.js +16 -0
  44. package/dist/core/command/agent-control.js +54 -25
  45. package/dist/core/command/cli-intent-parser.js +18 -0
  46. package/dist/core/command/command-handler.js +91 -16
  47. package/dist/core/command/menu-catalog.js +1 -0
  48. package/dist/core/command/menu-handler.js +41 -5
  49. package/dist/core/command/menu-protocol.js +1 -0
  50. package/dist/core/command/slash-handler.js +9 -9
  51. package/dist/core/event-catalog.js +5 -0
  52. package/dist/core/evolagent.js +3 -3
  53. package/dist/core/message/response-engine.js +12 -6
  54. package/dist/core/permission/tool-policy.js +45 -6
  55. package/dist/index.js +209 -162
  56. package/dist/stats/db.js +159 -159
  57. package/dist/stats/eck-vars.js +9 -9
  58. package/dist/stats/query.js +139 -139
  59. package/dist/stats/role-budget.js +6 -6
  60. package/dist/stats/writer.js +51 -51
  61. package/dist/utils/avatar-upload.js +282 -23
  62. package/dist/utils/error-dict.json +160 -160
  63. package/dist/utils/macos-autostart.js +22 -22
  64. package/dist/utils/model-prices.jsonl +23 -23
  65. package/dist/utils/welcome.js +22 -22
  66. package/ecagent/LICENSE +21 -21
  67. package/ecagent/NOTICE +11 -11
  68. package/ecagent/README.md +61 -61
  69. package/ecagent/dist/harness/compaction/branch-summarization.js +30 -30
  70. package/ecagent/dist/harness/compaction/compaction.js +83 -83
  71. package/ecagent/dist/harness/messages.js +7 -7
  72. package/kits/docs/GUIDE.md +20 -20
  73. package/kits/docs/INDEX.md +66 -66
  74. package/kits/docs/aun/CHEATSHEET.md +19 -19
  75. package/kits/docs/aun/SYNC_PROTOCOL.md +15 -15
  76. package/kits/docs/channels/aun.md +56 -56
  77. package/kits/docs/channels/feishu.md +56 -56
  78. package/kits/docs/context-assembly.md +365 -364
  79. package/kits/docs/eck_templates/GUIDE.template.md +22 -22
  80. package/kits/docs/eck_templates/INDEX.template.md +28 -28
  81. package/kits/docs/eck_templates/path-registry.template.md +33 -33
  82. package/kits/docs/eck_templates/runtime.template.md +19 -19
  83. package/kits/docs/evolcore/INDEX.md +71 -69
  84. package/kits/docs/evolcore/agent-create.md +170 -156
  85. package/kits/docs/evolcore/agent.md +83 -73
  86. package/kits/docs/evolcore/aid.md +64 -62
  87. package/kits/docs/evolcore/codex-windows-sandbox.md +146 -0
  88. package/kits/docs/evolcore/config.md +195 -195
  89. package/kits/docs/evolcore/contact.md +68 -68
  90. package/kits/docs/evolcore/ctl.md +46 -46
  91. package/kits/docs/evolcore/event.md +216 -216
  92. package/kits/docs/evolcore/fs-architecture.md +1215 -1215
  93. package/kits/docs/evolcore/fs.md +110 -110
  94. package/kits/docs/evolcore/group-collaboration.md +243 -243
  95. package/kits/docs/evolcore/group-fs.md +17 -17
  96. package/kits/docs/evolcore/group-rules.md +251 -251
  97. package/kits/docs/evolcore/group.md +152 -152
  98. package/kits/docs/evolcore/menu-client.md +34 -34
  99. package/kits/docs/evolcore/model.md +50 -50
  100. package/kits/docs/evolcore/msg.md +165 -165
  101. package/kits/docs/evolcore/response.md +75 -75
  102. package/kits/docs/evolcore/rpc.md +37 -37
  103. package/kits/docs/evolcore/self-summary.md +29 -29
  104. package/kits/docs/evolcore/stats.md +83 -83
  105. package/kits/docs/evolcore/storage.md +50 -50
  106. package/kits/docs/evolcore/trigger.md +575 -575
  107. package/kits/docs/identity/AID_PROFILE_SPEC.md +26 -26
  108. package/kits/docs/identity/PATH_OPS.md +16 -16
  109. package/kits/docs/identity/ROLE_DETAIL.md +25 -25
  110. package/kits/docs/identity/identity-tools.md +26 -26
  111. package/kits/docs/path-registry.md +62 -62
  112. package/kits/docs/prompt-loading-architecture.md +270 -269
  113. package/kits/docs/venues/aun-group.md +45 -45
  114. package/kits/docs/venues/aun-private.md +10 -10
  115. package/kits/docs/venues/client-desktop.md +10 -10
  116. package/kits/docs/venues/client-mobile.md +10 -10
  117. package/kits/docs/venues/feishu-group.md +13 -13
  118. package/kits/docs/venues/feishu-private.md +9 -9
  119. package/kits/docs/venues/group.md +25 -25
  120. package/kits/docs/venues/private.md +10 -10
  121. package/kits/eck_manifest.auxiliary.json +43 -43
  122. package/kits/eck_manifest.json +195 -183
  123. package/kits/eck_message_manifest.json +74 -74
  124. package/kits/migrations/README-role-config-v4.md +32 -32
  125. package/kits/migrations/migrate-contact-book-v2.mjs +748 -748
  126. package/kits/migrations/migrate-role-config-v4.mjs +614 -614
  127. package/kits/migrations/migrate-role-config-v5.mjs +365 -365
  128. package/kits/migrations/rename-config-file.mjs +99 -99
  129. package/kits/rules/01-overview.md +162 -162
  130. package/kits/rules/02-navigation.md +96 -96
  131. package/kits/rules/03-identity.md +39 -37
  132. package/kits/rules/04-relation.md +75 -75
  133. package/kits/rules/05-venue.md +60 -60
  134. package/kits/rules/06-channel.md +63 -63
  135. package/kits/schemas/_meta.json +53 -53
  136. package/kits/schemas/agent-config.schema.1.json +177 -177
  137. package/kits/schemas/agent-config.schema.10.json +395 -395
  138. package/kits/schemas/agent-config.schema.11.json +421 -421
  139. package/kits/schemas/agent-config.schema.12.json +427 -427
  140. package/kits/schemas/agent-config.schema.2.json +239 -239
  141. package/kits/schemas/agent-config.schema.3.json +119 -119
  142. package/kits/schemas/agent-config.schema.4.json +208 -208
  143. package/kits/schemas/agent-config.schema.5.json +326 -326
  144. package/kits/schemas/agent-config.schema.6.json +322 -322
  145. package/kits/schemas/agent-config.schema.7.json +303 -303
  146. package/kits/schemas/agent-config.schema.8.json +304 -304
  147. package/kits/schemas/agent-config.schema.9.json +364 -364
  148. package/kits/schemas/contact-book.schema.1.json +36 -36
  149. package/kits/schemas/contact-book.schema.2.json +43 -43
  150. package/kits/schemas/contact-book.schema.3.json +67 -67
  151. package/kits/schemas/daemon.schema.1.json +91 -91
  152. package/kits/schemas/daemon.schema.2.json +101 -101
  153. package/kits/schemas/daemon.schema.3.json +123 -123
  154. package/kits/schemas/daemon.schema.4.json +136 -136
  155. package/kits/schemas/daemon.schema.5.json +135 -135
  156. package/kits/schemas/daemon.schema.6.json +131 -131
  157. package/kits/schemas/defaults.schema.1.json +81 -81
  158. package/kits/schemas/defaults.schema.2.json +85 -85
  159. package/kits/schemas/defaults.schema.3.json +73 -73
  160. package/kits/schemas/defaults.schema.4.json +107 -107
  161. package/kits/schemas/defaults.schema.5.json +119 -119
  162. package/kits/schemas/menu-exec-schema-commands.md +208 -208
  163. package/kits/schemas/migrations/README.md +30 -30
  164. package/kits/schemas/relation-config.schema.1.json +158 -158
  165. package/kits/schemas/relation-config.schema.2.json +73 -73
  166. package/kits/schemas/relation-config.schema.3.json +50 -50
  167. package/kits/schemas/relation-config.schema.4.json +47 -47
  168. package/kits/schemas/relation-config.schema.5.json +47 -47
  169. package/kits/schemas/relation-config.schema.6.json +46 -46
  170. package/kits/schemas/relation-config.schema.7.json +59 -59
  171. package/kits/schemas/relation-config.schema.8.json +103 -103
  172. package/kits/schemas/role-config.schema.1.json +201 -201
  173. package/kits/schemas/role-registry.schema.1.json +35 -35
  174. package/kits/schemas/single-session.schema.1.json +31 -31
  175. package/kits/schemas/single-session.schema.2.json +57 -57
  176. package/kits/schemas/single-session.schema.3.json +32 -32
  177. package/kits/templates/bootstrap-welcome.md +17 -17
  178. package/kits/templates/message-fragments/handoff-context-to-target.md +9 -9
  179. package/kits/templates/message-fragments/handoff-request-to-target.md +19 -19
  180. package/kits/templates/message-fragments/handoff-response-to-origin.md +9 -9
  181. package/kits/templates/message-fragments/inject-default.md +2 -2
  182. package/kits/templates/message-fragments/item.md +2 -2
  183. package/kits/templates/roles/admin.json +96 -84
  184. package/kits/templates/roles/member.json +97 -96
  185. package/kits/templates/roles/owner.json +9 -9
  186. package/kits/templates/roles/visitor.json +86 -85
  187. package/kits/templates/system-fragments/baseagent.md +14 -14
  188. package/kits/templates/system-fragments/bootstrap.md +38 -29
  189. package/kits/templates/system-fragments/channel.md +64 -64
  190. package/kits/templates/system-fragments/commands.md +28 -28
  191. package/kits/templates/system-fragments/identity.md +13 -13
  192. package/kits/templates/system-fragments/relation.md +19 -19
  193. package/kits/templates/system-fragments/session.md +59 -59
  194. package/kits/templates/system-fragments/venue.md +31 -31
  195. package/package.json +98 -98
  196. package/skills/eclink/SKILL.md +0 -84
  197. package/skills/eclink/agents/openai.yaml +0 -4
@@ -192,7 +192,8 @@ function authorizeCommandInternal(ctx, resolvedConfigCommand) {
192
192
  };
193
193
  }
194
194
  // Any authenticated role may inspect the current Agent through the daemon's
195
- // redacted view. Cross-Agent inspection is handled by requiresDaemonOwner().
195
+ // redacted view. Management roles may inspect another Agent's redacted view;
196
+ // mutation and reload remain target-scoped below.
196
197
  if (operation === 'agent.show' && isCurrentAgentTarget(ctx)) {
197
198
  return {
198
199
  allow: true,
@@ -203,6 +204,17 @@ function authorizeCommandInternal(ctx, resolvedConfigCommand) {
203
204
  matchedRule: 'self-agent-read',
204
205
  };
205
206
  }
207
+ if (operation === 'agent.persona' && isCurrentAgentTarget(ctx)
208
+ && (role === 'owner' || hasDaemonOwnerRole(ctx))) {
209
+ return {
210
+ allow: true,
211
+ operation,
212
+ scope: intent.scope,
213
+ role,
214
+ dangerous: opMeta.dangerous,
215
+ matchedRule: role === 'owner' ? 'agent-owner-persona' : 'daemon-owner-persona',
216
+ };
217
+ }
206
218
  if (resolvedConfigCommand) {
207
219
  const targetCheck = checkResolvedConfigTarget(ctx, resolvedConfigCommand);
208
220
  if (!targetCheck.ok) {
@@ -215,7 +227,7 @@ function authorizeCommandInternal(ctx, resolvedConfigCommand) {
215
227
  if (!isManagementRole(role) && intent.scope !== 'relation') {
216
228
  return denyDecision(ctx, 'SCOPE_MISMATCH', `Role ${role} may only access relation-scoped config`, operation, intent.scope, opMeta.dangerous);
217
229
  }
218
- if (role === 'owner') {
230
+ if (role === 'owner' && isCurrentAgentTarget(ctx)) {
219
231
  return {
220
232
  allow: true,
221
233
  operation,
@@ -225,6 +237,22 @@ function authorizeCommandInternal(ctx, resolvedConfigCommand) {
225
237
  matchedRule: 'agent-owner',
226
238
  };
227
239
  }
240
+ // The daemon control identity is allowed to inspect any Agent config,
241
+ // including sensitive fields. This is intentionally handled before
242
+ // role-definition matching because daemon-owner subjects do not need an
243
+ // Agent relation role to read the daemon's local configuration store.
244
+ if (resolvedConfigCommand.kind === 'field'
245
+ && resolvedConfigCommand.subcommand === 'get'
246
+ && hasDaemonOwnerRole(ctx)) {
247
+ return {
248
+ allow: true,
249
+ operation,
250
+ scope: intent.scope,
251
+ role: 'daemon-owner',
252
+ dangerous: opMeta.dangerous,
253
+ matchedRule: 'daemon-owner-config-read',
254
+ };
255
+ }
228
256
  }
229
257
  const roleDef = getRoleDefinition(role, ctx.selfAid);
230
258
  if (!roleDef) {
@@ -357,10 +385,25 @@ function requiresDaemonOwner(ctx, resolvedConfigCommand) {
357
385
  || resolvedConfigCommand.configScope === 'process'
358
386
  || resolvedConfigCommand.configScope === 'defaults'))
359
387
  return true;
388
+ // Sensitive reads of another Agent's config are daemon-scoped. Agent
389
+ // owner/admin roles may inspect safe fields cross-Agent, but only the
390
+ // daemon control identity may access config.read targets that are not the
391
+ // current Agent.
392
+ if (operation === 'config.read'
393
+ && resolvedConfigCommand?.kind === 'field'
394
+ && resolvedConfigCommand.configScope === 'agent'
395
+ && !isCurrentAgentTarget(ctx)) {
396
+ return true;
397
+ }
360
398
  if (operation === 'agent.reload' && hasDaemonOwnerRole(ctx)) {
361
399
  return true;
362
400
  }
363
- if (operation === 'agent.show' || operation === 'agent.reload') {
401
+ // Avatar publication is an Agent-scoped mutation for Agent channels, but
402
+ // the trusted control plane may publish it for a selected Agent target.
403
+ if (operation === 'ec.aid.avatar' && hasDaemonOwnerRole(ctx)) {
404
+ return true;
405
+ }
406
+ if (operation === 'agent.reload') {
364
407
  return !isCurrentAgentTarget(ctx);
365
408
  }
366
409
  if (operation.startsWith('trigger.')) {
@@ -529,6 +572,12 @@ function checkConfigFieldPolicy(ctx, policy, resolvedConfigOp) {
529
572
  return { ok: false, reason: 'Resolved config operation is required' };
530
573
  const field = resolvedConfigOp.field;
531
574
  if (!resolvedConfigOp.route || (resolvedConfigOp.fieldRule.class !== 'safe-scalar' && resolvedConfigOp.fieldRule.class !== 'safe-readonly-object')) {
575
+ if (policy === 'behavior-read'
576
+ && !isCurrentAgentTarget(ctx)
577
+ && hasDaemonOwnerRole(ctx)
578
+ && resolvedConfigOp.route) {
579
+ return { ok: true };
580
+ }
532
581
  return { ok: false, reason: `Config field ${field} is not available to user roles` };
533
582
  }
534
583
  if (policy === 'role-overridable-write'
@@ -541,8 +590,12 @@ function checkConfigFieldPolicy(ctx, policy, resolvedConfigOp) {
541
590
  const fieldPermission = roleDef
542
591
  ? resolveRoleFieldPermission(roleDef.permissions || {}, field)
543
592
  : undefined;
544
- if (policy === 'behavior-read')
593
+ if (policy === 'behavior-read') {
594
+ // Only the daemon control identity may read sensitive config fields from
595
+ // another Agent. Agent owners retain unrestricted reads for their own
596
+ // target via the self-target fast path above.
545
597
  return { ok: true };
598
+ }
546
599
  if (fieldPermission && !fieldPermission.allowOverride) {
547
600
  return { ok: false, reason: `Role ${ctx.role} cannot override ${field}` };
548
601
  }
@@ -579,6 +632,11 @@ function checkResolvedConfigTarget(ctx, command) {
579
632
  return { ok: true };
580
633
  }
581
634
  if (command.configScope === 'agent') {
635
+ // Management roles may read another Agent's non-sensitive config fields;
636
+ // sensitive fields are rejected by the field policy. Writes stay self-only.
637
+ if (command.subcommand === 'get' && (isManagementRole(ctx.role) || hasDaemonOwnerRole(ctx))) {
638
+ return { ok: true };
639
+ }
582
640
  if (!ctx.selfAid || !command.self || command.self !== ctx.selfAid) {
583
641
  return { ok: false, reason: 'Only the current agent can be targeted' };
584
642
  }
@@ -601,6 +601,14 @@ const OPERATIONS = [
601
601
  description: '获取 agent 配置(含凭证)',
602
602
  sources: ['menu', 'menu.cli', 'control'],
603
603
  },
604
+ {
605
+ id: 'agent.persona',
606
+ category: 'write-agent',
607
+ dangerous: false,
608
+ defaultScopes: ['agent'],
609
+ description: '读取或更新当前 Agent 的 persona.md',
610
+ sources: ['agent-tool', 'control'],
611
+ },
604
612
  {
605
613
  id: 'agent.create',
606
614
  category: 'process',
@@ -944,6 +952,14 @@ const OPERATIONS = [
944
952
  description: 'Sign and publish the current Agent local agent.md',
945
953
  sources: ['agent-tool'],
946
954
  },
955
+ {
956
+ id: 'ec.aid.avatar',
957
+ category: 'write-agent',
958
+ dangerous: true,
959
+ defaultScopes: ['agent'],
960
+ description: 'Upload and publish the current Agent avatar',
961
+ sources: ['agent-tool', 'menu', 'menu.cli'],
962
+ },
947
963
  {
948
964
  id: 'ec.msg.send',
949
965
  category: 'write-own',
@@ -6,11 +6,11 @@ import path from 'path';
6
6
  import { ConfigTarget, ensureFile as cfgEnsure, read as cfgRead, write as cfgWrite, } from '../../config/config-manager.js';
7
7
  import { CreateStatusWriter, readCreateStatus } from '../message/create-status.js';
8
8
  import { agentProjectRootFromDefaults, deriveAgentProjectPath } from '../../utils/project-path.js';
9
- import { uploadAvatar } from '../../utils/avatar-upload.js';
9
+ import { uploadAvatar, uploadAvatarUrl } from '../../utils/avatar-upload.js';
10
10
  import { agentmdGet, agentmdPut, updateAgentMdFrontmatterName } from '../../aun/aid/agentmd.js';
11
11
  import { isValidAid } from '../../aun/aid/validation.js';
12
12
  import { isSupportedBaseagent } from '../../agents/baseagent.js';
13
- const SUPPORTED_AGENT_PATCH_FIELDS = new Set(['aid', 'name', 'avatar', 'active_baseagent', 'baseagents', 'projects', 'owners', 'chatmode', 'channels', 'channelOwners']);
13
+ const SUPPORTED_AGENT_PATCH_FIELDS = new Set(['aid', 'name', 'active_baseagent', 'baseagents', 'projects', 'owners', 'chatmode', 'channels', 'channelOwners']);
14
14
  const HIDDEN_VALUE = '[hidden]';
15
15
  const SENSITIVE_CONFIG_KEYS = new Set([
16
16
  'apikey',
@@ -48,6 +48,42 @@ function classifyAgentMdError(error) {
48
48
  return 'INVALID_ARGS';
49
49
  return 'UPLOAD_FAILED';
50
50
  }
51
+ /** Canonical avatar mutation for the `aid.avatar` menu action. */
52
+ export async function execAidAvatar(args) {
53
+ const a = args ?? {};
54
+ const aid = typeof a.aid === 'string' ? a.aid.trim().replace(/^@/, '') : '';
55
+ if (!aid)
56
+ return { error: '缺少 aid', code: 'INVALID_ARGS' };
57
+ if (!isValidAid(aid))
58
+ return { error: `无效 aid: ${aid}`, code: 'INVALID_ARGS' };
59
+ const hasData = a.data !== undefined;
60
+ const hasUrl = a.url !== undefined;
61
+ if ((hasData ? 1 : 0) + (hasUrl ? 1 : 0) > 1) {
62
+ return { error: '头像数据源必须在 data 与 url 中二选一', code: 'INVALID_ARGS' };
63
+ }
64
+ if (!hasData && !hasUrl) {
65
+ return { error: '缺少头像数据源:需要 data 或 url', code: 'INVALID_ARGS' };
66
+ }
67
+ const result = hasUrl
68
+ ? await uploadAvatarUrl(aid, a.url, { aunPath: resolveRoot() })
69
+ : await uploadAvatar(aid, a.data, { aunPath: resolveRoot() });
70
+ if (!result.ok) {
71
+ return { error: `头像上传失败: ${result.error}`, code: result.code ?? 'UPLOAD_FAILED' };
72
+ }
73
+ return {
74
+ data: {
75
+ aid,
76
+ avatar: result.publicUrl,
77
+ objectKey: result.objectKey,
78
+ sha256: result.sha256,
79
+ mime: result.mime,
80
+ width: result.width,
81
+ height: result.height,
82
+ aspectRatio: result.aspectRatio,
83
+ saved: true,
84
+ },
85
+ };
86
+ }
51
87
  async function updateAgentMdName(aid, value) {
52
88
  const name = typeof value === 'string' ? value.trim() : '';
53
89
  if (!name)
@@ -299,6 +335,7 @@ async function runCreateInBackground(opts, w, service) {
299
335
  aid: opts.aid, name: opts.name, baseagent: opts.baseagent,
300
336
  project: opts.project, owner: opts.owner,
301
337
  description: opts.description,
338
+ tags: opts.tags,
302
339
  onPhase: (phase, state, detail) => {
303
340
  if (state === 'begin') {
304
341
  curPhase = phase;
@@ -398,6 +435,7 @@ export async function execAgentAction(action, args, peerId, eventBus, service) {
398
435
  const description = typeof a.description === 'string' && a.description.trim()
399
436
  ? a.description.trim()
400
437
  : undefined;
438
+ const tags = Array.isArray(a.tags) ? a.tags : undefined;
401
439
  // D3: accepted=true 必须意味着后续 query 能查到该 AID 的创建状态。
402
440
  let progressWriter;
403
441
  try {
@@ -410,7 +448,7 @@ export async function execAgentAction(action, args, peerId, eventBus, service) {
410
448
  void runCreateInBackground({
411
449
  aid: a.aid, name: a.name, baseagent: a.baseagent,
412
450
  project: a.project, owner,
413
- model, effort, description, chatmode: a.chatmode,
451
+ model, effort, description, tags, chatmode: a.chatmode,
414
452
  eventBus,
415
453
  }, progressWriter, service).catch(e => logger.error(`[agent-control] runCreateInBackground unhandled ${a.aid}: ${e?.message || e}`));
416
454
  return { data: { accepted: true, aid: a.aid } };
@@ -461,14 +499,6 @@ export async function execAgentAction(action, args, peerId, eventBus, service) {
461
499
  if (action === 'update') {
462
500
  const result = await execAgentUpdate(a);
463
501
  if (!('error' in result)) {
464
- if (result.data?.avatar) {
465
- eventBus?.publish({
466
- type: 'agent:avatar_updated',
467
- aid: a.aid,
468
- avatar: result.data.avatar,
469
- timestamp: Date.now(),
470
- });
471
- }
472
502
  eventBus?.publish({
473
503
  type: 'agent:updated',
474
504
  aid: a.aid,
@@ -501,12 +531,6 @@ export async function execAgentUpdate(args) {
501
531
  if (p.aid !== undefined) {
502
532
  return { error: 'aid 不可修改(AUN 身份绑定,如需换 AID 请删除后重建)', code: 'INVALID_ARGS' };
503
533
  }
504
- if (p.avatar !== undefined) {
505
- const otherKeys = Object.keys(p).filter(k => k !== 'avatar');
506
- if (otherKeys.length > 0) {
507
- return { error: 'avatar 不能与其他 patch 字段同时提交,请拆分为两次 update', code: 'INVALID_ARGS' };
508
- }
509
- }
510
534
  if (p.name !== undefined) {
511
535
  const otherKeys = Object.keys(p).filter(k => k !== 'name');
512
536
  if (otherKeys.length > 0) {
@@ -522,13 +546,6 @@ export async function execAgentUpdate(args) {
522
546
  const config = cfgRead(ConfigTarget.Agent, { self: a.aid });
523
547
  if (!config)
524
548
  return { error: `Agent "${a.aid}" not found`, code: 'NOT_FOUND' };
525
- if (p.avatar !== undefined) {
526
- const result = await uploadAvatar(a.aid, p.avatar, { aunPath: resolveRoot() });
527
- if (!result.ok) {
528
- return { error: `头像上传失败: ${result.error}`, code: result.code ?? 'UPLOAD_FAILED' };
529
- }
530
- return { data: { aid: a.aid, avatar: result.publicUrl, saved: true } };
531
- }
532
549
  let touched = false;
533
550
  const data = { aid: a.aid };
534
551
  const hasConfigPatch = p.active_baseagent !== undefined || p.baseagents !== undefined || p.projects !== undefined || p.owners !== undefined || p.chatmode !== undefined || p.channels !== undefined || p.channelOwners !== undefined;
@@ -564,7 +581,19 @@ export async function execAgentUpdate(args) {
564
581
  delete config.projects;
565
582
  }
566
583
  if (p.owners !== undefined) {
567
- config.owners = p.owners;
584
+ if (!Array.isArray(p.owners) || p.owners.length === 0) {
585
+ return { error: 'owners must contain at least one owner', code: 'LAST_OWNER_PROTECTED' };
586
+ }
587
+ const owners = [];
588
+ for (const rawOwner of p.owners) {
589
+ if (typeof rawOwner !== 'string' || !rawOwner.trim() || !isValidAid(rawOwner.trim())) {
590
+ return { error: `Invalid owner AID: ${JSON.stringify(rawOwner)}`, code: 'INVALID_ARGS' };
591
+ }
592
+ const owner = rawOwner.trim();
593
+ if (!owners.includes(owner))
594
+ owners.push(owner);
595
+ }
596
+ config.owners = owners;
568
597
  touched = true;
569
598
  }
570
599
  if (p.chatmode !== undefined) {
@@ -156,6 +156,14 @@ function parseStatsCommand(argv, source) {
156
156
  }
157
157
  function parseAgentCommand(argv, source) {
158
158
  const subcmd = argv[1];
159
+ if (subcmd === 'persona') {
160
+ const tail = argv.at(-2) === '--format' && argv.at(-1) === 'json' ? argv.slice(0, -2) : argv;
161
+ const isShow = tail.length === 3 && tail[2] === 'show';
162
+ const isWrite = tail.length === 4 && (tail[2] === '--text' || tail[2] === '--file');
163
+ return isShow || isWrite
164
+ ? recognized('agent.persona', 'agent', source, {}, argv)
165
+ : raw(source, argv, 'Invalid agent persona command');
166
+ }
159
167
  const parsed = parseArgs(argv.slice(2));
160
168
  const args = parsed.args;
161
169
  if (parsed.positionals.length > 0)
@@ -170,6 +178,16 @@ function parseAgentCommand(argv, source) {
170
178
  }
171
179
  function parseAidCommand(argv, source) {
172
180
  const subcmd = argv[1];
181
+ if (subcmd === 'avatar') {
182
+ const tail = argv.slice(2);
183
+ const aid = tail[0];
184
+ const sourceFlag = tail[1];
185
+ if (!aid || (sourceFlag !== '--file' && sourceFlag !== '--url') || !tail[2] || tail[2].startsWith('-')
186
+ || (tail.length !== 3 && !(tail.length === 5 && tail[3] === '--format' && tail[4] === 'json'))) {
187
+ return raw(source, argv, 'Invalid aid avatar command');
188
+ }
189
+ return recognized('ec.aid.avatar', 'agent', source, { aid, ...(sourceFlag === '--file' ? { file: tail[2] } : { url: tail[2] }) }, argv, true);
190
+ }
173
191
  const parsed = parseArgs(argv.slice(2));
174
192
  const args = parsed.args;
175
193
  if (parsed.positionals.length > 0)
@@ -1399,17 +1399,17 @@ export class CommandHandler {
1399
1399
  const nextStr = reg.trigger.nextFireAt ? new Date(reg.trigger.nextFireAt).toLocaleString() : '未计算';
1400
1400
  return `✅ 触发器已注册:**${reg.trigger.name}**\n下次触发:${nextStr}`;
1401
1401
  }
1402
- return `❌ 未知子命令。用法:
1403
- /trigger — 查看活跃触发器
1404
- /trigger list [--all] — 查看所有触发器
1405
- /trigger create <参数> — 创建触发器(set 为兼容别名)
1406
- /trigger update <名称|ID> <参数> — 修改触发器
1407
- 常用参数:--delay/--at/--cron/--every、--prompt、--description、--model、--effort、--permission(省略则继承;update 可用 inherit 清除覆盖)、--feedback、--target-channel/--target-channel-id、--trigger-thread、--baseagent、--on-error、--retry-attempts/--retry-backoff、--script-runtime/--script-args/--script-timeout
1408
- /trigger enable <名称|ID> — 启用触发器
1409
- /trigger disable <名称|ID> — 暂停触发器
1410
- /trigger show <名称|ID> — 查看触发器详情
1411
- /trigger run <名称|ID> [--dry-run] — 手动触发
1412
- /trigger cancel <名称|ID> — 禁用触发器(disable 的兼容别名)
1402
+ return `❌ 未知子命令。用法:
1403
+ /trigger — 查看活跃触发器
1404
+ /trigger list [--all] — 查看所有触发器
1405
+ /trigger create <参数> — 创建触发器(set 为兼容别名)
1406
+ /trigger update <名称|ID> <参数> — 修改触发器
1407
+ 常用参数:--delay/--at/--cron/--every、--prompt、--description、--model、--effort、--permission(省略则继承;update 可用 inherit 清除覆盖)、--feedback、--target-channel/--target-channel-id、--trigger-thread、--baseagent、--on-error、--retry-attempts/--retry-backoff、--script-runtime/--script-args/--script-timeout
1408
+ /trigger enable <名称|ID> — 启用触发器
1409
+ /trigger disable <名称|ID> — 暂停触发器
1410
+ /trigger show <名称|ID> — 查看触发器详情
1411
+ /trigger run <名称|ID> [--dry-run] — 手动触发
1412
+ /trigger cancel <名称|ID> — 禁用触发器(disable 的兼容别名)
1413
1413
  /trigger delete <名称|ID> — 删除触发器`;
1414
1414
  }
1415
1415
  async handleTrigger(content, channel, channelId, peerId, isAdmin, messageId, chatType, threadId, isDaemonOwner = false, canApprovePersistentFullAccess = isDaemonOwner) {
@@ -1946,10 +1946,10 @@ export class CommandHandler {
1946
1946
  if (sessionSelfAid && sessionSelfAid !== grantSelfAid) {
1947
1947
  return { ok: false, code: 'INVALID_DELEGATION', error: 'Delegation self agent does not match the active session' };
1948
1948
  }
1949
- const requiresSelfAid = parsed.command.kind === 'show' || parsed.command.kind === 'agentmd.put';
1949
+ const requiresSelfAid = parsed.command.kind === 'show' || parsed.command.kind === 'agentmd.put' || parsed.command.kind === 'avatar';
1950
1950
  if (requiresSelfAid
1951
1951
  && parsed.command.aid !== grantSelfAid
1952
- && !hasTrustedFullAccessDelegation(delegation.grant)) {
1952
+ && (parsed.command.kind === 'avatar' || !hasTrustedFullAccessDelegation(delegation.grant))) {
1953
1953
  return { ok: false, code: 'SCOPE_MISMATCH', error: 'Only the current Agent AID can be targeted' };
1954
1954
  }
1955
1955
  let conversationId;
@@ -1988,12 +1988,87 @@ export class CommandHandler {
1988
1988
  });
1989
1989
  if (!decision.allow)
1990
1990
  return { ok: false, code: decision.code, error: decision.reason };
1991
+ let avatarBuffer;
1992
+ if (parsed.command.kind === 'avatar' && parsed.command.filePath) {
1993
+ const filePath = String(parsed.command.filePath || '').trim();
1994
+ if (typeof session.projectPath !== 'string' || !session.projectPath.trim()) {
1995
+ return { ok: false, code: 'INVALID_AVATAR_PATH', error: 'Avatar file requires a current session project path' };
1996
+ }
1997
+ const projectPath = path.resolve(session.projectPath);
1998
+ const managedTempPath = this.getManagedSessionTempDir(session);
1999
+ const resolvedPath = path.isAbsolute(filePath) ? path.resolve(filePath) : path.resolve(projectPath, filePath);
2000
+ const allowedRoots = [projectPath, managedTempPath].filter((value) => !!value);
2001
+ if (!filePath || !allowedRoots.some(root => isSameOrDescendant(resolvedPath, root))) {
2002
+ return { ok: false, code: 'INVALID_AVATAR_PATH', error: 'Avatar file must be inside the current session project or managed TMPDIR' };
2003
+ }
2004
+ if (isHClassPath(resolvedPath) || isLClassPath(resolvedPath)) {
2005
+ return { ok: false, code: 'PROTECTED_AVATAR_PATH', error: 'Avatar file cannot be read from a protected EvolCore path' };
2006
+ }
2007
+ let fd;
2008
+ try {
2009
+ const realProjectPath = fs.realpathSync(projectPath);
2010
+ const realPath = fs.realpathSync(resolvedPath);
2011
+ let realManagedTempPath;
2012
+ try {
2013
+ realManagedTempPath = managedTempPath ? fs.realpathSync(managedTempPath) : undefined;
2014
+ }
2015
+ catch { }
2016
+ const realAllowedRoots = [realProjectPath, realManagedTempPath].filter((value) => !!value);
2017
+ if (!realAllowedRoots.some(root => isSameOrDescendant(realPath, root))) {
2018
+ return { ok: false, code: 'INVALID_AVATAR_PATH', error: 'Avatar file resolves outside the current session project or managed TMPDIR' };
2019
+ }
2020
+ if (isHClassPath(realPath) || isLClassPath(realPath)) {
2021
+ return { ok: false, code: 'PROTECTED_AVATAR_PATH', error: 'Avatar file cannot be read from a protected EvolCore path' };
2022
+ }
2023
+ const stat = fs.lstatSync(resolvedPath);
2024
+ if (stat.isSymbolicLink() || !stat.isFile()) {
2025
+ return { ok: false, code: 'INVALID_AVATAR_FILE', error: 'Avatar source must be a regular non-symlink file' };
2026
+ }
2027
+ if (realManagedTempPath && isSameOrDescendant(realPath, realManagedTempPath) && stat.nlink > 1) {
2028
+ return { ok: false, code: 'INVALID_AVATAR_PATH', error: 'Avatar source cannot be a hard link inside managed TMPDIR' };
2029
+ }
2030
+ if (stat.size < 1 || stat.size > 500 * 1024) {
2031
+ return { ok: false, code: 'INVALID_AVATAR_FILE', error: 'Avatar source must be between 1 byte and 500KB' };
2032
+ }
2033
+ // Open the user-supplied node with no-follow semantics after checking
2034
+ // its canonical ancestors; the bytes passed to the uploader come from
2035
+ // this descriptor rather than from a second path lookup.
2036
+ fd = fs.openSync(resolvedPath, fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW);
2037
+ const finalStat = fs.fstatSync(fd);
2038
+ if (!finalStat.isFile() || finalStat.size < 1 || finalStat.size > 500 * 1024 || finalStat.nlink !== stat.nlink) {
2039
+ return { ok: false, code: 'INVALID_AVATAR_FILE', error: 'Avatar source changed during validation' };
2040
+ }
2041
+ avatarBuffer = fs.readFileSync(fd);
2042
+ }
2043
+ catch (error) {
2044
+ return { ok: false, code: 'INVALID_AVATAR_FILE', error: error instanceof Error ? error.message : String(error) };
2045
+ }
2046
+ finally {
2047
+ if (fd !== undefined)
2048
+ fs.closeSync(fd);
2049
+ }
2050
+ }
1991
2051
  try {
2052
+ const result = await executeManagedAidOperation(parsed.command, {
2053
+ showView: managedAidShowViewForRole(subject.role),
2054
+ ...(avatarBuffer ? { avatarBuffer } : {}),
2055
+ });
2056
+ if (result.kind === 'avatar' && result.avatar) {
2057
+ this.eventBus.publish({
2058
+ type: 'agent:avatar_updated',
2059
+ aid: result.aid,
2060
+ avatar: result.avatar,
2061
+ ...(result.sha256 ? { sha256: result.sha256 } : {}),
2062
+ ...(result.mime ? { mime: result.mime } : {}),
2063
+ ...(result.width ? { width: result.width } : {}),
2064
+ ...(result.height ? { height: result.height } : {}),
2065
+ ...(result.aspectRatio ? { aspectRatio: result.aspectRatio } : {}),
2066
+ timestamp: Date.now(),
2067
+ });
2068
+ }
1992
2069
  return {
1993
2070
  ok: true,
1994
- result: await executeManagedAidOperation(parsed.command, {
1995
- showView: managedAidShowViewForRole(subject.role),
1996
- }),
2071
+ result,
1997
2072
  };
1998
2073
  }
1999
2074
  catch (error) {
@@ -79,6 +79,7 @@ const ENTRIES = [
79
79
  { id: 'chatmode.agent.nothuman', type: 'setting', contexts: ['agent'], name: 'chatmode', label: 'Agent 非人类对话模式', section: 'agent', order: 43, query: { cmd: '/chatmode', args: { scope: 'agent', field: 'nothuman' }, operation: 'chatmode.current', scope: 'agent' }, options: { cmd: '/chatmode', args: { scope: 'agent', field: 'nothuman' }, operation: 'chatmode.current', scope: 'agent' }, update: { cmd: '/chatmode', args: { scope: 'agent', field: 'nothuman' }, operation: 'chatmode.update', scope: 'agent' }, control: 'select' },
80
80
  { id: 'activity.agent', type: 'setting', contexts: ['agent'], name: 'activity', label: 'Agent 中间输出', section: 'agent', order: 50, query: { cmd: '/activity', args: { scope: 'agent' }, operation: 'activity.current', scope: 'agent' }, options: { cmd: '/activity', args: { scope: 'agent' }, operation: 'activity.current', scope: 'agent' }, update: { cmd: '/activity', args: { scope: 'agent' }, operation: 'activity.update', scope: 'agent' }, control: 'select' },
81
81
  { id: 'observable.agent', type: 'setting', contexts: ['agent'], name: 'observable', label: '观察模式', section: 'agent', order: 60, query: { cmd: '/observable', operation: 'observable.current', scope: 'agent' }, update: { cmd: '/observable', operation: 'observable.update', scope: 'agent' }, valueSchema: { type: 'boolean' }, control: 'toggle' },
82
+ { id: 'aid.avatar', type: 'action', contexts: ['agent'], name: 'aid', label: '更换头像', section: 'agent', order: 70, action: { cmd: '/aid', action: 'avatar', operation: 'ec.aid.avatar', scope: 'agent' }, valueSchema: { type: 'object', properties: { data: { type: 'string' }, url: { type: 'string', format: 'uri' } }, oneOf: [{ required: ['data'] }, { required: ['url'] }] }, control: 'action' },
82
83
  { id: 'system.status', type: 'list', contexts: ['process'], name: 'system', label: '系统状态', section: 'process', order: 10, query: { cmd: '/system', operation: 'system.status', scope: 'process' }, control: 'list' },
83
84
  { id: 'system.monitor', type: 'list', contexts: ['process'], name: 'system', label: '运行监控快照', section: 'process', order: 11, query: { cmd: '/system', args: { view: 'monitor' }, operation: 'system.status', scope: 'process' }, control: 'list' },
84
85
  { id: 'system.logs', type: 'list', contexts: ['process'], name: 'system', label: 'Daemon 日志', section: 'process', order: 12, query: { cmd: '/system', args: { view: 'logs' }, operation: 'system.status', scope: 'process' }, options: { cmd: '/system', args: { option: 'logs' }, operation: 'system.status', scope: 'process', mode: 'dynamic' }, control: 'list' },
@@ -19,7 +19,7 @@ import { scanInstances } from '../../utils/instance-registry.js';
19
19
  import { isFullAccessEnabled, loadDefaults, loadDaemonConfig } from '../../config-store.js';
20
20
  import { WEB_PACKAGE_NAME } from '../../product.js';
21
21
  import { read as cfgRead, resolveEffective, resolveEffectiveFieldWithSource, routeFieldPath, write as cfgWrite, ConfigTarget, } from '../../config/config-manager.js';
22
- import { execAgentAction, execAgentQuery, execAgentOptions, resolveProjectPath } from './agent-control.js';
22
+ import { execAgentAction, execAgentQuery, execAgentOptions, execAidAvatar, resolveProjectPath } from './agent-control.js';
23
23
  import { gatewayList, gatewayUpdate, gatewayDelete, gatewayTest, gatewayModels, gatewaySetPrice, gatewaySyncEnv } from '../../config/gateway-config.js';
24
24
  import { displaySessionTitle } from '../session/session-title.js';
25
25
  import { buildSessionTurnList } from '../session/session-turns.js';
@@ -1028,6 +1028,8 @@ function buildMenuIntent(verb, cmdBase, args, action, value, fromControlChannel
1028
1028
  if (action === 'disable')
1029
1029
  return intent('agent.disable', 'control', { action });
1030
1030
  }
1031
+ if (cmdBase === '/aid' && action === 'avatar')
1032
+ return intent('ec.aid.avatar', 'agent', { action });
1031
1033
  if (cmdBase === '/trigger') {
1032
1034
  if (action === 'create' || action === 'set')
1033
1035
  return intent('trigger.create', 'relation', { action });
@@ -1287,7 +1289,11 @@ function menuResultFailure(error) {
1287
1289
  return { error: normalized.message, code: normalized.code, ...(normalized.data !== undefined ? { data: normalized.data } : {}) };
1288
1290
  }
1289
1291
  function resolveExternalMenuAgentChannel(owner, payload, fallbackChannel, required) {
1290
- const requestedAid = typeof payload?.agent === 'string' ? payload.agent.trim() : '';
1292
+ const requestedAid = typeof payload?.agent === 'string' && payload.agent.trim()
1293
+ ? payload.agent.trim()
1294
+ : typeof payload?.args?.aid === 'string'
1295
+ ? payload.args.aid.trim()
1296
+ : '';
1291
1297
  if (!requestedAid) {
1292
1298
  return required
1293
1299
  ? { error: '缺少顶层 agent 参数', code: 'MISSING_AID' }
@@ -3061,7 +3067,7 @@ export async function execMenuAction(cmd, action, args, channel, channelId, user
3061
3067
  return menuResultFailure(error);
3062
3068
  }
3063
3069
  }
3064
- if (cmdBase === '/agent' && !fromControlChannel && !args?.aid) {
3070
+ if ((cmdBase === '/agent' || cmdBase === '/aid') && !args?.aid) {
3065
3071
  const selfAid = this.getOwningAgent?.(channel)?.aid;
3066
3072
  if (selfAid)
3067
3073
  args = { ...(args ?? {}), aid: selfAid };
@@ -3096,6 +3102,30 @@ export async function execMenuAction(cmd, action, args, channel, channelId, user
3096
3102
  return { error: `不支持 gateway action: ${action}`, code: 'NOT_SUPPORTED' };
3097
3103
  }
3098
3104
  const isMenuAdmin = isManagementRole(authIdentity.role);
3105
+ // ── /aid avatar ──
3106
+ // The AID namespace is the only avatar mutation surface.
3107
+ if (cmdBase === '/aid') {
3108
+ if (action !== 'avatar')
3109
+ return { error: `不支持 aid action: ${action}`, code: 'NOT_SUPPORTED' };
3110
+ const result = await execAidAvatar(args);
3111
+ if (!('error' in result)) {
3112
+ const data = result.data;
3113
+ if (data?.avatar) {
3114
+ this.eventBus.publish({
3115
+ type: 'agent:avatar_updated',
3116
+ aid: data.aid,
3117
+ avatar: data.avatar,
3118
+ ...(data.sha256 !== undefined ? { sha256: data.sha256 } : {}),
3119
+ ...(data.mime !== undefined ? { mime: data.mime } : {}),
3120
+ ...(data.width !== undefined ? { width: data.width } : {}),
3121
+ ...(data.height !== undefined ? { height: data.height } : {}),
3122
+ ...(data.aspectRatio !== undefined ? { aspectRatio: data.aspectRatio } : {}),
3123
+ timestamp: Date.now(),
3124
+ });
3125
+ }
3126
+ }
3127
+ return result;
3128
+ }
3099
3129
  // ── /agent action ──
3100
3130
  // 控制 channel:验 evolcore.owners,可执行进程级(create/delete/enable/disable)+ 自管理(update/reload)。
3101
3131
  // agent channel:闸门已挡掉进程级 + 跨 agent,仅 update/reload 能到此;reload 允许 owner/admin,update 仅 owner。
@@ -4157,9 +4187,9 @@ async function execMenuForSystemControl(payload, context) {
4157
4187
  const isRoleRequest = name === 'role' || compatCmd === '/role';
4158
4188
  const isConnectRequest = name === 'connect' || compatCmd === '/connect';
4159
4189
  const requiresAgentTarget = isRoleRequest || isConnectRequest || [
4160
- 'observable', 'baseagent', 'model', 'effort', 'trigger', 'capability', 'group',
4190
+ 'observable', 'baseagent', 'model', 'effort', 'trigger', 'capability', 'group', 'aid',
4161
4191
  ].includes(name)
4162
- || ['/observable', '/baseagent', '/model', '/effort', '/trigger', '/capability', '/group'].includes(compatCmd ?? '')
4192
+ || ['/observable', '/baseagent', '/model', '/effort', '/trigger', '/capability', '/group', '/aid'].includes(compatCmd ?? '')
4163
4193
  || (!isProcessLevel && requestedContextKind !== undefined && requestedContextKind !== 'process');
4164
4194
  const target = isProcessLevel
4165
4195
  ? { channel: SYSTEM_CONTROL_CHANNEL }
@@ -4169,6 +4199,12 @@ async function execMenuForSystemControl(payload, context) {
4169
4199
  const targetChannel = target.channel;
4170
4200
  const targetAid = this.getOwningAgent?.(targetChannel)?.aid
4171
4201
  ?? this.getDaemonTriggerOwnerAid?.(targetChannel);
4202
+ if (name === 'aid' || compatCmd === '/aid') {
4203
+ const requestedAid = typeof payload?.args?.aid === 'string' ? payload.args.aid.trim().replace(/^@/, '') : '';
4204
+ if (requestedAid && targetAid && requestedAid !== targetAid) {
4205
+ return ecwebErr(id, name, 'INVALID_ARGUMENT', 'args.aid 必须与目标 Agent 一致');
4206
+ }
4207
+ }
4172
4208
  const trustedSubject = buildAuthSubject({
4173
4209
  selfAid: targetAid,
4174
4210
  actorId: context.actorAid,
@@ -120,6 +120,7 @@ export const MENU_NAME_COMMANDS = Object.freeze({
120
120
  system: '/system',
121
121
  cli: '/cli',
122
122
  agent: '/agent',
123
+ aid: '/aid',
123
124
  trigger: '/trigger',
124
125
  file: '/file',
125
126
  gateway: '/gateway',
@@ -3223,10 +3223,10 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
3223
3223
  // /slist cli — 仅 CLI 会话(未导入的)
3224
3224
  if (normalizedContent === '/slist' || normalizedContent === '/slist cli') {
3225
3225
  if (!session) {
3226
- return { kind: 'command.error', text: `❌ 当前没有活跃会话
3227
-
3228
- 请先执行以下操作之一:
3229
- 1. 发送任意消息 - 自动创建新会话
3226
+ return { kind: 'command.error', text: `❌ 当前没有活跃会话
3227
+
3228
+ 请先执行以下操作之一:
3229
+ 1. 发送任意消息 - 自动创建新会话
3230
3230
  2. /new [名称] - 创建命名会话` };
3231
3231
  }
3232
3232
  const showCliOnly = normalizedContent === '/slist cli';
@@ -3497,11 +3497,11 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
3497
3497
  if (!newName)
3498
3498
  return { kind: 'command.result', text: '用法: /name <新名称> 或 /rename <新名称>' };
3499
3499
  if (!session) {
3500
- return { kind: 'command.error', text: `❌ 当前没有活跃会话
3501
-
3502
- 请先执行以下操作之一:
3503
- 1. 发送任意消息 - 自动创建新会话
3504
- 2. /new [名称] - 创建命名会话
3500
+ return { kind: 'command.error', text: `❌ 当前没有活跃会话
3501
+
3502
+ 请先执行以下操作之一:
3503
+ 1. 发送任意消息 - 自动创建新会话
3504
+ 2. /new [名称] - 创建命名会话
3505
3505
  3. /session <名称> - 切换到已有会话` };
3506
3506
  }
3507
3507
  const existing = await this.sessionManager.getSessionByName(channel, channelId, newName);
@@ -796,6 +796,11 @@ const CATALOG = [
796
796
  fields: [
797
797
  { path: 'aid', type: 'string' },
798
798
  { path: 'avatar', type: 'string' },
799
+ { path: 'sha256', type: 'string', optional: true },
800
+ { path: 'mime', type: 'string', optional: true },
801
+ { path: 'width', type: 'number', optional: true },
802
+ { path: 'height', type: 'number', optional: true },
803
+ { path: 'aspectRatio', type: 'number', optional: true },
799
804
  { path: 'timestamp', type: 'number', optional: true },
800
805
  ],
801
806
  },
@@ -275,9 +275,9 @@ export class EvolAgent {
275
275
  return `agent-files:${this.aid}`;
276
276
  }
277
277
  /**
278
- * 读取 personal/persona.md 内容。走 fileCache(mtime 门控):persona 没有任何
279
- * 写入命令、由 agent 自己带外改写,与 working memory 同样改了即应生效,故每次读
280
- * stat 比对、变了自动重读。文件不存在返回 null。
278
+ * 读取 personal/persona.md 内容。走 fileCache(mtime 门控):persona 可由
279
+ * `ec agent persona` 或 agent 自己更新,与 working memory 一样改了即应生效,
280
+ * 故每次读 stat 比对、变了自动重读。文件不存在返回 null。
281
281
  */
282
282
  getPersona() {
283
283
  const personaPath = path.join(agentPersonalDir(this.aid), 'persona.md');