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
@@ -1,9 +1,22 @@
1
+ import { assertAgentPolicyAuthorization } from '../role/runtime-policy.js';
1
2
  import crypto from 'crypto';
2
3
  import { authorizeOperation, buildAuthSubject } from './auth-gateway.js';
3
4
  import { formatPeerKey, parsePeerKey } from '../relation/peer-identity.js';
4
5
  import { logger } from '../../utils/logger.js';
5
6
  export const AGENT_DELEGATION_TOKEN_ENV = 'EVOLCORE_DELEGATION_TOKEN';
7
+ export const CODEX_SESSION_TOKEN_ENV = 'EVOLCORE_CODEX_SESSION_TOKEN';
6
8
  export const AGENT_DELEGATION_COMMAND_TTL_MS = 5 * 60_000;
9
+ /**
10
+ * Resolve the credential carried by an EC CLI process. A live task token has
11
+ * priority; persistent Codex threads fall back to their session credential.
12
+ */
13
+ export function readDelegationCredential(env = process.env) {
14
+ const taskToken = env[AGENT_DELEGATION_TOKEN_ENV]?.trim();
15
+ if (taskToken)
16
+ return taskToken;
17
+ const sessionToken = env[CODEX_SESSION_TOKEN_ENV]?.trim();
18
+ return sessionToken || undefined;
19
+ }
7
20
  export class AgentDelegationRegistry {
8
21
  runtimeEpoch = crypto.randomBytes(16).toString('hex');
9
22
  grantsByHash = new Map();
@@ -24,6 +37,13 @@ export class AgentDelegationRegistry {
24
37
  return token;
25
38
  }
26
39
  validate(token, sessionId, commandHash) {
40
+ return this.validateToken(token, sessionId, commandHash, true);
41
+ }
42
+ /** Authenticate a CLI context lookup without spending its approved operation. */
43
+ validateForContext(token, sessionId, commandHash) {
44
+ return this.validateToken(token, sessionId, commandHash, false);
45
+ }
46
+ validateToken(token, sessionId, commandHash, consumeApproval) {
27
47
  if (!token) {
28
48
  return {
29
49
  ok: false,
@@ -37,10 +57,19 @@ export class AgentDelegationRegistry {
37
57
  if (grant
38
58
  && grant.sessionId === sessionId
39
59
  && this.activeHashBySession.get(sessionId) === tokenHash) {
60
+ if (grant.executionIdentity?.source === 'agent-policy') {
61
+ try {
62
+ assertAgentPolicyAuthorization(grant.executionIdentity);
63
+ }
64
+ catch {
65
+ this.revokeSession(sessionId);
66
+ return { ok: false, code: 'INVALID_DELEGATION', reason: 'Agent fullaccess policy was revoked', diagnosticCause: 'task_replaced' };
67
+ }
68
+ }
40
69
  return { ok: true, grant };
41
70
  }
42
71
  const approved = commandHash
43
- ? this.consumeApprovedCommand(tokenHash, sessionId, commandHash)
72
+ ? this.readApprovedCommand(tokenHash, sessionId, commandHash, consumeApproval)
44
73
  : undefined;
45
74
  if (approved)
46
75
  return { ok: true, grant: approved };
@@ -66,11 +95,12 @@ export class AgentDelegationRegistry {
66
95
  const grant = activeTokenHash ? this.grantsByHash.get(activeTokenHash) : undefined;
67
96
  if (!grant || grant.taskId !== input.taskId)
68
97
  return false;
69
- if (!input.carrierToken || !isDelegationCommandHash(input.commandHash))
98
+ const sessionToken = input.sessionToken ?? input.carrierToken;
99
+ if (!sessionToken || !isDelegationCommandHash(input.commandHash))
70
100
  return false;
71
101
  this.deleteExpiredApprovedCommands();
72
- const carrierTokenHash = hashDelegationToken(input.carrierToken);
73
- const key = approvedCommandKey(carrierTokenHash, input.sessionId, input.commandHash);
102
+ const sessionTokenHash = hashDelegationToken(sessionToken);
103
+ const key = approvedCommandKey(sessionTokenHash, input.sessionId, input.commandHash);
74
104
  const existing = this.approvedCommands.get(key);
75
105
  const ttlMs = Math.max(1, Math.min(input.ttlMs ?? AGENT_DELEGATION_COMMAND_TTL_MS, AGENT_DELEGATION_COMMAND_TTL_MS));
76
106
  this.approvedCommands.set(key, {
@@ -105,10 +135,10 @@ export class AgentDelegationRegistry {
105
135
  this.activeHashBySession.delete(sessionId);
106
136
  this.deleteApprovedCommandsForSession(sessionId);
107
137
  }
108
- consumeApprovedCommand(carrierTokenHash, sessionId, commandHash) {
138
+ readApprovedCommand(sessionTokenHash, sessionId, commandHash, consume) {
109
139
  if (!isDelegationCommandHash(commandHash))
110
140
  return undefined;
111
- const key = approvedCommandKey(carrierTokenHash, sessionId, commandHash);
141
+ const key = approvedCommandKey(sessionTokenHash, sessionId, commandHash);
112
142
  const approved = this.approvedCommands.get(key);
113
143
  if (!approved)
114
144
  return undefined;
@@ -124,10 +154,12 @@ export class AgentDelegationRegistry {
124
154
  this.approvedCommands.delete(key);
125
155
  return undefined;
126
156
  }
127
- if (approved.remainingUses <= 1)
128
- this.approvedCommands.delete(key);
129
- else
130
- approved.remainingUses--;
157
+ if (consume) {
158
+ if (approved.remainingUses <= 1)
159
+ this.approvedCommands.delete(key);
160
+ else
161
+ approved.remainingUses--;
162
+ }
131
163
  return grant;
132
164
  }
133
165
  deleteExpiredApprovedCommands() {
@@ -216,8 +248,10 @@ function isValidFullAccessExecutionIdentity(identity) {
216
248
  || identity.processRole !== 'fullaccess-run'
217
249
  || identity.dataScope !== 'daemon')
218
250
  return false;
219
- if (identity.source === 'fullaccess-command') {
220
- return typeof identity.authorizedBy === 'string' && identity.authorizedBy.length > 0;
251
+ if (identity.source === 'fullaccess-command' || identity.source === 'agent-policy') {
252
+ return typeof identity.authorizedBy === 'string' && identity.authorizedBy.length > 0
253
+ && (identity.source !== 'agent-policy'
254
+ || (typeof identity.agentAid === 'string' && identity.agentAid.length > 0));
221
255
  }
222
256
  return identity.source === 'trigger'
223
257
  && typeof identity.authorizedBy === 'string' && identity.authorizedBy.length > 0
@@ -228,8 +262,8 @@ function isValidFullAccessExecutionIdentity(identity) {
228
262
  function hashDelegationToken(token) {
229
263
  return crypto.createHash('sha256').update(token).digest('hex');
230
264
  }
231
- function approvedCommandKey(carrierTokenHash, sessionId, commandHash) {
232
- return JSON.stringify([carrierTokenHash, sessionId, commandHash]);
265
+ function approvedCommandKey(sessionTokenHash, sessionId, commandHash) {
266
+ return JSON.stringify([sessionTokenHash, sessionId, commandHash]);
233
267
  }
234
268
  function isDelegationCommandHash(value) {
235
269
  return /^[a-f0-9]{64}$/.test(value);
@@ -70,6 +70,7 @@ export function buildAuthSubject(input) {
70
70
  identity: input.identity ?? roleToSessionIdentity(role === 'none' ? null : role),
71
71
  isDaemonOwner,
72
72
  fromControlChannel: !!input.fromControlChannel,
73
+ ecwebManagement: input.ecwebManagement === true && isTrustedDaemonOwner && input.fromControlChannel === true,
73
74
  allowAccess: processRole !== 'none' || (suppliedRole
74
75
  ? checkRoleAccess(suppliedRole, input.selfAid)
75
76
  : roleDetail.allowAccess && checkRoleAccess(role, input.selfAid)),
@@ -114,6 +115,7 @@ export function authorizeOperation(params) {
114
115
  return access;
115
116
  }
116
117
  const ctx = {
118
+ principalId: params.subject.principalId,
117
119
  intent: params.intent,
118
120
  actorId: params.subject.actorId,
119
121
  channel: params.subject.channel,
@@ -2,7 +2,7 @@ import { logger } from '../../utils/logger.js';
2
2
  import { LogWriter } from '../../utils/log-writer.js';
3
3
  import { resolvePaths } from '../../paths.js';
4
4
  import crypto from 'node:crypto';
5
- import { buildAuthorizationEventKey, buildToolLifecycleEventKey } from '../audit/event-key.js';
5
+ import { buildAuthorizationEventKey, buildToolLifecycleEventKey, normalizeToolCorrelation } from '../audit/event-key.js';
6
6
  import { normalizeExecutionPermissionMode } from '../permission/mode.js';
7
7
  export function auditCommandAuthorization(event) {
8
8
  const shouldAudit = event.source === 'menu.cli' ||
@@ -14,6 +14,8 @@ export function auditCommandAuthorization(event) {
14
14
  event.operation === 'permission.runtime' ||
15
15
  event.operation.startsWith('fullaccess.') ||
16
16
  event.operation.startsWith('role.') ||
17
+ event.operation === 'file.root.storage' ||
18
+ event.operation.startsWith('file.storage.') ||
17
19
  event.operation === 'cli.exec.raw';
18
20
  if (!shouldAudit)
19
21
  return;
@@ -25,6 +27,8 @@ export function auditFullAccessEvent(input) {
25
27
  || input.event === 'fullaccess.trigger.deescalated';
26
28
  const executionEvent = input.event === 'fullaccess.execution.started'
27
29
  || input.event === 'fullaccess.execution.ended'
30
+ || input.event === 'fullaccess.agent-policy.execution.started'
31
+ || input.event === 'fullaccess.agent-policy.execution.ended'
28
32
  || input.event === 'fullaccess.trigger.execution.started'
29
33
  || input.event === 'fullaccess.trigger.execution.ended';
30
34
  // The scheduler triggers a run, but the executing subject is the task-bound
@@ -39,7 +43,7 @@ export function auditFullAccessEvent(input) {
39
43
  ts: Date.now(),
40
44
  callId: input.attemptId ?? input.runId ?? input.taskId ?? input.messageId,
41
45
  correlationId: input.runId ?? input.taskId,
42
- source: input.source === 'trigger' ? 'agent-tool' : 'slash',
46
+ source: input.source === 'fullaccess-command' ? 'slash' : 'agent-tool',
43
47
  operation: input.event,
44
48
  // CommandScope has no separate daemon value; process is the closest
45
49
  // existing scope for daemon-owner authorization and keeps audit records
@@ -97,20 +101,11 @@ export function auditFullAccessEvent(input) {
97
101
  * denials otherwise only reach `logger.warn` and `TriggerExecutionAnomaly`.
98
102
  */
99
103
  export function auditToolPreflightDenial(input) {
100
- const lifecycleId = input.callId ?? input.correlationId ?? input.requestId;
101
104
  auditCommandAuthorization({
102
105
  ts: Date.now(),
103
- ...(lifecycleId ? {
104
- eventKey: buildToolLifecycleEventKey({
105
- sessionId: input.sessionId,
106
- callId: input.callId,
107
- correlationId: input.correlationId,
108
- requestId: input.requestId,
109
- }),
110
- eventPhase: 'preflight',
111
- } : {}),
112
- callId: input.callId ?? input.requestId,
113
- correlationId: input.correlationId ?? input.callId ?? input.requestId,
106
+ eventKey: buildToolLifecycleEventKey(input),
107
+ eventPhase: 'preflight',
108
+ ...normalizeToolCorrelation(input),
114
109
  source: 'agent-tool',
115
110
  operation: 'tool.preflight.deny',
116
111
  scope: 'filesystem',
@@ -138,29 +133,20 @@ export function auditToolPreflightDenial(input) {
138
133
  role: input.role ?? 'unknown',
139
134
  selfAid: input.selfAid,
140
135
  peerKey: input.peerKey,
141
- requestId: input.requestId,
142
136
  taskId: input.taskId,
143
137
  });
144
138
  }
145
139
  /**
146
140
  * Emit one canonical, structured record for every Codex app-server approval
147
- * decision. The app-server's request id is the lifecycle key; method and
141
+ * decision. Prefer the tool call id over the approval request id; method and
148
142
  * decision source make policy, user, and infrastructure denials distinct.
149
143
  */
150
144
  export function auditCodexApprovalDecision(input) {
151
- const lifecycleId = input.callId ?? input.correlationId ?? input.requestId;
152
145
  auditCommandAuthorization({
153
146
  ts: Date.now(),
154
- ...(lifecycleId ? {
155
- eventKey: buildToolLifecycleEventKey({
156
- sessionId: input.sessionId,
157
- callId: input.callId,
158
- correlationId: input.correlationId,
159
- requestId: input.requestId,
160
- }),
161
- eventPhase: 'approval',
162
- } : {}),
163
- callId: input.callId ?? input.correlationId ?? input.requestId,
147
+ eventKey: buildToolLifecycleEventKey(input),
148
+ eventPhase: 'approval',
149
+ ...normalizeToolCorrelation(input),
164
150
  source: 'agent-tool',
165
151
  operation: 'codex.approval',
166
152
  scope: input.toolName === 'PermissionGrant' ? 'agent' : 'filesystem',
@@ -172,8 +158,6 @@ export function auditCodexApprovalDecision(input) {
172
158
  errorCode: input.decision === 'deny'
173
159
  ? input.decisionSource === 'approval' ? 'USER_DENIED' : input.decisionSource === 'policy' ? 'POLICY_DENIED' : infrastructureErrorCode(input.policyCode)
174
160
  : undefined,
175
- requestId: input.requestId,
176
- correlationId: input.correlationId ?? input.callId ?? input.requestId,
177
161
  sessionId: input.sessionId ?? 'unknown',
178
162
  agentAid: input.agentAid ?? 'unknown',
179
163
  agentName: input.agentName,
@@ -204,6 +188,8 @@ export function auditPermissionDecision(input) {
204
188
  auditPermissionLifecycle({
205
189
  phase: 'final',
206
190
  requestId,
191
+ callId: input.callId,
192
+ correlationId: input.correlationId,
207
193
  sessionId: input.sessionId,
208
194
  agentAid: input.agentAid,
209
195
  agentName: input.agentName,
@@ -235,9 +221,7 @@ export function auditPermissionLifecycle(input) {
235
221
  requestId: input.requestId,
236
222
  }),
237
223
  eventPhase: input.phase,
238
- callId: input.callId ?? input.requestId,
239
- correlationId: input.correlationId ?? input.callId ?? input.requestId,
240
- requestId: input.requestId,
224
+ ...normalizeToolCorrelation(input),
241
225
  sessionId,
242
226
  agentAid: input.agentAid ?? 'unknown',
243
227
  agentName: input.agentName,
@@ -279,6 +263,8 @@ export function auditPermissionLifecycle(input) {
279
263
  export function auditToolInfrastructureFailure(input) {
280
264
  auditCommandAuthorization({
281
265
  ts: Date.now(),
266
+ eventKey: buildToolLifecycleEventKey(input),
267
+ eventPhase: 'final',
282
268
  source: 'agent-tool',
283
269
  operation: 'tool.execution.blocked',
284
270
  scope: 'filesystem',
@@ -288,9 +274,7 @@ export function auditToolInfrastructureFailure(input) {
288
274
  errorCode: infrastructureErrorCode(input.policyCode),
289
275
  executed: false,
290
276
  executionState: 'blocked',
291
- callId: input.callId,
292
- correlationId: input.callId,
293
- requestId: input.callId,
277
+ ...normalizeToolCorrelation(input),
294
278
  sessionId: input.sessionId ?? 'unknown',
295
279
  agentAid: input.agentAid ?? 'unknown',
296
280
  agentName: input.agentName,
@@ -300,8 +284,20 @@ export function auditToolInfrastructureFailure(input) {
300
284
  reason: input.reason,
301
285
  role: input.role ?? 'unknown',
302
286
  taskId: input.taskId,
303
- argsSummary: input.occurrences && input.occurrences > 1
304
- ? { occurrences: input.occurrences }
287
+ argsSummary: input.occurrences || input.sandboxFailure
288
+ ? {
289
+ ...(input.occurrences ? { occurrences: input.occurrences } : {}),
290
+ // Flat primitive fields survive the audit argument redactor and can
291
+ // be aggregated without reparsing (possibly truncated) stderr.
292
+ ...(input.sandboxFailure ? {
293
+ sandboxFailureKind: input.sandboxFailure.kind,
294
+ sandboxFailureObservation: input.sandboxFailure.observation,
295
+ sandboxInitializationFailures: input.sandboxFailure.counters.total,
296
+ sandboxApplySeccompFailures: input.sandboxFailure.counters.applySeccomp,
297
+ ...Object.fromEntries(Object.entries(input.sandboxFailure.counters.byKind)
298
+ .map(([kind, count]) => [`sandboxFailures_${kind}`, count])),
299
+ } : {}),
300
+ }
305
301
  : undefined,
306
302
  });
307
303
  }
@@ -333,23 +329,31 @@ function buildAuditRecord(event) {
333
329
  : executed
334
330
  ? (event.exitCode === undefined || event.exitCode === 0 ? 'completed' : 'failed')
335
331
  : 'authorized');
336
- const lifecycleId = event.callId ?? event.correlationId ?? event.requestId;
332
+ const ecwebRequestId = event.source === 'ecweb' ? event.ecwebRequestId : undefined;
333
+ const requestId = event.requestId ?? ecwebRequestId;
334
+ const correlationId = event.correlationId ?? event.callId ?? requestId;
335
+ const lifecycleId = event.callId ?? correlationId ?? requestId;
337
336
  return {
338
337
  ts: event.ts,
339
- eventKey: event.eventKey ?? (lifecycleId
340
- ? buildAuthorizationEventKey({
341
- sessionId: event.sessionId,
342
- callId: event.callId,
343
- correlationId: event.correlationId,
344
- requestId: event.requestId,
345
- operation: event.operation,
346
- })
347
- : undefined),
338
+ eventKey: event.eventKey ?? (ecwebRequestId
339
+ ? `ecweb:${crypto.createHash('sha256').update(JSON.stringify([
340
+ ecwebRequestId, event.targetAgentAid ?? event.selfAid, event.operation,
341
+ ])).digest('hex').slice(0, 20)}`
342
+ : lifecycleId
343
+ ? buildAuthorizationEventKey({
344
+ sessionId: event.sessionId,
345
+ callId: event.callId,
346
+ correlationId,
347
+ requestId,
348
+ operation: event.operation,
349
+ })
350
+ : undefined),
348
351
  eventPhase: event.eventPhase,
349
352
  callId: event.callId,
350
- correlationId: event.correlationId ?? event.requestId,
351
- requestId: event.requestId,
353
+ correlationId,
354
+ requestId,
352
355
  sessionId: event.sessionId,
356
+ ...(event.source === 'ecweb' && !event.sessionId ? { sessionScope: 'not_applicable' } : {}),
353
357
  agentAid: redactIdentifier(event.agentAid ?? event.selfAid),
354
358
  agentName: resolveAgentName(event),
355
359
  permissionMode: normalizeAuditPermissionMode(event.permissionMode),
@@ -1,18 +1,21 @@
1
1
  import { getRoleDefinition } from '../../config/roles.js';
2
2
  import { isManagementRole } from '../../config/builtin-roles.js';
3
- import { relationFieldWriteOperation, resolveRoleFieldPermission, } from '../../config/config-field-policy.js';
3
+ import { isUnsupportedBaseagentConfigField, relationFieldWriteOperation, resolveRoleFieldPermission, } from '../../config/config-field-policy.js';
4
4
  import { isResolvedConfigMutation, } from '../../config/resolved-config-op.js';
5
5
  import { normalizePeer } from '../model/config-scope.js';
6
6
  import { getOperationMeta } from './operation-catalog.js';
7
7
  import { formatPeerKey, parsePeerKey } from '../relation/peer-identity.js';
8
+ import { sameGroupAid } from '../../aun/group/index.js';
8
9
  const DAEMON_OWNER_PROCESS_OPERATIONS = new Set([
9
10
  'agent.list',
10
11
  'agent.create',
12
+ 'agent.initialize',
11
13
  'agent.delete',
12
14
  'agent.enable',
13
15
  'agent.disable',
14
16
  'system.restart',
15
17
  'system.upgrade',
18
+ 'system.paircode',
16
19
  'gateway.read',
17
20
  'gateway.write',
18
21
  'stats.peers',
@@ -98,6 +101,12 @@ function authorizeCommandInternal(ctx, resolvedConfigCommand) {
98
101
  && (resolvedConfigCommand.operationId !== operation || resolvedConfigCommand.commandScope !== intent.scope)) {
99
102
  return denyDecision(ctx, 'NOT_ALLOWED', 'Resolved config operation does not match the command intent', operation, intent.scope, opMeta.dangerous);
100
103
  }
104
+ if (resolvedConfigCommand?.kind === 'field'
105
+ && resolvedConfigCommand.configScope === 'agent'
106
+ && (resolvedConfigCommand.field === 'fullaccess' || resolvedConfigCommand.field.startsWith('fullaccess.'))
107
+ && resolvedConfigCommand.subcommand !== 'get') {
108
+ return denyDecision(ctx, 'NOT_ALLOWED', 'Agent fullaccess can only be configured in ECWeb Agent settings', operation, intent.scope, true);
109
+ }
101
110
  // A fullaccess task is deliberately a daemon-scoped runtime identity. It
102
111
  // still has to name a known operation and pass structural command parsing,
103
112
  // but relation/Agent role rules, source lists, and daemon-owner gates are
@@ -204,6 +213,14 @@ function authorizeCommandInternal(ctx, resolvedConfigCommand) {
204
213
  matchedRule: 'self-agent-read',
205
214
  };
206
215
  }
216
+ // Cross-Agent reads are a management/control-plane capability. A custom
217
+ // role must not widen this boundary merely by granting itself agent.show.
218
+ if (operation === 'agent.show'
219
+ && !isCurrentAgentTarget(ctx)
220
+ && !isManagementRole(role)
221
+ && !hasDaemonOwnerRole(ctx)) {
222
+ return denyDecision(ctx, 'NOT_ALLOWED', `${operation} may only target the current Agent from a managed Agent task.`, operation, intent.scope, opMeta.dangerous, undefined, 'TARGET_SELF_ONLY');
223
+ }
207
224
  if (operation === 'agent.persona' && isCurrentAgentTarget(ctx)
208
225
  && (role === 'owner' || hasDaemonOwnerRole(ctx))) {
209
226
  return {
@@ -215,11 +232,30 @@ function authorizeCommandInternal(ctx, resolvedConfigCommand) {
215
232
  matchedRule: role === 'owner' ? 'agent-owner-persona' : 'daemon-owner-persona',
216
233
  };
217
234
  }
235
+ if (operation === 'agent.welcome') {
236
+ if (!isCurrentAgentTarget(ctx) || ctx.chatType !== 'private'
237
+ || (role !== 'owner' && !(role === 'admin' && intent.args.action === 'show'))) {
238
+ return denyDecision(ctx, 'NOT_ALLOWED', 'welcome requires the current Agent Owner in private (admin may read)', operation, intent.scope, opMeta.dangerous);
239
+ }
240
+ return {
241
+ allow: true,
242
+ operation,
243
+ scope: intent.scope,
244
+ role,
245
+ dangerous: opMeta.dangerous,
246
+ matchedRule: 'self-agent-welcome',
247
+ };
248
+ }
218
249
  if (resolvedConfigCommand) {
219
250
  const targetCheck = checkResolvedConfigTarget(ctx, resolvedConfigCommand);
220
251
  if (!targetCheck.ok) {
221
252
  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);
222
253
  }
254
+ if (resolvedConfigCommand.kind === 'field'
255
+ && resolvedConfigCommand.subcommand === 'get'
256
+ && isUnsupportedBaseagentConfigField(resolvedConfigCommand.field)) {
257
+ return denyDecision(ctx, 'UNSUPPORTED_FIELD', `Config field ${resolvedConfigCommand.field} is not supported by this version`, operation, intent.scope, opMeta.dangerous);
258
+ }
223
259
  const identityMutationCheck = checkManagementIdentityMutation(ctx, resolvedConfigCommand);
224
260
  if (!identityMutationCheck.ok) {
225
261
  return denyDecision(ctx, 'NOT_ALLOWED', identityMutationCheck.reason || 'Static management identities cannot be changed by this role', operation, intent.scope, opMeta.dangerous);
@@ -227,7 +263,9 @@ function authorizeCommandInternal(ctx, resolvedConfigCommand) {
227
263
  if (!isManagementRole(role) && intent.scope !== 'relation') {
228
264
  return denyDecision(ctx, 'SCOPE_MISMATCH', `Role ${role} may only access relation-scoped config`, operation, intent.scope, opMeta.dangerous);
229
265
  }
230
- if (role === 'owner' && isCurrentAgentTarget(ctx)) {
266
+ if (role === 'owner'
267
+ && isCurrentAgentTarget(ctx)
268
+ && !isCrossRelationConfigRead(ctx, resolvedConfigCommand)) {
231
269
  return {
232
270
  allow: true,
233
271
  operation,
@@ -627,6 +665,19 @@ function checkConfigFieldPolicy(ctx, policy, resolvedConfigOp) {
627
665
  }
628
666
  return { ok: true };
629
667
  }
668
+ function isCrossRelationConfigRead(ctx, command) {
669
+ if (!command || command.kind !== 'field' || command.subcommand !== 'get' || command.configScope !== 'relation') {
670
+ return false;
671
+ }
672
+ if (!command.peerKey || !ctx.peerKey)
673
+ return true;
674
+ try {
675
+ return normalizeRelationPeer(command.peerKey) !== normalizeRelationPeer(ctx.peerKey);
676
+ }
677
+ catch {
678
+ return true;
679
+ }
680
+ }
630
681
  function checkResolvedConfigTarget(ctx, command) {
631
682
  if (command.kind === 'global' || command.configScope === 'process' || command.configScope === 'defaults') {
632
683
  return { ok: true };
@@ -642,7 +693,13 @@ function checkResolvedConfigTarget(ctx, command) {
642
693
  }
643
694
  return { ok: true };
644
695
  }
645
- if (ctx.allowExplicitRelationTarget && ctx.role === 'owner') {
696
+ if (ctx.allowExplicitRelationTarget
697
+ && isManagementRole(ctx.role)
698
+ && command.kind === 'field'
699
+ && (command.operationId.startsWith('role.') || command.subcommand === 'get')) {
700
+ // Management reads may select another relation of this Agent. This only
701
+ // relaxes target matching; admin field grants/constraints are checked later.
702
+ // Ordinary config writes and whole-config commands remain current-relation only.
646
703
  if (!ctx.selfAid || !command.self || command.self !== ctx.selfAid) {
647
704
  return { ok: false, reason: 'Only the current agent relation can be targeted' };
648
705
  }
@@ -682,7 +739,8 @@ function checkCurrentRelation(ctx, resolvedConfigCommand) {
682
739
  return { ok: false, reason: 'Current relation context is required' };
683
740
  }
684
741
  try {
685
- if (normalizeRelationPeer(requestedPeer) !== normalizeRelationPeer(ctx.peerKey)) {
742
+ if (normalizeRelationPeer(requestedPeer) !== normalizeRelationPeer(ctx.peerKey)
743
+ && !sameAunGroupRelation(ctx, requestedPeer, ctx.peerKey)) {
686
744
  if (ctx.intent.operation.startsWith('ec.group.')) {
687
745
  const currentGroup = peerIdFromArg(ctx.peerKey);
688
746
  const targetGroup = stringArg(ctx.intent.args.targetId) ?? peerIdFromArg(requestedPeer);
@@ -777,6 +835,20 @@ function collectStringValues(value, out) {
777
835
  out.add(item);
778
836
  }
779
837
  }
838
+ function sameAunGroupRelation(ctx, requested, current) {
839
+ if (ctx.chatType !== 'group'
840
+ || (!ctx.intent.operation.startsWith('ec.group.') && !ctx.intent.operation.startsWith('ec.fs.')))
841
+ return false;
842
+ try {
843
+ const target = parsePeerKey(requested);
844
+ const relation = parsePeerKey(current);
845
+ return target.channelType === 'aun' && relation.channelType === 'aun'
846
+ && sameGroupAid(target.channelId, relation.channelId);
847
+ }
848
+ catch {
849
+ return false;
850
+ }
851
+ }
780
852
  function checkOwnPeer(ctx) {
781
853
  const argPeer = stringArg(ctx.intent.args.peer);
782
854
  const argPeerKey = stringArg(ctx.intent.args.peerKey);
@@ -801,15 +873,16 @@ function checkOwnPeer(ctx) {
801
873
  candidateKeys.add(`${legacyChannelType}::${argPeer}`);
802
874
  }
803
875
  return candidateKeys.has(expectedPeerKey)
876
+ || [...candidateKeys].some(key => sameAunGroupRelation(ctx, key, expectedPeerKey))
804
877
  ? { ok: true }
805
878
  : { ok: false, reason: 'Only the current group relation can be targeted' };
806
879
  }
807
880
  if (ctx.actorId) {
808
881
  if (requestedPeerId && requestedPeerId !== ctx.actorId) {
809
- return { ok: false, reason: 'Only the actor peer can be targeted' };
882
+ return { ok: false, reason: ownPeerOnlyDenialReason(ctx, requestedPeerId) };
810
883
  }
811
884
  if (requestedPeerKeyId && requestedPeerKeyId !== ctx.actorId) {
812
- return { ok: false, reason: 'Only the actor peer can be targeted' };
885
+ return { ok: false, reason: ownPeerOnlyDenialReason(ctx, requestedPeerKeyId) };
813
886
  }
814
887
  }
815
888
  if (!expectedPeerKey)
@@ -827,10 +900,20 @@ function checkOwnPeer(ctx) {
827
900
  candidateKeys.add(`${legacyChannelType}::${argPeer}`);
828
901
  }
829
902
  if (!candidateKeys.has(expectedPeerKey)) {
830
- return { ok: false, reason: 'Only the actor peer can be targeted' };
903
+ return { ok: false, reason: ownPeerOnlyDenialReason(ctx, requestedPeerId ?? requestedPeerKeyId) };
831
904
  }
832
905
  return { ok: true };
833
906
  }
907
+ function ownPeerOnlyDenialReason(ctx, requestedPeer) {
908
+ if (ctx.intent.operation !== 'ec.msg.send' && ctx.intent.operation !== 'ec.msg.file') {
909
+ return 'Only the actor peer can be targeted';
910
+ }
911
+ const currentPeer = ctx.actorId || peerIdFromArg(ctx.peerKey || '') || '当前私聊对端';
912
+ const target = requestedPeer || '指定目标';
913
+ return `当前托管会话只能联系当前私聊对端(${currentPeer}),不能联系第三方目标(${target})。`
914
+ + ' 如果本次请求来自 handoff,请执行 ec handoff return <handoff-id> "<失败原因>"(已有命令)将失败原因反馈给原会话;'
915
+ + '否则请让目标先直接发起会话,或由 Agent owner 为当前角色配置允许 relay/第三方外联后再发送。';
916
+ }
834
917
  function peerIdFromArg(value) {
835
918
  try {
836
919
  return parsePeerKey(value).channelId;