evolcore 0.0.25 → 0.0.26

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (231) hide show
  1. package/CHANGELOG.md +607 -565
  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 +40 -29
  8. package/bin/install-codex-managed-hooks.mjs +582 -555
  9. package/dist/agents/baseagent.js +96 -46
  10. package/dist/agents/claude-runner.js +50 -12
  11. package/dist/agents/codex/app-server-client.js +13 -5
  12. package/dist/agents/codex/catalog.js +1 -1
  13. package/dist/agents/codex/diagnostics.js +34 -1
  14. package/dist/agents/codex-runner.js +121 -38
  15. package/dist/agents/ecagent-runner.js +4 -9
  16. package/dist/agents/gemini-runner.js +2 -7
  17. package/dist/agents/runner-types.js +25 -17
  18. package/dist/aun/aid/agentmd.js +81 -8
  19. package/dist/aun/aid/client.js +1 -1
  20. package/dist/aun/aid/identity.js +1 -1
  21. package/dist/aun/aid/index.js +1 -1
  22. package/dist/aun/aid/store.js +17 -13
  23. package/dist/aun/menu-client.js +74 -0
  24. package/dist/aun/msg/group.js +1 -1
  25. package/dist/aun/msg/managed-operation.js +1 -1
  26. package/dist/aun/msg/p2p.js +6 -1
  27. package/dist/aun/msg/upload.js +30 -22
  28. package/dist/channels/aun.js +329 -94
  29. package/dist/cli/agent-command.js +105 -84
  30. package/dist/cli/agent-create.js +112 -10
  31. package/dist/cli/agent.js +16 -5
  32. package/dist/cli/aun-commands.js +297 -296
  33. package/dist/cli/bench.js +20 -20
  34. package/dist/cli/config.js +47 -28
  35. package/dist/cli/contact.js +70 -13
  36. package/dist/cli/ctl-command.js +22 -22
  37. package/dist/cli/daemon-commands.js +105 -40
  38. package/dist/cli/diag.js +31 -0
  39. package/dist/cli/fs-command.js +67 -67
  40. package/dist/cli/handoff-command.js +41 -9
  41. package/dist/cli/index.js +83 -77
  42. package/dist/cli/link-rules.js +21 -21
  43. package/dist/cli/managed-command-guard.js +3 -0
  44. package/dist/cli/menu-client.js +46 -31
  45. package/dist/cli/net-check.js +22 -22
  46. package/dist/cli/queue-command.js +29 -29
  47. package/dist/cli/response.js +30 -30
  48. package/dist/cli/restart-monitor.js +87 -40
  49. package/dist/cli/stats.js +51 -51
  50. package/dist/cli/trigger-command.js +56 -56
  51. package/dist/config/builtin-roles.js +1 -0
  52. package/dist/config/config-operation-service.js +145 -0
  53. package/dist/config/contact-operation-service.js +96 -7
  54. package/dist/config/resolved-config-op.js +27 -7
  55. package/dist/core/agent-application-service.js +2 -1
  56. package/dist/core/auth/authorization-audit.js +20 -12
  57. package/dist/core/auth/operation-authorizer.js +25 -4
  58. package/dist/core/auth/operation-catalog.js +35 -4
  59. package/dist/core/auth/remote-access-store.js +40 -1
  60. package/dist/core/bootstrap-service.js +1 -1
  61. package/dist/core/command/command-handler.js +83 -26
  62. package/dist/core/command/diag-service.js +118 -0
  63. package/dist/core/command/menu-catalog.js +12 -3
  64. package/dist/core/command/menu-handler.js +47 -26
  65. package/dist/core/command/menu-protocol.js +5 -0
  66. package/dist/core/command/menu-token-store.js +41 -8
  67. package/dist/core/command/slash-gate.js +34 -4
  68. package/dist/core/command/slash-handler.js +86 -21
  69. package/dist/core/handoff/runtime.js +205 -27
  70. package/dist/core/handoff/store.js +193 -4
  71. package/dist/core/message/im-renderer.js +72 -21
  72. package/dist/core/message/message-bridge.js +69 -9
  73. package/dist/core/message/message-queue.js +26 -0
  74. package/dist/core/message/message-utils.js +1 -0
  75. package/dist/core/message/response-engine.js +47 -28
  76. package/dist/core/model/catalog-service.js +79 -49
  77. package/dist/core/model/context-window-suffix.js +33 -0
  78. package/dist/core/model/model-catalog.js +1 -1
  79. package/dist/core/model/model-permission.js +12 -3
  80. package/dist/core/permission/ec-command-parser.js +51 -12
  81. package/dist/core/permission/sandbox-runtime.js +36 -14
  82. package/dist/core/permission/tool-policy.js +2 -1
  83. package/dist/index.js +280 -163
  84. package/dist/ipc.js +54 -2
  85. package/dist/response-system/engines/v1/proactive-flow.js +59 -2
  86. package/dist/stats/billing.js +41 -15
  87. package/dist/stats/db.js +159 -159
  88. package/dist/stats/eck-vars.js +9 -9
  89. package/dist/stats/price-resolver.js +28 -10
  90. package/dist/stats/query.js +139 -139
  91. package/dist/stats/role-budget.js +6 -6
  92. package/dist/stats/writer.js +51 -51
  93. package/dist/utils/cross-platform.js +30 -1
  94. package/dist/utils/error-dict.json +160 -160
  95. package/dist/utils/error-utils.js +21 -0
  96. package/dist/utils/evolcore-version.js +129 -0
  97. package/dist/utils/logger.js +3 -1
  98. package/dist/utils/macos-autostart.js +22 -22
  99. package/dist/utils/model-prices.jsonl +23 -23
  100. package/dist/utils/npm-ops.js +176 -35
  101. package/dist/utils/welcome.js +22 -22
  102. package/ecagent/LICENSE +21 -21
  103. package/ecagent/NOTICE +11 -11
  104. package/ecagent/README.md +61 -61
  105. package/ecagent/dist/harness/compaction/branch-summarization.js +30 -30
  106. package/ecagent/dist/harness/compaction/compaction.js +83 -83
  107. package/ecagent/dist/harness/messages.js +7 -7
  108. package/kits/docs/GUIDE.md +20 -20
  109. package/kits/docs/INDEX.md +66 -66
  110. package/kits/docs/aun/CHEATSHEET.md +19 -19
  111. package/kits/docs/aun/SYNC_PROTOCOL.md +15 -15
  112. package/kits/docs/channels/aun.md +56 -56
  113. package/kits/docs/channels/feishu.md +56 -56
  114. package/kits/docs/context-assembly.md +364 -364
  115. package/kits/docs/eck_templates/GUIDE.template.md +22 -22
  116. package/kits/docs/eck_templates/INDEX.template.md +28 -28
  117. package/kits/docs/eck_templates/path-registry.template.md +33 -33
  118. package/kits/docs/eck_templates/runtime.template.md +19 -19
  119. package/kits/docs/evolcore/INDEX.md +71 -71
  120. package/kits/docs/evolcore/agent-create.md +170 -165
  121. package/kits/docs/evolcore/agent.md +66 -66
  122. package/kits/docs/evolcore/aid.md +60 -60
  123. package/kits/docs/evolcore/codex-windows-sandbox.md +146 -146
  124. package/kits/docs/evolcore/config.md +202 -195
  125. package/kits/docs/evolcore/contact.md +75 -68
  126. package/kits/docs/evolcore/ctl.md +46 -46
  127. package/kits/docs/evolcore/event.md +216 -216
  128. package/kits/docs/evolcore/fs-architecture.md +1215 -1215
  129. package/kits/docs/evolcore/fs.md +110 -110
  130. package/kits/docs/evolcore/group-collaboration.md +243 -243
  131. package/kits/docs/evolcore/group-fs.md +17 -17
  132. package/kits/docs/evolcore/group-rules.md +251 -251
  133. package/kits/docs/evolcore/group.md +152 -152
  134. package/kits/docs/evolcore/menu-client.md +42 -34
  135. package/kits/docs/evolcore/model.md +50 -50
  136. package/kits/docs/evolcore/msg.md +166 -165
  137. package/kits/docs/evolcore/response.md +75 -75
  138. package/kits/docs/evolcore/rpc.md +37 -37
  139. package/kits/docs/evolcore/self-summary.md +29 -29
  140. package/kits/docs/evolcore/stats.md +83 -83
  141. package/kits/docs/evolcore/storage.md +50 -50
  142. package/kits/docs/evolcore/trigger.md +575 -575
  143. package/kits/docs/identity/AID_PROFILE_SPEC.md +26 -26
  144. package/kits/docs/identity/PATH_OPS.md +16 -16
  145. package/kits/docs/identity/ROLE_DETAIL.md +25 -25
  146. package/kits/docs/identity/identity-tools.md +26 -26
  147. package/kits/docs/path-registry.md +62 -62
  148. package/kits/docs/prompt-loading-architecture.md +269 -269
  149. package/kits/docs/venues/aun-group.md +45 -45
  150. package/kits/docs/venues/aun-private.md +10 -10
  151. package/kits/docs/venues/client-desktop.md +10 -10
  152. package/kits/docs/venues/client-mobile.md +10 -10
  153. package/kits/docs/venues/feishu-group.md +13 -13
  154. package/kits/docs/venues/feishu-private.md +9 -9
  155. package/kits/docs/venues/group.md +37 -25
  156. package/kits/docs/venues/private.md +10 -10
  157. package/kits/eck_manifest.auxiliary.json +43 -43
  158. package/kits/eck_manifest.json +190 -190
  159. package/kits/eck_message_manifest.json +74 -74
  160. package/kits/migrations/README-role-config-v4.md +32 -32
  161. package/kits/migrations/migrate-contact-book-v2.mjs +748 -748
  162. package/kits/migrations/migrate-role-config-v4.mjs +614 -614
  163. package/kits/migrations/migrate-role-config-v5.mjs +365 -365
  164. package/kits/migrations/rename-config-file.mjs +99 -99
  165. package/kits/rules/01-overview.md +162 -162
  166. package/kits/rules/02-navigation.md +96 -96
  167. package/kits/rules/03-identity.md +36 -36
  168. package/kits/rules/04-relation.md +75 -75
  169. package/kits/rules/05-venue.md +60 -60
  170. package/kits/rules/06-channel.md +63 -63
  171. package/kits/schemas/_meta.json +53 -53
  172. package/kits/schemas/agent-config.schema.1.json +177 -177
  173. package/kits/schemas/agent-config.schema.10.json +395 -395
  174. package/kits/schemas/agent-config.schema.11.json +421 -421
  175. package/kits/schemas/agent-config.schema.12.json +427 -427
  176. package/kits/schemas/agent-config.schema.2.json +239 -239
  177. package/kits/schemas/agent-config.schema.3.json +119 -119
  178. package/kits/schemas/agent-config.schema.4.json +208 -208
  179. package/kits/schemas/agent-config.schema.5.json +326 -326
  180. package/kits/schemas/agent-config.schema.6.json +322 -322
  181. package/kits/schemas/agent-config.schema.7.json +303 -303
  182. package/kits/schemas/agent-config.schema.8.json +304 -304
  183. package/kits/schemas/agent-config.schema.9.json +364 -364
  184. package/kits/schemas/contact-book.schema.1.json +36 -36
  185. package/kits/schemas/contact-book.schema.2.json +43 -43
  186. package/kits/schemas/contact-book.schema.3.json +67 -67
  187. package/kits/schemas/daemon.schema.1.json +91 -91
  188. package/kits/schemas/daemon.schema.2.json +101 -101
  189. package/kits/schemas/daemon.schema.3.json +123 -123
  190. package/kits/schemas/daemon.schema.4.json +136 -136
  191. package/kits/schemas/daemon.schema.5.json +135 -135
  192. package/kits/schemas/daemon.schema.6.json +131 -131
  193. package/kits/schemas/defaults.schema.1.json +81 -81
  194. package/kits/schemas/defaults.schema.2.json +85 -85
  195. package/kits/schemas/defaults.schema.3.json +73 -73
  196. package/kits/schemas/defaults.schema.4.json +107 -107
  197. package/kits/schemas/defaults.schema.5.json +119 -119
  198. package/kits/schemas/menu-exec-schema-commands.md +208 -208
  199. package/kits/schemas/migrations/README.md +30 -30
  200. package/kits/schemas/relation-config.schema.1.json +158 -158
  201. package/kits/schemas/relation-config.schema.2.json +73 -73
  202. package/kits/schemas/relation-config.schema.3.json +50 -50
  203. package/kits/schemas/relation-config.schema.4.json +47 -47
  204. package/kits/schemas/relation-config.schema.5.json +47 -47
  205. package/kits/schemas/relation-config.schema.6.json +46 -46
  206. package/kits/schemas/relation-config.schema.7.json +59 -59
  207. package/kits/schemas/relation-config.schema.8.json +103 -103
  208. package/kits/schemas/role-config.schema.1.json +201 -201
  209. package/kits/schemas/role-registry.schema.1.json +35 -35
  210. package/kits/schemas/single-session.schema.1.json +31 -31
  211. package/kits/schemas/single-session.schema.2.json +57 -57
  212. package/kits/schemas/single-session.schema.3.json +32 -32
  213. package/kits/templates/bootstrap-welcome.md +17 -17
  214. package/kits/templates/message-fragments/handoff-context-to-target.md +9 -9
  215. package/kits/templates/message-fragments/handoff-request-to-target.md +19 -19
  216. package/kits/templates/message-fragments/handoff-response-to-origin.md +9 -9
  217. package/kits/templates/message-fragments/inject-default.md +2 -2
  218. package/kits/templates/message-fragments/item.md +2 -2
  219. package/kits/templates/roles/admin.json +92 -92
  220. package/kits/templates/roles/member.json +96 -96
  221. package/kits/templates/roles/owner.json +9 -9
  222. package/kits/templates/roles/visitor.json +85 -85
  223. package/kits/templates/system-fragments/baseagent.md +14 -14
  224. package/kits/templates/system-fragments/bootstrap.md +31 -29
  225. package/kits/templates/system-fragments/channel.md +64 -64
  226. package/kits/templates/system-fragments/commands.md +28 -28
  227. package/kits/templates/system-fragments/identity.md +13 -13
  228. package/kits/templates/system-fragments/relation.md +19 -19
  229. package/kits/templates/system-fragments/session.md +59 -59
  230. package/kits/templates/system-fragments/venue.md +31 -31
  231. package/package.json +100 -98
@@ -369,7 +369,7 @@ export class BootstrapService {
369
369
  const content = existing.trim() || buildInitialAgentMd({
370
370
  aid,
371
371
  name: fallbackName,
372
- type: 'codeagent',
372
+ type: 'agent',
373
373
  });
374
374
  try {
375
375
  await adapter.uploadAgentMd(content);
@@ -1,4 +1,5 @@
1
1
  import { BaseagentRunnerUnavailableError } from '../../agents/runner-types.js';
2
+ import { parseMenuClientArgs } from '../../cli/menu-client.js';
2
3
  import { renderCommandCardAsText } from '../interaction-router.js';
3
4
  import { buildEnvelope, deliveryTargetFromSession, isInteractionSendAccepted, replyContextFromSession, sendInteractionPayload } from '../message/message-utils.js';
4
5
  import { resolvePaths, getPackageRoot } from '../../paths.js';
@@ -17,7 +18,7 @@ import { tryParseChannelKey } from '../channel-loader.js';
17
18
  import { displaySessionTitle } from '../session/session-title.js';
18
19
  import { buildSessionTurnList } from '../session/session-turns.js';
19
20
  import { resolveConfigCommand } from '../../config/resolved-config-op.js';
20
- import { AUTHENTICATED_CONFIG_ROLE_ENV, executeResolvedConfigCommand } from '../../config/config-operation-service.js';
21
+ import { AUTHENTICATED_CONFIG_ROLE_ENV, executeResolvedConfigCommandAsync } from '../../config/config-operation-service.js';
21
22
  import { splitConfigBatchGetArgv } from '../../cli/cli-argv.js';
22
23
  import { auditCommandAuthorization, auditFullAccessEvent, hashArgv } from '../auth/authorization-audit.js';
23
24
  import { executeResolvedContactCommand, resolveContactCommand } from '../../config/contact-operation-service.js';
@@ -123,6 +124,43 @@ export class CommandHandler {
123
124
  daemonMonitorProvider;
124
125
  agentDelegationRegistry;
125
126
  handoffRuntime;
127
+ diagService;
128
+ setDiagService(service) { this.diagService = service; }
129
+ async handleMenuClientOperation(argv, sessionId, delegationToken, delegationCommandHash, send) {
130
+ const validation = this.agentDelegationRegistry?.validate(delegationToken, sessionId, delegationCommandHash);
131
+ if (!validation?.ok)
132
+ return { ok: false, code: validation?.code ?? 'DELEGATION_REQUIRED', error: validation?.reason ?? 'Task delegation is required' };
133
+ const grant = validation.grant;
134
+ const session = await this.sessionManager.getSessionById(sessionId);
135
+ if (!session || session.selfAID !== grant.selfAid || argv[0] !== 'request' || argv[1] !== grant.selfAid)
136
+ return { ok: false, code: 'SCOPE_MISMATCH', error: 'Menu requests must use the current Agent identity and session' };
137
+ try {
138
+ const channelId = parsePeerKey(grant.peerKey).channelId;
139
+ const subject = buildAuthSubject({ selfAid: grant.selfAid, actorId: grant.actorId, channel: grant.channel,
140
+ channelType: grant.channelType, channelId, chatType: grant.chatType, conversationId: channelId,
141
+ processOwners: [], ...trustedExecutionIdentityInput(grant) });
142
+ const decision = authorizeOperation({ source: 'agent-tool', subject,
143
+ intent: { operation: 'ec.menu.request', scope: 'relation', source: 'agent-tool', args: { self: grant.selfAid, peer: argv[2], targetId: argv[2] } },
144
+ auditMetadata: { taskId: grant.taskId, sessionId, messageId: grant.messageId } });
145
+ if (!decision.allow)
146
+ return { ok: false, code: decision.code, error: decision.reason };
147
+ const options = parseMenuClientArgs(argv, file => {
148
+ const target = fs.realpathSync(path.resolve(session.projectPath, file));
149
+ const roots = [session.projectPath, this.getManagedSessionTempDir(session)].filter((v) => !!v)
150
+ .map(root => fs.realpathSync(root));
151
+ if (!roots.some(root => isSameOrDescendant(target, root)) || isHClassPath(target) || isLClassPath(target))
152
+ throw new Error('Menu request file must be an unprotected file in the project or session TMPDIR');
153
+ const stat = fs.statSync(target);
154
+ if (!stat.isFile() || stat.nlink > 1 || stat.size > 128 * 1024)
155
+ throw new Error('Menu request file must be a regular file of at most 128 KiB');
156
+ return fs.readFileSync(target, 'utf8');
157
+ });
158
+ return { ok: true, response: await send(options) };
159
+ }
160
+ catch (error) {
161
+ return { ok: false, code: error.code ?? 'MENU_REQUEST_FAILED', error: error.message };
162
+ }
163
+ }
126
164
  contactRequestSubmitter;
127
165
  /**
128
166
  * Get the runner for a (channel, baseagent) pair.
@@ -983,13 +1021,14 @@ export class CommandHandler {
983
1021
  getMenuItems(role, chatType = 'private', scope = 'agent', authSubject) {
984
1022
  return menuGetMenuItems.call(this, role, chatType, scope, authSubject);
985
1023
  }
986
- async getMenuCatalog(channel, channelId, subject, request, channelScope = 'agent') {
1024
+ async getMenuCatalog(channel, channelId, subject, request, channelScope = 'agent', paircodeAllowed) {
987
1025
  return await getMenuCatalog({
988
1026
  subject,
989
1027
  channelType: this.resolveChannelType(channel),
990
1028
  channelScope,
991
1029
  channelId,
992
1030
  request,
1031
+ ...(paircodeAllowed === undefined ? {} : { paircodeAllowed }),
993
1032
  });
994
1033
  }
995
1034
  async execMenuValueBatch(channel, channelId, subject, args, request, channelScope = 'agent') {
@@ -1176,9 +1215,9 @@ export class CommandHandler {
1176
1215
  /**
1177
1216
  * 主命令处理入口
1178
1217
  */
1179
- async handle(content, channel, channelId, sendMessage, userId, threadId, chatType, source, messageId, selfAID, overrideIdentity, authSubject, replyContext) {
1218
+ async handle(content, channel, channelId, sendMessage, userId, threadId, chatType, source, messageId, selfAID, overrideIdentity, authSubject, replyContext, enforceGroupSlashPolicy = false) {
1180
1219
  try {
1181
- const result = await this._handleInternal(content, channel, channelId, sendMessage, userId, threadId, chatType, source, messageId, selfAID, overrideIdentity, authSubject, replyContext);
1220
+ const result = await this._handleInternal(content, channel, channelId, sendMessage, userId, threadId, chatType, source, messageId, selfAID, overrideIdentity, authSubject, replyContext, enforceGroupSlashPolicy);
1182
1221
  // Lifecycle commands are control-plane operations. Their replies must
1183
1222
  // use notification payloads so other agents in a group do not treat the
1184
1223
  // text as ordinary model input. Normalize guard-path replies here too,
@@ -1210,8 +1249,10 @@ export class CommandHandler {
1210
1249
  throw error;
1211
1250
  }
1212
1251
  }
1213
- async _handleInternal(content, channel, channelId, sendMessage, userId, threadId, chatType, source, messageId, selfAID, overrideIdentity, authSubject, replyContext) {
1214
- return await handleSlashCommand.call(this, content, channel, channelId, sendMessage, userId, threadId, chatType, source, messageId, selfAID, overrideIdentity, authSubject, replyContext);
1252
+ async _handleInternal(content, channel, channelId, sendMessage, userId, threadId, chatType, source, messageId, selfAID, overrideIdentity, authSubject, replyContext,
1253
+ // Menu and delegated CLI operations reuse this implementation without the chat-only policy.
1254
+ applyGroupSlashPolicy = false) {
1255
+ return await handleSlashCommand.call(this, content, channel, channelId, sendMessage, userId, threadId, chatType, source, messageId, selfAID, overrideIdentity, authSubject, replyContext, applyGroupSlashPolicy);
1215
1256
  }
1216
1257
  async handleTriggerCommand(scheduler, content, channel, channelId, peerId, isAdmin, messageId, chatType, threadId, isDaemonOwner = false, canApprovePersistentFullAccess = isDaemonOwner) {
1217
1258
  if (content === '/trigger') {
@@ -1399,17 +1440,17 @@ export class CommandHandler {
1399
1440
  const nextStr = reg.trigger.nextFireAt ? new Date(reg.trigger.nextFireAt).toLocaleString() : '未计算';
1400
1441
  return `✅ 触发器已注册:**${reg.trigger.name}**\n下次触发:${nextStr}`;
1401
1442
  }
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 的兼容别名)
1443
+ return `❌ 未知子命令。用法:
1444
+ /trigger — 查看活跃触发器
1445
+ /trigger list [--all] — 查看所有触发器
1446
+ /trigger create <参数> — 创建触发器(set 为兼容别名)
1447
+ /trigger update <名称|ID> <参数> — 修改触发器
1448
+ 常用参数:--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
1449
+ /trigger enable <名称|ID> — 启用触发器
1450
+ /trigger disable <名称|ID> — 暂停触发器
1451
+ /trigger show <名称|ID> — 查看触发器详情
1452
+ /trigger run <名称|ID> [--dry-run] — 手动触发
1453
+ /trigger cancel <名称|ID> — 禁用触发器(disable 的兼容别名)
1413
1454
  /trigger delete <名称|ID> — 删除触发器`;
1414
1455
  }
1415
1456
  async handleTrigger(content, channel, channelId, peerId, isAdmin, messageId, chatType, threadId, isDaemonOwner = false, canApprovePersistentFullAccess = isDaemonOwner) {
@@ -1677,8 +1718,9 @@ export class CommandHandler {
1677
1718
  buildCtlReplyContext(session) {
1678
1719
  const ctx = { delivery: deliveryTargetFromSession(session) };
1679
1720
  const meta = session.metadata;
1680
- if (meta?.replyContext?.threadId)
1681
- ctx.threadId = meta.replyContext.threadId;
1721
+ const threadId = session.threadId || meta?.replyContext?.threadId;
1722
+ if (threadId)
1723
+ ctx.threadId = threadId;
1682
1724
  if (meta?.replyContext?.peerId)
1683
1725
  ctx.peerId = meta.replyContext.peerId;
1684
1726
  if (!ctx.peerId && meta?.peerId)
@@ -1759,7 +1801,7 @@ export class CommandHandler {
1759
1801
  processOwners: loadDaemonConfig().owners ?? [],
1760
1802
  ...trustedExecutionIdentityInput(grant),
1761
1803
  });
1762
- const executeOne = (fieldArgv) => {
1804
+ const executeOne = async (fieldArgv) => {
1763
1805
  const resolved = resolveConfigCommand(fieldArgv, {
1764
1806
  defaultRelation: { self: grant.selfAid, peerKey: grant.peerKey },
1765
1807
  });
@@ -1780,6 +1822,7 @@ export class CommandHandler {
1780
1822
  dangerous: resolved.command.dangerous,
1781
1823
  },
1782
1824
  resolvedConfigCommand: resolved.command,
1825
+ allowExplicitRelationTarget: true,
1783
1826
  auditMetadata: {
1784
1827
  argvHash: hashArgv(resolved.command.canonicalArgv),
1785
1828
  taskId: grant.taskId,
@@ -1788,22 +1831,35 @@ export class CommandHandler {
1788
1831
  });
1789
1832
  if (!decision.allow)
1790
1833
  return { ok: false, code: decision.code, error: decision.reason };
1791
- return executeResolvedConfigCommand(resolved.command, { role: subject.role });
1834
+ const roleContext = {
1835
+ self: grant.selfAid,
1836
+ actorRole: subject.role,
1837
+ actorRoleSource: subject.roleSource,
1838
+ actorAllowAccess: subject.allowAccess,
1839
+ actorAid: grant.actorId,
1840
+ isDaemonOwner: subject.isDaemonOwner,
1841
+ fromControlChannel: subject.fromControlChannel,
1842
+ chatType: grant.chatType,
1843
+ };
1844
+ return await executeResolvedConfigCommandAsync(resolved.command, {
1845
+ role: subject.role,
1846
+ roleContext,
1847
+ });
1792
1848
  };
1793
1849
  const batch = splitConfigBatchGetArgv(argv);
1794
1850
  if (batch && batch.fields.length > 1) {
1795
- const results = batch.fields.map(field => {
1796
- const result = executeOne(['config', 'get', field, ...batch.rest]);
1851
+ const results = await Promise.all(batch.fields.map(async (field) => {
1852
+ const result = await executeOne(['config', 'get', field, ...batch.rest]);
1797
1853
  if (!result.ok)
1798
1854
  return { ok: false, field, code: result.code, error: result.error };
1799
1855
  if (result.subcommand !== 'get') {
1800
1856
  return { ok: false, field, code: 'UNEXPECTED_RESULT', error: `config get ${field} produced a non-get result` };
1801
1857
  }
1802
1858
  return result;
1803
- });
1859
+ }));
1804
1860
  return { ok: true, result: { ok: true, subcommand: 'batch-get', results } };
1805
1861
  }
1806
- const result = executeOne(argv);
1862
+ const result = await executeOne(argv);
1807
1863
  return result.ok
1808
1864
  ? { ok: true, result }
1809
1865
  : { ok: false, code: result.code, error: result.error, ...(result.data !== undefined ? { data: result.data } : {}) };
@@ -1827,7 +1883,8 @@ export class CommandHandler {
1827
1883
  catch {
1828
1884
  return { ok: false, code: 'INVALID_DELEGATION', error: 'Delegation contains an invalid relation key' };
1829
1885
  }
1830
- const resolved = resolveContactCommand(argv);
1886
+ const resolvedArgv = argv.includes('--self') ? argv : [...argv, '--self', grant.selfAid];
1887
+ const resolved = resolveContactCommand(resolvedArgv);
1831
1888
  if (!resolved.ok)
1832
1889
  return { ok: false, code: resolved.code, error: resolved.reason };
1833
1890
  if (resolved.command.selfAid !== grant.selfAid && !hasTrustedFullAccessDelegation(grant)) {
@@ -0,0 +1,118 @@
1
+ import { isValidAid } from '../../aun/aid/validation.js';
2
+ import { remoteAccessService } from '../auth/remote-access-store.js';
3
+ export const DIAGNOSTIC_AGENT_AID = 'diag.agentid.cn';
4
+ /** IPC verifies local control authority; managed calls must independently prove their actor. */
5
+ export async function executeDiagIpc(service, registry, owners, cmd) {
6
+ if (cmd.sessionId !== undefined) {
7
+ if (typeof cmd.sessionId !== 'string' || !cmd.sessionId)
8
+ return { ok: false, code: 'INVALID_SESSION', message: '无效托管会话' };
9
+ const auth = registry.validate(cmd.delegationToken, cmd.sessionId, cmd.delegationCommandHash);
10
+ if (!auth.ok)
11
+ return { ok: false, code: auth.code, message: auth.reason };
12
+ if (auth.grant.channelType !== 'aun' || !owners.includes(auth.grant.actorId))
13
+ return { ok: false, code: 'DAEMON_OWNER_REQUIRED', message: '托管诊断发起需要本机 AUN DaemonOwner' };
14
+ }
15
+ try {
16
+ if (!Array.isArray(cmd.argv) || cmd.argv.some((v) => typeof v !== 'string'))
17
+ throw new Error('Invalid diag argv');
18
+ return await service.execute(parseDiagArgs(cmd.argv));
19
+ }
20
+ catch (error) {
21
+ return { ok: false, code: error.code ?? 'INVALID_ARGUMENT', message: error.message };
22
+ }
23
+ }
24
+ export function parseDiagArgs(args) {
25
+ const words = [];
26
+ let ownerAid;
27
+ let literal = false;
28
+ for (let i = 0; i < args.length; i++) {
29
+ const word = args[i];
30
+ if (!literal && word === '--') {
31
+ literal = true;
32
+ continue;
33
+ }
34
+ if (!literal && word === '--owner') {
35
+ if (ownerAid || !args[i + 1] || !isValidAid(args[i + 1]))
36
+ throw new Error('--owner 需要一个有效 AID');
37
+ ownerAid = args[++i];
38
+ }
39
+ else if (!literal && word.startsWith('--'))
40
+ throw new Error(`不支持的参数:${word}`);
41
+ else
42
+ words.push(word);
43
+ }
44
+ const problem = words.join(' ').trim();
45
+ if (problem.toLowerCase() === 'stop') {
46
+ if (ownerAid)
47
+ throw new Error('diag stop 不需要指定对象');
48
+ return { action: 'stop' };
49
+ }
50
+ if (!problem || problem.length > 16_000)
51
+ throw new Error('请提供 1 到 16000 字符的问题描述');
52
+ return { action: 'start', problem, ...(ownerAid ? { ownerAid } : {}) };
53
+ }
54
+ export class DiagService {
55
+ context;
56
+ send;
57
+ tokens;
58
+ constructor(context, send, tokens = remoteAccessService) {
59
+ this.context = context;
60
+ this.send = send;
61
+ this.tokens = tokens;
62
+ }
63
+ /** Local CLI authority is checked by IPC; slash identity is authenticated AUN peerId. */
64
+ async execute(command, actorAid) {
65
+ const { controlAid, owners } = this.context();
66
+ if (!controlAid || !isValidAid(controlAid))
67
+ return { ok: false, code: 'CONTROL_AID_REQUIRED', message: '本机尚未配置 Control AID' };
68
+ if (actorAid !== undefined && !owners.includes(actorAid))
69
+ return { ok: false, code: 'DAEMON_OWNER_REQUIRED', message: '只有本机 DaemonOwner 可以发起或停止诊断' };
70
+ if (command.action === 'stop') {
71
+ const previous = this.tokens.revokeDiagnosticToken(controlAid);
72
+ if (!previous)
73
+ return { ok: true, message: '当前没有诊断授权' };
74
+ const diag = previous.diagnostic;
75
+ try {
76
+ await this.send(diag.agentAid, `诊断授权已取消。目标 daemon:${controlAid};case:${diag.caseId};沟通 Owner:${diag.ownerAid}。请停止这次诊断的后续访问,并告知 Owner。`);
77
+ return { ok: true, message: '诊断授权已撤销,已通知诊断 Agent' };
78
+ }
79
+ catch {
80
+ return { ok: true, code: 'DIAG_STOP_NOTICE_FAILED', message: '诊断授权已撤销;取消通知发送失败,诊断 Agent 后续访问将被拒绝' };
81
+ }
82
+ }
83
+ const ownerAid = actorAid ?? command.ownerAid ?? owners[0];
84
+ if (!ownerAid || !isValidAid(ownerAid) || !owners.includes(ownerAid))
85
+ return { ok: false, code: 'DAEMON_OWNER_REQUIRED', message: '请配置本机 owners,或指定其中一个有效 Owner AID' };
86
+ if (!command.problem?.trim() || command.problem.length > 16_000)
87
+ return { ok: false, code: 'INVALID_ARGUMENT', message: '请提供 1 到 16000 字符的问题描述' };
88
+ let record;
89
+ try {
90
+ record = this.tokens.issueDiagnosticToken(controlAid, DIAGNOSTIC_AGENT_AID, ownerAid);
91
+ }
92
+ catch (error) {
93
+ return { ok: false, code: error.code ?? 'DIAG_AUTH_FAILED', message: error.message };
94
+ }
95
+ const caseId = record.diagnostic.caseId;
96
+ const invitation = [
97
+ '请协助诊断以下主机,并主动联系指定 Owner 展示进展、追问和报告。',
98
+ `目标 daemon Control AID:${controlAid}`, `沟通 Owner AID:${ownerAid}`, `case:${caseId}`,
99
+ `远程访问 Token:${record.token}`,
100
+ '请通过标准 Menu 调用诊断;Token 放入 E2EE 受保护头。权限复用该主机既有 Control Menu,包含 restart。有效访问滑动续期,空闲 60 分钟失效。',
101
+ '先给 Owner 发开始消息,使用通用 handoff 保留目标、Token、问题和发现;无需等待 Owner 回复即可开始。授权失效后停止访问。',
102
+ `问题描述:\n${command.problem.trim()}`,
103
+ ].join('\n');
104
+ try {
105
+ await this.send(DIAGNOSTIC_AGENT_AID, invitation);
106
+ }
107
+ catch (error) {
108
+ this.tokens.revokeDiagnosticToken(controlAid, record.token);
109
+ const reason = error instanceof Error ? error.message : String(error);
110
+ return { ok: false, code: 'DIAG_INVITATION_FAILED', message: `未能确认诊断邀请发送成功:${reason}。本次授权已撤销,请重试` };
111
+ }
112
+ // A stop may race with delivery; never report an already revoked grant as active.
113
+ if (this.tokens.getDiagnosticToken(controlAid)?.token !== record.token)
114
+ return { ok: false, code: 'DIAG_CANCELLED', message: '邀请发送期间授权已撤销,本次诊断已取消' };
115
+ return { ok: true, caseId, ownerAid, agentAid: DIAGNOSTIC_AGENT_AID,
116
+ message: `诊断邀请已发出,远程诊断 Agent 将联系 ${ownerAid},请在与 ${DIAGNOSTIC_AGENT_AID} 的会话中查看处理过程。` };
117
+ }
118
+ }
@@ -87,6 +87,7 @@ const ENTRIES = [
87
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' },
88
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' } },
89
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' } },
90
+ { id: 'system.paircode', type: 'action', contexts: ['process'], name: 'system', label: '获取配对码', section: 'process', order: 35, action: { cmd: '/system', action: 'paircode', operation: 'system.paircode', scope: 'process' } },
90
91
  { 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' },
91
92
  { id: 'agent.create', type: 'action', contexts: ['process'], name: 'agent', label: '创建 Agent', section: 'process', order: 50, action: { cmd: '/agent', action: 'create', operation: 'agent.create', scope: 'control' } },
92
93
  { id: 'agent.reload', type: 'action', contexts: ['process'], name: 'agent', label: '重载 Agent', section: 'process', order: 60, action: { cmd: '/agent', action: 'reload', operation: 'agent.reload', scope: 'control' } },
@@ -304,11 +305,18 @@ export async function resolveExplicitMenuExecutionContext(input) {
304
305
  }
305
306
  function entryVisible(entry, context) { return entry.contexts.includes(context.kind); }
306
307
  function intentFor(binding, context) { return { operation: binding.operation, scope: binding.scope, source: 'menu', args: { ...bindingArgs(binding, context) } }; }
307
- async function operationView(binding, context) {
308
+ async function operationView(binding, context, options = {}) {
308
309
  if (!binding)
309
310
  return undefined;
310
311
  if (binding.supported === false)
311
312
  return { supported: false, allowed: false, reason: { code: 'NOT_SUPPORTED', message: 'operation is not supported' } };
313
+ if (binding.operation === 'system.paircode' && options.paircodeAllowed === false) {
314
+ return {
315
+ supported: true,
316
+ allowed: false,
317
+ reason: { code: 'NOT_ALLOWED', message: 'Pairing code issuance requires encrypted DaemonOwner authentication' },
318
+ };
319
+ }
312
320
  if (binding.operation === 'group.rulespolicy.update' && context.subject.role !== 'owner') {
313
321
  return { supported: true, allowed: false, reason: { code: 'PERMISSION_DENIED', message: 'rulesPolicy can only be changed by an Agent owner' } };
314
322
  }
@@ -331,16 +339,17 @@ function wireBinding(binding, context) {
331
339
  export async function getMenuCatalog(input) {
332
340
  const context = await resolveMenuCatalogContext(input);
333
341
  const entries = [];
342
+ const paircodeAllowed = input.paircodeAllowed ?? input.channelScope !== 'control';
334
343
  for (const entry of ENTRIES.filter(candidate => entryVisible(candidate, context)).sort((a, b) => a.order - b.order)) {
335
344
  const operations = {};
336
345
  for (const [key, binding] of [['query', entry.query], ['options', entry.options], ['update', entry.update], ['action', entry.action]]) {
337
- const view = await operationView(binding, context);
346
+ const view = await operationView(binding, context, { paircodeAllowed });
338
347
  if (view)
339
348
  operations[key] = view;
340
349
  }
341
350
  entries.push({ id: entry.id, type: entry.type, name: entry.name, label: entry.label, section: entry.section, order: entry.order, target: { scope: context.kind === 'agent' ? 'agent' : context.kind === 'process' ? 'process' : 'relation', ...(context.self ? { self: context.self } : {}), ...(context.peerKey ? { peerKey: context.peerKey } : {}) }, valueSchema: entry.valueSchema ?? { type: 'object' }, operations, wire: { name: entry.name, operations: { ...(wireBinding(entry.query, context) ? { query: wireBinding(entry.query, context) } : {}), ...(wireBinding(entry.options, context) ? { options: wireBinding(entry.options, context) } : {}), ...(wireBinding(entry.update, context) ? { update: wireBinding(entry.update, context) } : {}), ...(wireBinding(entry.action, context) ? { action: wireBinding(entry.action, context) } : {}) } }, ui: { control: entry.control ?? 'action' } });
342
351
  }
343
- const policyRevision = hash({ role: context.subject.role, self: context.subject.selfAid, peerKey: context.subject.peerKey, groupMembershipRole: context.groupMembershipRole, baselineRole: context.baselineRole, roles: readRolesConfig(context.subject.selfAid) });
352
+ const policyRevision = hash({ role: context.subject.role, self: context.subject.selfAid, peerKey: context.subject.peerKey, groupMembershipRole: context.groupMembershipRole, baselineRole: context.baselineRole, paircodeAllowed, roles: readRolesConfig(context.subject.selfAid) });
344
353
  const data = { $schema_version: 1, kind: 'menu.catalog', complete: true, catalogRevision: hash({ entries, context: context.contextKey, policyRevision }), policyRevision, generatedAt: Date.now(), context: { contextKey: context.contextKey, kind: context.kind, channelScope: context.channelScope, transport: context.transport, chatType: context.chatType, ...(context.self ? { self: context.self } : {}), ...(context.actor ? { actor: context.actor } : {}), ...(context.groupId ? { groupId: context.groupId } : {}), ...(context.peerKey ? { peerKey: context.peerKey } : {}), requesterRole: context.requesterRole, ...(context.groupMembershipRole ? { groupMembershipRole: context.groupMembershipRole } : {}), ...(context.baselineRole !== undefined ? { baselineRole: context.baselineRole } : {}) }, features: { batchValuesV1: true, verifiedExplicitGroupContext: context.kind === 'group_relation' }, entries };
345
354
  return data;
346
355
  }
@@ -40,7 +40,7 @@ import { shouldSuppressRealRestart } from '../../utils/restart-safety.js';
40
40
  import { AGENT_DELEGATION_TOKEN_ENV } from '../auth/agent-delegation.js';
41
41
  import { evolMenuResponseTransportMetadata, menuFailure, menuCommandForName, menuSuccess, normalizeMenuError, validateConfigWriteScope, validateMenuRequest, } from './menu-protocol.js';
42
42
  import { parseMenuClientProtectedHeaders } from './menu-protocol.js';
43
- import { validateAndRenewRemoteAccessToken } from '../auth/remote-access-store.js';
43
+ import { remoteAccessService, validateAndRenewRemoteAccessToken } from '../auth/remote-access-store.js';
44
44
  import { listDiagnosticLogOptions, listRawConfigResources, queryDiagnosticLogs, readRawConfigResource, } from './daemon-diagnostics.js';
45
45
  import { roleMenuAction, roleMenuOperation, roleMenuOptions, roleMenuQuery, roleMenuUpdate, } from './role-menu.js';
46
46
  import { handleConnectMenu, connectMenuOperation } from './connect-menu.js';
@@ -1015,6 +1015,8 @@ function buildMenuIntent(verb, cmdBase, args, action, value, fromControlChannel
1015
1015
  return intent('system.upgrade', 'process', { action });
1016
1016
  if (action === 'check')
1017
1017
  return intent('system.status', 'process', { action });
1018
+ if (action === 'paircode')
1019
+ return intent('system.paircode', 'process', { action });
1018
1020
  }
1019
1021
  if (cmdBase === '/agent') {
1020
1022
  if (action === 'reload')
@@ -1767,7 +1769,7 @@ export async function getSubMenuItems(cmd, channel, channelId, userId, args, ove
1767
1769
  const requestedModel = menuStringArg(args, 'model') ?? menuStringArg(args, 'current');
1768
1770
  const currentModel = requestedModel || readMenuModel(target, agent).value || agent.getModel();
1769
1771
  if (models.length > 0)
1770
- return models.map((m) => ({ value: m, label: modelDisplayLabel(agent, m) + (catalog.models.find(e => e.id === m)?.verification === 'unverified' ? ' [unverified]' : '') + (catalog.freshness === 'stale' ? ' [stale]' : ''), selected: modelMatches(agent, m, currentModel) }));
1772
+ return models.map((m) => ({ value: m, label: modelDisplayLabel(agent, m) + (catalog.models.find(e => e.id === m)?.verification === 'unverified' ? ' [unverified]' : ''), selected: modelMatches(agent, m, currentModel) }));
1771
1773
  }
1772
1774
  return null;
1773
1775
  }
@@ -2372,6 +2374,24 @@ export async function execMenuQuery(cmd, channel, channelId, userId, args, expli
2372
2374
  }
2373
2375
  if (cmdBase === '/system') {
2374
2376
  const view = args?.view ?? 'status';
2377
+ if (view === 'version') {
2378
+ const data = {};
2379
+ try {
2380
+ const pkgPath = path.join(getPackageRoot(), 'package.json');
2381
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
2382
+ if (pkg?.version)
2383
+ data.version = pkg.version;
2384
+ }
2385
+ catch { }
2386
+ try {
2387
+ const fp = path.join(getPackageRoot(), 'node_modules', '@agentunion', 'fastaun', 'package.json');
2388
+ const fp2 = JSON.parse(fs.readFileSync(fp, 'utf-8'));
2389
+ if (fp2?.version)
2390
+ data.fastaunVersion = fp2.version;
2391
+ }
2392
+ catch { }
2393
+ return { data };
2394
+ }
2375
2395
  if (view === 'monitor') {
2376
2396
  if (typeof this.daemonMonitorProvider !== 'function') {
2377
2397
  return { error: 'Daemon monitor snapshot is unavailable', code: 'DEPENDENCY_UNAVAILABLE' };
@@ -3885,6 +3905,13 @@ export async function execMenuAction(cmd, action, args, channel, channelId, user
3885
3905
  this.eventBus.publish({ type: 'system:restart', channel, channelId });
3886
3906
  return { data: { action: 'restart', success: true } };
3887
3907
  }
3908
+ if (action === 'paircode') {
3909
+ // Keep Menu pairing on the same daemon-owned store as /pair, ECWeb, and
3910
+ // the local IPC endpoint. The store reuses an unexpired code and creates
3911
+ // a new one only after the shared TTL has elapsed.
3912
+ const pair = remoteAccessService.getPairingCode(subject.actorId);
3913
+ return { data: { code: pair.code, expiresAt: pair.expiresAt } };
3914
+ }
3888
3915
  if (action === 'check') {
3889
3916
  const r = await this.delegateAsAction(action, '/check', channel, channelId, userId, { overrideIdentity, authSubject: subject, threadId: transportReplyContext?.threadId, chatType: transportReplyContext?.delivery?.chatType, replyContext: transportReplyContext });
3890
3917
  // delegateAsAction 会把 structured 展开到 data 顶层,并保留 data.structured 兼容旧客户端。
@@ -4221,6 +4248,12 @@ async function execMenuForSystemControl(payload, context) {
4221
4248
  });
4222
4249
  const trustedIdentity = trustedSubject.identity;
4223
4250
  const cmd = name ? (menuCommandForName(name) ?? payload.cmd) : payload.cmd;
4251
+ if (payload?.type === 'menu.action'
4252
+ && menuCommandToken(cmd) === '/system'
4253
+ && payload.action === 'paircode'
4254
+ && (context.authMethod !== 'daemon-owner' || (context.source === 'control' && !context.encrypted))) {
4255
+ return ecwebErr(id, name, 'NOT_ALLOWED', 'Pairing code issuance requires encrypted DaemonOwner authentication');
4256
+ }
4224
4257
  try {
4225
4258
  if (menuCommandToken(cmd) === '/baseagent-models') {
4226
4259
  if (payload.type !== 'menu.query')
@@ -4239,6 +4272,8 @@ async function execMenuForSystemControl(payload, context) {
4239
4272
  channelScope: 'control',
4240
4273
  channelId: targetChannel,
4241
4274
  request: payload.args.context,
4275
+ paircodeAllowed: context.authMethod === 'daemon-owner'
4276
+ && (context.source !== 'control' || context.encrypted),
4242
4277
  });
4243
4278
  return menuSuccess({ id, ...(name ? { name } : {}) }, data);
4244
4279
  }
@@ -4343,15 +4378,6 @@ export async function execMenuForEcweb(payload, trusted) {
4343
4378
  const validationError = validateMenuRequest(payload ?? {});
4344
4379
  if (validationError)
4345
4380
  return menuFailure({ id, ...(name ? { name } : {}) }, validationError);
4346
- const isProcessLevel = isProcessLevelMenu(name, payload?.cmd) || isProcessContextMenu(payload);
4347
- const owners = loadDaemonConfig().owners ?? [];
4348
- if (isProcessLevel && owners.length === 0) {
4349
- return menuFailure({ id, ...(name ? { name } : {}) }, {
4350
- code: 'NOT_ALLOWED',
4351
- message: formatAuthorizationDenial('Configure daemon.json.owners before using process-level operations.', 'DAEMON_OWNER_REQUIRED'),
4352
- data: authorizationDenialData('DAEMON_OWNER_REQUIRED'),
4353
- });
4354
- }
4355
4381
  if (!trusted || (!trusted.localDirect && trusted.authenticated !== true)) {
4356
4382
  return menuFailure({ id, ...(name ? { name } : {}) }, {
4357
4383
  code: 'PERMISSION_DENIED',
@@ -4359,29 +4385,22 @@ export async function execMenuForEcweb(payload, trusted) {
4359
4385
  data: { $schema_version: 1, kind: 'role_permission_denied', self: payload?.args?.self ?? null },
4360
4386
  });
4361
4387
  }
4362
- // A verified ECWeb connection has daemon-owner management authority. If the
4363
- // bearer token has no explicit owner provenance (the normal pairing path),
4364
- // use the first currently configured owner as the canonical management
4365
- // principal. This keeps audit/Trigger approval attribution deterministic
4366
- // without requiring owner-bound pairing metadata.
4388
+ // ECWeb authentication grants daemon-owner authority independently of the
4389
+ // configured owners. Prefer an owner for audit/Trigger attribution when
4390
+ // available; otherwise keep local and remote management actors distinct.
4391
+ const owners = loadDaemonConfig().owners ?? [];
4367
4392
  const userId = (trusted.actorAid && owners.includes(trusted.actorAid))
4368
4393
  ? trusted.actorAid
4369
- : owners[0] ?? (trusted.localDirect ? 'local-direct' : undefined);
4370
- if (!userId) {
4371
- return menuFailure({ id, ...(name ? { name } : {}) }, {
4372
- code: 'PERMISSION_DENIED',
4373
- message: 'ECWeb operation requires configured daemon owners',
4374
- data: { $schema_version: 1, kind: 'role_permission_denied', self: payload?.args?.self ?? null },
4375
- });
4376
- }
4394
+ : owners[0] ?? (trusted.localDirect ? 'local-direct' : 'ecweb-remote');
4377
4395
  return execMenuForSystemControl.call(this, payload, {
4378
4396
  source: 'ecweb',
4379
4397
  actorAid: userId,
4398
+ authMethod: trusted.localDirect ? 'daemon-owner' : 'remote-access-token',
4399
+ encrypted: trusted.localDirect,
4380
4400
  localDirect: trusted.localDirect,
4381
4401
  // The ECWeb server/IPC control-token gate authenticates this management
4382
4402
  // request. All authenticated ECWeb connections are equally eligible to
4383
- // approve persistent fullaccess; actorAid is used only when it identifies
4384
- // a current configured owner.
4403
+ // approve persistent fullaccess without owner-bound token metadata.
4385
4404
  canApprovePersistentFullAccess: true,
4386
4405
  owners,
4387
4406
  });
@@ -4415,6 +4434,8 @@ export async function execMenuForControl(payload, peerId, auth = {}) {
4415
4434
  return execMenuForSystemControl.call(this, payload, {
4416
4435
  source: 'control',
4417
4436
  actorAid: managementActor,
4437
+ authMethod: authorization.allowed ? authorization.authMethod : 'daemon-owner',
4438
+ encrypted: auth.encrypted === true,
4418
4439
  localDirect: false,
4419
4440
  canApprovePersistentFullAccess: false,
4420
4441
  owners,
@@ -567,6 +567,11 @@ function sanitizeLogArgv(value, depth, seen) {
567
567
  export function sanitizeMenuLogValue(value, key = '', depth = 0, seen = new WeakSet()) {
568
568
  if (isSensitiveLogKey(key))
569
569
  return '[REDACTED]';
570
+ // Pairing codes are intentionally returned as `code` on the wire for
571
+ // compatibility with /pair and the ECWeb API. Do not persist six-digit
572
+ // credentials in menu-protocol.log.
573
+ if (key === 'code' && typeof value === 'string' && /^\d{6}$/.test(value))
574
+ return '[REDACTED]';
570
575
  if (value === null || value === undefined || typeof value === 'number' || typeof value === 'boolean')
571
576
  return value;
572
577
  if (typeof value === 'bigint')