evolcore 0.0.25 → 0.0.27

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 (304) hide show
  1. package/CHANGELOG.md +632 -565
  2. package/LICENSE +21 -21
  3. package/README.md +455 -455
  4. package/bin/codex-managed-acl.ps1 +203 -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-availability.js +68 -0
  10. package/dist/agents/baseagent.js +96 -46
  11. package/dist/agents/claude-runner.js +172 -33
  12. package/dist/agents/codex/app-server-client.js +76 -31
  13. package/dist/agents/codex/catalog.js +1 -1
  14. package/dist/agents/codex/diagnostics.js +34 -1
  15. package/dist/agents/codex-runner.js +434 -446
  16. package/dist/agents/ecagent-runner.js +174 -34
  17. package/dist/agents/gemini-runner.js +8 -7
  18. package/dist/agents/runner-types.js +75 -19
  19. package/dist/aun/aid/agentmd.js +151 -36
  20. package/dist/aun/aid/client.js +1 -1
  21. package/dist/aun/aid/control-aid.js +180 -1
  22. package/dist/aun/aid/identity.js +4 -4
  23. package/dist/aun/aid/index.js +1 -1
  24. package/dist/aun/aid/store.js +17 -13
  25. package/dist/aun/group/history.js +303 -0
  26. package/dist/aun/group/identity.js +43 -0
  27. package/dist/aun/group/index.js +5 -0
  28. package/dist/aun/{msg/group.js → group/operations.js} +162 -39
  29. package/dist/aun/{menu-client.js → menu/client.js} +85 -6
  30. package/dist/aun/menu/index.js +2 -0
  31. package/dist/aun/menu/transport.js +49 -0
  32. package/dist/aun/msg/index.js +2 -1
  33. package/dist/aun/msg/managed-operation.js +85 -19
  34. package/dist/aun/msg/mention-schema.js +51 -0
  35. package/dist/aun/msg/p2p.js +19 -9
  36. package/dist/aun/msg/upload.js +43 -80
  37. package/dist/aun/outbox.js +29 -7
  38. package/dist/aun/rpc/client.js +1 -0
  39. package/dist/aun/storage/client.js +39 -50
  40. package/dist/channels/aun.js +842 -318
  41. package/dist/channels/daemon.js +10 -6
  42. package/dist/cli/agent-command.js +212 -89
  43. package/dist/cli/agent-create.js +626 -11
  44. package/dist/cli/agent.js +125 -13
  45. package/dist/cli/aun-commands.js +487 -337
  46. package/dist/cli/bench.js +20 -20
  47. package/dist/cli/config.js +55 -33
  48. package/dist/cli/contact.js +72 -15
  49. package/dist/cli/ctl-command.js +24 -24
  50. package/dist/cli/daemon-commands.js +140 -63
  51. package/dist/cli/diag.js +31 -0
  52. package/dist/cli/fs-command.js +70 -70
  53. package/dist/cli/handoff-command.js +46 -14
  54. package/dist/cli/index.js +91 -79
  55. package/dist/cli/init.js +34 -1
  56. package/dist/cli/link-rules.js +21 -21
  57. package/dist/cli/managed-command-guard.js +5 -1
  58. package/dist/cli/menu-client.js +51 -32
  59. package/dist/cli/model.js +3 -3
  60. package/dist/cli/net-check.js +22 -22
  61. package/dist/cli/queue-command.js +31 -31
  62. package/dist/cli/response.js +32 -32
  63. package/dist/cli/restart-monitor.js +103 -42
  64. package/dist/cli/stats.js +51 -51
  65. package/dist/cli/task-command.js +59 -0
  66. package/dist/cli/task-context.js +97 -49
  67. package/dist/cli/trigger-command.js +74 -64
  68. package/dist/config/builtin-roles.js +11 -0
  69. package/dist/config/config-field-policy.js +11 -0
  70. package/dist/config/config-manager.js +32 -4
  71. package/dist/config/config-operation-service.js +160 -0
  72. package/dist/config/contact-book-store.js +1 -1
  73. package/dist/config/contact-book.js +1 -1
  74. package/dist/config/contact-operation-service.js +97 -8
  75. package/dist/config/peer-role-resolver.js +1 -1
  76. package/dist/config/resolved-config-op.js +30 -7
  77. package/dist/config-store.js +1 -1
  78. package/dist/core/agent-application-service.js +7 -1
  79. package/dist/core/audit/event-key.js +17 -2
  80. package/dist/core/audit/log-integrity.js +34 -8
  81. package/dist/core/auth/agent-delegation.js +48 -14
  82. package/dist/core/auth/auth-gateway.js +2 -0
  83. package/dist/core/auth/authorization-audit.js +53 -49
  84. package/dist/core/auth/operation-authorizer.js +90 -7
  85. package/dist/core/auth/operation-catalog.js +145 -4
  86. package/dist/core/auth/remote-access-store.js +155 -10
  87. package/dist/core/bootstrap-messages.js +66 -7
  88. package/dist/core/bootstrap-service.js +111 -20
  89. package/dist/core/command/agent-control.js +88 -7
  90. package/dist/core/command/agent-storage-menu.js +443 -0
  91. package/dist/core/command/cli-intent-parser.js +19 -0
  92. package/dist/core/command/command-handler.js +159 -65
  93. package/dist/core/command/data-root-resolver.js +273 -0
  94. package/dist/core/command/diag-service.js +118 -0
  95. package/dist/core/command/group-menu.js +7 -2
  96. package/dist/core/command/menu-catalog.js +46 -7
  97. package/dist/core/command/menu-handler.js +392 -100
  98. package/dist/core/command/menu-protocol.js +45 -6
  99. package/dist/core/command/menu-token-store.js +41 -8
  100. package/dist/core/command/role-menu.js +2 -2
  101. package/dist/core/command/slash-gate.js +34 -4
  102. package/dist/core/command/slash-handler.js +123 -43
  103. package/dist/core/event-catalog.js +4 -0
  104. package/dist/core/evolagent-registry.js +28 -2
  105. package/dist/core/group/invite-service.js +924 -0
  106. package/dist/core/handoff/runtime.js +205 -27
  107. package/dist/core/handoff/store.js +193 -4
  108. package/dist/core/interaction-router.js +22 -8
  109. package/dist/core/message/im-renderer.js +146 -34
  110. package/dist/core/message/message-bridge.js +163 -20
  111. package/dist/core/message/message-log.js +5 -3
  112. package/dist/core/message/message-queue.js +99 -41
  113. package/dist/core/message/message-utils.js +1 -0
  114. package/dist/core/message/response-engine.js +755 -144
  115. package/dist/core/model/catalog-service.js +79 -49
  116. package/dist/core/model/context-window-suffix.js +33 -0
  117. package/dist/core/model/model-catalog.js +1 -1
  118. package/dist/core/model/model-permission.js +12 -3
  119. package/dist/core/permission/approval-gateway.js +36 -3
  120. package/dist/core/permission/ec-command-parser.js +112 -41
  121. package/dist/core/permission/protected-paths.js +134 -16
  122. package/dist/core/permission/sandbox-runtime.js +108 -17
  123. package/dist/core/permission/tool-error-code.js +15 -0
  124. package/dist/core/permission/tool-policy.js +9 -8
  125. package/dist/core/relation/peer-identity.js +57 -6
  126. package/dist/core/relation/shared-data.js +106 -0
  127. package/dist/core/role/runtime-policy.js +64 -0
  128. package/dist/core/runtime-lock.js +10 -26
  129. package/dist/core/session/runner-task-history.js +170 -0
  130. package/dist/core/session/session-manager.js +34 -6
  131. package/dist/core/session/session-mapper.js +41 -0
  132. package/dist/core/session/session-turn-coordinator.js +37 -7
  133. package/dist/core/session/session-turns.js +48 -7
  134. package/dist/core/task/task-recovery-service.js +176 -0
  135. package/dist/core/task/task-resume-operation.js +32 -0
  136. package/dist/eck/group-rules-sync.js +5 -3
  137. package/dist/eck/kit-renderer.js +1 -0
  138. package/dist/index.js +588 -217
  139. package/dist/ipc.js +193 -15
  140. package/dist/paths.js +17 -4
  141. package/dist/product.js +2 -0
  142. package/dist/response-system/engines/v1/proactive-flow.js +59 -2
  143. package/dist/stats/billing.js +41 -15
  144. package/dist/stats/db.js +159 -159
  145. package/dist/stats/eck-vars.js +9 -9
  146. package/dist/stats/price-resolver.js +28 -10
  147. package/dist/stats/query.js +139 -139
  148. package/dist/stats/role-budget.js +6 -6
  149. package/dist/stats/writer.js +51 -51
  150. package/dist/trigger/event-source.js +19 -0
  151. package/dist/trigger/feedback.js +233 -40
  152. package/dist/trigger/parser.js +55 -5
  153. package/dist/trigger/patch.js +9 -0
  154. package/dist/trigger/scheduler.js +30 -4
  155. package/dist/trigger/validation.js +20 -3
  156. package/dist/utils/cross-platform.js +30 -1
  157. package/dist/utils/ecweb-utils.js +1 -0
  158. package/dist/utils/error-dict.json +160 -160
  159. package/dist/utils/error-utils.js +61 -2
  160. package/dist/utils/evolcore-version.js +129 -0
  161. package/dist/utils/logger.js +26 -14
  162. package/dist/utils/macos-autostart.js +22 -22
  163. package/dist/utils/model-prices.jsonl +23 -23
  164. package/dist/utils/npm-ops.js +176 -35
  165. package/dist/utils/tool-summary.js +59 -3
  166. package/dist/utils/welcome.js +196 -23
  167. package/ecagent/LICENSE +21 -21
  168. package/ecagent/NOTICE +11 -11
  169. package/ecagent/README.md +61 -61
  170. package/ecagent/dist/harness/agent-harness.js +3 -0
  171. package/ecagent/dist/harness/compaction/branch-summarization.js +30 -30
  172. package/ecagent/dist/harness/compaction/compaction.js +83 -83
  173. package/ecagent/dist/harness/messages.js +7 -7
  174. package/ecagent/dist/harness/session/jsonl-storage.js +21 -2
  175. package/kits/docs/GUIDE.md +20 -20
  176. package/kits/docs/INDEX.md +60 -66
  177. package/kits/docs/aun/CHEATSHEET.md +19 -19
  178. package/kits/docs/aun/SYNC_PROTOCOL.md +15 -15
  179. package/kits/docs/channels/aun.md +69 -56
  180. package/kits/docs/channels/feishu.md +56 -56
  181. package/kits/docs/context-assembly.md +379 -367
  182. package/kits/docs/eck_templates/GUIDE.template.md +22 -22
  183. package/kits/docs/eck_templates/INDEX.template.md +28 -28
  184. package/kits/docs/eck_templates/path-registry.template.md +33 -33
  185. package/kits/docs/eck_templates/runtime.template.md +19 -19
  186. package/kits/docs/evolcore/INDEX.md +76 -71
  187. package/kits/docs/evolcore/agent-create.md +202 -165
  188. package/kits/docs/evolcore/agent.md +99 -67
  189. package/kits/docs/evolcore/aid.md +68 -60
  190. package/kits/docs/evolcore/codex-windows-sandbox.md +146 -146
  191. package/kits/docs/evolcore/config.md +241 -195
  192. package/kits/docs/evolcore/contact.md +75 -68
  193. package/kits/docs/evolcore/ctl.md +46 -46
  194. package/kits/docs/evolcore/event.md +216 -216
  195. package/kits/docs/evolcore/fs-architecture.md +1215 -1215
  196. package/kits/docs/evolcore/fs.md +110 -110
  197. package/kits/docs/evolcore/group-collaboration.md +244 -244
  198. package/kits/docs/evolcore/group-fs.md +17 -17
  199. package/kits/docs/evolcore/group-rules.md +251 -251
  200. package/kits/docs/evolcore/group.md +267 -152
  201. package/kits/docs/evolcore/menu-client.md +58 -34
  202. package/kits/docs/evolcore/model.md +50 -50
  203. package/kits/docs/evolcore/msg.md +169 -165
  204. package/kits/docs/evolcore/response.md +75 -75
  205. package/kits/docs/evolcore/rpc.md +37 -37
  206. package/kits/docs/evolcore/self-summary.md +29 -29
  207. package/kits/docs/evolcore/stats.md +83 -83
  208. package/kits/docs/evolcore/storage.md +53 -50
  209. package/kits/docs/evolcore/task.md +14 -0
  210. package/kits/docs/evolcore/trigger.md +623 -575
  211. package/kits/docs/identity/AID_PROFILE_SPEC.md +26 -26
  212. package/kits/docs/identity/PATH_OPS.md +16 -16
  213. package/kits/docs/identity/ROLE_DETAIL.md +25 -25
  214. package/kits/docs/identity/identity-tools.md +26 -26
  215. package/kits/docs/path-registry.md +62 -62
  216. package/kits/docs/prompt-loading-architecture.md +269 -269
  217. package/kits/docs/venues/aun-group.md +45 -45
  218. package/kits/docs/venues/aun-private.md +10 -10
  219. package/kits/docs/venues/client-desktop.md +10 -10
  220. package/kits/docs/venues/client-mobile.md +10 -10
  221. package/kits/docs/venues/feishu-group.md +13 -13
  222. package/kits/docs/venues/feishu-private.md +9 -9
  223. package/kits/docs/venues/group.md +42 -25
  224. package/kits/docs/venues/private.md +10 -10
  225. package/kits/eck_manifest.auxiliary.json +43 -43
  226. package/kits/eck_manifest.json +217 -190
  227. package/kits/eck_message_manifest.json +74 -74
  228. package/kits/migrations/README-role-config-v4.md +32 -32
  229. package/kits/migrations/migrate-contact-book-v2.mjs +748 -748
  230. package/kits/migrations/migrate-role-config-v4.mjs +614 -614
  231. package/kits/migrations/migrate-role-config-v5.mjs +365 -365
  232. package/kits/migrations/rename-config-file.mjs +99 -99
  233. package/kits/rules/01-overview.md +166 -162
  234. package/kits/rules/02-navigation.md +96 -96
  235. package/kits/rules/03-identity.md +36 -36
  236. package/kits/rules/04-relation.md +76 -75
  237. package/kits/rules/05-venue.md +60 -60
  238. package/kits/rules/06-channel.md +65 -63
  239. package/kits/schemas/_meta.json +53 -53
  240. package/kits/schemas/agent-config.schema.1.json +177 -177
  241. package/kits/schemas/agent-config.schema.10.json +395 -395
  242. package/kits/schemas/agent-config.schema.11.json +421 -421
  243. package/kits/schemas/agent-config.schema.12.json +451 -427
  244. package/kits/schemas/agent-config.schema.2.json +239 -239
  245. package/kits/schemas/agent-config.schema.3.json +119 -119
  246. package/kits/schemas/agent-config.schema.4.json +208 -208
  247. package/kits/schemas/agent-config.schema.5.json +326 -326
  248. package/kits/schemas/agent-config.schema.6.json +322 -322
  249. package/kits/schemas/agent-config.schema.7.json +303 -303
  250. package/kits/schemas/agent-config.schema.8.json +304 -304
  251. package/kits/schemas/agent-config.schema.9.json +364 -364
  252. package/kits/schemas/contact-book.schema.1.json +36 -36
  253. package/kits/schemas/contact-book.schema.2.json +43 -43
  254. package/kits/schemas/contact-book.schema.3.json +67 -67
  255. package/kits/schemas/daemon.schema.1.json +91 -91
  256. package/kits/schemas/daemon.schema.2.json +101 -101
  257. package/kits/schemas/daemon.schema.3.json +123 -123
  258. package/kits/schemas/daemon.schema.4.json +136 -136
  259. package/kits/schemas/daemon.schema.5.json +135 -135
  260. package/kits/schemas/daemon.schema.6.json +131 -131
  261. package/kits/schemas/defaults.schema.1.json +81 -81
  262. package/kits/schemas/defaults.schema.2.json +85 -85
  263. package/kits/schemas/defaults.schema.3.json +73 -73
  264. package/kits/schemas/defaults.schema.4.json +107 -107
  265. package/kits/schemas/defaults.schema.5.json +119 -119
  266. package/kits/schemas/menu-exec-schema-commands.md +208 -208
  267. package/kits/schemas/migrations/README.md +30 -30
  268. package/kits/schemas/relation-config.schema.1.json +158 -158
  269. package/kits/schemas/relation-config.schema.2.json +73 -73
  270. package/kits/schemas/relation-config.schema.3.json +50 -50
  271. package/kits/schemas/relation-config.schema.4.json +47 -47
  272. package/kits/schemas/relation-config.schema.5.json +47 -47
  273. package/kits/schemas/relation-config.schema.6.json +46 -46
  274. package/kits/schemas/relation-config.schema.7.json +59 -59
  275. package/kits/schemas/relation-config.schema.8.json +103 -103
  276. package/kits/schemas/role-config.schema.1.json +215 -201
  277. package/kits/schemas/role-registry.schema.1.json +35 -35
  278. package/kits/schemas/single-session.schema.1.json +31 -31
  279. package/kits/schemas/single-session.schema.2.json +57 -57
  280. package/kits/schemas/single-session.schema.3.json +32 -32
  281. package/kits/templates/bootstrap-welcome.md +17 -17
  282. package/kits/templates/message-fragments/handoff-context-to-target.md +9 -9
  283. package/kits/templates/message-fragments/handoff-request-to-target.md +19 -19
  284. package/kits/templates/message-fragments/handoff-response-to-origin.md +9 -9
  285. package/kits/templates/message-fragments/inject-default.md +2 -2
  286. package/kits/templates/message-fragments/item.md +2 -2
  287. package/kits/templates/roles/admin.json +92 -92
  288. package/kits/templates/roles/member.json +101 -96
  289. package/kits/templates/roles/owner.json +9 -9
  290. package/kits/templates/roles/visitor.json +90 -85
  291. package/kits/templates/system-fragments/baseagent.md +14 -14
  292. package/kits/templates/system-fragments/bootstrap.md +53 -34
  293. package/kits/templates/system-fragments/channel.md +28 -64
  294. package/kits/templates/system-fragments/commands.md +36 -28
  295. package/kits/templates/system-fragments/group-unread-history.md +7 -0
  296. package/kits/templates/system-fragments/identity.md +13 -13
  297. package/kits/templates/system-fragments/relation.md +19 -19
  298. package/kits/templates/system-fragments/reply.md +56 -0
  299. package/kits/templates/system-fragments/session.md +59 -59
  300. package/kits/templates/system-fragments/venue.md +31 -31
  301. package/package.json +104 -98
  302. package/dist/aun/group-identity.js +0 -9
  303. /package/dist/aun/{group-fs-download.js → group/fs-download.js} +0 -0
  304. /package/dist/aun/{msg/group-index.js → group/index-rpc.js} +0 -0
@@ -0,0 +1,273 @@
1
+ import crypto from 'crypto';
2
+ import fs from 'fs';
3
+ import path from 'path';
4
+ import { isValidAid } from '../../aun/aid/validation.js';
5
+ import { isExplicitGroupId } from '../../aun/group/index.js';
6
+ import { isStaticAgentOwner } from '../../config/peer-role-resolver.js';
7
+ import { agentDir, agentRelationSharedDir } from '../../paths.js';
8
+ import { formatPeerKey } from '../relation/peer-identity.js';
9
+ export class DataRootError extends Error {
10
+ code;
11
+ data;
12
+ constructor(code, message, data) {
13
+ super(message);
14
+ this.name = 'DataRootError';
15
+ this.code = code;
16
+ this.data = data;
17
+ }
18
+ }
19
+ const DEFAULT_ROOT_TTL_MS = 10 * 60 * 1000;
20
+ const TOKEN_PREFIX = 'dr1';
21
+ function base64Url(value) {
22
+ return Buffer.from(value).toString('base64url');
23
+ }
24
+ function isInside(root, candidate) {
25
+ const relative = path.relative(root, candidate);
26
+ return relative === '' || (!relative.startsWith(`..${path.sep}`) && relative !== '..' && !path.isAbsolute(relative));
27
+ }
28
+ function authenticatedPrincipal(subject) {
29
+ const principalId = String(subject.principalId ?? '').trim();
30
+ if (!isValidAid(principalId) || isExplicitGroupId(principalId)) {
31
+ throw new DataRootError('ROOT_FORBIDDEN', 'A verified private AUN principal is required');
32
+ }
33
+ return principalId;
34
+ }
35
+ export class DataRootResolver {
36
+ secret;
37
+ ttlMs;
38
+ now;
39
+ isOwner;
40
+ resolveAgentPath;
41
+ resolveRelationSharedPath;
42
+ constructor(options = {}) {
43
+ this.secret = options.secret ?? crypto.randomBytes(32);
44
+ if (this.secret.length < 32)
45
+ throw new Error('Data root signing secret must be at least 32 bytes');
46
+ this.ttlMs = options.ttlMs ?? DEFAULT_ROOT_TTL_MS;
47
+ this.now = options.now ?? (() => Date.now());
48
+ this.isOwner = options.isOwner ?? isStaticAgentOwner;
49
+ this.resolveAgentPath = options.resolveAgentPath ?? agentDir;
50
+ this.resolveRelationSharedPath = options.resolveRelationSharedPath ?? agentRelationSharedDir;
51
+ }
52
+ issueAgentRoot(subject) {
53
+ const targetAid = this.targetAid(subject);
54
+ const principalId = authenticatedPrincipal(subject);
55
+ if (subject.role !== 'owner' || !this.isOwner(targetAid, principalId)) {
56
+ throw new DataRootError('ROOT_FORBIDDEN', 'Agent data root requires Agent owner permission');
57
+ }
58
+ const rootPath = this.canonicalRoot(this.resolveAgentPath(targetAid), undefined);
59
+ return this.issueDescriptor({ scope: 'agent', targetAid, principalId }, rootPath);
60
+ }
61
+ issueRelationRoot(subject, requestedPeerAid) {
62
+ const targetAid = this.targetAid(subject);
63
+ this.assertPrivateAun(subject);
64
+ const principalId = authenticatedPrincipal(subject);
65
+ const owner = subject.role === 'owner' && this.isOwner(targetAid, principalId);
66
+ const explicitPeerAid = typeof requestedPeerAid === 'string' ? requestedPeerAid.trim() : '';
67
+ if (requestedPeerAid !== undefined && !explicitPeerAid) {
68
+ throw new DataRootError('INVALID_ARGUMENT', 'args.peerAid must be a non-empty AUN AID');
69
+ }
70
+ if (explicitPeerAid && !owner) {
71
+ throw new DataRootError('ROOT_FORBIDDEN', 'Only the Agent owner may select another relation');
72
+ }
73
+ const peerAid = explicitPeerAid || principalId;
74
+ if (!isValidAid(peerAid) || isExplicitGroupId(peerAid)) {
75
+ throw new DataRootError('INVALID_ARGUMENT', 'args.peerAid must identify an AUN private-chat peer');
76
+ }
77
+ const peerKey = formatPeerKey('aun', peerAid);
78
+ const agentRoot = this.canonicalRoot(this.resolveAgentPath(targetAid), undefined);
79
+ const rootPath = this.canonicalRoot(this.resolveRelationSharedPath(targetAid, peerKey), agentRoot);
80
+ return this.issueDescriptor({ scope: 'relation', targetAid, principalId, peerAid, peerKey }, rootPath);
81
+ }
82
+ resolveRoot(rootId, subject) {
83
+ const claims = this.verifyToken(rootId);
84
+ const targetAid = this.targetAid(subject);
85
+ const principalId = authenticatedPrincipal(subject);
86
+ if (claims.targetAid !== targetAid) {
87
+ throw new DataRootError('ROOT_SCOPE_MISMATCH', 'Root does not belong to the receiving Agent');
88
+ }
89
+ if (claims.principalId !== principalId) {
90
+ throw new DataRootError('ROOT_FORBIDDEN', 'Root was issued to another authenticated principal');
91
+ }
92
+ let rootPath;
93
+ if (claims.scope === 'agent') {
94
+ if (subject.role !== 'owner' || !this.isOwner(targetAid, principalId)) {
95
+ throw new DataRootError('ROOT_FORBIDDEN', 'Agent data root requires Agent owner permission');
96
+ }
97
+ rootPath = this.canonicalRoot(this.resolveAgentPath(targetAid), undefined);
98
+ }
99
+ else {
100
+ this.assertPrivateAun(subject);
101
+ if (!claims.peerAid || !claims.peerKey || claims.peerKey !== formatPeerKey('aun', claims.peerAid)) {
102
+ throw new DataRootError('ROOT_SCOPE_MISMATCH', 'Relation root claims are invalid');
103
+ }
104
+ const owner = subject.role === 'owner' && this.isOwner(targetAid, principalId);
105
+ if (!owner && principalId !== claims.peerAid) {
106
+ throw new DataRootError('ROOT_PEER_MISMATCH', 'Only the relation peer may use this root');
107
+ }
108
+ const agentRoot = this.canonicalRoot(this.resolveAgentPath(targetAid), undefined);
109
+ rootPath = this.canonicalRoot(this.resolveRelationSharedPath(targetAid, claims.peerKey), agentRoot);
110
+ }
111
+ const revision = this.rootRevision(rootPath);
112
+ if (revision !== claims.revision) {
113
+ throw new DataRootError('ROOT_SCOPE_MISMATCH', 'Root identity changed; request a new root');
114
+ }
115
+ return {
116
+ scope: claims.scope,
117
+ targetAid,
118
+ ...(claims.peerAid ? { peerAid: claims.peerAid } : {}),
119
+ ...(claims.peerKey ? { peerKey: claims.peerKey } : {}),
120
+ rootPath,
121
+ revision,
122
+ expiresAt: claims.expiresAt,
123
+ };
124
+ }
125
+ resolvePath(rootId, subject, input, expectType) {
126
+ const root = this.resolveRoot(rootId, subject);
127
+ const raw = String(input ?? '').trim();
128
+ if (!raw)
129
+ throw new DataRootError('INVALID_ROOT_PATH', 'A root-relative path is required');
130
+ if (raw.includes('\0'))
131
+ throw new DataRootError('INVALID_ROOT_PATH', 'NUL is not allowed in a path');
132
+ if (path.isAbsolute(raw) || path.win32.isAbsolute(raw) || path.posix.isAbsolute(raw)) {
133
+ throw new DataRootError('INVALID_ROOT_PATH', 'Data root paths must be relative');
134
+ }
135
+ if (raw.split(/[\\/]+/).includes('..')) {
136
+ throw new DataRootError('INVALID_ROOT_PATH', 'Parent traversal is not allowed');
137
+ }
138
+ const lexicalPath = path.resolve(root.rootPath, raw);
139
+ if (!isInside(root.rootPath, lexicalPath)) {
140
+ throw new DataRootError('INVALID_ROOT_PATH', 'Path escapes the data root');
141
+ }
142
+ let realPath;
143
+ let stat;
144
+ try {
145
+ realPath = fs.realpathSync(lexicalPath);
146
+ stat = fs.statSync(realPath);
147
+ }
148
+ catch {
149
+ throw new DataRootError('NOT_FOUND', 'File or directory does not exist');
150
+ }
151
+ if (!isInside(root.rootPath, realPath)) {
152
+ throw new DataRootError('INVALID_ROOT_PATH', 'Symbolic link escapes the data root');
153
+ }
154
+ if (expectType === 'file' && !stat.isFile()) {
155
+ throw new DataRootError('NOT_SUPPORTED', 'Path is not a regular file');
156
+ }
157
+ if (expectType === 'directory' && !stat.isDirectory()) {
158
+ throw new DataRootError('NOT_A_DIRECTORY', 'Path is not a directory');
159
+ }
160
+ const relativePath = path.relative(root.rootPath, realPath).split(path.sep).join('/') || '.';
161
+ return { ...root, realPath, relativePath, stat };
162
+ }
163
+ issueDescriptor(base, rootPath) {
164
+ const issuedAt = this.now();
165
+ const claims = {
166
+ v: 1,
167
+ ...base,
168
+ revision: this.rootRevision(rootPath),
169
+ issuedAt,
170
+ expiresAt: issuedAt + this.ttlMs,
171
+ nonce: crypto.randomBytes(12).toString('base64url'),
172
+ };
173
+ return {
174
+ rootId: this.sign(claims),
175
+ scope: claims.scope,
176
+ targetAid: claims.targetAid,
177
+ ...(claims.peerAid ? { peerAid: claims.peerAid } : {}),
178
+ ...(claims.peerKey ? { peerKey: claims.peerKey } : {}),
179
+ virtualPath: claims.scope === 'agent'
180
+ ? '$AGENTS_DIR/<targetAid>'
181
+ : '$AGENTS_DIR/<targetAid>/relations/<peerKey>/shared',
182
+ revision: `sha256:${claims.revision}`,
183
+ expiresAt: claims.expiresAt,
184
+ readOnly: true,
185
+ capabilities: { list: true, query: true, fetch: true },
186
+ };
187
+ }
188
+ sign(claims) {
189
+ const encoded = base64Url(JSON.stringify(claims));
190
+ const signature = crypto.createHmac('sha256', this.secret).update(encoded).digest('base64url');
191
+ return `${TOKEN_PREFIX}.${encoded}.${signature}`;
192
+ }
193
+ verifyToken(value) {
194
+ if (typeof value !== 'string' || !value.trim()) {
195
+ throw new DataRootError('MISSING_ROOT_ID', 'A data rootId is required');
196
+ }
197
+ const parts = value.split('.');
198
+ if (parts.length !== 3 || parts[0] !== TOKEN_PREFIX) {
199
+ throw new DataRootError('INVALID_ROOT_ID', 'Invalid data rootId');
200
+ }
201
+ const expected = crypto.createHmac('sha256', this.secret).update(parts[1]).digest();
202
+ let actual;
203
+ try {
204
+ actual = Buffer.from(parts[2], 'base64url');
205
+ }
206
+ catch {
207
+ throw new DataRootError('INVALID_ROOT_ID', 'Invalid data rootId');
208
+ }
209
+ if (actual.length !== expected.length || !crypto.timingSafeEqual(actual, expected)) {
210
+ throw new DataRootError('INVALID_ROOT_ID', 'Invalid data rootId signature');
211
+ }
212
+ let claims;
213
+ try {
214
+ claims = JSON.parse(Buffer.from(parts[1], 'base64url').toString('utf8'));
215
+ }
216
+ catch {
217
+ throw new DataRootError('INVALID_ROOT_ID', 'Invalid data rootId payload');
218
+ }
219
+ if (claims.v !== 1
220
+ || (claims.scope !== 'agent' && claims.scope !== 'relation')
221
+ || typeof claims.targetAid !== 'string'
222
+ || typeof claims.principalId !== 'string'
223
+ || typeof claims.revision !== 'string'
224
+ || !Number.isFinite(claims.expiresAt)) {
225
+ throw new DataRootError('INVALID_ROOT_ID', 'Invalid data rootId claims');
226
+ }
227
+ if (claims.expiresAt <= this.now()) {
228
+ throw new DataRootError('ROOT_EXPIRED', 'Data rootId expired; query the root again');
229
+ }
230
+ return claims;
231
+ }
232
+ canonicalRoot(candidate, requiredParent) {
233
+ let realPath;
234
+ let stat;
235
+ try {
236
+ realPath = fs.realpathSync(candidate);
237
+ stat = fs.statSync(realPath);
238
+ }
239
+ catch {
240
+ throw new DataRootError('ROOT_NOT_FOUND', 'Data root does not exist');
241
+ }
242
+ if (!stat.isDirectory())
243
+ throw new DataRootError('ROOT_NOT_FOUND', 'Data root is not a directory');
244
+ if (requiredParent && !isInside(requiredParent, realPath)) {
245
+ throw new DataRootError('ROOT_SCOPE_MISMATCH', 'Data root escapes the Agent directory');
246
+ }
247
+ return realPath;
248
+ }
249
+ rootRevision(rootPath) {
250
+ const stat = fs.statSync(rootPath);
251
+ return crypto.createHash('sha256')
252
+ .update(JSON.stringify({ rootPath, dev: stat.dev, ino: stat.ino, birthtimeMs: stat.birthtimeMs }))
253
+ .digest('hex');
254
+ }
255
+ targetAid(subject) {
256
+ const targetAid = String(subject.selfAid ?? '').trim();
257
+ if (!isValidAid(targetAid))
258
+ throw new DataRootError('ROOT_FORBIDDEN', 'Receiving Agent AID is unavailable');
259
+ return targetAid;
260
+ }
261
+ assertPrivateAun(subject) {
262
+ if (subject.channelType !== 'aun' || subject.chatType !== 'private') {
263
+ throw new DataRootError('NOT_SUPPORTED', 'Relation data roots currently support AUN private chats only');
264
+ }
265
+ }
266
+ }
267
+ export const dataRootResolver = new DataRootResolver();
268
+ export function dataRootFailure(error) {
269
+ if (error instanceof DataRootError) {
270
+ return { error: error.message, code: error.code, ...(error.data ? { data: error.data } : {}) };
271
+ }
272
+ return { error: 'Internal data root error', code: 'INTERNAL_ERROR' };
273
+ }
@@ -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
+ }
@@ -2,7 +2,7 @@ import crypto from 'crypto';
2
2
  import { ConfigTarget, mutateConfig, read as readConfig, resolveEffectiveFieldWithSource, } from '../../config/config-manager.js';
3
3
  import { normalizeMentionMode } from '../../config/mention-mode.js';
4
4
  import { readRolesConfig } from '../../config/roles.js';
5
- import { groupInfo, groupList, groupDirectoryErrorCode } from '../../aun/msg/group.js';
5
+ import { groupInfo, groupList, groupDirectoryErrorCode, normalizeGroupAid } from '../../aun/group/index.js';
6
6
  import { formatPeerKey, parsePeerKey } from '../relation/peer-identity.js';
7
7
  const DEFAULT_DIRECTORY = {
8
8
  getGroup: groupInfo,
@@ -185,7 +185,12 @@ function groupIdFromArgs(args, currentGroupId) {
185
185
  if (!groupId) {
186
186
  throw new GroupMenuError('INVALID_ARGUMENT', 'relation settings require args.groupId or args.peerKey');
187
187
  }
188
- return groupId;
188
+ try {
189
+ return normalizeGroupAid(groupId);
190
+ }
191
+ catch (error) {
192
+ throw new GroupMenuError('INVALID_ARGUMENT', error.message);
193
+ }
189
194
  }
190
195
  function settingOptions(key, target) {
191
196
  const state = readSettingState(key, target);
@@ -1,5 +1,5 @@
1
1
  import { createHash } from 'node:crypto';
2
- import { groupInfo, groupMembers } from '../../aun/msg/group.js';
2
+ import { groupInfo, groupMembers } from '../../aun/group/index.js';
3
3
  import { formatPeerKey } from '../relation/peer-identity.js';
4
4
  import { authorizeAccess, authorizeOperation, buildAuthSubject } from '../auth/auth-gateway.js';
5
5
  import { authorizationDenialData, formatAuthorizationDenial } from '../auth/authorization-denial.js';
@@ -8,6 +8,7 @@ import { readRolesConfig } from '../../config/roles.js';
8
8
  import { normalizeMenuError } from './menu-protocol.js';
9
9
  import { isValidAid } from '../../aun/aid/validation.js';
10
10
  const DEFAULT_DIRECTORY = { getGroup: groupInfo, getMembers: groupMembers };
11
+ const HOST_BASEAGENTS = ['claude', 'codex', 'ecagent', 'gemini'];
11
12
  export function validateMenuValueBatchArgs(args) {
12
13
  if (!args || typeof args !== 'object' || Array.isArray(args))
13
14
  throw { code: 'INVALID_ARGUMENT', message: 'batch args must be an object' };
@@ -80,13 +81,14 @@ const ENTRIES = [
80
81
  { 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
82
  { 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
83
  { 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' },
84
+ { id: 'baseagents.inventory', type: 'list', contexts: ['process'], name: 'baseagents', label: 'Base Agent 清单', section: 'process', order: 9, query: { cmd: '/baseagents', operation: 'system.status', scope: 'process' }, control: 'list' },
83
85
  { id: 'system.status', type: 'list', contexts: ['process'], name: 'system', label: '系统状态', section: 'process', order: 10, query: { cmd: '/system', operation: 'system.status', scope: 'process' }, control: 'list' },
84
86
  { 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' },
85
87
  { 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' },
86
88
  { 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' },
88
89
  { 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
90
  { id: 'system.upgrade', type: 'action', contexts: ['process'], name: 'system', label: '升级服务', section: 'process', order: 30, action: { cmd: '/system', action: 'upgrade', operation: 'system.upgrade', scope: 'process' } },
91
+ { 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
92
  { 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
93
  { 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
94
  { 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' } },
@@ -303,12 +305,47 @@ export async function resolveExplicitMenuExecutionContext(input) {
303
305
  };
304
306
  }
305
307
  function entryVisible(entry, context) { return entry.contexts.includes(context.kind); }
308
+ function configuredHostBaseagentEntries() {
309
+ const defaults = readConfig(ConfigTarget.Defaults);
310
+ const configured = defaults?.baseagents;
311
+ if (!configured || typeof configured !== 'object' || Array.isArray(configured))
312
+ return [];
313
+ return HOST_BASEAGENTS
314
+ .filter(baseagent => {
315
+ const block = configured[baseagent];
316
+ return Object.hasOwn(configured, baseagent) && block !== null
317
+ && typeof block === 'object' && !Array.isArray(block);
318
+ })
319
+ .map((baseagent, index) => ({
320
+ // Value batches only carry entry IDs, so each configured backend gets a
321
+ // binding with an explicit backend instead of an implicit Claude default.
322
+ id: `baseagent.models.${baseagent}`,
323
+ type: 'list',
324
+ contexts: ['process'],
325
+ name: 'baseagent-models',
326
+ label: `${baseagent} 模型目录`,
327
+ section: 'process',
328
+ order: 41 + index,
329
+ query: { cmd: '/baseagent-models', args: { baseagent }, operation: 'baseagent.models.list', scope: 'process' },
330
+ control: 'list',
331
+ }));
332
+ }
333
+ function menuEntryDefinitions(context) {
334
+ return context.kind === 'process' ? [...ENTRIES, ...configuredHostBaseagentEntries()] : ENTRIES;
335
+ }
306
336
  function intentFor(binding, context) { return { operation: binding.operation, scope: binding.scope, source: 'menu', args: { ...bindingArgs(binding, context) } }; }
307
- async function operationView(binding, context) {
337
+ async function operationView(binding, context, options = {}) {
308
338
  if (!binding)
309
339
  return undefined;
310
340
  if (binding.supported === false)
311
341
  return { supported: false, allowed: false, reason: { code: 'NOT_SUPPORTED', message: 'operation is not supported' } };
342
+ if (binding.operation === 'system.paircode' && options.paircodeAllowed === false) {
343
+ return {
344
+ supported: true,
345
+ allowed: false,
346
+ reason: { code: 'NOT_ALLOWED', message: 'Pairing code issuance requires encrypted DaemonOwner authentication' },
347
+ };
348
+ }
312
349
  if (binding.operation === 'group.rulespolicy.update' && context.subject.role !== 'owner') {
313
350
  return { supported: true, allowed: false, reason: { code: 'PERMISSION_DENIED', message: 'rulesPolicy can only be changed by an Agent owner' } };
314
351
  }
@@ -331,16 +368,18 @@ function wireBinding(binding, context) {
331
368
  export async function getMenuCatalog(input) {
332
369
  const context = await resolveMenuCatalogContext(input);
333
370
  const entries = [];
334
- for (const entry of ENTRIES.filter(candidate => entryVisible(candidate, context)).sort((a, b) => a.order - b.order)) {
371
+ const paircodeAllowed = input.paircodeAllowed ?? input.channelScope !== 'control';
372
+ const definitions = menuEntryDefinitions(context);
373
+ for (const entry of definitions.filter(candidate => entryVisible(candidate, context)).sort((a, b) => a.order - b.order)) {
335
374
  const operations = {};
336
375
  for (const [key, binding] of [['query', entry.query], ['options', entry.options], ['update', entry.update], ['action', entry.action]]) {
337
- const view = await operationView(binding, context);
376
+ const view = await operationView(binding, context, { paircodeAllowed });
338
377
  if (view)
339
378
  operations[key] = view;
340
379
  }
341
380
  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
381
  }
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) });
382
+ 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
383
  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
384
  return data;
346
385
  }
@@ -369,7 +408,7 @@ export async function execMenuValueBatch(input) {
369
408
  normalizedItems.push({ itemId, entryId });
370
409
  }
371
410
  const results = new Array(normalizedItems.length);
372
- const byId = new Map(ENTRIES.map(entry => [entry.id, entry]));
411
+ const byId = new Map(menuEntryDefinitions(context).map(entry => [entry.id, entry]));
373
412
  let cursor = 0;
374
413
  const resolveItem = async (item) => {
375
414
  const { itemId, entryId } = item;