evolcore 0.0.23 → 0.0.25

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (245) hide show
  1. package/CHANGELOG.md +583 -516
  2. package/LICENSE +21 -21
  3. package/README.md +455 -453
  4. package/bin/codex-managed-acl.ps1 +160 -155
  5. package/bin/codex-managed-hook.mjs +209 -92
  6. package/bin/ec-safe-output.js +327 -245
  7. package/bin/ec.js +29 -29
  8. package/bin/install-codex-managed-hooks.mjs +555 -544
  9. package/dist/agents/baseagent-availability.js +4 -4
  10. package/dist/agents/baseagent.js +29 -1
  11. package/dist/agents/claude-runner.js +152 -14
  12. package/dist/agents/codex/app-server-client.js +18 -8
  13. package/dist/agents/codex/catalog.js +1 -1
  14. package/dist/agents/codex/diagnostics.js +215 -0
  15. package/dist/agents/codex-runner.js +445 -63
  16. package/dist/agents/ecagent-runner.js +31 -16
  17. package/dist/agents/gemini-runner.js +82 -4
  18. package/dist/agents/runner-types.js +1 -1
  19. package/dist/aun/aid/agentmd.js +56 -4
  20. package/dist/aun/aid/index.js +1 -0
  21. package/dist/aun/aid/managed-operation.js +80 -6
  22. package/dist/aun/aid/store.js +41 -0
  23. package/dist/aun/msg/managed-operation.js +6 -1
  24. package/dist/aun/outbox.js +56 -18
  25. package/dist/aun/rpc/client.js +4 -5
  26. package/dist/aun/storage/client.js +4 -1
  27. package/dist/aun/storage/index.js +1 -1
  28. package/dist/channels/aun.js +147 -76
  29. package/dist/channels/daemon.js +7 -1
  30. package/dist/channels/dingtalk.js +1 -0
  31. package/dist/channels/feishu.js +10 -4
  32. package/dist/channels/wecom.js +2 -1
  33. package/dist/cli/agent-command.js +214 -89
  34. package/dist/cli/agent-create.js +576 -0
  35. package/dist/cli/agent.js +129 -676
  36. package/dist/cli/aun-commands.js +419 -313
  37. package/dist/cli/bench.js +20 -20
  38. package/dist/cli/config.js +20 -20
  39. package/dist/cli/contact.js +10 -10
  40. package/dist/cli/ctl-command.js +22 -20
  41. package/dist/cli/daemon-commands.js +163 -32
  42. package/dist/cli/fs-command.js +67 -67
  43. package/dist/cli/handoff-command.js +7 -7
  44. package/dist/cli/index.js +79 -79
  45. package/dist/cli/link-rules.js +21 -21
  46. package/dist/cli/managed-command-guard.js +3 -0
  47. package/dist/cli/menu-client.js +11 -11
  48. package/dist/cli/net-check.js +22 -22
  49. package/dist/cli/queue-command.js +29 -29
  50. package/dist/cli/response.js +30 -30
  51. package/dist/cli/restart-monitor.js +462 -38
  52. package/dist/cli/stats.js +161 -51
  53. package/dist/cli/trigger-command.js +56 -56
  54. package/dist/config/builtin-roles.js +3 -0
  55. package/dist/config/config-manager.js +15 -0
  56. package/dist/config/config-operation-service.js +20 -3
  57. package/dist/config/resolved-config-op.js +39 -1
  58. package/dist/core/auth/auth-gateway.js +3 -2
  59. package/dist/core/auth/authorization-audit.js +72 -19
  60. package/dist/core/auth/operation-authorizer.js +125 -7
  61. package/dist/core/auth/operation-catalog.js +26 -2
  62. package/dist/core/auth/remote-access-store.js +3 -2
  63. package/dist/core/baseagent-loader.js +4 -4
  64. package/dist/core/bootstrap-messages.js +39 -18
  65. package/dist/core/bootstrap-service.js +32 -26
  66. package/dist/core/capability/capability-manager.js +8 -4
  67. package/dist/core/capability/providers/codex-capability-provider.js +1 -1
  68. package/dist/core/command/agent-control.js +60 -26
  69. package/dist/core/command/cli-intent-parser.js +18 -0
  70. package/dist/core/command/command-handler.js +173 -36
  71. package/dist/core/command/menu-catalog.js +4 -1
  72. package/dist/core/command/menu-handler.js +315 -75
  73. package/dist/core/command/menu-protocol.js +7 -2
  74. package/dist/core/command/slash-gate.js +1 -3
  75. package/dist/core/command/slash-handler.js +166 -57
  76. package/dist/core/data-migration.js +5 -15
  77. package/dist/core/event-catalog.js +8 -0
  78. package/dist/core/evolagent.js +3 -3
  79. package/dist/core/handoff/runtime.js +3 -1
  80. package/dist/core/handoff/store.js +7 -1
  81. package/dist/core/message/im-renderer.js +13 -6
  82. package/dist/core/message/message-bridge.js +5 -36
  83. package/dist/core/message/message-utils.js +6 -3
  84. package/dist/core/message/response-engine.js +85 -27
  85. package/dist/core/model/catalog-service.js +3 -1
  86. package/dist/core/model/model-catalog.js +10 -0
  87. package/dist/core/model/model-permission.js +4 -1
  88. package/dist/core/permission/approval-gateway.js +169 -33
  89. package/dist/core/permission/tool-policy.js +56 -10
  90. package/dist/core/session/adapters/codex-session-file-adapter.js +71 -3
  91. package/dist/core/session/session-manager.js +49 -13
  92. package/dist/core/session/session-renew.js +1 -0
  93. package/dist/index.js +327 -134
  94. package/dist/ipc.js +68 -21
  95. package/dist/paths.js +23 -1
  96. package/dist/stats/billing.js +18 -4
  97. package/dist/stats/db.js +159 -159
  98. package/dist/stats/eck-vars.js +9 -9
  99. package/dist/stats/query.js +139 -139
  100. package/dist/stats/role-budget.js +6 -6
  101. package/dist/stats/writer.js +51 -51
  102. package/dist/utils/atomic-write.js +26 -2
  103. package/dist/utils/avatar-upload.js +282 -23
  104. package/dist/utils/cross-platform.js +436 -1
  105. package/dist/utils/error-dict.json +160 -160
  106. package/dist/utils/error-utils.js +11 -4
  107. package/dist/utils/macos-autostart.js +22 -22
  108. package/dist/utils/model-prices.jsonl +23 -20
  109. package/dist/utils/npm-ops.js +24 -1
  110. package/dist/utils/welcome.js +22 -22
  111. package/dist/utils/windows-autostart.js +1 -1
  112. package/ecagent/LICENSE +21 -21
  113. package/ecagent/NOTICE +11 -11
  114. package/ecagent/README.md +61 -61
  115. package/ecagent/dist/harness/compaction/branch-summarization.js +30 -30
  116. package/ecagent/dist/harness/compaction/compaction.js +83 -83
  117. package/ecagent/dist/harness/messages.js +7 -7
  118. package/kits/docs/GUIDE.md +20 -20
  119. package/kits/docs/INDEX.md +66 -66
  120. package/kits/docs/aun/CHEATSHEET.md +19 -19
  121. package/kits/docs/aun/SYNC_PROTOCOL.md +15 -15
  122. package/kits/docs/channels/aun.md +56 -56
  123. package/kits/docs/channels/feishu.md +56 -56
  124. package/kits/docs/context-assembly.md +365 -364
  125. package/kits/docs/eck_templates/GUIDE.template.md +22 -22
  126. package/kits/docs/eck_templates/INDEX.template.md +28 -28
  127. package/kits/docs/eck_templates/path-registry.template.md +33 -33
  128. package/kits/docs/eck_templates/runtime.template.md +19 -19
  129. package/kits/docs/evolcore/INDEX.md +71 -69
  130. package/kits/docs/evolcore/agent-create.md +170 -156
  131. package/kits/docs/evolcore/agent.md +83 -73
  132. package/kits/docs/evolcore/aid.md +64 -62
  133. package/kits/docs/evolcore/codex-windows-sandbox.md +146 -0
  134. package/kits/docs/evolcore/config.md +195 -195
  135. package/kits/docs/evolcore/contact.md +68 -68
  136. package/kits/docs/evolcore/ctl.md +46 -46
  137. package/kits/docs/evolcore/event.md +216 -216
  138. package/kits/docs/evolcore/fs-architecture.md +1215 -1215
  139. package/kits/docs/evolcore/fs.md +110 -110
  140. package/kits/docs/evolcore/group-collaboration.md +243 -243
  141. package/kits/docs/evolcore/group-fs.md +17 -17
  142. package/kits/docs/evolcore/group-rules.md +251 -251
  143. package/kits/docs/evolcore/group.md +152 -152
  144. package/kits/docs/evolcore/menu-client.md +34 -34
  145. package/kits/docs/evolcore/model.md +50 -50
  146. package/kits/docs/evolcore/msg.md +165 -165
  147. package/kits/docs/evolcore/response.md +75 -75
  148. package/kits/docs/evolcore/rpc.md +37 -37
  149. package/kits/docs/evolcore/self-summary.md +29 -29
  150. package/kits/docs/evolcore/stats.md +83 -83
  151. package/kits/docs/evolcore/storage.md +50 -50
  152. package/kits/docs/evolcore/trigger.md +575 -575
  153. package/kits/docs/identity/AID_PROFILE_SPEC.md +26 -26
  154. package/kits/docs/identity/PATH_OPS.md +16 -16
  155. package/kits/docs/identity/ROLE_DETAIL.md +25 -25
  156. package/kits/docs/identity/identity-tools.md +26 -26
  157. package/kits/docs/path-registry.md +62 -62
  158. package/kits/docs/prompt-loading-architecture.md +270 -269
  159. package/kits/docs/venues/aun-group.md +45 -45
  160. package/kits/docs/venues/aun-private.md +10 -10
  161. package/kits/docs/venues/client-desktop.md +10 -10
  162. package/kits/docs/venues/client-mobile.md +10 -10
  163. package/kits/docs/venues/feishu-group.md +13 -13
  164. package/kits/docs/venues/feishu-private.md +9 -9
  165. package/kits/docs/venues/group.md +25 -25
  166. package/kits/docs/venues/private.md +10 -10
  167. package/kits/eck_manifest.auxiliary.json +43 -43
  168. package/kits/eck_manifest.json +195 -183
  169. package/kits/eck_message_manifest.json +74 -74
  170. package/kits/migrations/README-role-config-v4.md +32 -32
  171. package/kits/migrations/migrate-contact-book-v2.mjs +748 -748
  172. package/kits/migrations/migrate-role-config-v4.mjs +614 -614
  173. package/kits/migrations/migrate-role-config-v5.mjs +365 -365
  174. package/kits/migrations/rename-config-file.mjs +99 -99
  175. package/kits/rules/01-overview.md +162 -162
  176. package/kits/rules/02-navigation.md +96 -96
  177. package/kits/rules/03-identity.md +39 -37
  178. package/kits/rules/04-relation.md +75 -75
  179. package/kits/rules/05-venue.md +60 -60
  180. package/kits/rules/06-channel.md +63 -63
  181. package/kits/schemas/_meta.json +53 -53
  182. package/kits/schemas/agent-config.schema.1.json +177 -177
  183. package/kits/schemas/agent-config.schema.10.json +395 -395
  184. package/kits/schemas/agent-config.schema.11.json +421 -421
  185. package/kits/schemas/agent-config.schema.12.json +427 -427
  186. package/kits/schemas/agent-config.schema.2.json +239 -239
  187. package/kits/schemas/agent-config.schema.3.json +119 -119
  188. package/kits/schemas/agent-config.schema.4.json +208 -208
  189. package/kits/schemas/agent-config.schema.5.json +326 -326
  190. package/kits/schemas/agent-config.schema.6.json +322 -322
  191. package/kits/schemas/agent-config.schema.7.json +303 -303
  192. package/kits/schemas/agent-config.schema.8.json +304 -304
  193. package/kits/schemas/agent-config.schema.9.json +364 -364
  194. package/kits/schemas/contact-book.schema.1.json +36 -36
  195. package/kits/schemas/contact-book.schema.2.json +43 -43
  196. package/kits/schemas/contact-book.schema.3.json +67 -67
  197. package/kits/schemas/daemon.schema.1.json +91 -91
  198. package/kits/schemas/daemon.schema.2.json +101 -101
  199. package/kits/schemas/daemon.schema.3.json +123 -123
  200. package/kits/schemas/daemon.schema.4.json +136 -136
  201. package/kits/schemas/daemon.schema.5.json +135 -135
  202. package/kits/schemas/daemon.schema.6.json +131 -131
  203. package/kits/schemas/defaults.schema.1.json +81 -81
  204. package/kits/schemas/defaults.schema.2.json +85 -85
  205. package/kits/schemas/defaults.schema.3.json +73 -73
  206. package/kits/schemas/defaults.schema.4.json +107 -107
  207. package/kits/schemas/defaults.schema.5.json +119 -119
  208. package/kits/schemas/menu-exec-schema-commands.md +208 -208
  209. package/kits/schemas/migrations/README.md +30 -30
  210. package/kits/schemas/relation-config.schema.1.json +158 -158
  211. package/kits/schemas/relation-config.schema.2.json +73 -73
  212. package/kits/schemas/relation-config.schema.3.json +50 -50
  213. package/kits/schemas/relation-config.schema.4.json +47 -47
  214. package/kits/schemas/relation-config.schema.5.json +47 -47
  215. package/kits/schemas/relation-config.schema.6.json +46 -46
  216. package/kits/schemas/relation-config.schema.7.json +59 -59
  217. package/kits/schemas/relation-config.schema.8.json +103 -103
  218. package/kits/schemas/role-config.schema.1.json +201 -201
  219. package/kits/schemas/role-registry.schema.1.json +35 -35
  220. package/kits/schemas/single-session.schema.1.json +31 -31
  221. package/kits/schemas/single-session.schema.2.json +57 -57
  222. package/kits/schemas/single-session.schema.3.json +32 -32
  223. package/kits/templates/bootstrap-welcome.md +17 -17
  224. package/kits/templates/message-fragments/handoff-context-to-target.md +9 -9
  225. package/kits/templates/message-fragments/handoff-request-to-target.md +19 -19
  226. package/kits/templates/message-fragments/handoff-response-to-origin.md +9 -9
  227. package/kits/templates/message-fragments/inject-default.md +2 -2
  228. package/kits/templates/message-fragments/item.md +2 -2
  229. package/kits/templates/roles/admin.json +96 -82
  230. package/kits/templates/roles/member.json +97 -96
  231. package/kits/templates/roles/owner.json +9 -9
  232. package/kits/templates/roles/visitor.json +86 -85
  233. package/kits/templates/system-fragments/baseagent.md +14 -14
  234. package/kits/templates/system-fragments/bootstrap.md +38 -29
  235. package/kits/templates/system-fragments/channel.md +64 -64
  236. package/kits/templates/system-fragments/commands.md +28 -28
  237. package/kits/templates/system-fragments/identity.md +13 -13
  238. package/kits/templates/system-fragments/relation.md +19 -19
  239. package/kits/templates/system-fragments/session.md +59 -59
  240. package/kits/templates/system-fragments/venue.md +31 -31
  241. package/package.json +98 -94
  242. package/AGENTS.md +0 -62
  243. package/dist/utils/codex-cli.js +0 -43
  244. package/skills/eclink/SKILL.md +0 -84
  245. package/skills/eclink/agents/openai.yaml +0 -4
@@ -10,6 +10,8 @@ export function auditCommandAuthorization(event) {
10
10
  (event.decision === 'allow' && event.dangerous) ||
11
11
  (event.source === 'agent-tool' && event.operation.startsWith('config.')) ||
12
12
  event.operation === 'codex.approval' ||
13
+ event.operation === 'permission.approval' ||
14
+ event.operation === 'permission.runtime' ||
13
15
  event.operation.startsWith('fullaccess.') ||
14
16
  event.operation.startsWith('role.') ||
15
17
  event.operation === 'cli.exec.raw';
@@ -198,33 +200,78 @@ export function auditCodexApprovalDecision(input) {
198
200
  * infrastructure denials without parsing localized log text.
199
201
  */
200
202
  export function auditPermissionDecision(input) {
201
- auditCommandAuthorization({
202
- ts: Date.now(),
203
- callId: input.requestId,
204
- correlationId: input.requestId,
205
- requestId: input.requestId,
203
+ const requestId = input.requestId ?? `perm-audit-${crypto.randomUUID()}`;
204
+ auditPermissionLifecycle({
205
+ phase: 'final',
206
+ requestId,
206
207
  sessionId: input.sessionId,
207
208
  agentAid: input.agentAid,
208
209
  agentName: input.agentName,
209
210
  permissionMode: input.permissionMode,
210
211
  toolName: input.toolName,
212
+ decision: 'deny',
213
+ decisionSource: input.decisionSource,
214
+ policyCode: input.policyCode,
215
+ reason: input.reason,
216
+ role: input.role,
217
+ taskId: input.taskId,
218
+ summary: input.summary,
219
+ operation: 'permission.runtime',
220
+ });
221
+ }
222
+ /**
223
+ * Emit a canonical request/response/final lifecycle for runtime approvals.
224
+ * All three phases share one eventKey and complete correlation fields so
225
+ * daily analysis can group them without parsing runner-specific log text.
226
+ */
227
+ export function auditPermissionLifecycle(input) {
228
+ const sessionId = input.sessionId ?? 'unknown';
229
+ auditCommandAuthorization({
230
+ ts: Date.now(),
231
+ eventKey: buildToolLifecycleEventKey({
232
+ sessionId,
233
+ callId: input.callId,
234
+ correlationId: input.correlationId,
235
+ requestId: input.requestId,
236
+ }),
237
+ eventPhase: input.phase,
238
+ callId: input.callId ?? input.requestId,
239
+ correlationId: input.correlationId ?? input.callId ?? input.requestId,
240
+ requestId: input.requestId,
241
+ sessionId,
242
+ agentAid: input.agentAid ?? 'unknown',
243
+ agentName: input.agentName,
244
+ permissionMode: input.permissionMode ?? 'unknown',
245
+ toolName: input.toolName,
246
+ approvalMethod: input.approvalMethod,
211
247
  policyCode: input.policyCode,
212
248
  decisionSource: input.decisionSource,
213
- errorCode: input.decisionSource === 'approval'
214
- ? 'USER_DENIED'
215
- : input.decisionSource === 'policy'
216
- ? 'POLICY_DENIED'
217
- : infrastructureErrorCode(input.policyCode),
249
+ errorCode: input.decision === 'deny'
250
+ ? input.decisionSource === 'approval'
251
+ ? 'USER_DENIED'
252
+ : input.decisionSource === 'policy'
253
+ ? 'POLICY_DENIED'
254
+ : infrastructureErrorCode(input.policyCode)
255
+ : undefined,
218
256
  source: 'agent-tool',
219
- operation: 'permission.runtime',
257
+ operation: input.operation ?? 'permission.approval',
220
258
  scope: 'filesystem',
221
259
  dangerous: false,
222
260
  role: input.role ?? 'unknown',
223
261
  taskId: input.taskId,
224
- argsSummary: input.summary ? { summary: input.summary } : undefined,
225
- decision: 'deny',
262
+ argsSummary: input.summary || input.response
263
+ ? {
264
+ ...(input.summary ? { summary: input.summary } : {}),
265
+ ...(input.response ? { response: input.response } : {}),
266
+ }
267
+ : undefined,
268
+ decision: input.decision,
226
269
  executed: false,
227
- executionState: 'blocked',
270
+ executionState: input.phase === 'request'
271
+ ? 'requested'
272
+ : input.phase === 'response'
273
+ ? 'responded'
274
+ : input.decision === 'deny' ? 'blocked' : 'authorized',
228
275
  reason: input.reason,
229
276
  });
230
277
  }
@@ -270,7 +317,7 @@ function infrastructureErrorCode(policyCode) {
270
317
  }
271
318
  function buildAuditRecord(event) {
272
319
  const executed = event.executed ?? false;
273
- const decisionSource = event.decisionSource ?? (event.decision === 'deny' ? 'policy' : 'command');
320
+ const decisionSource = event.decisionSource ?? (event.decision === 'deny' ? 'policy' : event.decision === 'pending' ? 'approval' : 'command');
274
321
  const errorCode = event.errorCode ?? (event.decision === 'deny'
275
322
  ? decisionSource === 'approval'
276
323
  ? 'USER_DENIED'
@@ -281,9 +328,11 @@ function buildAuditRecord(event) {
281
328
  const executionState = event.executionState
282
329
  ?? (event.decision === 'deny'
283
330
  ? 'blocked'
284
- : executed
285
- ? (event.exitCode === undefined || event.exitCode === 0 ? 'completed' : 'failed')
286
- : 'authorized');
331
+ : event.decision === 'pending'
332
+ ? 'requested'
333
+ : executed
334
+ ? (event.exitCode === undefined || event.exitCode === 0 ? 'completed' : 'failed')
335
+ : 'authorized');
287
336
  const lifecycleId = event.callId ?? event.correlationId ?? event.requestId;
288
337
  return {
289
338
  ts: event.ts,
@@ -386,7 +435,11 @@ function logAuditEvent(record) {
386
435
  : routineEcwebRoleRead
387
436
  ? 'debug'
388
437
  : 'info';
389
- const marker = record.decision === 'deny' ? 'DENY' : record.dangerous ? 'DANGEROUS_ALLOW' : 'ALLOW';
438
+ const marker = record.decision === 'deny'
439
+ ? 'DENY'
440
+ : record.decision === 'pending'
441
+ ? 'PENDING'
442
+ : record.dangerous ? 'DANGEROUS_ALLOW' : 'ALLOW';
390
443
  const message = [
391
444
  `[CommandAudit:${marker}]`,
392
445
  `operation=${record.operation}`,
@@ -34,6 +34,9 @@ const DAEMON_SERVICE_OPERATIONS = new Set([
34
34
  'trigger.cancel',
35
35
  'trigger.delete',
36
36
  'trigger.run',
37
+ 'agent.reload',
38
+ 'system.restart',
39
+ 'system.upgrade',
37
40
  ]);
38
41
  export function authorizeCommand(ctx) {
39
42
  return authorizeCommandInternal(ctx);
@@ -121,6 +124,47 @@ function authorizeCommandInternal(ctx, resolvedConfigCommand) {
121
124
  return denyDecision(ctx, 'NOT_ALLOWED', `Operation ${operation} is not available from source ${ctx.source}`, operation, intent.scope, opMeta.dangerous);
122
125
  }
123
126
  if (ctx.processRole === 'daemon-service') {
127
+ // ECWeb uses the authenticated daemon service for its read-only menu
128
+ // bootstrap (model/effort, agent metadata, trigger views, etc.). These
129
+ // queries are safe to authorize by catalog category; mutating operations
130
+ // remain restricted to the explicit daemon-service allowlist below.
131
+ const ECWEB_SERVICE_READ_OPERATIONS = new Set([
132
+ 'system.status', 'model.current', 'model.list', 'baseagent.models.list',
133
+ 'model.effort.current', 'agent.baseagent.current', 'agent.baseagent.list',
134
+ 'agent.show',
135
+ 'agent.list',
136
+ 'capability.read',
137
+ 'gateway.read', 'stats.summary', 'stats.session', 'stats.context',
138
+ 'role.policy.read', 'role.relation.read', 'config.read',
139
+ 'model.info', 'model.check',
140
+ 'trigger.list', 'trigger.show', 'trigger.history', 'trigger.eventCatalog',
141
+ ]);
142
+ if (ECWEB_SERVICE_READ_OPERATIONS.has(operation)) {
143
+ return {
144
+ allow: true,
145
+ operation,
146
+ scope: intent.scope,
147
+ role: 'daemon-service',
148
+ dangerous: opMeta.dangerous,
149
+ matchedRule: 'daemon-service-read',
150
+ };
151
+ }
152
+ // The in-process service may perform system lifecycle actions only when
153
+ // the request came through the authenticated ECWeb management surface.
154
+ // A raw daemon-service subject on the control source is not a human owner.
155
+ if ((operation === 'system.restart' || operation === 'system.upgrade') && ctx.source === 'control') {
156
+ return denyDecision(ctx, 'NOT_ALLOWED', `Daemon service is not authorized for ${operation}`, operation, intent.scope, opMeta.dangerous, 'daemon-service');
157
+ }
158
+ if (ctx.source !== 'control' && (operation === 'observable.current' || operation === 'observable.update')) {
159
+ return {
160
+ allow: true,
161
+ operation,
162
+ scope: intent.scope,
163
+ role: 'daemon-service',
164
+ dangerous: opMeta.dangerous,
165
+ matchedRule: 'ecweb-daemon-service-agent-management',
166
+ };
167
+ }
124
168
  if (!DAEMON_SERVICE_OPERATIONS.has(operation)) {
125
169
  return denyDecision(ctx, 'NOT_ALLOWED', `Daemon service is not authorized for ${operation}`, operation, intent.scope, opMeta.dangerous, 'daemon-service');
126
170
  }
@@ -148,7 +192,8 @@ function authorizeCommandInternal(ctx, resolvedConfigCommand) {
148
192
  };
149
193
  }
150
194
  // Any authenticated role may inspect the current Agent through the daemon's
151
- // redacted view. Cross-Agent inspection is handled by requiresDaemonOwner().
195
+ // redacted view. Management roles may inspect another Agent's redacted view;
196
+ // mutation and reload remain target-scoped below.
152
197
  if (operation === 'agent.show' && isCurrentAgentTarget(ctx)) {
153
198
  return {
154
199
  allow: true,
@@ -159,6 +204,17 @@ function authorizeCommandInternal(ctx, resolvedConfigCommand) {
159
204
  matchedRule: 'self-agent-read',
160
205
  };
161
206
  }
207
+ if (operation === 'agent.persona' && isCurrentAgentTarget(ctx)
208
+ && (role === 'owner' || hasDaemonOwnerRole(ctx))) {
209
+ return {
210
+ allow: true,
211
+ operation,
212
+ scope: intent.scope,
213
+ role,
214
+ dangerous: opMeta.dangerous,
215
+ matchedRule: role === 'owner' ? 'agent-owner-persona' : 'daemon-owner-persona',
216
+ };
217
+ }
162
218
  if (resolvedConfigCommand) {
163
219
  const targetCheck = checkResolvedConfigTarget(ctx, resolvedConfigCommand);
164
220
  if (!targetCheck.ok) {
@@ -171,7 +227,7 @@ function authorizeCommandInternal(ctx, resolvedConfigCommand) {
171
227
  if (!isManagementRole(role) && intent.scope !== 'relation') {
172
228
  return denyDecision(ctx, 'SCOPE_MISMATCH', `Role ${role} may only access relation-scoped config`, operation, intent.scope, opMeta.dangerous);
173
229
  }
174
- if (role === 'owner') {
230
+ if (role === 'owner' && isCurrentAgentTarget(ctx)) {
175
231
  return {
176
232
  allow: true,
177
233
  operation,
@@ -181,6 +237,22 @@ function authorizeCommandInternal(ctx, resolvedConfigCommand) {
181
237
  matchedRule: 'agent-owner',
182
238
  };
183
239
  }
240
+ // The daemon control identity is allowed to inspect any Agent config,
241
+ // including sensitive fields. This is intentionally handled before
242
+ // role-definition matching because daemon-owner subjects do not need an
243
+ // Agent relation role to read the daemon's local configuration store.
244
+ if (resolvedConfigCommand.kind === 'field'
245
+ && resolvedConfigCommand.subcommand === 'get'
246
+ && hasDaemonOwnerRole(ctx)) {
247
+ return {
248
+ allow: true,
249
+ operation,
250
+ scope: intent.scope,
251
+ role: 'daemon-owner',
252
+ dangerous: opMeta.dangerous,
253
+ matchedRule: 'daemon-owner-config-read',
254
+ };
255
+ }
184
256
  }
185
257
  const roleDef = getRoleDefinition(role, ctx.selfAid);
186
258
  if (!roleDef) {
@@ -189,6 +261,20 @@ function authorizeCommandInternal(ctx, resolvedConfigCommand) {
189
261
  if (roleDef.allowAccess === false) {
190
262
  return denyDecision(ctx, 'ROLE_ACCESS_DENIED', `Role ${role} is not allowed to access commands`, operation, intent.scope, opMeta.dangerous);
191
263
  }
264
+ // AID lookup and public agent.md reads are available to every accessible
265
+ // role and may target any AID that passed managed-command validation. The
266
+ // operation source is restricted to agent-tool by the catalog.
267
+ if ((operation === 'ec.aid.lookup' || operation === 'ec.aid.agentmd.get')
268
+ && (intent.scope === 'relation' || intent.scope === 'agent')) {
269
+ return {
270
+ allow: true,
271
+ operation,
272
+ scope: intent.scope,
273
+ role,
274
+ dangerous: opMeta.dangerous,
275
+ matchedRule: 'public-aid-read',
276
+ };
277
+ }
192
278
  let matchResult = matchCommandPermission(operation, opMeta.category, roleDef.commandPermissions || {}, opMeta.dangerous);
193
279
  // Agent self-service contact add deliberately reuses the same low-trust
194
280
  // relation boundary as ec.msg.send. Older per-Agent role files may still
@@ -299,10 +385,25 @@ function requiresDaemonOwner(ctx, resolvedConfigCommand) {
299
385
  || resolvedConfigCommand.configScope === 'process'
300
386
  || resolvedConfigCommand.configScope === 'defaults'))
301
387
  return true;
388
+ // Sensitive reads of another Agent's config are daemon-scoped. Agent
389
+ // owner/admin roles may inspect safe fields cross-Agent, but only the
390
+ // daemon control identity may access config.read targets that are not the
391
+ // current Agent.
392
+ if (operation === 'config.read'
393
+ && resolvedConfigCommand?.kind === 'field'
394
+ && resolvedConfigCommand.configScope === 'agent'
395
+ && !isCurrentAgentTarget(ctx)) {
396
+ return true;
397
+ }
302
398
  if (operation === 'agent.reload' && hasDaemonOwnerRole(ctx)) {
303
399
  return true;
304
400
  }
305
- if (operation === 'agent.show' || operation === 'agent.reload') {
401
+ // Avatar publication is an Agent-scoped mutation for Agent channels, but
402
+ // the trusted control plane may publish it for a selected Agent target.
403
+ if (operation === 'ec.aid.avatar' && hasDaemonOwnerRole(ctx)) {
404
+ return true;
405
+ }
406
+ if (operation === 'agent.reload') {
306
407
  return !isCurrentAgentTarget(ctx);
307
408
  }
308
409
  if (operation.startsWith('trigger.')) {
@@ -317,7 +418,9 @@ function requiresDaemonOwner(ctx, resolvedConfigCommand) {
317
418
  return false;
318
419
  }
319
420
  function hasDaemonOwnerRole(ctx) {
320
- return ctx.isDaemonOwner === true || ctx.processRole === 'daemon-owner';
421
+ return ctx.isDaemonOwner === true
422
+ || ctx.processRole === 'daemon-owner'
423
+ || ctx.processRole === 'daemon-service';
321
424
  }
322
425
  function daemonOwnerDenialReasonCode(ctx) {
323
426
  if ((ctx.intent.operation === 'agent.show' || ctx.intent.operation === 'agent.reload')
@@ -340,8 +443,8 @@ function isCurrentAgentTarget(ctx) {
340
443
  return !!ctx.selfAid && !!targetAid && targetAid === ctx.selfAid;
341
444
  }
342
445
  function daemonOwnerRequiredMessage(operation) {
343
- return `${operation} is a daemon-level operation and requires DaemonOwner. `
344
- + 'Agent owner/admin and permissionMode=bypass do not grant this permission.';
446
+ return `无权限:${operation} 是 daemon 级操作,需要 DaemonOwner。`
447
+ + 'Agent owner/admin 和 permissionMode=bypass 均不能授予此权限。';
345
448
  }
346
449
  function getRuleRank(rule, permission, operation, category, isDangerous) {
347
450
  const denyOffset = permission.allow ? 0 : 1;
@@ -469,6 +572,12 @@ function checkConfigFieldPolicy(ctx, policy, resolvedConfigOp) {
469
572
  return { ok: false, reason: 'Resolved config operation is required' };
470
573
  const field = resolvedConfigOp.field;
471
574
  if (!resolvedConfigOp.route || (resolvedConfigOp.fieldRule.class !== 'safe-scalar' && resolvedConfigOp.fieldRule.class !== 'safe-readonly-object')) {
575
+ if (policy === 'behavior-read'
576
+ && !isCurrentAgentTarget(ctx)
577
+ && hasDaemonOwnerRole(ctx)
578
+ && resolvedConfigOp.route) {
579
+ return { ok: true };
580
+ }
472
581
  return { ok: false, reason: `Config field ${field} is not available to user roles` };
473
582
  }
474
583
  if (policy === 'role-overridable-write'
@@ -481,8 +590,12 @@ function checkConfigFieldPolicy(ctx, policy, resolvedConfigOp) {
481
590
  const fieldPermission = roleDef
482
591
  ? resolveRoleFieldPermission(roleDef.permissions || {}, field)
483
592
  : undefined;
484
- if (policy === 'behavior-read')
593
+ if (policy === 'behavior-read') {
594
+ // Only the daemon control identity may read sensitive config fields from
595
+ // another Agent. Agent owners retain unrestricted reads for their own
596
+ // target via the self-target fast path above.
485
597
  return { ok: true };
598
+ }
486
599
  if (fieldPermission && !fieldPermission.allowOverride) {
487
600
  return { ok: false, reason: `Role ${ctx.role} cannot override ${field}` };
488
601
  }
@@ -519,6 +632,11 @@ function checkResolvedConfigTarget(ctx, command) {
519
632
  return { ok: true };
520
633
  }
521
634
  if (command.configScope === 'agent') {
635
+ // Management roles may read another Agent's non-sensitive config fields;
636
+ // sensitive fields are rejected by the field policy. Writes stay self-only.
637
+ if (command.subcommand === 'get' && (isManagementRole(ctx.role) || hasDaemonOwnerRole(ctx))) {
638
+ return { ok: true };
639
+ }
522
640
  if (!ctx.selfAid || !command.self || command.self !== ctx.selfAid) {
523
641
  return { ok: false, reason: 'Only the current agent can be targeted' };
524
642
  }
@@ -116,6 +116,14 @@ const QUEUE_OPERATIONS = [['read', 'Read the current Agent message queue'], ['cl
116
116
  */
117
117
  const OPERATIONS = [
118
118
  // ── Model Operations ──
119
+ {
120
+ id: 'baseagent.models.list',
121
+ category: 'read',
122
+ dangerous: false,
123
+ defaultScopes: ['process'],
124
+ description: '查询主机默认 Baseagent 模型目录',
125
+ sources: ['menu', 'ecweb', 'control'],
126
+ },
119
127
  {
120
128
  id: 'model.list',
121
129
  category: 'read',
@@ -593,6 +601,14 @@ const OPERATIONS = [
593
601
  description: '获取 agent 配置(含凭证)',
594
602
  sources: ['menu', 'menu.cli', 'control'],
595
603
  },
604
+ {
605
+ id: 'agent.persona',
606
+ category: 'write-agent',
607
+ dangerous: false,
608
+ defaultScopes: ['agent'],
609
+ description: '读取或更新当前 Agent 的 persona.md',
610
+ sources: ['agent-tool', 'control'],
611
+ },
596
612
  {
597
613
  id: 'agent.create',
598
614
  category: 'process',
@@ -917,7 +933,7 @@ const OPERATIONS = [
917
933
  category: 'read',
918
934
  dangerous: false,
919
935
  defaultScopes: ['relation', 'agent'],
920
- description: 'Look up the current Agent public AID record',
936
+ description: 'Look up a public AID record',
921
937
  sources: ['agent-tool'],
922
938
  },
923
939
  {
@@ -925,7 +941,7 @@ const OPERATIONS = [
925
941
  category: 'read',
926
942
  dangerous: false,
927
943
  defaultScopes: ['relation', 'agent'],
928
- description: 'Read and verify the current Agent public agent.md',
944
+ description: 'Read and verify a public Agent agent.md',
929
945
  sources: ['agent-tool'],
930
946
  },
931
947
  {
@@ -936,6 +952,14 @@ const OPERATIONS = [
936
952
  description: 'Sign and publish the current Agent local agent.md',
937
953
  sources: ['agent-tool'],
938
954
  },
955
+ {
956
+ id: 'ec.aid.avatar',
957
+ category: 'write-agent',
958
+ dangerous: true,
959
+ defaultScopes: ['agent'],
960
+ description: 'Upload and publish the current Agent avatar',
961
+ sources: ['agent-tool', 'menu', 'menu.cli'],
962
+ },
939
963
  {
940
964
  id: 'ec.msg.send',
941
965
  category: 'write-own',
@@ -2,8 +2,9 @@ import crypto from 'node:crypto';
2
2
  import fs from 'node:fs';
3
3
  import path from 'node:path';
4
4
  import { resolvePaths } from '../../paths.js';
5
- export const REMOTE_ACCESS_TOKEN_TTL_MS = 30 * 24 * 60 * 60 * 1000;
6
- export const REMOTE_ACCESS_PAIRING_TTL_MS = 5 * 60 * 1000;
5
+ export const REMOTE_ACCESS_TOKEN_TTL_MS = 7 * 24 * 60 * 60 * 1000;
6
+ // 与 owner 绑定二维码(BindService)的默认有效期保持一致:10 分钟。
7
+ export const REMOTE_ACCESS_PAIRING_TTL_MS = 10 * 60 * 1000;
7
8
  export function remoteAccessTokenStorePath() {
8
9
  return path.join(resolvePaths().instanceDir, 'control-access-tokens.json');
9
10
  }
@@ -20,16 +20,16 @@ export class AgentLoader {
20
20
  * Iterate over all EvolAgents in the registry × all registered plugins.
21
21
  * Each successful (agent, plugin) pair yields one runner instance.
22
22
  */
23
- createAll(registry, callbacks, creationErrors) {
23
+ async createAll(registry, callbacks, creationErrors) {
24
24
  const instances = [];
25
25
  const allAgents = registry.runnableAgents();
26
26
  for (const agent of allAgents) {
27
- instances.push(...this.createForAgent(agent, callbacks, creationErrors));
27
+ instances.push(...await this.createForAgent(agent, callbacks, creationErrors));
28
28
  }
29
29
  return instances;
30
30
  }
31
31
  /** Create runners for one EvolAgent, used by runtime hot-load. */
32
- createForAgent(agent, callbacks, creationErrors) {
32
+ async createForAgent(agent, callbacks, creationErrors) {
33
33
  const instances = [];
34
34
  for (const [pluginName, plugin] of this.plugins) {
35
35
  if (!plugin.isEnabled(agent)) {
@@ -37,7 +37,7 @@ export class AgentLoader {
37
37
  continue;
38
38
  }
39
39
  try {
40
- const instance = plugin.createAgent(agent, callbacks);
40
+ const instance = await plugin.createAgent(agent, callbacks);
41
41
  if (!instance) {
42
42
  logger.debug(`Plugin '${pluginName}' returned null for agent '${agent.name}', skipping`);
43
43
  continue;
@@ -18,8 +18,16 @@ export function bootstrapInitialMessageOperationPrefix(aid) {
18
18
  export function bootstrapInitialRetryOperationId(aid, at = Date.now()) {
19
19
  return `${bootstrapInitialMessageOperationPrefix(aid)}:${at}`;
20
20
  }
21
- export function postBootstrapWelcomeOperationId(aid) {
22
- return `bootstrap-complete:v1:${aid}`;
21
+ export function postBootstrapWelcomeOperationId(aid, owner) {
22
+ const normalizedAid = aid.replace(/^@/, '');
23
+ return owner ? `bootstrap-complete:v2:${normalizedAid}:${owner}` : `bootstrap-complete:v1:${normalizedAid}`;
24
+ }
25
+ export function isPostBootstrapWelcomeOperationId(operationId, aid) {
26
+ if (!operationId)
27
+ return false;
28
+ const normalizedAid = aid.replace(/^@/, '');
29
+ return operationId === postBootstrapWelcomeOperationId(normalizedAid)
30
+ || operationId.startsWith(`bootstrap-complete:v2:${normalizedAid}:`);
23
31
  }
24
32
  export function renderPostBootstrapWelcome(aid, owner, ownerName) {
25
33
  const mdPath = agentMdPath(aid);
@@ -36,9 +44,13 @@ export function renderPostBootstrapWelcome(aid, owner, ownerName) {
36
44
  });
37
45
  }
38
46
  export function preparePostBootstrapWelcomeOutbox(aid, owner, ownerName, delivery = { chatType: 'private' }) {
39
- const operationId = postBootstrapWelcomeOperationId(aid);
47
+ const operationId = postBootstrapWelcomeOperationId(aid, owner);
40
48
  const chatDir = chatDirPath(resolvePaths().sessionsDir, 'aun', owner, aid);
41
- if (hasMessageLogOperation(chatDir, operationId))
49
+ const legacyOperationId = postBootstrapWelcomeOperationId(aid);
50
+ if (hasMessageLogOperation(chatDir, operationId) || hasMessageLogOperation(chatDir, legacyOperationId))
51
+ return;
52
+ const legacy = outbox.findByDedupeKey(aid, legacyOperationId);
53
+ if (legacy?.channelId === owner)
42
54
  return;
43
55
  const existing = outbox.findByDedupeKey(aid, operationId);
44
56
  if (existing
@@ -68,21 +80,30 @@ export function preparePostBootstrapWelcomeOutbox(aid, owner, ownerName, deliver
68
80
  });
69
81
  }
70
82
  export function hasPendingPostBootstrapWelcomeOutbox(aid) {
71
- const entry = outbox.findByDedupeKey(aid, postBootstrapWelcomeOperationId(aid));
72
- return !!entry && outbox.isDeliveryForChannel(entry.delivery, entry.channelId);
83
+ return findPostBootstrapWelcomeOutbox(aid)
84
+ .some(entry => outbox.isDeliveryForChannel(entry.delivery, entry.channelId));
85
+ }
86
+ /** Keep pending welcomes from the single-owner v1 format retryable after upgrades. */
87
+ export function findPostBootstrapWelcomeOutbox(aid, opts = {}) {
88
+ const normalizedAid = aid.replace(/^@/, '');
89
+ const entries = outbox.findByDedupePrefix(normalizedAid, `bootstrap-complete:v2:${normalizedAid}:`, opts);
90
+ const legacy = outbox.findByDedupeKey(normalizedAid, postBootstrapWelcomeOperationId(normalizedAid), opts);
91
+ return [...entries, ...(legacy ? [legacy] : [])];
92
+ }
93
+ export function pendingPostBootstrapWelcomeOwners(aid) {
94
+ return findPostBootstrapWelcomeOutbox(aid)
95
+ .filter(entry => outbox.isDeliveryForChannel(entry.delivery, entry.channelId))
96
+ .map(entry => entry.channelId)
97
+ .filter((owner, index, all) => all.indexOf(owner) === index);
73
98
  }
74
99
  /** Bind the durable completion welcome to the fresh post-bootstrap session. */
75
- export function bindPostBootstrapWelcomeOutboxSession(aid, sessionId) {
76
- const entry = outbox.findByDedupeKey(aid, postBootstrapWelcomeOperationId(aid));
77
- if (!entry || !outbox.isDeliveryForChannel(entry.delivery, entry.channelId))
100
+ export function bindPostBootstrapWelcomeOutboxSession(aid, sessionId, owner) {
101
+ const normalizedAid = aid.replace(/^@/, '');
102
+ const entries = findPostBootstrapWelcomeOutbox(normalizedAid)
103
+ .filter(entry => (!owner || entry.channelId === owner) && outbox.isDeliveryForChannel(entry.delivery, entry.channelId));
104
+ // A session is scoped to one peer: never silently pick an arbitrary Owner.
105
+ if (!entries.length || (!owner && new Set(entries.map(entry => entry.channelId)).size !== 1))
78
106
  return false;
79
- if (entry.context?.sessionId === sessionId)
80
- return true;
81
- return outbox.replaceIfRouteMatches(aid, entry, {
82
- ...entry,
83
- context: {
84
- ...(entry.context ?? {}),
85
- sessionId,
86
- },
87
- }) === 'replaced';
107
+ return entries.every(entry => entry.context?.sessionId === sessionId
108
+ || outbox.replaceIfRouteMatches(normalizedAid, entry, { ...entry, context: { ...(entry.context ?? {}), sessionId } }) === 'replaced');
88
109
  }
@@ -52,32 +52,38 @@ export async function completeBootstrapWithWelcome(service, aid, welcome, sessio
52
52
  reloaded: false,
53
53
  };
54
54
  }
55
- try {
56
- mainSession = await sessions.ensurePostBootstrapMainSession();
57
- }
58
- catch (e) {
59
- return {
60
- ok: false,
61
- code: 'MAIN_SESSION_CREATE_FAILED',
62
- error: e instanceof Error ? e.message : `Failed to create the post-bootstrap main session for ${aid}`,
63
- reloaded: false,
64
- };
65
- }
66
- if (!mainSession.sessionId) {
67
- return {
68
- ok: false,
69
- code: 'MAIN_SESSION_CREATE_FAILED',
70
- error: `Failed to create the post-bootstrap main session for ${aid}`,
71
- reloaded: false,
72
- };
73
- }
74
- if (welcomePending && welcome && !await welcome.bindPostBootstrapWelcomeToSession(mainSession.sessionId)) {
75
- return {
76
- ok: false,
77
- code: 'WELCOME_SESSION_BIND_FAILED',
78
- error: `Failed to bind the post-bootstrap welcome to session ${mainSession.sessionId}`,
79
- reloaded: false,
80
- };
55
+ const pendingOwners = welcome?.pendingPostBootstrapWelcomeOwners?.();
56
+ const sessionOwners = pendingOwners?.length ? pendingOwners : [undefined];
57
+ for (const owner of sessionOwners) {
58
+ let ownerSession;
59
+ try {
60
+ ownerSession = await sessions.ensurePostBootstrapMainSession(owner);
61
+ }
62
+ catch (e) {
63
+ return {
64
+ ok: false,
65
+ code: 'MAIN_SESSION_CREATE_FAILED',
66
+ error: e instanceof Error ? e.message : `Failed to create the post-bootstrap main session for ${aid}`,
67
+ reloaded: false,
68
+ };
69
+ }
70
+ if (!ownerSession.sessionId) {
71
+ return {
72
+ ok: false,
73
+ code: 'MAIN_SESSION_CREATE_FAILED',
74
+ error: `Failed to create the post-bootstrap main session for ${aid}`,
75
+ reloaded: false,
76
+ };
77
+ }
78
+ if (welcomePending && welcome && !await welcome.bindPostBootstrapWelcomeToSession(ownerSession.sessionId, owner)) {
79
+ return {
80
+ ok: false,
81
+ code: 'WELCOME_SESSION_BIND_FAILED',
82
+ error: `Failed to bind the post-bootstrap welcome to session ${ownerSession.sessionId}`,
83
+ reloaded: false,
84
+ };
85
+ }
86
+ mainSession ??= ownerSession;
81
87
  }
82
88
  }
83
89
  const result = service.completeBootstrap(aid);
@@ -13,10 +13,11 @@ function defaultTypeConfig() {
13
13
  return { mode: 'inherit', overrides: {} };
14
14
  }
15
15
  function resolveBaseagent(input) {
16
- return input.baseagent
16
+ const value = input.baseagent
17
17
  || input.sessionBaseagent
18
- || input.config?.active_baseagent
19
- || 'claude';
18
+ || input.config?.active_baseagent;
19
+ const normalized = typeof value === 'string' ? value.trim() : '';
20
+ return normalized || undefined;
20
21
  }
21
22
  export function resolveCapabilityContext(input) {
22
23
  const aid = input.aid || input.config?.aid;
@@ -25,9 +26,12 @@ export function resolveCapabilityContext(input) {
25
26
  const projectPath = input.config?.projects?.defaultPath || input.projectPath || input.sessionProjectPath || null;
26
27
  if (!projectPath)
27
28
  return { error: '无法解析当前 agent projectPath', code: 'NO_PROJECT' };
29
+ const baseagent = resolveBaseagent(input);
30
+ if (!baseagent)
31
+ return { error: '无法解析当前 agent 的 baseagent', code: 'BASEAGENT_REQUIRED' };
28
32
  return {
29
33
  aid,
30
- baseagent: resolveBaseagent(input),
34
+ baseagent,
31
35
  projectPath,
32
36
  };
33
37
  }
@@ -1,6 +1,6 @@
1
1
  import os from 'os';
2
2
  import path from 'path';
3
- import { execCodexCliSync } from '../../../utils/codex-cli.js';
3
+ import { execCodexCliSync } from '../../../agents/baseagent.js';
4
4
  import { discoverSkillDirectories } from '../skill-discovery.js';
5
5
  function discoverSkills(ctx) {
6
6
  const roots = [