evolcore 0.0.23 → 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 (245) hide show
  1. package/CHANGELOG.md +583 -516
  2. package/LICENSE +21 -21
  3. package/README.md +455 -453
  4. package/bin/codex-managed-acl.ps1 +160 -155
  5. package/bin/codex-managed-hook.mjs +209 -92
  6. package/bin/ec-safe-output.js +327 -245
  7. package/bin/ec.js +29 -29
  8. package/bin/install-codex-managed-hooks.mjs +555 -544
  9. package/dist/agents/baseagent-availability.js +4 -4
  10. package/dist/agents/baseagent.js +29 -1
  11. package/dist/agents/claude-runner.js +152 -14
  12. package/dist/agents/codex/app-server-client.js +18 -8
  13. package/dist/agents/codex/catalog.js +1 -1
  14. package/dist/agents/codex/diagnostics.js +215 -0
  15. package/dist/agents/codex-runner.js +445 -63
  16. package/dist/agents/ecagent-runner.js +31 -16
  17. package/dist/agents/gemini-runner.js +82 -4
  18. package/dist/agents/runner-types.js +1 -1
  19. package/dist/aun/aid/agentmd.js +56 -4
  20. package/dist/aun/aid/index.js +1 -0
  21. package/dist/aun/aid/managed-operation.js +80 -6
  22. package/dist/aun/aid/store.js +41 -0
  23. package/dist/aun/msg/managed-operation.js +6 -1
  24. package/dist/aun/outbox.js +56 -18
  25. package/dist/aun/rpc/client.js +4 -5
  26. package/dist/aun/storage/client.js +4 -1
  27. package/dist/aun/storage/index.js +1 -1
  28. package/dist/channels/aun.js +147 -76
  29. package/dist/channels/daemon.js +7 -1
  30. package/dist/channels/dingtalk.js +1 -0
  31. package/dist/channels/feishu.js +10 -4
  32. package/dist/channels/wecom.js +2 -1
  33. package/dist/cli/agent-command.js +214 -89
  34. package/dist/cli/agent-create.js +576 -0
  35. package/dist/cli/agent.js +129 -676
  36. package/dist/cli/aun-commands.js +419 -313
  37. package/dist/cli/bench.js +20 -20
  38. package/dist/cli/config.js +20 -20
  39. package/dist/cli/contact.js +10 -10
  40. package/dist/cli/ctl-command.js +22 -20
  41. package/dist/cli/daemon-commands.js +163 -32
  42. package/dist/cli/fs-command.js +67 -67
  43. package/dist/cli/handoff-command.js +7 -7
  44. package/dist/cli/index.js +79 -79
  45. package/dist/cli/link-rules.js +21 -21
  46. package/dist/cli/managed-command-guard.js +3 -0
  47. package/dist/cli/menu-client.js +11 -11
  48. package/dist/cli/net-check.js +22 -22
  49. package/dist/cli/queue-command.js +29 -29
  50. package/dist/cli/response.js +30 -30
  51. package/dist/cli/restart-monitor.js +462 -38
  52. package/dist/cli/stats.js +161 -51
  53. package/dist/cli/trigger-command.js +56 -56
  54. package/dist/config/builtin-roles.js +3 -0
  55. package/dist/config/config-manager.js +15 -0
  56. package/dist/config/config-operation-service.js +20 -3
  57. package/dist/config/resolved-config-op.js +39 -1
  58. package/dist/core/auth/auth-gateway.js +3 -2
  59. package/dist/core/auth/authorization-audit.js +72 -19
  60. package/dist/core/auth/operation-authorizer.js +125 -7
  61. package/dist/core/auth/operation-catalog.js +26 -2
  62. package/dist/core/auth/remote-access-store.js +3 -2
  63. package/dist/core/baseagent-loader.js +4 -4
  64. package/dist/core/bootstrap-messages.js +39 -18
  65. package/dist/core/bootstrap-service.js +32 -26
  66. package/dist/core/capability/capability-manager.js +8 -4
  67. package/dist/core/capability/providers/codex-capability-provider.js +1 -1
  68. package/dist/core/command/agent-control.js +60 -26
  69. package/dist/core/command/cli-intent-parser.js +18 -0
  70. package/dist/core/command/command-handler.js +173 -36
  71. package/dist/core/command/menu-catalog.js +4 -1
  72. package/dist/core/command/menu-handler.js +315 -75
  73. package/dist/core/command/menu-protocol.js +7 -2
  74. package/dist/core/command/slash-gate.js +1 -3
  75. package/dist/core/command/slash-handler.js +166 -57
  76. package/dist/core/data-migration.js +5 -15
  77. package/dist/core/event-catalog.js +8 -0
  78. package/dist/core/evolagent.js +3 -3
  79. package/dist/core/handoff/runtime.js +3 -1
  80. package/dist/core/handoff/store.js +7 -1
  81. package/dist/core/message/im-renderer.js +13 -6
  82. package/dist/core/message/message-bridge.js +5 -36
  83. package/dist/core/message/message-utils.js +6 -3
  84. package/dist/core/message/response-engine.js +85 -27
  85. package/dist/core/model/catalog-service.js +3 -1
  86. package/dist/core/model/model-catalog.js +10 -0
  87. package/dist/core/model/model-permission.js +4 -1
  88. package/dist/core/permission/approval-gateway.js +169 -33
  89. package/dist/core/permission/tool-policy.js +56 -10
  90. package/dist/core/session/adapters/codex-session-file-adapter.js +71 -3
  91. package/dist/core/session/session-manager.js +49 -13
  92. package/dist/core/session/session-renew.js +1 -0
  93. package/dist/index.js +327 -134
  94. package/dist/ipc.js +68 -21
  95. package/dist/paths.js +23 -1
  96. package/dist/stats/billing.js +18 -4
  97. package/dist/stats/db.js +159 -159
  98. package/dist/stats/eck-vars.js +9 -9
  99. package/dist/stats/query.js +139 -139
  100. package/dist/stats/role-budget.js +6 -6
  101. package/dist/stats/writer.js +51 -51
  102. package/dist/utils/atomic-write.js +26 -2
  103. package/dist/utils/avatar-upload.js +282 -23
  104. package/dist/utils/cross-platform.js +436 -1
  105. package/dist/utils/error-dict.json +160 -160
  106. package/dist/utils/error-utils.js +11 -4
  107. package/dist/utils/macos-autostart.js +22 -22
  108. package/dist/utils/model-prices.jsonl +23 -20
  109. package/dist/utils/npm-ops.js +24 -1
  110. package/dist/utils/welcome.js +22 -22
  111. package/dist/utils/windows-autostart.js +1 -1
  112. package/ecagent/LICENSE +21 -21
  113. package/ecagent/NOTICE +11 -11
  114. package/ecagent/README.md +61 -61
  115. package/ecagent/dist/harness/compaction/branch-summarization.js +30 -30
  116. package/ecagent/dist/harness/compaction/compaction.js +83 -83
  117. package/ecagent/dist/harness/messages.js +7 -7
  118. package/kits/docs/GUIDE.md +20 -20
  119. package/kits/docs/INDEX.md +66 -66
  120. package/kits/docs/aun/CHEATSHEET.md +19 -19
  121. package/kits/docs/aun/SYNC_PROTOCOL.md +15 -15
  122. package/kits/docs/channels/aun.md +56 -56
  123. package/kits/docs/channels/feishu.md +56 -56
  124. package/kits/docs/context-assembly.md +365 -364
  125. package/kits/docs/eck_templates/GUIDE.template.md +22 -22
  126. package/kits/docs/eck_templates/INDEX.template.md +28 -28
  127. package/kits/docs/eck_templates/path-registry.template.md +33 -33
  128. package/kits/docs/eck_templates/runtime.template.md +19 -19
  129. package/kits/docs/evolcore/INDEX.md +71 -69
  130. package/kits/docs/evolcore/agent-create.md +170 -156
  131. package/kits/docs/evolcore/agent.md +83 -73
  132. package/kits/docs/evolcore/aid.md +64 -62
  133. package/kits/docs/evolcore/codex-windows-sandbox.md +146 -0
  134. package/kits/docs/evolcore/config.md +195 -195
  135. package/kits/docs/evolcore/contact.md +68 -68
  136. package/kits/docs/evolcore/ctl.md +46 -46
  137. package/kits/docs/evolcore/event.md +216 -216
  138. package/kits/docs/evolcore/fs-architecture.md +1215 -1215
  139. package/kits/docs/evolcore/fs.md +110 -110
  140. package/kits/docs/evolcore/group-collaboration.md +243 -243
  141. package/kits/docs/evolcore/group-fs.md +17 -17
  142. package/kits/docs/evolcore/group-rules.md +251 -251
  143. package/kits/docs/evolcore/group.md +152 -152
  144. package/kits/docs/evolcore/menu-client.md +34 -34
  145. package/kits/docs/evolcore/model.md +50 -50
  146. package/kits/docs/evolcore/msg.md +165 -165
  147. package/kits/docs/evolcore/response.md +75 -75
  148. package/kits/docs/evolcore/rpc.md +37 -37
  149. package/kits/docs/evolcore/self-summary.md +29 -29
  150. package/kits/docs/evolcore/stats.md +83 -83
  151. package/kits/docs/evolcore/storage.md +50 -50
  152. package/kits/docs/evolcore/trigger.md +575 -575
  153. package/kits/docs/identity/AID_PROFILE_SPEC.md +26 -26
  154. package/kits/docs/identity/PATH_OPS.md +16 -16
  155. package/kits/docs/identity/ROLE_DETAIL.md +25 -25
  156. package/kits/docs/identity/identity-tools.md +26 -26
  157. package/kits/docs/path-registry.md +62 -62
  158. package/kits/docs/prompt-loading-architecture.md +270 -269
  159. package/kits/docs/venues/aun-group.md +45 -45
  160. package/kits/docs/venues/aun-private.md +10 -10
  161. package/kits/docs/venues/client-desktop.md +10 -10
  162. package/kits/docs/venues/client-mobile.md +10 -10
  163. package/kits/docs/venues/feishu-group.md +13 -13
  164. package/kits/docs/venues/feishu-private.md +9 -9
  165. package/kits/docs/venues/group.md +25 -25
  166. package/kits/docs/venues/private.md +10 -10
  167. package/kits/eck_manifest.auxiliary.json +43 -43
  168. package/kits/eck_manifest.json +195 -183
  169. package/kits/eck_message_manifest.json +74 -74
  170. package/kits/migrations/README-role-config-v4.md +32 -32
  171. package/kits/migrations/migrate-contact-book-v2.mjs +748 -748
  172. package/kits/migrations/migrate-role-config-v4.mjs +614 -614
  173. package/kits/migrations/migrate-role-config-v5.mjs +365 -365
  174. package/kits/migrations/rename-config-file.mjs +99 -99
  175. package/kits/rules/01-overview.md +162 -162
  176. package/kits/rules/02-navigation.md +96 -96
  177. package/kits/rules/03-identity.md +39 -37
  178. package/kits/rules/04-relation.md +75 -75
  179. package/kits/rules/05-venue.md +60 -60
  180. package/kits/rules/06-channel.md +63 -63
  181. package/kits/schemas/_meta.json +53 -53
  182. package/kits/schemas/agent-config.schema.1.json +177 -177
  183. package/kits/schemas/agent-config.schema.10.json +395 -395
  184. package/kits/schemas/agent-config.schema.11.json +421 -421
  185. package/kits/schemas/agent-config.schema.12.json +427 -427
  186. package/kits/schemas/agent-config.schema.2.json +239 -239
  187. package/kits/schemas/agent-config.schema.3.json +119 -119
  188. package/kits/schemas/agent-config.schema.4.json +208 -208
  189. package/kits/schemas/agent-config.schema.5.json +326 -326
  190. package/kits/schemas/agent-config.schema.6.json +322 -322
  191. package/kits/schemas/agent-config.schema.7.json +303 -303
  192. package/kits/schemas/agent-config.schema.8.json +304 -304
  193. package/kits/schemas/agent-config.schema.9.json +364 -364
  194. package/kits/schemas/contact-book.schema.1.json +36 -36
  195. package/kits/schemas/contact-book.schema.2.json +43 -43
  196. package/kits/schemas/contact-book.schema.3.json +67 -67
  197. package/kits/schemas/daemon.schema.1.json +91 -91
  198. package/kits/schemas/daemon.schema.2.json +101 -101
  199. package/kits/schemas/daemon.schema.3.json +123 -123
  200. package/kits/schemas/daemon.schema.4.json +136 -136
  201. package/kits/schemas/daemon.schema.5.json +135 -135
  202. package/kits/schemas/daemon.schema.6.json +131 -131
  203. package/kits/schemas/defaults.schema.1.json +81 -81
  204. package/kits/schemas/defaults.schema.2.json +85 -85
  205. package/kits/schemas/defaults.schema.3.json +73 -73
  206. package/kits/schemas/defaults.schema.4.json +107 -107
  207. package/kits/schemas/defaults.schema.5.json +119 -119
  208. package/kits/schemas/menu-exec-schema-commands.md +208 -208
  209. package/kits/schemas/migrations/README.md +30 -30
  210. package/kits/schemas/relation-config.schema.1.json +158 -158
  211. package/kits/schemas/relation-config.schema.2.json +73 -73
  212. package/kits/schemas/relation-config.schema.3.json +50 -50
  213. package/kits/schemas/relation-config.schema.4.json +47 -47
  214. package/kits/schemas/relation-config.schema.5.json +47 -47
  215. package/kits/schemas/relation-config.schema.6.json +46 -46
  216. package/kits/schemas/relation-config.schema.7.json +59 -59
  217. package/kits/schemas/relation-config.schema.8.json +103 -103
  218. package/kits/schemas/role-config.schema.1.json +201 -201
  219. package/kits/schemas/role-registry.schema.1.json +35 -35
  220. package/kits/schemas/single-session.schema.1.json +31 -31
  221. package/kits/schemas/single-session.schema.2.json +57 -57
  222. package/kits/schemas/single-session.schema.3.json +32 -32
  223. package/kits/templates/bootstrap-welcome.md +17 -17
  224. package/kits/templates/message-fragments/handoff-context-to-target.md +9 -9
  225. package/kits/templates/message-fragments/handoff-request-to-target.md +19 -19
  226. package/kits/templates/message-fragments/handoff-response-to-origin.md +9 -9
  227. package/kits/templates/message-fragments/inject-default.md +2 -2
  228. package/kits/templates/message-fragments/item.md +2 -2
  229. package/kits/templates/roles/admin.json +96 -82
  230. package/kits/templates/roles/member.json +97 -96
  231. package/kits/templates/roles/owner.json +9 -9
  232. package/kits/templates/roles/visitor.json +86 -85
  233. package/kits/templates/system-fragments/baseagent.md +14 -14
  234. package/kits/templates/system-fragments/bootstrap.md +38 -29
  235. package/kits/templates/system-fragments/channel.md +64 -64
  236. package/kits/templates/system-fragments/commands.md +28 -28
  237. package/kits/templates/system-fragments/identity.md +13 -13
  238. package/kits/templates/system-fragments/relation.md +19 -19
  239. package/kits/templates/system-fragments/session.md +59 -59
  240. package/kits/templates/system-fragments/venue.md +31 -31
  241. package/package.json +98 -94
  242. package/AGENTS.md +0 -62
  243. package/dist/utils/codex-cli.js +0 -43
  244. package/skills/eclink/SKILL.md +0 -84
  245. package/skills/eclink/agents/openai.yaml +0 -4
@@ -1,15 +1,16 @@
1
- import { agentCreateNonInteractive, agentDelete, agentEnable, agentDisable, agentList, agentShow, agentSet, agentReload, } from '../../cli/agent.js';
1
+ import { agentDelete, agentEnable, agentDisable, agentList, agentShow, agentSet, agentReload, } from '../../cli/agent.js';
2
+ import { agentCreateNonInteractive } from '../../cli/agent-create.js';
2
3
  import { logger } from '../../utils/logger.js';
3
4
  import { resolvePaths, resolveRoot } from '../../paths.js';
4
5
  import path from 'path';
5
6
  import { ConfigTarget, ensureFile as cfgEnsure, read as cfgRead, write as cfgWrite, } from '../../config/config-manager.js';
6
7
  import { CreateStatusWriter, readCreateStatus } from '../message/create-status.js';
7
8
  import { agentProjectRootFromDefaults, deriveAgentProjectPath } from '../../utils/project-path.js';
8
- import { uploadAvatar } from '../../utils/avatar-upload.js';
9
+ import { uploadAvatar, uploadAvatarUrl } from '../../utils/avatar-upload.js';
9
10
  import { agentmdGet, agentmdPut, updateAgentMdFrontmatterName } from '../../aun/aid/agentmd.js';
10
11
  import { isValidAid } from '../../aun/aid/validation.js';
11
12
  import { isSupportedBaseagent } from '../../agents/baseagent.js';
12
- 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']);
13
14
  const HIDDEN_VALUE = '[hidden]';
14
15
  const SENSITIVE_CONFIG_KEYS = new Set([
15
16
  'apikey',
@@ -47,6 +48,42 @@ function classifyAgentMdError(error) {
47
48
  return 'INVALID_ARGS';
48
49
  return 'UPLOAD_FAILED';
49
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
+ }
50
87
  async function updateAgentMdName(aid, value) {
51
88
  const name = typeof value === 'string' ? value.trim() : '';
52
89
  if (!name)
@@ -297,6 +334,8 @@ async function runCreateInBackground(opts, w, service) {
297
334
  const createOptions = {
298
335
  aid: opts.aid, name: opts.name, baseagent: opts.baseagent,
299
336
  project: opts.project, owner: opts.owner,
337
+ description: opts.description,
338
+ tags: opts.tags,
300
339
  onPhase: (phase, state, detail) => {
301
340
  if (state === 'begin') {
302
341
  curPhase = phase;
@@ -393,6 +432,10 @@ export async function execAgentAction(action, args, peerId, eventBus, service) {
393
432
  const model = typeof a.model === 'string' && a.model.trim() ? a.model.trim() : undefined;
394
433
  const rawEffort = typeof a.effort === 'string' && a.effort.trim() ? a.effort.trim() : undefined;
395
434
  const effort = rawEffort && rawEffort !== 'auto' ? rawEffort : undefined;
435
+ const description = typeof a.description === 'string' && a.description.trim()
436
+ ? a.description.trim()
437
+ : undefined;
438
+ const tags = Array.isArray(a.tags) ? a.tags : undefined;
396
439
  // D3: accepted=true 必须意味着后续 query 能查到该 AID 的创建状态。
397
440
  let progressWriter;
398
441
  try {
@@ -405,7 +448,7 @@ export async function execAgentAction(action, args, peerId, eventBus, service) {
405
448
  void runCreateInBackground({
406
449
  aid: a.aid, name: a.name, baseagent: a.baseagent,
407
450
  project: a.project, owner,
408
- model, effort, chatmode: a.chatmode,
451
+ model, effort, description, tags, chatmode: a.chatmode,
409
452
  eventBus,
410
453
  }, progressWriter, service).catch(e => logger.error(`[agent-control] runCreateInBackground unhandled ${a.aid}: ${e?.message || e}`));
411
454
  return { data: { accepted: true, aid: a.aid } };
@@ -456,14 +499,6 @@ export async function execAgentAction(action, args, peerId, eventBus, service) {
456
499
  if (action === 'update') {
457
500
  const result = await execAgentUpdate(a);
458
501
  if (!('error' in result)) {
459
- if (result.data?.avatar) {
460
- eventBus?.publish({
461
- type: 'agent:avatar_updated',
462
- aid: a.aid,
463
- avatar: result.data.avatar,
464
- timestamp: Date.now(),
465
- });
466
- }
467
502
  eventBus?.publish({
468
503
  type: 'agent:updated',
469
504
  aid: a.aid,
@@ -496,12 +531,6 @@ export async function execAgentUpdate(args) {
496
531
  if (p.aid !== undefined) {
497
532
  return { error: 'aid 不可修改(AUN 身份绑定,如需换 AID 请删除后重建)', code: 'INVALID_ARGS' };
498
533
  }
499
- if (p.avatar !== undefined) {
500
- const otherKeys = Object.keys(p).filter(k => k !== 'avatar');
501
- if (otherKeys.length > 0) {
502
- return { error: 'avatar 不能与其他 patch 字段同时提交,请拆分为两次 update', code: 'INVALID_ARGS' };
503
- }
504
- }
505
534
  if (p.name !== undefined) {
506
535
  const otherKeys = Object.keys(p).filter(k => k !== 'name');
507
536
  if (otherKeys.length > 0) {
@@ -517,13 +546,6 @@ export async function execAgentUpdate(args) {
517
546
  const config = cfgRead(ConfigTarget.Agent, { self: a.aid });
518
547
  if (!config)
519
548
  return { error: `Agent "${a.aid}" not found`, code: 'NOT_FOUND' };
520
- if (p.avatar !== undefined) {
521
- const result = await uploadAvatar(a.aid, p.avatar, { aunPath: resolveRoot() });
522
- if (!result.ok) {
523
- return { error: `头像上传失败: ${result.error}`, code: result.code ?? 'UPLOAD_FAILED' };
524
- }
525
- return { data: { aid: a.aid, avatar: result.publicUrl, saved: true } };
526
- }
527
549
  let touched = false;
528
550
  const data = { aid: a.aid };
529
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;
@@ -559,7 +581,19 @@ export async function execAgentUpdate(args) {
559
581
  delete config.projects;
560
582
  }
561
583
  if (p.owners !== undefined) {
562
- 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;
563
597
  touched = true;
564
598
  }
565
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)
@@ -22,7 +22,7 @@ import { splitConfigBatchGetArgv } from '../../cli/cli-argv.js';
22
22
  import { auditCommandAuthorization, auditFullAccessEvent, hashArgv } from '../auth/authorization-audit.js';
23
23
  import { executeResolvedContactCommand, resolveContactCommand } from '../../config/contact-operation-service.js';
24
24
  import { hasTrustedFullAccessDelegation, trustedExecutionIdentityInput } from '../auth/agent-delegation.js';
25
- import { executeManagedAidOperation, ManagedAidOperationError, resolveManagedAidOperation, } from '../../aun/aid/managed-operation.js';
25
+ import { executeManagedAidOperation, ManagedAidOperationError, managedAidShowViewForRole, resolveManagedAidOperation, } from '../../aun/aid/managed-operation.js';
26
26
  import { executeManagedGroupOperation, isManagedGroupRelationRead, ManagedGroupOperationError, resolveManagedGroupOperation, executeManagedMsgOperation, ManagedMsgOperationError, resolveManagedMsgOperation, executeManagedFsOperation, ManagedFsOperationError, resolveManagedFsOperation, } from '../../aun/msg/managed-operation.js';
27
27
  import { authorizeOperation, buildAuthSubject } from '../auth/auth-gateway.js';
28
28
  import { isHClassPath, isLClassPath, isSameOrDescendant } from '../permission/protected-paths.js';
@@ -108,6 +108,7 @@ export class CommandHandler {
108
108
  interactionRouter;
109
109
  statsCollector;
110
110
  agentMap;
111
+ legacySingleRunnerMode;
111
112
  primaryRunnerKey;
112
113
  agentRegistry;
113
114
  agentApplicationService;
@@ -131,23 +132,42 @@ export class CommandHandler {
131
132
  * different backend; that would corrupt session metadata.
132
133
  */
133
134
  getAgent(channel, baseagent) {
134
- if (channel && baseagent) {
135
+ const requestedBaseagent = typeof baseagent === 'string' ? baseagent.trim() : '';
136
+ // Support callers that intentionally provide a single-runner map keyed by
137
+ // the backend name. This exact lookup does not select another agent.
138
+ if (requestedBaseagent && this.agentMap.has(requestedBaseagent)) {
139
+ return this.agentMap.get(requestedBaseagent);
140
+ }
141
+ // A caller that passed one runner explicitly has already selected the
142
+ // backend; this compatibility path does not exist for multi-agent maps.
143
+ if (this.legacySingleRunnerMode && this.agentMap.size === 1
144
+ && this.agentMap.values().next().value?.name === requestedBaseagent) {
145
+ return this.agentMap.values().next().value;
146
+ }
147
+ if (channel && requestedBaseagent) {
135
148
  const owner = this.agentRegistry?.resolveByChannel(channel);
136
149
  const evolName = owner?.name || '<unknown>';
137
- const key = `${evolName}::${baseagent}`;
150
+ const key = `${evolName}::${requestedBaseagent}`;
138
151
  if (this.agentMap.has(key))
139
152
  return this.agentMap.get(key);
140
153
  if (owner) {
141
- throw new BaseagentRunnerUnavailableError(evolName, baseagent, this.getAvailableBaseagentsForOwner(evolName));
154
+ throw new BaseagentRunnerUnavailableError(evolName, requestedBaseagent, this.getAvailableBaseagentsForOwner(evolName));
142
155
  }
143
- const globalRunner = [...this.agentMap.entries()]
144
- .find(([runnerKey]) => runnerKey === baseagent || runnerKey.endsWith(`::${baseagent}`));
145
- if (globalRunner)
146
- return globalRunner[1];
147
- }
148
- if (this.agentMap.has(this.primaryRunnerKey))
149
- return this.agentMap.get(this.primaryRunnerKey);
150
- return this.agentMap.values().next().value;
156
+ const matches = [...this.agentMap.entries()]
157
+ .filter(([runnerKey]) => runnerKey.endsWith(`::${requestedBaseagent}`));
158
+ if (matches.length === 1)
159
+ return matches[0][1];
160
+ logger.error(`[RunnerLookup] unavailable channel=${channel ?? '<none>'} baseagent=${requestedBaseagent} owner=${evolName} keys=${[...this.agentMap.keys()].join(',')}`);
161
+ throw new BaseagentRunnerUnavailableError('<unknown>', requestedBaseagent, []);
162
+ }
163
+ if (requestedBaseagent) {
164
+ const matches = [...this.agentMap.entries()]
165
+ .filter(([runnerKey]) => runnerKey.endsWith(`::${requestedBaseagent}`));
166
+ if (matches.length === 1)
167
+ return matches[0][1];
168
+ }
169
+ logger.error(`[RunnerLookup] unavailable channel=${channel ?? '<none>'} baseagent=${requestedBaseagent || '<missing>'} keys=${[...this.agentMap.keys()].join(',')}`);
170
+ throw new BaseagentRunnerUnavailableError('<unknown>', requestedBaseagent || '<missing>', []);
151
171
  }
152
172
  getAvailableBaseagentsForOwner(evolName) {
153
173
  const prefix = `${evolName}::`;
@@ -205,11 +225,13 @@ export class CommandHandler {
205
225
  this.eventBus = eventBus;
206
226
  if (agentRunnerOrMap instanceof Map) {
207
227
  this.agentMap = agentRunnerOrMap;
228
+ this.legacySingleRunnerMode = false;
208
229
  this.primaryRunnerKey = primaryRunnerKey || '<unknown>::claude';
209
230
  }
210
231
  else {
211
232
  // 测试 / 单 runner 路径:占位 agent name 用 '<unknown>'
212
233
  this.agentMap = new Map([[`<unknown>::${agentRunnerOrMap.name}`, agentRunnerOrMap]]);
234
+ this.legacySingleRunnerMode = true;
213
235
  this.primaryRunnerKey = `<unknown>::${agentRunnerOrMap.name}`;
214
236
  }
215
237
  }
@@ -424,13 +446,13 @@ export class CommandHandler {
424
446
  return fallback;
425
447
  }
426
448
  }
427
- validateTriggerPermissionModeForRole(mode, isAdmin, role, selfAid, isDaemonOwner = false) {
449
+ validateTriggerPermissionModeForRole(mode, isAdmin, role, selfAid, isDaemonOwner = false, canApprovePersistentFullAccess = isDaemonOwner) {
428
450
  if (!mode)
429
451
  return undefined;
430
452
  if (mode === 'fullaccess') {
431
453
  if (!isFullAccessEnabled())
432
454
  return 'fullaccess 功能未启用';
433
- return isDaemonOwner ? undefined : '无权限:只有 daemon-owner 可以配置 fullaccess Trigger';
455
+ return canApprovePersistentFullAccess ? undefined : '无权限:只有 daemon-owner 可以配置 fullaccess Trigger';
434
456
  }
435
457
  if (!role) {
436
458
  if (mode === 'bypass' && !isAdmin)
@@ -486,7 +508,9 @@ export class CommandHandler {
486
508
  return modelDecision.message || '当前角色不允许使用该 Trigger 模型';
487
509
  }
488
510
  if (definition.execution.effort) {
489
- const base = executionBaseagent || 'claude';
511
+ if (!executionBaseagent)
512
+ return '无法确定 Trigger 会话的 baseagent';
513
+ const base = executionBaseagent;
490
514
  const effortDecision = validateRuntimeStringFieldOverride({
491
515
  selfAid: definition.agentAid,
492
516
  role,
@@ -575,7 +599,7 @@ export class CommandHandler {
575
599
  } : undefined;
576
600
  const permissionMode = this.triggerPermissionFromParsed(parsed);
577
601
  const triggerRole = this.triggerRoleContext(schedulerAid, channel, peerId, chatType);
578
- const permissionError = this.validateTriggerPermissionModeForRole(permissionMode, isAdmin, triggerRole.role, schedulerAid, isDaemonOwner);
602
+ const permissionError = this.validateTriggerPermissionModeForRole(permissionMode, isAdmin, triggerRole.role, schedulerAid, isDaemonOwner, canApprovePersistentFullAccess);
579
603
  if (permissionError)
580
604
  return { error: permissionError };
581
605
  if (permissionMode === 'fullaccess' && !canApprovePersistentFullAccess) {
@@ -604,7 +628,7 @@ export class CommandHandler {
604
628
  };
605
629
  }
606
630
  }
607
- const authorizedBy = permissionMode === 'fullaccess' && isDaemonOwner
631
+ const authorizedBy = permissionMode === 'fullaccess' && canApprovePersistentFullAccess
608
632
  ? this.canonicalDaemonOwnerId(schedulerAid, channel, peerId, chatType)
609
633
  : undefined;
610
634
  const definition = {
@@ -1000,8 +1024,8 @@ export class CommandHandler {
1000
1024
  return await menuExecMenuQuery.call(this, cmd, channel, channelId, userId, args, explicitChatType, fromControlChannel, overrideIdentity, authSubject, source);
1001
1025
  }
1002
1026
  /** menu.update — 写入新值。 */
1003
- async execMenuUpdate(cmd, value, channel, channelId, userId, overrideIdentity, fromControlChannel = false, args, authSubject, source = 'menu') {
1004
- return await menuExecMenuUpdate.call(this, cmd, value, channel, channelId, userId, overrideIdentity, fromControlChannel, args, authSubject, source);
1027
+ async execMenuUpdate(cmd, value, channel, channelId, userId, overrideIdentity, fromControlChannel = false, args, authSubject, source = 'menu', transportReplyContext) {
1028
+ return await menuExecMenuUpdate.call(this, cmd, value, channel, channelId, userId, overrideIdentity, fromControlChannel, args, authSubject, source, transportReplyContext);
1005
1029
  }
1006
1030
  /** menu.action — 触发动词。 */
1007
1031
  async execMenuAction(cmd, action, args, channel, channelId, userId, overrideIdentity, explicitChatType, requestId, fromControlChannel = false, authSubject, source = 'menu', transportReplyContext, deferSideEffect) {
@@ -1103,7 +1127,7 @@ export class CommandHandler {
1103
1127
  /** 把 menu.action 委派给已有 slash 命令处理逻辑,把 OutboundPayload 包成结构化结果。 */
1104
1128
  async delegateAsAction(action, slashCmd, channel, channelId, userId, opts = {}) {
1105
1129
  try {
1106
- const result = await this._handleInternal(slashCmd, channel, channelId, undefined, userId, undefined, undefined, undefined, undefined, undefined, opts.overrideIdentity, opts.authSubject);
1130
+ const result = await this._handleInternal(slashCmd, channel, channelId, undefined, userId, opts.threadId, opts.chatType, undefined, undefined, undefined, opts.overrideIdentity, opts.authSubject, opts.replyContext);
1107
1131
  if (result == null) {
1108
1132
  // null / undefined: 命令未识别或前置守卫拦截(如 idle 检查),视为失败
1109
1133
  return { error: '命令未执行(可能被前置守卫拦截)', code: 'EXEC_FAILED' };
@@ -1375,17 +1399,17 @@ export class CommandHandler {
1375
1399
  const nextStr = reg.trigger.nextFireAt ? new Date(reg.trigger.nextFireAt).toLocaleString() : '未计算';
1376
1400
  return `✅ 触发器已注册:**${reg.trigger.name}**\n下次触发:${nextStr}`;
1377
1401
  }
1378
- return `❌ 未知子命令。用法:
1379
- /trigger — 查看活跃触发器
1380
- /trigger list [--all] — 查看所有触发器
1381
- /trigger create <参数> — 创建触发器(set 为兼容别名)
1382
- /trigger update <名称|ID> <参数> — 修改触发器
1383
- 常用参数:--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
1384
- /trigger enable <名称|ID> — 启用触发器
1385
- /trigger disable <名称|ID> — 暂停触发器
1386
- /trigger show <名称|ID> — 查看触发器详情
1387
- /trigger run <名称|ID> [--dry-run] — 手动触发
1388
- /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 的兼容别名)
1389
1413
  /trigger delete <名称|ID> — 删除触发器`;
1390
1414
  }
1391
1415
  async handleTrigger(content, channel, channelId, peerId, isAdmin, messageId, chatType, threadId, isDaemonOwner = false, canApprovePersistentFullAccess = isDaemonOwner) {
@@ -1432,7 +1456,7 @@ export class CommandHandler {
1432
1456
  return { ok: false, error: err?.message || String(err) };
1433
1457
  }
1434
1458
  if (updated.execution.permissionMode === 'fullaccess') {
1435
- if (!isDaemonOwner) {
1459
+ if (!isDaemonOwner && !canApprovePersistentFullAccess) {
1436
1460
  return { ok: false, code: 'FORBIDDEN', error: '无权限:只有 daemon-owner 可以配置 fullaccess Trigger' };
1437
1461
  }
1438
1462
  if (!canApprovePersistentFullAccess) {
@@ -1455,7 +1479,7 @@ export class CommandHandler {
1455
1479
  return { ok: false, error: '无权限:只有 owner/admin 可以修改 script trigger' };
1456
1480
  }
1457
1481
  const triggerRole = this.triggerRoleContext(definition.agentAid, definition.origin?.channelKey || channel, definition.origin?.peerId || peerId);
1458
- const permissionError = this.validateTriggerPermissionModeForRole(updated.execution.permissionMode, isAdmin, triggerRole.role, definition.agentAid, isDaemonOwner);
1482
+ const permissionError = this.validateTriggerPermissionModeForRole(updated.execution.permissionMode, isAdmin, triggerRole.role, definition.agentAid, isDaemonOwner, canApprovePersistentFullAccess);
1459
1483
  if (permissionError) {
1460
1484
  return {
1461
1485
  ok: false,
@@ -1922,7 +1946,10 @@ export class CommandHandler {
1922
1946
  if (sessionSelfAid && sessionSelfAid !== grantSelfAid) {
1923
1947
  return { ok: false, code: 'INVALID_DELEGATION', error: 'Delegation self agent does not match the active session' };
1924
1948
  }
1925
- if (parsed.command.aid !== grantSelfAid && !hasTrustedFullAccessDelegation(delegation.grant)) {
1949
+ const requiresSelfAid = parsed.command.kind === 'show' || parsed.command.kind === 'agentmd.put' || parsed.command.kind === 'avatar';
1950
+ if (requiresSelfAid
1951
+ && parsed.command.aid !== grantSelfAid
1952
+ && (parsed.command.kind === 'avatar' || !hasTrustedFullAccessDelegation(delegation.grant))) {
1926
1953
  return { ok: false, code: 'SCOPE_MISMATCH', error: 'Only the current Agent AID can be targeted' };
1927
1954
  }
1928
1955
  let conversationId;
@@ -1961,8 +1988,88 @@ export class CommandHandler {
1961
1988
  });
1962
1989
  if (!decision.allow)
1963
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
+ }
1964
2051
  try {
1965
- return { ok: true, result: await executeManagedAidOperation(parsed.command) };
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
+ }
2069
+ return {
2070
+ ok: true,
2071
+ result,
2072
+ };
1966
2073
  }
1967
2074
  catch (error) {
1968
2075
  const message = error instanceof Error ? error.message : String(error);
@@ -2188,7 +2295,14 @@ export class CommandHandler {
2188
2295
  if (!hasTrustedFullAccessDelegation(grant) && (grant.channelType !== 'aun' || grant.chatType !== 'private')) {
2189
2296
  return { ok: false, code: 'SCOPE_MISMATCH', error: 'Managed msg reads require the current AUN private relation' };
2190
2297
  }
2191
- if (!hasTrustedFullAccessDelegation(grant) && parsed.command.target !== conversationId) {
2298
+ // A management role may read history for any peer, one peer per query.
2299
+ // `issuedRole` is resolved daemon-side from the persisted relation
2300
+ // config, so it cannot be spoofed by the calling task. Narrower roles
2301
+ // stay pinned here, and authorizeOperation below remains the authority
2302
+ // for every role (member carries currentRelationOnly; visitor is denied).
2303
+ const mayReadCrossPeer = hasTrustedFullAccessDelegation(grant)
2304
+ || (parsed.command.kind === 'history' && isAdminRole(grant.issuedRole));
2305
+ if (!mayReadCrossPeer && parsed.command.target !== conversationId) {
2192
2306
  return { ok: false, code: 'SCOPE_MISMATCH', error: 'Only the current AUN private relation can be targeted' };
2193
2307
  }
2194
2308
  }
@@ -2458,6 +2572,29 @@ export class CommandHandler {
2458
2572
  });
2459
2573
  if (!ctlDecision.allow)
2460
2574
  return { ok: false, code: ctlDecision.code, error: ctlDecision.reason };
2575
+ if (cmd.startsWith('/status ') && cmd !== '/status --format json') {
2576
+ return { ok: false, code: 'INVALID_ARGUMENT', error: 'status 仅支持可选参数 --format json' };
2577
+ }
2578
+ if (cmd === '/status --format json') {
2579
+ const context = {
2580
+ selfAid: grantSelfAid,
2581
+ sessionId,
2582
+ taskId: grant.taskId,
2583
+ ...(grant.messageId ? { messageId: grant.messageId } : {}),
2584
+ baseagent: session.baseagent,
2585
+ channel: grant.channelType,
2586
+ channelKey: grant.channel,
2587
+ channelId: conversationId,
2588
+ chatType: grant.chatType,
2589
+ peerKey: grant.peerKey,
2590
+ peerId: conversationId,
2591
+ peerRole: ctlSubject.role,
2592
+ permissionMode: grant.permissionMode ?? ctlSubject.permissionMode,
2593
+ projectPath: path.resolve(session.projectPath),
2594
+ ...(grant.sessionRuntimeDir ? { tmpDir: grant.sessionRuntimeDir } : {}),
2595
+ };
2596
+ return { ok: true, result: JSON.stringify(context, null, 2), data: context };
2597
+ }
2461
2598
  if (cmd.startsWith('/model info ')) {
2462
2599
  const modelId = cmd.slice('/model info '.length).trim();
2463
2600
  if (!modelId || /\s/.test(modelId))
@@ -79,10 +79,12 @@ 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' },
85
86
  { id: 'config.rawFile', type: 'list', contexts: ['process'], name: 'config', label: '原始配置文件', section: 'process', order: 13, query: { cmd: '/config', args: { view: 'rawFile' }, operation: 'config.read', scope: 'process' }, options: { cmd: '/config', args: { option: 'rawFile' }, operation: 'config.read', scope: 'process', mode: 'dynamic' }, control: 'list' },
87
+ { id: 'baseagent.models', type: 'list', contexts: ['process'], name: 'baseagent-models', label: 'Baseagent 模型目录', section: 'process', order: 41, query: { cmd: '/baseagent-models', args: { baseagent: 'claude' }, operation: 'baseagent.models.list', scope: 'process' }, control: 'list' },
86
88
  { id: 'system.restart', type: 'action', contexts: ['process'], name: 'system', label: '重启服务', section: 'process', order: 20, action: { cmd: '/system', action: 'restart', operation: 'system.restart', scope: 'process' } },
87
89
  { id: 'system.upgrade', type: 'action', contexts: ['process'], name: 'system', label: '升级服务', section: 'process', order: 30, action: { cmd: '/system', action: 'upgrade', operation: 'system.upgrade', scope: 'process' } },
88
90
  { id: 'agent.list', type: 'list', contexts: ['process'], name: 'agent', label: 'Agent 列表', section: 'process', order: 40, options: { cmd: '/agent', args: { options: 'all' }, operation: 'agent.list', scope: 'control', mode: 'dynamic' }, control: 'list' },
@@ -110,7 +112,8 @@ export async function resolveMenuCatalogContext(input) {
110
112
  throw { code: 'FORBIDDEN', message: 'control catalog context requires a control subject' };
111
113
  }
112
114
  if (requested === 'process') {
113
- if (input.channelScope !== 'control' || !subject.fromControlChannel || !subject.isDaemonOwner) {
115
+ if (input.channelScope !== 'control' || !subject.fromControlChannel
116
+ || (!subject.isDaemonOwner && subject.processRole !== 'daemon-service')) {
114
117
  throw {
115
118
  code: 'NOT_ALLOWED',
116
119
  message: formatAuthorizationDenial('Process context requires a DaemonOwner control subject.', 'DAEMON_OWNER_REQUIRED'),