evolcore 0.0.1 → 0.0.3

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 (379) hide show
  1. package/CHANGELOG.md +64 -0
  2. package/LICENSE +21 -0
  3. package/MIGRATION-0.5.0.md +378 -0
  4. package/README.md +318 -14
  5. package/ROLE_ACCESS_CONTROL.md +174 -0
  6. package/assets/.env.template +4 -0
  7. package/bin/ec-safe-output.js +161 -0
  8. package/bin/ec.js +29 -0
  9. package/dist/agents/baseagent.js +163 -0
  10. package/dist/agents/claude-runner.js +2565 -0
  11. package/dist/agents/codex-app-server-client.js +448 -0
  12. package/dist/agents/codex-runner.js +2682 -0
  13. package/dist/agents/gemini-runner.js +666 -0
  14. package/dist/agents/runner-types.js +75 -0
  15. package/dist/aun/aid/agentmd.js +216 -0
  16. package/dist/aun/aid/client.js +132 -0
  17. package/dist/aun/aid/control-aid.js +91 -0
  18. package/dist/aun/aid/identity.js +518 -0
  19. package/dist/aun/aid/index.js +4 -0
  20. package/dist/aun/aid/store.js +74 -0
  21. package/dist/aun/aid/types.js +1 -0
  22. package/dist/aun/aid/validation.js +21 -0
  23. package/dist/aun/group-identity.js +10 -0
  24. package/dist/aun/msg/group-index.js +6 -0
  25. package/dist/aun/msg/group.js +1231 -0
  26. package/dist/aun/msg/history.js +123 -0
  27. package/dist/aun/msg/index.js +5 -0
  28. package/dist/aun/msg/p2p.js +393 -0
  29. package/dist/aun/msg/payload-type.js +27 -0
  30. package/dist/aun/msg/upload.js +137 -0
  31. package/dist/aun/outbox.js +168 -0
  32. package/dist/aun/rpc/caller.js +42 -0
  33. package/dist/aun/rpc/connection.js +25 -0
  34. package/dist/aun/rpc/index.js +2 -0
  35. package/dist/aun/service-proxy.js +225 -0
  36. package/dist/aun/storage/download.js +29 -0
  37. package/dist/aun/storage/index.js +3 -0
  38. package/dist/aun/storage/manage.js +10 -0
  39. package/dist/aun/storage/upload.js +68 -0
  40. package/dist/channels/aun.js +4164 -0
  41. package/dist/channels/contact-bind-code.js +134 -0
  42. package/dist/channels/daemon.js +422 -0
  43. package/dist/channels/dingtalk.js +1479 -0
  44. package/dist/channels/feishu.js +1865 -0
  45. package/dist/channels/qqbot.js +409 -0
  46. package/dist/channels/wechat.js +817 -0
  47. package/dist/channels/wecom-card.js +101 -0
  48. package/dist/channels/wecom-onboarding.js +82 -0
  49. package/dist/channels/wecom-state.js +191 -0
  50. package/dist/channels/wecom.js +1157 -0
  51. package/dist/cli/agent-command.js +642 -0
  52. package/dist/cli/agent.js +1059 -0
  53. package/dist/cli/aun-commands.js +2003 -0
  54. package/dist/cli/bench.js +1228 -0
  55. package/dist/cli/cli-argv.js +66 -0
  56. package/dist/cli/code-stats.js +329 -0
  57. package/dist/cli/command-log.js +82 -0
  58. package/dist/cli/config-selector.js +69 -0
  59. package/dist/cli/config.js +261 -0
  60. package/dist/cli/contact.js +71 -0
  61. package/dist/cli/ctl-command.js +62 -0
  62. package/dist/cli/daemon-commands.js +2750 -0
  63. package/dist/cli/fs-command.js +1447 -0
  64. package/dist/cli/handoff-command.js +302 -0
  65. package/dist/cli/help.js +35 -0
  66. package/dist/cli/index.js +374 -0
  67. package/dist/cli/init-channel.js +1372 -0
  68. package/dist/cli/init.js +590 -0
  69. package/dist/cli/link-rules.js +240 -0
  70. package/dist/cli/model.js +591 -0
  71. package/dist/cli/net-check.js +723 -0
  72. package/dist/cli/queue-command.js +150 -0
  73. package/dist/cli/raw-key-input.js +25 -0
  74. package/dist/cli/response.js +344 -0
  75. package/dist/cli/restart-monitor.js +480 -0
  76. package/dist/cli/stats.js +609 -0
  77. package/dist/cli/task-context.js +80 -0
  78. package/dist/cli/trigger-command.js +545 -0
  79. package/dist/cli/version.js +93 -0
  80. package/dist/cli/watch-logs.js +33 -0
  81. package/dist/cli/watch-msg.js +673 -0
  82. package/dist/config/boot-log.js +266 -0
  83. package/dist/config/builtin-role-templates.js +42 -0
  84. package/dist/config/builtin-roles.js +91 -0
  85. package/dist/config/config-batch-get.js +11 -0
  86. package/dist/config/config-field-policy.js +261 -0
  87. package/dist/config/config-manager.js +1120 -0
  88. package/dist/config/config-operation-service.js +384 -0
  89. package/dist/config/contact-alias.js +68 -0
  90. package/dist/config/contact-book-store.js +454 -0
  91. package/dist/config/contact-book-v2-startup.js +35 -0
  92. package/dist/config/contact-book.js +224 -0
  93. package/dist/config/contact-operation-service.js +110 -0
  94. package/dist/config/gateway-config.js +858 -0
  95. package/dist/config/lifecycle.js +17 -0
  96. package/dist/config/mention-mode.js +27 -0
  97. package/dist/config/merge.js +161 -0
  98. package/dist/config/owner-policy.js +4 -0
  99. package/dist/config/peer-role-resolver.js +218 -0
  100. package/dist/config/resolved-config-op.js +483 -0
  101. package/dist/config/role-config-v4-startup.js +32 -0
  102. package/dist/config/role-config-v5-startup.js +27 -0
  103. package/dist/config/role-ranks.js +18 -0
  104. package/dist/config/role-schema.js +105 -0
  105. package/dist/config/role-service.js +156 -0
  106. package/dist/config/role-store.js +215 -0
  107. package/dist/config/roles.js +64 -0
  108. package/dist/config/schema-registry.js +154 -0
  109. package/dist/config/snapshot.js +598 -0
  110. package/dist/config-store.js +501 -0
  111. package/dist/core/auth/agent-delegation.js +111 -0
  112. package/dist/core/auth/auth-gateway.js +166 -0
  113. package/dist/core/auth/authenticated-actor.js +6 -0
  114. package/dist/core/auth/authorization-audit.js +119 -0
  115. package/dist/core/auth/operation-authorizer.js +720 -0
  116. package/dist/core/auth/operation-catalog.js +731 -0
  117. package/dist/core/baseagent-loader.js +54 -0
  118. package/dist/core/bootstrap-service.js +175 -0
  119. package/dist/core/capability/capability-manager.js +316 -0
  120. package/dist/core/capability/providers/claude-capability-provider.js +176 -0
  121. package/dist/core/capability/providers/codex-capability-provider.js +148 -0
  122. package/dist/core/capability/providers/gemini-capability-provider.js +10 -0
  123. package/dist/core/capability/types.js +27 -0
  124. package/dist/core/causation/audit.js +103 -0
  125. package/dist/core/causation/aun-association.js +111 -0
  126. package/dist/core/causation/context.js +93 -0
  127. package/dist/core/causation/index.js +4 -0
  128. package/dist/core/causation/types.js +2 -0
  129. package/dist/core/channel-loader.js +277 -0
  130. package/dist/core/command/agent-control.js +616 -0
  131. package/dist/core/command/cli-intent-parser.js +225 -0
  132. package/dist/core/command/command-handler.js +1733 -0
  133. package/dist/core/command/connect-menu.js +374 -0
  134. package/dist/core/command/menu-handler.js +3452 -0
  135. package/dist/core/command/menu-protocol.js +247 -0
  136. package/dist/core/command/role-menu.js +1623 -0
  137. package/dist/core/command/slash-gate.js +148 -0
  138. package/dist/core/command/slash-handler.js +3066 -0
  139. package/dist/core/daemon-file-cache.js +222 -0
  140. package/dist/core/event-bus.js +32 -0
  141. package/dist/core/event-catalog.js +810 -0
  142. package/dist/core/evolagent-registry.js +545 -0
  143. package/dist/core/evolagent.js +342 -0
  144. package/dist/core/handoff/dispatcher.js +229 -0
  145. package/dist/core/handoff/mutex.js +46 -0
  146. package/dist/core/handoff/runtime.js +324 -0
  147. package/dist/core/handoff/store.js +537 -0
  148. package/dist/core/handoff/types.js +12 -0
  149. package/dist/core/inference/text-inference.js +173 -0
  150. package/dist/core/interaction-registration.js +10 -0
  151. package/dist/core/interaction-router.js +278 -0
  152. package/dist/core/message/create-status.js +67 -0
  153. package/dist/core/message/im-renderer.js +659 -0
  154. package/dist/core/message/items-formatter.js +76 -0
  155. package/dist/core/message/logical-queue-bridge.js +123 -0
  156. package/dist/core/message/message-bridge.js +874 -0
  157. package/dist/core/message/message-cache.js +56 -0
  158. package/dist/core/message/message-log.js +339 -0
  159. package/dist/core/message/message-processor.js +4 -0
  160. package/dist/core/message/message-queue.js +1446 -0
  161. package/dist/core/message/message-utils.js +76 -0
  162. package/dist/core/message/peer-mode.js +105 -0
  163. package/dist/core/message/pending-hints.js +232 -0
  164. package/dist/core/message/response-depth.js +33 -0
  165. package/dist/core/message/response-engine.js +4020 -0
  166. package/dist/core/message/response-snapshot.js +83 -0
  167. package/dist/core/message/send-receipt.js +24 -0
  168. package/dist/core/message/stream-debouncer.js +139 -0
  169. package/dist/core/message/stream-idle-monitor.js +124 -0
  170. package/dist/core/model/config-scope.js +162 -0
  171. package/dist/core/model/field-scope.js +78 -0
  172. package/dist/core/model/model-catalog.js +227 -0
  173. package/dist/core/model/model-diagnostics.js +182 -0
  174. package/dist/core/model/model-permission.js +90 -0
  175. package/dist/core/permission/approval-gateway.js +1017 -0
  176. package/dist/core/permission/ec-command-parser.js +347 -0
  177. package/dist/core/permission/execution-sandbox.js +16 -0
  178. package/dist/core/permission/index.js +6 -0
  179. package/dist/core/permission/mode.js +24 -0
  180. package/dist/core/permission/sandbox-runtime.js +265 -0
  181. package/dist/core/permission/tool-policy.js +1019 -0
  182. package/dist/core/permission/unix-socket-policy.js +99 -0
  183. package/dist/core/protected-paths.js +332 -0
  184. package/dist/core/relation/peer-identity.js +222 -0
  185. package/dist/core/relation/peer-key.js +1 -0
  186. package/dist/core/role/runtime-policy.js +141 -0
  187. package/dist/core/session/adapters/claude-session-file-adapter.js +218 -0
  188. package/dist/core/session/adapters/codex-session-file-adapter.js +333 -0
  189. package/dist/core/session/adapters/gemini-session-file-adapter.js +181 -0
  190. package/dist/core/session/session-file-adapter.js +7 -0
  191. package/dist/core/session/session-file-health.js +45 -0
  192. package/dist/core/session/session-fs-store.js +273 -0
  193. package/dist/core/session/session-key.js +24 -0
  194. package/dist/core/session/session-manager.js +1643 -0
  195. package/dist/core/session/session-mapper.js +100 -0
  196. package/dist/core/session/session-renew.js +314 -0
  197. package/dist/core/session/session-title.js +128 -0
  198. package/dist/core/session/session-turn-coordinator.js +205 -0
  199. package/dist/core/session/session-turns.js +67 -0
  200. package/dist/core/system-channels.js +29 -0
  201. package/dist/eck/baseagent-caps.js +18 -0
  202. package/dist/eck/detect.js +47 -0
  203. package/dist/eck/group-rules-sync.js +345 -0
  204. package/dist/eck/init.js +77 -0
  205. package/dist/eck/kit-renderer.js +359 -0
  206. package/dist/eck/manifest-engine.js +446 -0
  207. package/dist/eck/message-renderer.js +199 -0
  208. package/dist/eck/rules-loader.js +28 -0
  209. package/dist/index.js +2926 -4
  210. package/dist/ipc.js +777 -0
  211. package/dist/paths.js +262 -0
  212. package/dist/product.js +18 -0
  213. package/dist/response-system/context-builder.js +71 -0
  214. package/dist/response-system/coordinator.js +117 -0
  215. package/dist/response-system/decision-executor.js +86 -0
  216. package/dist/response-system/engines/v1/index.js +21 -0
  217. package/dist/response-system/engines/v1/interactive-flow.js +27 -0
  218. package/dist/response-system/engines/v1/proactive-flow.js +137 -0
  219. package/dist/response-system/engines/v1/types.js +1 -0
  220. package/dist/response-system/extensions.js +41 -0
  221. package/dist/response-system/index.js +6 -0
  222. package/dist/response-system/modes/index.js +7 -0
  223. package/dist/response-system/modes/single-session/index.js +72 -0
  224. package/dist/response-system/queues/fifo-queue.js +44 -0
  225. package/dist/response-system/queues/index.js +6 -0
  226. package/dist/response-system/queues/lifo-queue.js +42 -0
  227. package/dist/response-system/queues/priority-queue.js +63 -0
  228. package/dist/response-system/registry.js +97 -0
  229. package/dist/response-system/resolver.js +37 -0
  230. package/dist/response-system/selector.js +23 -0
  231. package/dist/response-system/types.js +7 -0
  232. package/dist/stats/billing.js +163 -0
  233. package/dist/stats/budget.js +93 -0
  234. package/dist/stats/db.js +403 -0
  235. package/dist/stats/eck-vars.js +89 -0
  236. package/dist/stats/index.js +11 -0
  237. package/dist/stats/normalizer.js +80 -0
  238. package/dist/stats/price-resolver.js +138 -0
  239. package/dist/stats/query.js +763 -0
  240. package/dist/stats/role-budget.js +168 -0
  241. package/dist/stats/writer.js +151 -0
  242. package/dist/trigger/anomaly-store.js +258 -0
  243. package/dist/trigger/audit.js +152 -0
  244. package/dist/trigger/event-source.js +119 -0
  245. package/dist/trigger/feedback.js +685 -0
  246. package/dist/trigger/history.js +290 -0
  247. package/dist/trigger/manager.js +294 -0
  248. package/dist/trigger/parser.js +595 -0
  249. package/dist/trigger/patch.js +155 -0
  250. package/dist/trigger/scheduler.js +1602 -0
  251. package/dist/trigger/script-executor.js +155 -0
  252. package/dist/trigger/state.js +145 -0
  253. package/dist/trigger/types.js +1 -0
  254. package/dist/trigger/validation.js +634 -0
  255. package/dist/types.js +12 -0
  256. package/dist/utils/aid-bind.js +313 -0
  257. package/dist/utils/atomic-write.js +95 -0
  258. package/dist/utils/avatar-upload.js +123 -0
  259. package/dist/utils/cross-platform.js +297 -0
  260. package/dist/utils/ecweb-utils.js +73 -0
  261. package/dist/utils/error-dict.json +153 -0
  262. package/dist/utils/error-utils.js +349 -0
  263. package/dist/utils/instance-registry.js +444 -0
  264. package/dist/utils/locale.js +21 -0
  265. package/dist/utils/log-writer.js +270 -0
  266. package/dist/utils/logger.js +89 -0
  267. package/dist/utils/markdown-to-plain-text.js +20 -0
  268. package/dist/utils/media-cache.js +274 -0
  269. package/dist/utils/model-prices.jsonl +20 -0
  270. package/dist/utils/npm-ops.js +210 -0
  271. package/dist/utils/process-introspect.js +133 -0
  272. package/dist/utils/process-tree-stats.js +271 -0
  273. package/dist/utils/project-path.js +74 -0
  274. package/dist/utils/restart-safety.js +31 -0
  275. package/dist/utils/stats.js +410 -0
  276. package/dist/utils/system-memory.js +62 -0
  277. package/dist/utils/tool-summary.js +284 -0
  278. package/dist/utils/welcome.js +268 -0
  279. package/kits/docs/GUIDE.md +20 -0
  280. package/kits/docs/INDEX.md +66 -0
  281. package/kits/docs/aun/CHEATSHEET.md +19 -0
  282. package/kits/docs/aun/SYNC_PROTOCOL.md +15 -0
  283. package/kits/docs/channels/aun.md +65 -0
  284. package/kits/docs/channels/feishu.md +56 -0
  285. package/kits/docs/context-assembly.md +366 -0
  286. package/kits/docs/eck_templates/GUIDE.template.md +22 -0
  287. package/kits/docs/eck_templates/INDEX.template.md +28 -0
  288. package/kits/docs/eck_templates/path-registry.template.md +33 -0
  289. package/kits/docs/eck_templates/runtime.template.md +19 -0
  290. package/kits/docs/evolcore/INDEX.md +68 -0
  291. package/kits/docs/evolcore/agent.md +77 -0
  292. package/kits/docs/evolcore/aid.md +52 -0
  293. package/kits/docs/evolcore/config.md +149 -0
  294. package/kits/docs/evolcore/contact.md +57 -0
  295. package/kits/docs/evolcore/ctl.md +46 -0
  296. package/kits/docs/evolcore/event.md +216 -0
  297. package/kits/docs/evolcore/fs-architecture.md +1215 -0
  298. package/kits/docs/evolcore/fs.md +110 -0
  299. package/kits/docs/evolcore/group-fs.md +17 -0
  300. package/kits/docs/evolcore/group-rules.md +226 -0
  301. package/kits/docs/evolcore/group.md +150 -0
  302. package/kits/docs/evolcore/model.md +50 -0
  303. package/kits/docs/evolcore/msg.md +136 -0
  304. package/kits/docs/evolcore/response.md +75 -0
  305. package/kits/docs/evolcore/rpc.md +37 -0
  306. package/kits/docs/evolcore/self-summary.md +29 -0
  307. package/kits/docs/evolcore/stats.md +83 -0
  308. package/kits/docs/evolcore/storage.md +50 -0
  309. package/kits/docs/evolcore/trigger.md +539 -0
  310. package/kits/docs/identity/AID_PROFILE_SPEC.md +26 -0
  311. package/kits/docs/identity/PATH_OPS.md +16 -0
  312. package/kits/docs/identity/ROLE_DETAIL.md +23 -0
  313. package/kits/docs/identity/identity-tools.md +26 -0
  314. package/kits/docs/path-registry.md +43 -0
  315. package/kits/docs/prompt-loading-architecture.md +266 -0
  316. package/kits/docs/venues/aun-group.md +45 -0
  317. package/kits/docs/venues/aun-private.md +10 -0
  318. package/kits/docs/venues/client-desktop.md +10 -0
  319. package/kits/docs/venues/client-mobile.md +10 -0
  320. package/kits/docs/venues/feishu-group.md +13 -0
  321. package/kits/docs/venues/feishu-private.md +9 -0
  322. package/kits/docs/venues/group.md +25 -0
  323. package/kits/docs/venues/private.md +10 -0
  324. package/kits/eck_manifest.auxiliary.json +43 -0
  325. package/kits/eck_manifest.json +203 -0
  326. package/kits/eck_message_manifest.json +63 -0
  327. package/kits/migrations/README-role-config-v4.md +32 -0
  328. package/kits/migrations/migrate-contact-book-v2.mjs +747 -0
  329. package/kits/migrations/migrate-role-config-v4.mjs +623 -0
  330. package/kits/migrations/migrate-role-config-v5.mjs +346 -0
  331. package/kits/migrations/rename-config-file.mjs +99 -0
  332. package/kits/rules/01-overview.md +142 -0
  333. package/kits/rules/02-navigation.md +76 -0
  334. package/kits/rules/03-identity.md +34 -0
  335. package/kits/rules/04-relation.md +59 -0
  336. package/kits/rules/05-venue.md +44 -0
  337. package/kits/rules/06-channel.md +59 -0
  338. package/kits/schemas/_meta.json +32 -0
  339. package/kits/schemas/agent-config.schema.1.json +177 -0
  340. package/kits/schemas/agent-config.schema.2.json +239 -0
  341. package/kits/schemas/agent-config.schema.3.json +119 -0
  342. package/kits/schemas/agent-config.schema.4.json +208 -0
  343. package/kits/schemas/agent-config.schema.5.json +326 -0
  344. package/kits/schemas/agent-config.schema.6.json +322 -0
  345. package/kits/schemas/contact-book.schema.1.json +36 -0
  346. package/kits/schemas/contact-book.schema.2.json +43 -0
  347. package/kits/schemas/daemon.schema.1.json +90 -0
  348. package/kits/schemas/defaults.schema.1.json +81 -0
  349. package/kits/schemas/menu-exec-schema-commands.md +208 -0
  350. package/kits/schemas/migrations/README.md +28 -0
  351. package/kits/schemas/relation-config.schema.1.json +158 -0
  352. package/kits/schemas/relation-config.schema.2.json +73 -0
  353. package/kits/schemas/relation-config.schema.3.json +50 -0
  354. package/kits/schemas/relation-config.schema.4.json +47 -0
  355. package/kits/schemas/relation-config.schema.5.json +47 -0
  356. package/kits/schemas/role-config.schema.1.json +201 -0
  357. package/kits/schemas/role-registry.schema.1.json +35 -0
  358. package/kits/schemas/single-session.schema.1.json +31 -0
  359. package/kits/templates/bootstrap-welcome.md +15 -0
  360. package/kits/templates/message-fragments/handoff-request-to-target.md +13 -0
  361. package/kits/templates/message-fragments/handoff-response-to-origin.md +10 -0
  362. package/kits/templates/message-fragments/inject-default.md +2 -0
  363. package/kits/templates/message-fragments/item.md +2 -0
  364. package/kits/templates/roles/admin.json +9 -0
  365. package/kits/templates/roles/member.json +41 -0
  366. package/kits/templates/roles/owner.json +9 -0
  367. package/kits/templates/roles/visitor.json +40 -0
  368. package/kits/templates/system-fragments/baseagent.md +14 -0
  369. package/kits/templates/system-fragments/bootstrap.md +16 -0
  370. package/kits/templates/system-fragments/channel.md +48 -0
  371. package/kits/templates/system-fragments/commands.md +28 -0
  372. package/kits/templates/system-fragments/identity.md +11 -0
  373. package/kits/templates/system-fragments/relation.md +19 -0
  374. package/kits/templates/system-fragments/session.md +53 -0
  375. package/kits/templates/system-fragments/venue.md +31 -0
  376. package/package.json +50 -15
  377. package/dist/index.d.ts +0 -7
  378. package/dist/index.d.ts.map +0 -1
  379. package/dist/index.js.map +0 -1
@@ -0,0 +1,720 @@
1
+ import { getRoleDefinition } from '../../config/roles.js';
2
+ import { isManagementRole } from '../../config/builtin-roles.js';
3
+ import { relationFieldWriteOperation, resolveRoleFieldPermission, } from '../../config/config-field-policy.js';
4
+ import { isResolvedConfigMutation, } from '../../config/resolved-config-op.js';
5
+ import { normalizePeer } from '../model/config-scope.js';
6
+ import { getOperationMeta } from './operation-catalog.js';
7
+ import { formatPeerKey, parsePeerKey } from '../relation/peer-identity.js';
8
+ export const USER_PLANE_CAPABILITY_CEILING = {
9
+ allowOperations: new Set([
10
+ 'model.list',
11
+ 'model.current',
12
+ 'model.use',
13
+ 'model.effort',
14
+ 'model.reset',
15
+ 'permission.current',
16
+ 'permission.answer',
17
+ 'chatmode.current',
18
+ 'chatmode.update',
19
+ 'mentionmode.current',
20
+ 'mentionmode.update',
21
+ 'session.list',
22
+ 'session.create',
23
+ 'session.rename',
24
+ 'trigger.list',
25
+ 'trigger.show',
26
+ 'trigger.history',
27
+ 'trigger.eventCatalog',
28
+ 'trigger.create',
29
+ 'trigger.update',
30
+ 'trigger.setEnabled',
31
+ 'trigger.cancel',
32
+ 'trigger.delete',
33
+ 'trigger.run',
34
+ 'stats.summary',
35
+ 'stats.session',
36
+ 'stats.context',
37
+ 'file.list',
38
+ 'file.fetch',
39
+ 'ec.msg.send',
40
+ 'ec.msg.file',
41
+ 'ec.group.send',
42
+ 'ec.group.file',
43
+ 'config.get',
44
+ 'config.set',
45
+ 'config.unset',
46
+ ]),
47
+ denyOperations: new Set([
48
+ 'role.assign',
49
+ 'role.revoke',
50
+ 'config.write',
51
+ 'gateway.write',
52
+ 'cli.exec.raw',
53
+ 'shell.exec',
54
+ 'rce.exec',
55
+ ]),
56
+ denyNamespaces: new Set([
57
+ 'role',
58
+ 'connect',
59
+ 'contact',
60
+ 'agent',
61
+ 'system',
62
+ 'storage',
63
+ 'aid',
64
+ ]),
65
+ allowCategories: new Set(['read', 'write-own']),
66
+ denyCategories: new Set(['write-agent', 'process', 'dangerous']),
67
+ };
68
+ export function authorizeCommand(ctx) {
69
+ return authorizeCommandInternal(ctx);
70
+ }
71
+ export function authorizeResolvedConfigOperation(op, context) {
72
+ return authorizeResolvedConfigCommand(op, context);
73
+ }
74
+ export function authorizeResolvedConfigCommand(command, context) {
75
+ return authorizeCommandInternal({ ...context, intent: intentForResolvedConfig(command, context.source) }, command);
76
+ }
77
+ /**
78
+ * Resolve the role-level capability used by relation setting discovery.
79
+ * Target ownership/current-relation constraints are considered satisfied by
80
+ * the caller because the Role snapshot is already bound to one normalized
81
+ * relation. Context-sensitive private/group and control constraints remain
82
+ * enforced here.
83
+ */
84
+ export function evaluateRoleOperationCapability(params) {
85
+ const opMeta = getOperationMeta(params.operation);
86
+ if (!opMeta) {
87
+ return {
88
+ allow: false,
89
+ operation: params.operation,
90
+ reason: `Unknown operation: ${params.operation}`,
91
+ };
92
+ }
93
+ const roleDef = getRoleDefinition(params.role, params.selfAid);
94
+ if (!roleDef || roleDef.allowAccess === false) {
95
+ return {
96
+ allow: false,
97
+ operation: params.operation,
98
+ reason: `Role ${params.role} cannot access commands`,
99
+ };
100
+ }
101
+ const matched = matchCommandPermission(params.operation, opMeta.category, roleDef.commandPermissions || {}, opMeta.dangerous);
102
+ if (!matched) {
103
+ return {
104
+ allow: false,
105
+ operation: params.operation,
106
+ reason: `Role ${params.role} has no permission for ${params.operation}`,
107
+ };
108
+ }
109
+ const { permission, matchedRule } = matched;
110
+ if (!permission.allow) {
111
+ return {
112
+ allow: false,
113
+ operation: params.operation,
114
+ reason: permission.reason || `Role ${params.role} explicitly denies ${params.operation}`,
115
+ matchedRule,
116
+ };
117
+ }
118
+ const scopes = permission.scopes || opMeta.defaultScopes;
119
+ if (!scopes.includes(params.scope)) {
120
+ return {
121
+ allow: false,
122
+ operation: params.operation,
123
+ reason: `Scope ${params.scope} is not allowed for ${params.operation}`,
124
+ matchedRule,
125
+ };
126
+ }
127
+ const constraints = permission.constraints;
128
+ if (constraints?.privateOnly && params.chatType !== 'private') {
129
+ return { allow: false, operation: params.operation, reason: 'Operation is private-chat only', matchedRule };
130
+ }
131
+ if (constraints?.groupOnly && params.chatType !== 'group') {
132
+ return { allow: false, operation: params.operation, reason: 'Operation is group-chat only', matchedRule };
133
+ }
134
+ if (constraints?.requireAgentOwner && params.role !== 'owner') {
135
+ return { allow: false, operation: params.operation, reason: 'Operation requires Agent owner', matchedRule };
136
+ }
137
+ if (constraints?.requireAgentAdmin && params.role !== 'owner' && params.role !== 'admin') {
138
+ return { allow: false, operation: params.operation, reason: 'Operation requires Agent admin', matchedRule };
139
+ }
140
+ if (constraints?.requireControlChannel && !params.fromControlChannel) {
141
+ return { allow: false, operation: params.operation, reason: 'Operation requires the control channel', matchedRule };
142
+ }
143
+ if (opMeta.dangerous && !isExplicitDangerousGrant(matchedRule, permission)) {
144
+ return { allow: false, operation: params.operation, reason: 'Dangerous operation is not explicitly granted', matchedRule };
145
+ }
146
+ return { allow: true, operation: params.operation, matchedRule };
147
+ }
148
+ function authorizeCommandInternal(ctx, resolvedConfigCommand) {
149
+ const { intent, role } = ctx;
150
+ const operation = intent.operation;
151
+ const opMeta = getOperationMeta(operation);
152
+ if (!opMeta) {
153
+ return denyDecision(ctx, 'NOT_ALLOWED', `Unknown operation: ${operation}`, operation, intent.scope);
154
+ }
155
+ if (isFieldConfigOperation(operation) && !resolvedConfigCommand) {
156
+ return denyDecision(ctx, 'NOT_ALLOWED', 'Resolved config operation is required', operation, intent.scope, opMeta.dangerous);
157
+ }
158
+ if (resolvedConfigCommand
159
+ && (resolvedConfigCommand.operationId !== operation || resolvedConfigCommand.commandScope !== intent.scope)) {
160
+ return denyDecision(ctx, 'NOT_ALLOWED', 'Resolved config operation does not match the command intent', operation, intent.scope, opMeta.dangerous);
161
+ }
162
+ if (ctx.chatType === 'group'
163
+ && (resolvedConfigCommand ? isResolvedConfigMutation(resolvedConfigCommand) : isConfigMutationOperation(operation))
164
+ && intent.scope !== 'relation') {
165
+ return denyDecision(ctx, 'SCOPE_MISMATCH', 'Group chats may only modify relation-scoped config', operation, intent.scope, opMeta.dangerous);
166
+ }
167
+ const sourceAllowed = !opMeta.sources
168
+ || opMeta.sources.includes(ctx.source)
169
+ || ((ctx.source === 'ecweb' || ctx.source === 'control') && opMeta.sources.includes('menu'));
170
+ if (!sourceAllowed) {
171
+ return denyDecision(ctx, 'NOT_ALLOWED', `Operation ${operation} is not available from source ${ctx.source}`, operation, intent.scope, opMeta.dangerous);
172
+ }
173
+ if (resolvedConfigCommand) {
174
+ const targetCheck = checkResolvedConfigTarget(ctx, resolvedConfigCommand);
175
+ if (!targetCheck.ok) {
176
+ return denyDecision(ctx, intent.scope === 'relation' ? 'ARGUMENT_MISMATCH' : 'SCOPE_MISMATCH', targetCheck.reason || 'Config target is outside the current task context', operation, intent.scope, opMeta.dangerous);
177
+ }
178
+ const identityMutationCheck = checkManagementIdentityMutation(ctx, resolvedConfigCommand);
179
+ if (!identityMutationCheck.ok) {
180
+ return denyDecision(ctx, 'NOT_ALLOWED', identityMutationCheck.reason || 'Static management identities cannot be changed by this role', operation, intent.scope, opMeta.dangerous);
181
+ }
182
+ if (resolvedConfigCommand.kind === 'global' && !ctx.isDaemonOwner) {
183
+ return denyDecision(ctx, 'NOT_ALLOWED', `Global config command ${operation} requires daemon owner permission`, operation, intent.scope, opMeta.dangerous);
184
+ }
185
+ if (role === 'owner' && !ctx.isDaemonOwner && intent.scope === 'process') {
186
+ return denyDecision(ctx, 'SCOPE_MISMATCH', 'Agent owners cannot access process, defaults, or global config commands', operation, intent.scope, opMeta.dangerous);
187
+ }
188
+ if (!isManagementRole(role) && intent.scope !== 'relation') {
189
+ return denyDecision(ctx, 'SCOPE_MISMATCH', `Role ${role} may only access relation-scoped config`, operation, intent.scope, opMeta.dangerous);
190
+ }
191
+ if (ctx.isDaemonOwner || role === 'owner') {
192
+ return {
193
+ allow: true,
194
+ operation,
195
+ scope: intent.scope,
196
+ role: ctx.isDaemonOwner ? 'owner' : role,
197
+ dangerous: opMeta.dangerous,
198
+ matchedRule: ctx.isDaemonOwner ? 'daemon-owner' : 'agent-owner',
199
+ };
200
+ }
201
+ }
202
+ const roleDef = getRoleDefinition(role, ctx.selfAid);
203
+ if (!roleDef) {
204
+ return denyDecision(ctx, 'ROLE_ACCESS_DENIED', `Unknown role: ${role}`, operation, intent.scope, opMeta.dangerous);
205
+ }
206
+ if (roleDef.allowAccess === false) {
207
+ return denyDecision(ctx, 'ROLE_ACCESS_DENIED', `Role ${role} is not allowed to access commands`, operation, intent.scope, opMeta.dangerous);
208
+ }
209
+ if (!isManagementRole(role)
210
+ && !resolvedConfigCommand
211
+ && !isUserPlaneOperationAllowed(operation, opMeta.category, opMeta.dangerous)) {
212
+ return denyDecision(ctx, 'NOT_ALLOWED', `Operation ${operation} is outside the user permission plane`, operation, intent.scope, opMeta.dangerous);
213
+ }
214
+ const matchResult = matchCommandPermission(operation, opMeta.category, roleDef.commandPermissions || {}, opMeta.dangerous);
215
+ if (!matchResult) {
216
+ return denyDecision(ctx, 'NO_PERMISSION', `Role ${role} has no permission for ${operation}`, operation, intent.scope, opMeta.dangerous);
217
+ }
218
+ const { permission, matchedRule } = matchResult;
219
+ if (!permission.allow) {
220
+ return denyDecision(ctx, 'NOT_ALLOWED', permission.reason || `Role ${role} explicitly denies ${operation}`, operation, intent.scope, opMeta.dangerous, matchedRule);
221
+ }
222
+ if (resolvedConfigCommand?.kind === 'field'
223
+ && isFieldConfigOperation(operation)
224
+ && matchedRule !== operation
225
+ && matchedRule !== 'config.*') {
226
+ return denyDecision(ctx, 'NO_PERMISSION', `Role ${role} requires an explicit config permission`, operation, intent.scope, opMeta.dangerous, matchedRule);
227
+ }
228
+ if (resolvedConfigCommand?.dangerous) {
229
+ if (matchedRule !== operation || permission.allow !== true || permission.dangerous !== true) {
230
+ return denyDecision(ctx, 'DANGEROUS_NOT_GRANTED', `Dangerous config operation ${operation} requires an exact dangerous grant`, operation, intent.scope, opMeta.dangerous, matchedRule);
231
+ }
232
+ if (!permission.scopes || permission.scopes.length === 0) {
233
+ return denyDecision(ctx, 'SCOPE_MISMATCH', `Dangerous config operation ${operation} requires explicit scopes`, operation, intent.scope, opMeta.dangerous, matchedRule);
234
+ }
235
+ if (resolvedConfigCommand.kind === 'field') {
236
+ const allowedConfigKeys = permission.constraints?.allowedConfigKeys;
237
+ if (!allowedConfigKeys || allowedConfigKeys.length === 0 || !allowedConfigKeys.includes(resolvedConfigCommand.field)) {
238
+ return denyDecision(ctx, 'ARGUMENT_MISMATCH', `Dangerous config field ${resolvedConfigCommand.field} requires a non-empty allowedConfigKeys constraint`, operation, intent.scope, opMeta.dangerous, matchedRule);
239
+ }
240
+ }
241
+ }
242
+ if (permission.constraints?.requireExplicitDangerousGrant && !isExplicitDangerousGrant(matchedRule, permission)) {
243
+ return denyDecision(ctx, 'DANGEROUS_NOT_GRANTED', `Operation ${operation} requires an explicit dangerous grant`, operation, intent.scope, opMeta.dangerous, matchedRule);
244
+ }
245
+ if (opMeta.dangerous && !resolvedConfigCommand && !isExplicitDangerousGrant(matchedRule, permission)) {
246
+ return denyDecision(ctx, 'DANGEROUS_NOT_GRANTED', `Dangerous operation ${operation} requires an explicit dangerous grant`, operation, intent.scope, opMeta.dangerous, matchedRule);
247
+ }
248
+ const allowedScopes = permission.scopes || opMeta.defaultScopes;
249
+ if (!allowedScopes.includes(intent.scope)) {
250
+ return denyDecision(ctx, 'SCOPE_MISMATCH', `Scope ${intent.scope} is not in allowed scopes: ${allowedScopes.join(', ')}`, operation, intent.scope, opMeta.dangerous, matchedRule);
251
+ }
252
+ if (resolvedConfigCommand?.kind === 'field' && isFieldConfigOperation(operation)) {
253
+ const fieldPolicy = operation === 'config.get' ? 'behavior-read' : 'role-overridable-write';
254
+ const fieldCheck = checkConfigFieldPolicy(ctx, fieldPolicy, resolvedConfigCommand);
255
+ if (!fieldCheck.ok) {
256
+ return denyDecision(ctx, 'ARGUMENT_MISMATCH', fieldCheck.reason || 'Config field is not allowed', operation, intent.scope, opMeta.dangerous, matchedRule);
257
+ }
258
+ }
259
+ if (permission.constraints) {
260
+ const constraintCheck = checkConstraints(ctx, permission.constraints, resolvedConfigCommand);
261
+ if (!constraintCheck.ok) {
262
+ return denyDecision(ctx, 'ARGUMENT_MISMATCH', constraintCheck.reason || 'Command arguments do not satisfy permission constraints', operation, intent.scope, opMeta.dangerous, matchedRule);
263
+ }
264
+ }
265
+ return {
266
+ allow: true,
267
+ operation,
268
+ scope: intent.scope,
269
+ role,
270
+ dangerous: opMeta.dangerous,
271
+ matchedRule,
272
+ constraints: permission.constraints,
273
+ };
274
+ }
275
+ function matchCommandPermission(operation, category, commandPerms, isDangerous) {
276
+ const namespace = operation.split('.')[0];
277
+ const matches = [];
278
+ for (const [rule, permission] of Object.entries(commandPerms)) {
279
+ const rank = getRuleRank(rule, permission, operation, namespace, category, isDangerous);
280
+ if (rank > 0) {
281
+ matches.push({ permission, matchedRule: rule, rank });
282
+ }
283
+ }
284
+ if (matches.length === 0)
285
+ return null;
286
+ matches.sort((a, b) => b.rank - a.rank);
287
+ return matches[0];
288
+ }
289
+ function getRuleRank(rule, permission, operation, namespace, category, isDangerous) {
290
+ const denyOffset = permission.allow ? 0 : 1;
291
+ if (rule === operation)
292
+ return 1000 + denyOffset;
293
+ if (rule === `${namespace}.*`)
294
+ return 800 + denyOffset;
295
+ if (rule.startsWith('category:') && rule.slice('category:'.length) === category)
296
+ return 600 + denyOffset;
297
+ if (rule === 'dangerous:*' && isDangerous)
298
+ return 400 + denyOffset;
299
+ if (rule === '*' && !isDangerous)
300
+ return 200 + denyOffset;
301
+ return 0;
302
+ }
303
+ function isExplicitDangerousGrant(rule, permission) {
304
+ return permission.allow === true && permission.dangerous === true && (rule === 'dangerous:*' || !rule.includes('*') && !rule.startsWith('category:'));
305
+ }
306
+ function isUserPlaneOperationAllowed(operation, category, dangerous) {
307
+ const namespace = operation.split('.')[0];
308
+ if (dangerous)
309
+ return false;
310
+ if (USER_PLANE_CAPABILITY_CEILING.denyCategories.has(category))
311
+ return false;
312
+ if (!USER_PLANE_CAPABILITY_CEILING.allowCategories.has(category))
313
+ return false;
314
+ if (USER_PLANE_CAPABILITY_CEILING.denyOperations.has(operation))
315
+ return false;
316
+ if (USER_PLANE_CAPABILITY_CEILING.denyNamespaces.has(namespace))
317
+ return false;
318
+ return USER_PLANE_CAPABILITY_CEILING.allowOperations.has(operation);
319
+ }
320
+ function checkConstraints(ctx, constraints, resolvedConfigCommand) {
321
+ if (constraints.ownPeerOnly) {
322
+ const peerCheck = checkOwnPeer(ctx);
323
+ if (!peerCheck.ok)
324
+ return peerCheck;
325
+ }
326
+ if (constraints.ownAgentOnly || constraints.targetCurrentAgentOnly) {
327
+ const argSelf = stringArg(ctx.intent.args.self);
328
+ if (argSelf && argSelf !== ctx.selfAid) {
329
+ return { ok: false, reason: 'Only the current agent can be targeted' };
330
+ }
331
+ }
332
+ if (constraints.requireAgentOwner && ctx.role !== 'owner') {
333
+ return { ok: false, reason: 'This command requires agent owner permission' };
334
+ }
335
+ if (constraints.requireAgentAdmin && ctx.role !== 'owner' && ctx.role !== 'admin') {
336
+ return { ok: false, reason: 'This command requires agent admin permission' };
337
+ }
338
+ if (constraints.privateOnly && ctx.chatType !== 'private') {
339
+ return { ok: false, reason: 'This command is only allowed in private chats' };
340
+ }
341
+ if (constraints.groupOnly && ctx.chatType !== 'group') {
342
+ const target = stringArg(ctx.intent.args.targetId) ?? stringArg(ctx.intent.args.peer) ?? '<unknown>';
343
+ const sameGroup = constraints.currentRelationOnly ? ' and may only target that same group' : '';
344
+ return {
345
+ ok: false,
346
+ reason: `Denied ${ctx.intent.operation}: role=${ctx.role}, source_chat=${ctx.chatType ?? 'unknown'}, target_group=${target}. This role may send group messages only from a group-chat task${sameGroup}; the target being a group is not sufficient.`,
347
+ };
348
+ }
349
+ if (constraints.requireDaemonOwner && !ctx.isDaemonOwner) {
350
+ return { ok: false, reason: 'This command requires daemon owner permission' };
351
+ }
352
+ if (constraints.requireControlChannel && !ctx.fromControlChannel) {
353
+ return { ok: false, reason: 'This command requires the control channel' };
354
+ }
355
+ if (constraints.forbiddenFlags) {
356
+ for (const flag of constraints.forbiddenFlags) {
357
+ if (ctx.intent.args[flag] !== undefined) {
358
+ return { ok: false, reason: `Forbidden argument: --${flag}` };
359
+ }
360
+ }
361
+ }
362
+ if (constraints.allowedArgs) {
363
+ for (const [key, allowedValues] of Object.entries(constraints.allowedArgs)) {
364
+ const value = ctx.intent.args[key];
365
+ if (value !== undefined && Array.isArray(allowedValues) && !allowedValues.includes(value)) {
366
+ return { ok: false, reason: `Argument --${key} value ${String(value)} is not allowed` };
367
+ }
368
+ }
369
+ }
370
+ if (constraints.deniedArgs) {
371
+ for (const [key, deniedValues] of Object.entries(constraints.deniedArgs)) {
372
+ const value = ctx.intent.args[key];
373
+ if (value !== undefined && Array.isArray(deniedValues) && deniedValues.includes(value)) {
374
+ return { ok: false, reason: `Argument --${key} value ${String(value)} is denied` };
375
+ }
376
+ }
377
+ }
378
+ if (constraints.allowedConfigKeys) {
379
+ const keyCheck = checkAllowedConfigKeys(ctx, constraints.allowedConfigKeys);
380
+ if (!keyCheck.ok)
381
+ return keyCheck;
382
+ }
383
+ if (constraints.allowedPrefixes) {
384
+ const prefixCheck = checkAllowedPrefixes(ctx, constraints.allowedPrefixes);
385
+ if (!prefixCheck.ok)
386
+ return prefixCheck;
387
+ }
388
+ if (constraints.requireFieldOverride) {
389
+ const fieldCheck = checkFieldOverride(ctx, constraints.requireFieldOverride);
390
+ if (!fieldCheck.ok)
391
+ return fieldCheck;
392
+ }
393
+ if (constraints.configFieldPolicy) {
394
+ const fieldCheck = checkConfigFieldPolicy(ctx, constraints.configFieldPolicy, resolvedConfigCommand?.kind === 'field' ? resolvedConfigCommand : undefined);
395
+ if (!fieldCheck.ok)
396
+ return fieldCheck;
397
+ }
398
+ if (constraints.currentRelationOnly) {
399
+ const relationCheck = checkCurrentRelation(ctx, resolvedConfigCommand);
400
+ if (!relationCheck.ok)
401
+ return relationCheck;
402
+ }
403
+ return { ok: true };
404
+ }
405
+ function isFieldConfigOperation(operation) {
406
+ return operation === 'config.get' || operation === 'config.set' || operation === 'config.unset';
407
+ }
408
+ function isConfigMutationOperation(operation) {
409
+ return operation === 'config.set' || operation === 'config.unset' || operation === 'config.write';
410
+ }
411
+ function checkConfigFieldPolicy(ctx, policy, resolvedConfigOp) {
412
+ if (!resolvedConfigOp)
413
+ return { ok: false, reason: 'Resolved config operation is required' };
414
+ const field = resolvedConfigOp.field;
415
+ if (!resolvedConfigOp.route || (resolvedConfigOp.fieldRule.class !== 'safe-scalar' && resolvedConfigOp.fieldRule.class !== 'safe-readonly-object')) {
416
+ return { ok: false, reason: `Config field ${field} is not available to user roles` };
417
+ }
418
+ const roleDef = getRoleDefinition(ctx.role, ctx.selfAid);
419
+ const fieldPermission = roleDef
420
+ ? resolveRoleFieldPermission(roleDef.permissions || {}, field)
421
+ : undefined;
422
+ if (policy === 'behavior-read')
423
+ return { ok: true };
424
+ if (fieldPermission && !fieldPermission.allowOverride) {
425
+ return { ok: false, reason: `Role ${ctx.role} cannot override ${field}` };
426
+ }
427
+ if (ctx.intent.operation === 'config.set') {
428
+ const valueCheck = fieldPermission
429
+ ? checkFieldValueAllowed(fieldPermission, resolvedConfigOp.value)
430
+ : { ok: true };
431
+ if (!valueCheck.ok)
432
+ return valueCheck;
433
+ }
434
+ if (ctx.intent.operation === 'config.set' || ctx.intent.operation === 'config.unset') {
435
+ const operation = relationFieldWriteOperation(field);
436
+ if (operation) {
437
+ const capability = evaluateRoleOperationCapability({
438
+ role: ctx.role,
439
+ selfAid: ctx.selfAid,
440
+ operation,
441
+ scope: 'relation',
442
+ chatType: ctx.chatType,
443
+ fromControlChannel: ctx.fromControlChannel,
444
+ });
445
+ if (!capability.allow) {
446
+ return {
447
+ ok: false,
448
+ reason: capability.reason || `Role ${ctx.role} cannot perform ${operation}`,
449
+ };
450
+ }
451
+ }
452
+ }
453
+ return { ok: true };
454
+ }
455
+ function checkResolvedConfigTarget(ctx, command) {
456
+ if (command.kind === 'global' || command.configScope === 'process' || command.configScope === 'defaults') {
457
+ return { ok: true };
458
+ }
459
+ if (command.configScope === 'agent') {
460
+ if (!ctx.selfAid || !command.self || command.self !== ctx.selfAid) {
461
+ return { ok: false, reason: 'Only the current agent can be targeted' };
462
+ }
463
+ return { ok: true };
464
+ }
465
+ if (ctx.allowExplicitRelationTarget && ctx.role === 'owner') {
466
+ if (!ctx.selfAid || !command.self || command.self !== ctx.selfAid) {
467
+ return { ok: false, reason: 'Only the current agent relation can be targeted' };
468
+ }
469
+ if (!command.peerKey) {
470
+ return { ok: false, reason: 'Explicit relation target is required' };
471
+ }
472
+ try {
473
+ normalizeRelationPeer(command.peerKey);
474
+ return { ok: true };
475
+ }
476
+ catch {
477
+ return { ok: false, reason: 'Cannot verify the explicit relation target' };
478
+ }
479
+ }
480
+ return checkCurrentRelation(ctx, command);
481
+ }
482
+ function checkManagementIdentityMutation(ctx, command) {
483
+ if (command.kind !== 'field' || command.subcommand === 'get')
484
+ return { ok: true };
485
+ const field = command.field.split('.')[0];
486
+ if (field !== 'owners' && field !== 'admins')
487
+ return { ok: true };
488
+ if (command.configScope !== 'agent') {
489
+ return { ok: false, reason: `${field} requires an explicit current-agent target` };
490
+ }
491
+ if (ctx.isDaemonOwner || ctx.role === 'owner')
492
+ return { ok: true };
493
+ return { ok: false, reason: `Only an Agent owner can modify ${field}` };
494
+ }
495
+ function checkCurrentRelation(ctx, resolvedConfigCommand) {
496
+ const requestedSelf = resolvedConfigCommand?.self ?? stringArg(ctx.intent.args.self);
497
+ if (!ctx.selfAid || !requestedSelf || requestedSelf !== ctx.selfAid) {
498
+ return { ok: false, reason: 'Only the current agent relation can be targeted' };
499
+ }
500
+ const requestedPeer = resolvedConfigCommand?.peerKey || stringArg(ctx.intent.args.peerKey) || stringArg(ctx.intent.args.peer);
501
+ if (!ctx.peerKey || !requestedPeer) {
502
+ return { ok: false, reason: 'Current relation context is required' };
503
+ }
504
+ try {
505
+ if (normalizeRelationPeer(requestedPeer) !== normalizeRelationPeer(ctx.peerKey)) {
506
+ if (ctx.intent.operation.startsWith('ec.group.')) {
507
+ const currentGroup = peerIdFromArg(ctx.peerKey);
508
+ const targetGroup = stringArg(ctx.intent.args.targetId) ?? peerIdFromArg(requestedPeer);
509
+ return {
510
+ ok: false,
511
+ reason: `Denied ${ctx.intent.operation}: role=${ctx.role}, current_group=${currentGroup}, target_group=${targetGroup}. This role may send only to the current group; cross-group sends require admin or owner permission.`,
512
+ };
513
+ }
514
+ return { ok: false, reason: 'Only the current relation can be targeted' };
515
+ }
516
+ }
517
+ catch {
518
+ return { ok: false, reason: 'Cannot verify the current relation target' };
519
+ }
520
+ return { ok: true };
521
+ }
522
+ function intentForResolvedConfig(command, source) {
523
+ return {
524
+ operation: command.operationId,
525
+ scope: command.commandScope,
526
+ source,
527
+ args: {
528
+ ...(command.kind === 'field' ? {
529
+ field: command.field,
530
+ ...(command.subcommand === 'set' ? { value: command.value } : {}),
531
+ } : {}),
532
+ ...(command.kind === 'scoped' && command.field ? { field: command.field } : {}),
533
+ configScope: command.configScope,
534
+ ...(command.self ? { self: command.self } : {}),
535
+ ...(command.peerKey ? { peer: command.peerKey, peerKey: command.peerKey } : {}),
536
+ },
537
+ rawArgv: command.canonicalArgv,
538
+ ...(command.dangerous ? { dangerous: true } : {}),
539
+ };
540
+ }
541
+ function normalizeRelationPeer(peer) {
542
+ const legacyIndex = peer.indexOf('::');
543
+ if (legacyIndex > 0) {
544
+ return formatPeerKey(peer.slice(0, legacyIndex), peer.slice(legacyIndex + 2));
545
+ }
546
+ return normalizePeer(peer);
547
+ }
548
+ function checkAllowedConfigKeys(ctx, allowedConfigKeys) {
549
+ if (allowedConfigKeys.length === 0)
550
+ return { ok: true };
551
+ for (const key of configKeysFromArgs(ctx.intent.args)) {
552
+ if (!allowedConfigKeys.includes(key)) {
553
+ return { ok: false, reason: `Config key ${key} is not allowed` };
554
+ }
555
+ }
556
+ return { ok: true };
557
+ }
558
+ function checkAllowedPrefixes(ctx, allowedPrefixes) {
559
+ if (allowedPrefixes.length === 0)
560
+ return { ok: true };
561
+ for (const value of prefixedValuesFromArgs(ctx.intent.args)) {
562
+ if (!allowedPrefixes.some(prefix => value.startsWith(prefix))) {
563
+ return { ok: false, reason: `Value ${value} does not match an allowed prefix` };
564
+ }
565
+ }
566
+ return { ok: true };
567
+ }
568
+ function configKeysFromArgs(args) {
569
+ const keys = new Set();
570
+ for (const name of ['key', 'field', 'fieldPath', 'path', 'name', 'configKey']) {
571
+ collectStringValues(args[name], keys);
572
+ }
573
+ for (const name of ['keys', 'fields', 'fieldPaths', 'paths', 'configKeys']) {
574
+ collectStringValues(args[name], keys);
575
+ }
576
+ return [...keys];
577
+ }
578
+ function prefixedValuesFromArgs(args) {
579
+ const values = new Set();
580
+ for (const name of ['path', 'cwd', 'file', 'filePath', 'target', 'targetPath', 'prefix']) {
581
+ collectStringValues(args[name], values);
582
+ }
583
+ for (const name of ['paths', 'files', 'filePaths', 'targets', 'targetPaths']) {
584
+ collectStringValues(args[name], values);
585
+ }
586
+ return [...values];
587
+ }
588
+ function collectStringValues(value, out) {
589
+ if (typeof value === 'string' && value.length > 0) {
590
+ out.add(value);
591
+ return;
592
+ }
593
+ if (!Array.isArray(value))
594
+ return;
595
+ for (const item of value) {
596
+ if (typeof item === 'string' && item.length > 0)
597
+ out.add(item);
598
+ }
599
+ }
600
+ function checkOwnPeer(ctx) {
601
+ const argPeer = stringArg(ctx.intent.args.peer);
602
+ const argPeerKey = stringArg(ctx.intent.args.peerKey);
603
+ const expectedPeerKey = ctx.peerKey || inferPeerKey(ctx);
604
+ if (!argPeer && !argPeerKey)
605
+ return { ok: true };
606
+ const requestedPeerId = argPeer ? peerIdFromArg(argPeer) : undefined;
607
+ const requestedPeerKeyId = argPeerKey ? peerIdFromArg(argPeerKey) : undefined;
608
+ if (ctx.actorId) {
609
+ if (requestedPeerId && requestedPeerId !== ctx.actorId) {
610
+ return { ok: false, reason: 'Only the actor peer can be targeted' };
611
+ }
612
+ if (requestedPeerKeyId && requestedPeerKeyId !== ctx.actorId) {
613
+ return { ok: false, reason: 'Only the actor peer can be targeted' };
614
+ }
615
+ }
616
+ if (!expectedPeerKey)
617
+ return { ok: false, reason: 'Cannot verify peer ownership' };
618
+ const candidateKeys = new Set();
619
+ if (argPeerKey)
620
+ candidateKeys.add(argPeerKey);
621
+ if (argPeer) {
622
+ candidateKeys.add(argPeer);
623
+ const channelType = parseChannelType(expectedPeerKey);
624
+ if (channelType)
625
+ candidateKeys.add(formatPeerKey(channelType, argPeer));
626
+ const legacyChannelType = parseLegacyChannelType(expectedPeerKey);
627
+ if (legacyChannelType)
628
+ candidateKeys.add(`${legacyChannelType}::${argPeer}`);
629
+ }
630
+ if (!candidateKeys.has(expectedPeerKey)) {
631
+ return { ok: false, reason: 'Only the actor peer can be targeted' };
632
+ }
633
+ return { ok: true };
634
+ }
635
+ function peerIdFromArg(value) {
636
+ try {
637
+ return parsePeerKey(value).channelId;
638
+ }
639
+ catch {
640
+ const legacyIdx = value.indexOf('::');
641
+ if (legacyIdx > 0)
642
+ return value.slice(legacyIdx + 2);
643
+ return value;
644
+ }
645
+ }
646
+ function inferPeerKey(ctx) {
647
+ const actorId = ctx.actorId;
648
+ const argPeer = stringArg(ctx.intent.args.peer);
649
+ if (!actorId || !argPeer || argPeer !== actorId)
650
+ return undefined;
651
+ const channelType = typeof ctx.channel === 'string' ? ctx.channel.split('#')[0] : undefined;
652
+ return channelType ? formatPeerKey(channelType, actorId) : undefined;
653
+ }
654
+ function parseChannelType(peerKey) {
655
+ try {
656
+ return parsePeerKey(peerKey).channelType;
657
+ }
658
+ catch {
659
+ return undefined;
660
+ }
661
+ }
662
+ function parseLegacyChannelType(peerKey) {
663
+ const idx = peerKey.indexOf('::');
664
+ return idx > 0 ? peerKey.slice(0, idx) : undefined;
665
+ }
666
+ function checkFieldOverride(ctx, field) {
667
+ const roleDef = getRoleDefinition(ctx.role, ctx.selfAid);
668
+ const fieldPermission = roleDef?.permissions?.[field];
669
+ if (!fieldPermission) {
670
+ return { ok: false, reason: `Role ${ctx.role} has no field permission for ${field}` };
671
+ }
672
+ if (!fieldPermission.allowOverride) {
673
+ return { ok: false, reason: `Role ${ctx.role} cannot override ${field}` };
674
+ }
675
+ const value = valueForFieldOverride(ctx, field);
676
+ if (value === undefined)
677
+ return { ok: true };
678
+ return checkFieldValueAllowed(fieldPermission, value);
679
+ }
680
+ function valueForFieldOverride(ctx, field) {
681
+ if (field.endsWith('.model'))
682
+ return ctx.intent.args.model ?? ctx.intent.args.value;
683
+ if (field.endsWith('.effort'))
684
+ return ctx.intent.args.effort ?? ctx.intent.args.value;
685
+ return ctx.intent.args.value ?? ctx.intent.args[field];
686
+ }
687
+ function checkFieldValueAllowed(permission, value) {
688
+ if (permission.allowedModels && typeof value === 'string') {
689
+ const allowed = permission.allowedModels.some(pattern => globMatches(pattern, value));
690
+ if (!allowed)
691
+ return { ok: false, reason: `Model ${value} is not allowed for this role` };
692
+ }
693
+ if (permission.allowedValues && permission.allowedValues.length > 0) {
694
+ if (!permission.allowedValues.includes(value)) {
695
+ return { ok: false, reason: `Value ${String(value)} is not allowed for this role` };
696
+ }
697
+ }
698
+ return { ok: true };
699
+ }
700
+ function globMatches(pattern, value) {
701
+ if (pattern === '*')
702
+ return true;
703
+ const escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, '\\$&').replace(/\*/g, '.*');
704
+ return new RegExp(`^${escaped}$`).test(value);
705
+ }
706
+ function stringArg(value) {
707
+ return typeof value === 'string' && value.length > 0 ? value : undefined;
708
+ }
709
+ function denyDecision(ctx, code, reason, operation, scope, dangerous, matchedRule) {
710
+ return {
711
+ allow: false,
712
+ code,
713
+ reason,
714
+ operation,
715
+ scope,
716
+ role: ctx.role,
717
+ dangerous,
718
+ matchedRule,
719
+ };
720
+ }