evolcore 0.0.2 → 0.0.4

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 (206) hide show
  1. package/CHANGELOG.md +93 -778
  2. package/README.md +45 -10
  3. package/bin/ec-safe-output.js +89 -24
  4. package/dist/agents/baseagent.js +46 -0
  5. package/dist/agents/claude-runner.js +198 -48
  6. package/dist/agents/codex-app-server-client.js +68 -0
  7. package/dist/agents/codex-runner.js +203 -8
  8. package/dist/agents/runner-types.js +2 -2
  9. package/dist/aun/aid/agentmd.js +79 -0
  10. package/dist/aun/aid/encryption-seed-policy.js +29 -0
  11. package/dist/aun/aid/index.js +1 -1
  12. package/dist/aun/aid/store.js +2 -5
  13. package/dist/aun/outbox.js +8 -0
  14. package/dist/channels/aun.js +241 -116
  15. package/dist/channels/daemon.js +18 -4
  16. package/dist/channels/dingtalk.js +1005 -260
  17. package/dist/channels/feishu.js +184 -56
  18. package/dist/channels/qqbot.js +23 -1
  19. package/dist/channels/wechat.js +303 -155
  20. package/dist/channels/wecom.js +1132 -164
  21. package/dist/cli/agent-command.js +2 -1
  22. package/dist/cli/aun-commands.js +98 -43
  23. package/dist/cli/bench.js +2 -2
  24. package/dist/cli/contact.js +71 -0
  25. package/dist/cli/ctl-command.js +2 -2
  26. package/dist/cli/daemon-commands.js +77 -214
  27. package/dist/cli/handoff-command.js +4 -3
  28. package/dist/cli/help.js +9 -5
  29. package/dist/cli/index.js +132 -116
  30. package/dist/cli/init-channel.js +242 -129
  31. package/dist/cli/init.js +70 -31
  32. package/dist/cli/model.js +2 -1
  33. package/dist/cli/net-check.js +2 -2
  34. package/dist/cli/queue-command.js +30 -6
  35. package/dist/cli/raw-key-input.js +25 -0
  36. package/dist/cli/response.js +5 -6
  37. package/dist/cli/restart-monitor.js +25 -1
  38. package/dist/cli/stats.js +6 -4
  39. package/dist/cli/trigger-command.js +146 -25
  40. package/dist/cli/version.js +6 -1
  41. package/dist/config/access-policy-domain.js +38 -0
  42. package/dist/config/access-policy.js +134 -0
  43. package/dist/config/builtin-role-templates.js +42 -17
  44. package/dist/config/builtin-roles.js +31 -6
  45. package/dist/config/config-field-policy.js +17 -5
  46. package/dist/config/config-manager.js +409 -29
  47. package/dist/config/config-operation-service.js +35 -38
  48. package/dist/config/contact-alias.js +68 -0
  49. package/dist/config/contact-bind-code.js +274 -0
  50. package/dist/config/contact-book-store.js +622 -0
  51. package/dist/config/contact-book-v2-startup.js +35 -0
  52. package/dist/config/contact-book.js +185 -300
  53. package/dist/config/contact-operation-service.js +116 -0
  54. package/dist/config/contact-request-service.js +331 -0
  55. package/dist/config/peer-role-resolver.js +136 -55
  56. package/dist/config/role-ranks.js +18 -0
  57. package/dist/config/role-service.js +16 -19
  58. package/dist/config/role-store.js +16 -5
  59. package/dist/config/roles.js +10 -1
  60. package/dist/config/schema-registry.js +49 -24
  61. package/dist/config-store.js +14 -4
  62. package/dist/core/auth/agent-delegation.js +105 -11
  63. package/dist/core/auth/authorization-audit.js +16 -1
  64. package/dist/core/auth/operation-authorizer.js +10 -0
  65. package/dist/core/auth/operation-catalog.js +105 -1
  66. package/dist/core/auth/trigger-authorization.js +15 -0
  67. package/dist/core/bootstrap-service.js +2 -9
  68. package/dist/core/channel-loader.js +6 -2
  69. package/dist/core/command/command-handler.js +113 -21
  70. package/dist/core/command/connect-menu.js +599 -0
  71. package/dist/core/command/evol-menu-version-gate.js +38 -0
  72. package/dist/core/command/group-menu.js +421 -0
  73. package/dist/core/command/menu-handler.js +382 -100
  74. package/dist/core/command/menu-protocol.js +8 -2
  75. package/dist/core/command/menu-token-store.js +102 -0
  76. package/dist/core/command/role-menu.js +128 -29
  77. package/dist/core/command/slash-gate.js +1 -1
  78. package/dist/core/command/slash-handler.js +186 -49
  79. package/dist/core/daemon-file-cache.js +40 -6
  80. package/dist/core/event-catalog.js +99 -0
  81. package/dist/core/evolagent-registry.js +4 -40
  82. package/dist/core/evolagent.js +20 -9
  83. package/dist/core/handoff/runtime.js +162 -37
  84. package/dist/core/handoff/store.js +46 -2
  85. package/dist/core/handoff/types.js +1 -0
  86. package/dist/core/inference/text-inference.js +16 -74
  87. package/dist/core/message/file-markers.js +7 -0
  88. package/dist/core/message/im-renderer.js +20 -14
  89. package/dist/core/message/inbound-admission.js +124 -0
  90. package/dist/core/message/message-bridge.js +670 -57
  91. package/dist/core/message/message-log.js +1 -0
  92. package/dist/core/message/message-queue.js +195 -10
  93. package/dist/core/message/message-utils.js +8 -2
  94. package/dist/core/message/peer-mode.js +7 -8
  95. package/dist/core/message/response-engine.js +642 -96
  96. package/dist/core/message/send-receipt.js +24 -0
  97. package/dist/core/message/stream-debouncer.js +11 -2
  98. package/dist/core/permission/approval-gateway.js +17 -10
  99. package/dist/core/permission/ec-command-parser.js +101 -46
  100. package/dist/core/permission/tool-policy.js +115 -38
  101. package/dist/core/protected-paths.js +38 -11
  102. package/dist/core/session/session-fs-store.js +44 -3
  103. package/dist/core/session/session-manager.js +86 -8
  104. package/dist/core/session/session-mapper.js +2 -0
  105. package/dist/core/session/session-renew.js +125 -69
  106. package/dist/core/session/session-turn-coordinator.js +5 -1
  107. package/dist/eck/kit-renderer.js +12 -1
  108. package/dist/eck/message-renderer.js +79 -1
  109. package/dist/index.js +285 -94
  110. package/dist/ipc.js +114 -8
  111. package/dist/paths.js +3 -0
  112. package/dist/response-system/config-resolver.js +29 -0
  113. package/dist/response-system/coordinator.js +8 -32
  114. package/dist/response-system/engines/v1/proactive-flow.js +1 -1
  115. package/dist/response-system/index.js +1 -0
  116. package/dist/response-system/modes/single-session/index.js +7 -4
  117. package/dist/stats/billing.js +20 -8
  118. package/dist/trigger/manager.js +3 -0
  119. package/dist/trigger/parser.js +126 -11
  120. package/dist/trigger/patch.js +12 -2
  121. package/dist/trigger/scheduler.js +444 -40
  122. package/dist/trigger/validation.js +13 -0
  123. package/dist/utils/aid-bind.js +43 -29
  124. package/dist/utils/error-dict.json +7 -0
  125. package/dist/utils/evolcore-version.js +21 -0
  126. package/dist/utils/instance-registry.js +14 -7
  127. package/dist/utils/log-writer.js +46 -0
  128. package/dist/utils/logger.js +2 -2
  129. package/dist/utils/media-cache.js +4 -1
  130. package/dist/utils/model-prices.jsonl +6 -3
  131. package/dist/utils/restart-safety.js +31 -0
  132. package/dist/utils/stable-semver.js +21 -0
  133. package/dist/utils/stats.js +33 -9
  134. package/dist/utils/system-memory.js +62 -0
  135. package/kits/docs/INDEX.md +2 -1
  136. package/kits/docs/channels/aun.md +4 -13
  137. package/kits/docs/evolcore/INDEX.md +5 -3
  138. package/kits/docs/evolcore/agent.md +9 -1
  139. package/kits/docs/evolcore/aid.md +5 -2
  140. package/kits/docs/evolcore/config.md +47 -2
  141. package/kits/docs/evolcore/contact.md +61 -0
  142. package/kits/docs/evolcore/fs.md +9 -0
  143. package/kits/docs/evolcore/group-rules.md +46 -4
  144. package/kits/docs/evolcore/group.md +15 -6
  145. package/kits/docs/evolcore/model.md +4 -1
  146. package/kits/docs/evolcore/msg.md +12 -6
  147. package/kits/docs/evolcore/response.md +16 -21
  148. package/kits/docs/evolcore/rpc.md +2 -0
  149. package/kits/docs/evolcore/stats.md +15 -2
  150. package/kits/docs/evolcore/storage.md +1 -0
  151. package/kits/docs/evolcore/trigger.md +42 -10
  152. package/kits/docs/path-registry.md +36 -17
  153. package/kits/eck_manifest.json +12 -0
  154. package/kits/eck_message_manifest.json +12 -1
  155. package/kits/migrations/migrate-contact-book-v2.mjs +754 -0
  156. package/kits/rules/01-overview.md +17 -7
  157. package/kits/rules/02-navigation.md +38 -18
  158. package/kits/rules/03-identity.md +28 -24
  159. package/kits/rules/04-relation.md +44 -28
  160. package/kits/rules/05-venue.md +31 -15
  161. package/kits/rules/06-channel.md +11 -7
  162. package/kits/schemas/_meta.json +21 -7
  163. package/kits/schemas/agent-config.schema.6.json +322 -0
  164. package/kits/schemas/agent-config.schema.7.json +303 -0
  165. package/kits/schemas/agent-config.schema.8.json +304 -0
  166. package/kits/schemas/agent-config.schema.9.json +374 -0
  167. package/kits/schemas/contact-book.schema.2.json +43 -0
  168. package/kits/schemas/contact-book.schema.3.json +67 -0
  169. package/kits/schemas/daemon.schema.1.json +3 -2
  170. package/kits/schemas/daemon.schema.2.json +101 -0
  171. package/kits/schemas/daemon.schema.3.json +123 -0
  172. package/kits/schemas/defaults.schema.2.json +85 -0
  173. package/kits/schemas/defaults.schema.3.json +73 -0
  174. package/kits/schemas/relation-config.schema.5.json +47 -0
  175. package/kits/schemas/relation-config.schema.6.json +46 -0
  176. package/kits/schemas/relation-config.schema.7.json +59 -0
  177. package/kits/schemas/role-config.schema.1.json +1 -0
  178. package/kits/schemas/role-registry.schema.1.json +2 -2
  179. package/kits/schemas/single-session.schema.2.json +57 -0
  180. package/kits/templates/message-fragments/handoff-context-to-target.md +9 -0
  181. package/kits/templates/message-fragments/handoff-request-to-target.md +14 -8
  182. package/kits/templates/message-fragments/handoff-response-to-origin.md +5 -6
  183. package/kits/templates/roles/admin.json +47 -0
  184. package/kits/templates/roles/member.json +5 -0
  185. package/kits/templates/roles/owner.json +1 -0
  186. package/kits/templates/roles/visitor.json +5 -0
  187. package/kits/templates/system-fragments/channel.md +12 -2
  188. package/kits/templates/system-fragments/commands.md +3 -1
  189. package/kits/templates/system-fragments/identity.md +3 -1
  190. package/kits/templates/system-fragments/relation.md +1 -1
  191. package/kits/templates/system-fragments/session.md +6 -2
  192. package/package.json +7 -5
  193. package/MIGRATION-0.5.0.md +0 -378
  194. package/ROLE_ACCESS_CONTROL.md +0 -174
  195. package/assets/brand/evolcore/README.md +0 -19
  196. package/assets/brand/evolcore/evolcore-app-icon.png +0 -0
  197. package/assets/brand/evolcore/evolcore-app-icon.svg +0 -13
  198. package/assets/brand/evolcore/evolcore-brand-board.png +0 -0
  199. package/assets/brand/evolcore/evolcore-brand-board.svg +0 -126
  200. package/assets/brand/evolcore/evolcore-logo-kit.zip +0 -0
  201. package/assets/brand/evolcore/evolcore-logo-reverse.png +0 -0
  202. package/assets/brand/evolcore/evolcore-logo-reverse.svg +0 -14
  203. package/assets/brand/evolcore/evolcore-logo.png +0 -0
  204. package/assets/brand/evolcore/evolcore-logo.svg +0 -14
  205. package/assets/brand/evolcore/evolcore-mark.png +0 -0
  206. package/assets/brand/evolcore/evolcore-mark.svg +0 -10
@@ -2,9 +2,11 @@ import crypto from 'crypto';
2
2
  import { authorizeOperation, buildAuthSubject } from './auth-gateway.js';
3
3
  import { formatPeerKey, parsePeerKey } from '../relation/peer-identity.js';
4
4
  export const AGENT_DELEGATION_TOKEN_ENV = 'EVOLCORE_DELEGATION_TOKEN';
5
+ export const AGENT_DELEGATION_COMMAND_TTL_MS = 60_000;
5
6
  export class AgentDelegationRegistry {
6
7
  grantsByHash = new Map();
7
8
  activeHashBySession = new Map();
9
+ approvedCommands = new Map();
8
10
  issue(input) {
9
11
  this.revokeSession(input.sessionId);
10
12
  const token = crypto.randomBytes(32).toString('base64url');
@@ -13,7 +15,7 @@ export class AgentDelegationRegistry {
13
15
  this.activeHashBySession.set(input.sessionId, tokenHash);
14
16
  return token;
15
17
  }
16
- validate(token, sessionId) {
18
+ validate(token, sessionId, commandHash) {
17
19
  if (!token) {
18
20
  return {
19
21
  ok: false,
@@ -23,16 +25,49 @@ export class AgentDelegationRegistry {
23
25
  }
24
26
  const tokenHash = hashDelegationToken(token);
25
27
  const grant = this.grantsByHash.get(tokenHash);
26
- if (!grant
27
- || grant.sessionId !== sessionId
28
- || this.activeHashBySession.get(sessionId) !== tokenHash) {
29
- return {
30
- ok: false,
31
- code: 'INVALID_DELEGATION',
32
- reason: 'The task delegation token is invalid, revoked, or belongs to another session',
33
- };
28
+ if (grant
29
+ && grant.sessionId === sessionId
30
+ && this.activeHashBySession.get(sessionId) === tokenHash) {
31
+ return { ok: true, grant };
34
32
  }
35
- return { ok: true, grant };
33
+ const approved = commandHash
34
+ ? this.consumeApprovedCommand(tokenHash, sessionId, commandHash)
35
+ : undefined;
36
+ if (approved)
37
+ return { ok: true, grant: approved };
38
+ return {
39
+ ok: false,
40
+ code: 'INVALID_DELEGATION',
41
+ reason: 'The task delegation token is invalid, revoked, or belongs to another session',
42
+ };
43
+ }
44
+ armApprovedCommand(input) {
45
+ const activeTokenHash = this.activeHashBySession.get(input.sessionId);
46
+ if (!activeTokenHash)
47
+ return false;
48
+ const grant = activeTokenHash ? this.grantsByHash.get(activeTokenHash) : undefined;
49
+ if (!grant || grant.taskId !== input.taskId)
50
+ return false;
51
+ if (!input.carrierToken || !isDelegationCommandHash(input.commandHash))
52
+ return false;
53
+ this.deleteExpiredApprovedCommands();
54
+ const carrierTokenHash = hashDelegationToken(input.carrierToken);
55
+ const key = approvedCommandKey(carrierTokenHash, input.sessionId, input.commandHash);
56
+ const existing = this.approvedCommands.get(key);
57
+ const ttlMs = Math.max(1, Math.min(input.ttlMs ?? AGENT_DELEGATION_COMMAND_TTL_MS, AGENT_DELEGATION_COMMAND_TTL_MS));
58
+ this.approvedCommands.set(key, {
59
+ sessionId: input.sessionId,
60
+ taskId: input.taskId,
61
+ activeTokenHash,
62
+ commandHash: input.commandHash,
63
+ expiresAt: Date.now() + ttlMs,
64
+ remainingUses: existing
65
+ && existing.activeTokenHash === activeTokenHash
66
+ && existing.expiresAt > Date.now()
67
+ ? existing.remainingUses + 1
68
+ : 1,
69
+ });
70
+ return true;
36
71
  }
37
72
  revokeTask(sessionId, taskId) {
38
73
  const tokenHash = this.activeHashBySession.get(sessionId);
@@ -43,19 +78,59 @@ export class AgentDelegationRegistry {
43
78
  return;
44
79
  this.grantsByHash.delete(tokenHash);
45
80
  this.activeHashBySession.delete(sessionId);
81
+ this.deleteApprovedCommandsForSession(sessionId);
46
82
  }
47
83
  revokeSession(sessionId) {
48
84
  const tokenHash = this.activeHashBySession.get(sessionId);
49
85
  if (tokenHash)
50
86
  this.grantsByHash.delete(tokenHash);
51
87
  this.activeHashBySession.delete(sessionId);
88
+ this.deleteApprovedCommandsForSession(sessionId);
89
+ }
90
+ consumeApprovedCommand(carrierTokenHash, sessionId, commandHash) {
91
+ if (!isDelegationCommandHash(commandHash))
92
+ return undefined;
93
+ const key = approvedCommandKey(carrierTokenHash, sessionId, commandHash);
94
+ const approved = this.approvedCommands.get(key);
95
+ if (!approved)
96
+ return undefined;
97
+ if (approved.expiresAt <= Date.now()) {
98
+ this.approvedCommands.delete(key);
99
+ return undefined;
100
+ }
101
+ const activeTokenHash = this.activeHashBySession.get(sessionId);
102
+ const grant = activeTokenHash ? this.grantsByHash.get(activeTokenHash) : undefined;
103
+ if (!grant
104
+ || activeTokenHash !== approved.activeTokenHash
105
+ || grant.taskId !== approved.taskId) {
106
+ this.approvedCommands.delete(key);
107
+ return undefined;
108
+ }
109
+ if (approved.remainingUses <= 1)
110
+ this.approvedCommands.delete(key);
111
+ else
112
+ approved.remainingUses--;
113
+ return grant;
114
+ }
115
+ deleteExpiredApprovedCommands() {
116
+ const now = Date.now();
117
+ for (const [key, approved] of this.approvedCommands) {
118
+ if (approved.expiresAt <= now)
119
+ this.approvedCommands.delete(key);
120
+ }
121
+ }
122
+ deleteApprovedCommandsForSession(sessionId) {
123
+ for (const [key, approved] of this.approvedCommands) {
124
+ if (approved.sessionId === sessionId)
125
+ this.approvedCommands.delete(key);
126
+ }
52
127
  }
53
128
  }
54
129
  export function authorizeDelegatedAunMsgSend(registry, input) {
55
130
  if (!input.sessionId) {
56
131
  return { ok: false, code: 'DELEGATION_REQUIRED', reason: 'Origin session is required' };
57
132
  }
58
- const validation = registry.validate(input.delegationToken, input.sessionId);
133
+ const validation = registry.validate(input.delegationToken, input.sessionId, input.delegationCommandHash);
59
134
  if (!validation.ok)
60
135
  return validation;
61
136
  const grant = validation.grant;
@@ -109,3 +184,22 @@ export function authorizeDelegatedAunMsgSend(registry, input) {
109
184
  function hashDelegationToken(token) {
110
185
  return crypto.createHash('sha256').update(token).digest('hex');
111
186
  }
187
+ function approvedCommandKey(carrierTokenHash, sessionId, commandHash) {
188
+ return JSON.stringify([carrierTokenHash, sessionId, commandHash]);
189
+ }
190
+ function isDelegationCommandHash(value) {
191
+ return /^[a-f0-9]{64}$/.test(value);
192
+ }
193
+ export function hashDelegatedCommandArgv(argv) {
194
+ if (argv.length === 0 || argv.some(value => typeof value !== 'string' || value.includes('\0')))
195
+ return undefined;
196
+ return crypto.createHash('sha256')
197
+ .update('evolcore-agent-delegation-command-v1\0')
198
+ .update(JSON.stringify(argv))
199
+ .digest('hex');
200
+ }
201
+ export function hashCurrentDelegatedCommand(processArgv = process.argv) {
202
+ if (processArgv.length < 3)
203
+ return undefined;
204
+ return hashDelegatedCommandArgv(['ec', ...processArgv.slice(2)]);
205
+ }
@@ -21,6 +21,9 @@ function buildAuditRecord(event) {
21
21
  operation: event.operation,
22
22
  scope: event.scope,
23
23
  dangerous: event.dangerous,
24
+ name: event.name,
25
+ action: event.action,
26
+ args: event.args?.argv ? { argv: [...event.args.argv] } : undefined,
24
27
  actorId: redactIdentifier(event.actorId),
25
28
  selfAid: redactIdentifier(event.selfAid),
26
29
  peerKey: redactIdentifier(event.peerKey),
@@ -47,11 +50,23 @@ function redactIdentifier(value) {
47
50
  return crypto.createHash('sha256').update(value).digest('hex').slice(0, 12);
48
51
  }
49
52
  function logAuditEvent(record) {
50
- const level = record.decision === 'deny' ? 'warn' : 'info';
53
+ // A role-management snapshot fans out into several successful read checks.
54
+ // Keep those available for verbose ECWeb diagnostics without flooding INFO.
55
+ const routineEcwebRoleRead = record.source === 'ecweb'
56
+ && !record.dangerous
57
+ && (record.operation === 'role.policy.read' || record.operation === 'role.relation.read');
58
+ const level = record.decision === 'deny'
59
+ ? 'warn'
60
+ : routineEcwebRoleRead
61
+ ? 'debug'
62
+ : 'info';
51
63
  const marker = record.decision === 'deny' ? 'DENY' : record.dangerous ? 'DANGEROUS_ALLOW' : 'ALLOW';
52
64
  const message = [
53
65
  `[CommandAudit:${marker}]`,
54
66
  `operation=${record.operation}`,
67
+ record.name ? `name=${record.name}` : null,
68
+ record.action ? `action=${record.action}` : null,
69
+ record.args?.argv ? `args.argv=${JSON.stringify(record.args.argv)}` : null,
55
70
  `role=${record.role}`,
56
71
  `actor=${record.actorId || 'unknown'}`,
57
72
  record.taskId ? `task=${record.taskId}` : null,
@@ -18,6 +18,8 @@ export const USER_PLANE_CAPABILITY_CEILING = {
18
18
  'chatmode.update',
19
19
  'mentionmode.current',
20
20
  'mentionmode.update',
21
+ 'group.mentionmode.current',
22
+ 'group.rulespolicy.current',
21
23
  'session.list',
22
24
  'session.create',
23
25
  'session.rename',
@@ -55,6 +57,8 @@ export const USER_PLANE_CAPABILITY_CEILING = {
55
57
  ]),
56
58
  denyNamespaces: new Set([
57
59
  'role',
60
+ 'connect',
61
+ 'contact',
58
62
  'agent',
59
63
  'system',
60
64
  'storage',
@@ -413,6 +417,12 @@ function checkConfigFieldPolicy(ctx, policy, resolvedConfigOp) {
413
417
  if (!resolvedConfigOp.route || (resolvedConfigOp.fieldRule.class !== 'safe-scalar' && resolvedConfigOp.fieldRule.class !== 'safe-readonly-object')) {
414
418
  return { ok: false, reason: `Config field ${field} is not available to user roles` };
415
419
  }
420
+ if (policy === 'role-overridable-write'
421
+ && resolvedConfigOp.fieldRule.access === 'owner-only'
422
+ && ctx.role !== 'owner'
423
+ && !ctx.isDaemonOwner) {
424
+ return { ok: false, reason: `Config field ${field} can only be changed by an owner` };
425
+ }
416
426
  const roleDef = getRoleDefinition(ctx.role, ctx.selfAid);
417
427
  const fieldPermission = roleDef
418
428
  ? resolveRoleFieldPermission(roleDef.permissions || {}, field)
@@ -133,6 +133,46 @@ const OPERATIONS = [
133
133
  description: 'Update group mention mode',
134
134
  sources: ['slash', 'menu'],
135
135
  },
136
+ {
137
+ id: 'group.list',
138
+ category: 'read',
139
+ dangerous: false,
140
+ defaultScopes: ['agent'],
141
+ description: 'List joined AUN groups',
142
+ sources: ['menu'],
143
+ },
144
+ {
145
+ id: 'group.mentionmode.current',
146
+ category: 'read',
147
+ dangerous: false,
148
+ defaultScopes: ['relation', 'agent'],
149
+ description: 'Read a group mention mode',
150
+ sources: ['menu'],
151
+ },
152
+ {
153
+ id: 'group.mentionmode.update',
154
+ category: 'write-own',
155
+ dangerous: false,
156
+ defaultScopes: ['relation', 'agent'],
157
+ description: 'Update a group mention mode',
158
+ sources: ['menu'],
159
+ },
160
+ {
161
+ id: 'group.rulespolicy.current',
162
+ category: 'read',
163
+ dangerous: false,
164
+ defaultScopes: ['relation', 'agent'],
165
+ description: 'Read a group rules loading policy',
166
+ sources: ['menu'],
167
+ },
168
+ {
169
+ id: 'group.rulespolicy.update',
170
+ category: 'write-own',
171
+ dangerous: false,
172
+ defaultScopes: ['relation', 'agent'],
173
+ description: 'Update a group rules loading policy',
174
+ sources: ['menu'],
175
+ },
136
176
  {
137
177
  id: 'session.list',
138
178
  category: 'read',
@@ -180,7 +220,7 @@ const OPERATIONS = [
180
220
  dangerous: false,
181
221
  defaultScopes: ['filesystem'],
182
222
  description: '获取文件内容(需路径沙箱检查)',
183
- sources: ['menu'],
223
+ sources: ['slash', 'menu'],
184
224
  },
185
225
  // ── Trigger Operations ──
186
226
  {
@@ -304,6 +344,70 @@ const OPERATIONS = [
304
344
  description: 'Modify role definitions, command permissions, default roles, or usage limits',
305
345
  sources: ['menu', 'ecweb', 'control'],
306
346
  },
347
+ {
348
+ id: 'contact.read',
349
+ category: 'read',
350
+ dangerous: false,
351
+ defaultScopes: ['agent'],
352
+ description: 'Read the Agent contact block list',
353
+ sources: ['agent-tool'],
354
+ },
355
+ {
356
+ id: 'contact.block',
357
+ category: 'write-agent',
358
+ dangerous: false,
359
+ defaultScopes: ['agent'],
360
+ description: 'Block or unblock a canonical contact across bound private channels',
361
+ sources: ['agent-tool'],
362
+ },
363
+ {
364
+ id: 'connect.read',
365
+ category: 'read',
366
+ dangerous: false,
367
+ defaultScopes: ['agent'],
368
+ description: 'Read contacts, groups, assignments, or contact details',
369
+ sources: ['menu', 'ecweb', 'control'],
370
+ },
371
+ {
372
+ id: 'connect.assign',
373
+ category: 'write-agent',
374
+ dangerous: false,
375
+ defaultScopes: ['agent'],
376
+ description: 'Assign a role to a relation',
377
+ sources: ['menu', 'ecweb', 'control'],
378
+ },
379
+ {
380
+ id: 'connect.revoke',
381
+ category: 'write-agent',
382
+ dangerous: false,
383
+ defaultScopes: ['agent'],
384
+ description: 'Revoke a role assignment from a relation',
385
+ sources: ['menu', 'ecweb', 'control'],
386
+ },
387
+ {
388
+ id: 'connect.admin',
389
+ category: 'write-agent',
390
+ dangerous: false,
391
+ defaultScopes: ['agent'],
392
+ description: 'Grant or revoke admin status',
393
+ sources: ['menu', 'ecweb', 'control'],
394
+ },
395
+ {
396
+ id: 'connect.write',
397
+ category: 'write-agent',
398
+ dangerous: false,
399
+ defaultScopes: ['agent'],
400
+ description: 'Manage contacts, contact requests, and allowed group channels',
401
+ sources: ['menu', 'ecweb', 'control'],
402
+ },
403
+ {
404
+ id: 'connect.access.write',
405
+ category: 'write-agent',
406
+ dangerous: false,
407
+ defaultScopes: ['agent'],
408
+ description: 'Manage owner-only Agent access policy and request limit settings',
409
+ sources: ['menu', 'ecweb', 'control'],
410
+ },
307
411
  // Agent Operations
308
412
  {
309
413
  id: 'agent.list',
@@ -0,0 +1,15 @@
1
+ const CROSS_AGENT_READ_OPERATIONS = new Set([
2
+ 'trigger.list',
3
+ 'trigger.show',
4
+ 'trigger.history',
5
+ ]);
6
+ export function isCrossAgentTriggerOperationAllowed(input) {
7
+ if (input.control || input.taskAgentAid === input.targetAgentAid)
8
+ return true;
9
+ if (!input.taskAgentAid || !input.targetManagement)
10
+ return false;
11
+ return CROSS_AGENT_READ_OPERATIONS.has(input.operation);
12
+ }
13
+ export function isCrossAgentTriggerReadOperation(operation) {
14
+ return CROSS_AGENT_READ_OPERATIONS.has(operation);
15
+ }
@@ -2,6 +2,7 @@ import fs from 'fs';
2
2
  import path from 'path';
3
3
  import { randomBytes } from 'crypto';
4
4
  import { kitsTemplatesDir, agentMdPath } from '../paths.js';
5
+ import { resolveAgentDisplayName } from '../aun/aid/agentmd.js';
5
6
  import { logger } from '../utils/logger.js';
6
7
  import { loadAgent, saveAgent } from '../config-store.js';
7
8
  import { normalizeAgentLifecycle, withLifecycleForWrite } from '../config/lifecycle.js';
@@ -130,15 +131,7 @@ export class BootstrapService {
130
131
  return renderTemplate(template, vars).trim();
131
132
  }
132
133
  resolveAgentDisplayName(aid) {
133
- try {
134
- const content = fs.readFileSync(agentMdPath(aid), 'utf-8');
135
- const fm = content.match(/^---\n([\s\S]*?)\n---/)?.[1] || '';
136
- const name = fm.match(/^name:\s*["']?(.+?)["']?\s*$/m)?.[1]?.trim();
137
- if (name)
138
- return name;
139
- }
140
- catch { }
141
- return aid.split('.')[0];
134
+ return resolveAgentDisplayName(aid) || aid.split('.')[0];
142
135
  }
143
136
  resolveBaseagent(agent, aid) {
144
137
  try {
@@ -26,7 +26,11 @@ export function showActivitiesPolicy(mode, chatType, _identity) {
26
26
  }
27
27
  // ── ChannelLoader ──────────────────────────────────────────────────────────
28
28
  export class ChannelLoader {
29
+ processDebug;
29
30
  plugins = new Map();
31
+ constructor(processDebug) {
32
+ this.processDebug = processDebug;
33
+ }
30
34
  register(plugin) {
31
35
  if (this.plugins.has(plugin.name)) {
32
36
  throw new Error(`Channel plugin '${plugin.name}' already registered`);
@@ -46,7 +50,7 @@ export class ChannelLoader {
46
50
  const ctx = {
47
51
  agentName: agent.aid,
48
52
  defaultProjectPath: agent.config.projects?.defaultPath ?? process.cwd(),
49
- debug: agent.config.debug,
53
+ debug: this.processDebug,
50
54
  };
51
55
  // Build the full list of config instances to create.
52
56
  // AUN is synthesised from agent.aid; any explicit aun entry in channels[] is skipped.
@@ -252,7 +256,7 @@ export function buildReloadHooks(deps) {
252
256
  const ctx = {
253
257
  agentName: agent.aid ?? agent.config?.aid,
254
258
  defaultProjectPath: agent.config?.projects?.defaultPath ?? process.cwd(),
255
- debug: agent.config?.debug,
259
+ debug: channelLoader.processDebug,
256
260
  };
257
261
  const plugin = channelLoader.getPlugin(cfgInst.type);
258
262
  if (!plugin)
@@ -18,6 +18,7 @@ import { buildSessionTurnList } from '../session/session-turns.js';
18
18
  import { resolveConfigCommand } from '../../config/resolved-config-op.js';
19
19
  import { AUTHENTICATED_CONFIG_ROLE_ENV, executeResolvedConfigCommand } from '../../config/config-operation-service.js';
20
20
  import { hashArgv } from '../auth/authorization-audit.js';
21
+ import { executeResolvedContactCommand, resolveContactCommand } from '../../config/contact-operation-service.js';
21
22
  import { authorizeOperation, buildAuthSubject } from '../auth/auth-gateway.js';
22
23
  import { validateModelSelectionForRole } from '../model/model-permission.js';
23
24
  import { constrainRuntimePermissionMode, validateRuntimeStringFieldOverride } from '../role/runtime-policy.js';
@@ -353,10 +354,13 @@ export class CommandHandler {
353
354
  validateTriggerRuntimeFields(definition, role, baseagent) {
354
355
  if (!role)
355
356
  return undefined;
357
+ const executionBaseagent = definition.execution.type === 'trigger_session'
358
+ ? definition.execution.baseagent ?? baseagent
359
+ : baseagent;
356
360
  if (definition.execution.model) {
357
361
  const modelDecision = validateModelSelectionForRole({
358
362
  role,
359
- baseagent,
363
+ baseagent: executionBaseagent,
360
364
  requestedModel: definition.execution.model,
361
365
  selfAid: definition.agentAid,
362
366
  });
@@ -364,7 +368,7 @@ export class CommandHandler {
364
368
  return modelDecision.message || '当前角色不允许使用该 Trigger 模型';
365
369
  }
366
370
  if (definition.execution.effort) {
367
- const base = baseagent || 'claude';
371
+ const base = executionBaseagent || 'claude';
368
372
  const effortDecision = validateRuntimeStringFieldOverride({
369
373
  selfAid: definition.agentAid,
370
374
  role,
@@ -447,6 +451,29 @@ export class CommandHandler {
447
451
  const permissionError = this.validateTriggerPermissionModeForRole(permissionMode, isAdmin, triggerRole.role, schedulerAid);
448
452
  if (permissionError)
449
453
  return { error: permissionError };
454
+ if (parsed.baseagent !== undefined) {
455
+ if (typeof parsed.baseagent !== 'string' || !parsed.baseagent.trim()) {
456
+ return { error: 'Trigger baseagent 不能为空' };
457
+ }
458
+ if (parsed.executionType !== 'trigger_session') {
459
+ return { error: 'Trigger baseagent 仅适用于 trigger_session' };
460
+ }
461
+ }
462
+ const triggerOwner = this.agentRegistry?.get?.(schedulerAid) ?? this.getOwningAgent(channel);
463
+ const executionBaseagent = parsed.executionType === 'trigger_session'
464
+ ? parsed.baseagent?.trim() ?? triggerOwner?.baseagent ?? triggerRole.baseagent
465
+ : undefined;
466
+ if (parsed.executionType === 'trigger_session') {
467
+ if (!executionBaseagent)
468
+ return { error: '无法确定 Trigger 会话的 baseagent' };
469
+ const ownerName = triggerOwner?.name || '<unknown>';
470
+ const available = this.getAvailableBaseagentsForOwner(ownerName);
471
+ if (!available.includes(executionBaseagent)) {
472
+ return {
473
+ error: `Trigger baseagent 不可用:${executionBaseagent}(可用:${available.join(' / ') || '无'})`,
474
+ };
475
+ }
476
+ }
450
477
  const definition = {
451
478
  $schema_version: 3.1,
452
479
  id,
@@ -460,7 +487,10 @@ export class CommandHandler {
460
487
  execution: {
461
488
  type: parsed.executionType,
462
489
  ...(script ? { script } : { prompt: parsed.prompt || '' }),
463
- ...(parsed.executionType === 'trigger_session' ? { thread: parsed.triggerThread ?? 'by_trigger' } : {}),
490
+ ...(parsed.executionType === 'trigger_session' ? {
491
+ thread: parsed.triggerThread ?? 'by_trigger',
492
+ baseagent: executionBaseagent,
493
+ } : {}),
464
494
  model: parsed.model,
465
495
  effort: parsed.effort,
466
496
  ...(permissionMode !== undefined ? { permissionMode } : {}),
@@ -784,12 +814,9 @@ export class CommandHandler {
784
814
  item.lastActive = s.updatedAt;
785
815
  return item;
786
816
  }
787
- /**
788
- * 返回结构化命令菜单(供 menu.query 使用)
789
- * owner 看到全部命令,admin 看到管理级命令(不含 owner-only),visitor/member 仅看到用户级命令
790
- */
791
- getMenuItems(role, chatType = 'private', scope = 'agent') {
792
- return menuGetMenuItems.call(this, role, chatType, scope);
817
+ /** 返回结构化命令菜单,已迁移的命令按当前授权主体过滤。 */
818
+ getMenuItems(role, chatType = 'private', scope = 'agent', authSubject) {
819
+ return menuGetMenuItems.call(this, role, chatType, scope, authSubject);
793
820
  }
794
821
  /** 动态子菜单:根据 cmd 路径返回选项列表(供 menu.query + cmd 使用) */
795
822
  async getSubMenuItems(cmd, channel, channelId, userId, args, overrideIdentity, explicitChatType, fromControlChannel = false, authSubject, source = 'menu') {
@@ -813,8 +840,8 @@ export class CommandHandler {
813
840
  return await menuExecMenuUpdate.call(this, cmd, value, channel, channelId, userId, overrideIdentity, fromControlChannel, args, authSubject, source);
814
841
  }
815
842
  /** menu.action — 触发动词。 */
816
- async execMenuAction(cmd, action, args, channel, channelId, userId, overrideIdentity, explicitChatType, requestId, fromControlChannel = false, authSubject, source = 'menu') {
817
- return await menuExecMenuAction.call(this, cmd, action, args, channel, channelId, userId, overrideIdentity, explicitChatType, requestId, fromControlChannel, authSubject, source);
843
+ async execMenuAction(cmd, action, args, channel, channelId, userId, overrideIdentity, explicitChatType, requestId, fromControlChannel = false, authSubject, source = 'menu', transportReplyContext, deferSideEffect) {
844
+ return await menuExecMenuAction.call(this, cmd, action, args, channel, channelId, userId, overrideIdentity, explicitChatType, requestId, fromControlChannel, authSubject, source, transportReplyContext, deferSideEffect);
818
845
  }
819
846
  /** ECWeb 专用入口:身份只取可信 IPC 信封。 */
820
847
  async execMenuForEcweb(payload, auth) {
@@ -955,9 +982,9 @@ export class CommandHandler {
955
982
  /**
956
983
  * 主命令处理入口
957
984
  */
958
- async handle(content, channel, channelId, sendMessage, userId, threadId, chatType, source, messageId, selfAID, overrideIdentity, authSubject) {
985
+ async handle(content, channel, channelId, sendMessage, userId, threadId, chatType, source, messageId, selfAID, overrideIdentity, authSubject, replyContext) {
959
986
  try {
960
- const result = await this._handleInternal(content, channel, channelId, sendMessage, userId, threadId, chatType, source, messageId, selfAID, overrideIdentity, authSubject);
987
+ const result = await this._handleInternal(content, channel, channelId, sendMessage, userId, threadId, chatType, source, messageId, selfAID, overrideIdentity, authSubject, replyContext);
961
988
  return result;
962
989
  }
963
990
  catch (error) {
@@ -968,8 +995,8 @@ export class CommandHandler {
968
995
  throw error;
969
996
  }
970
997
  }
971
- async _handleInternal(content, channel, channelId, sendMessage, userId, threadId, chatType, source, messageId, selfAID, overrideIdentity, authSubject) {
972
- return await handleSlashCommand.call(this, content, channel, channelId, sendMessage, userId, threadId, chatType, source, messageId, selfAID, overrideIdentity, authSubject);
998
+ async _handleInternal(content, channel, channelId, sendMessage, userId, threadId, chatType, source, messageId, selfAID, overrideIdentity, authSubject, replyContext) {
999
+ return await handleSlashCommand.call(this, content, channel, channelId, sendMessage, userId, threadId, chatType, source, messageId, selfAID, overrideIdentity, authSubject, replyContext);
973
1000
  }
974
1001
  async handleTriggerCommand(scheduler, content, channel, channelId, peerId, isAdmin, messageId, chatType, threadId) {
975
1002
  if (content === '/trigger') {
@@ -1117,8 +1144,13 @@ export class CommandHandler {
1117
1144
  const nextStr = updated.trigger.nextFireAt ? new Date(updated.trigger.nextFireAt).toLocaleString() : '未计算';
1118
1145
  return `✅ 触发器已更新:**${updated.trigger.name}**\n下次触发:${nextStr}`;
1119
1146
  }
1120
- if (sub.startsWith('set ')) {
1121
- const args = sub.slice('set '.length);
1147
+ const createSubcommand = sub.startsWith('create ')
1148
+ ? 'create'
1149
+ : sub.startsWith('set ')
1150
+ ? 'set'
1151
+ : undefined;
1152
+ if (createSubcommand) {
1153
+ const args = sub.slice(`${createSubcommand} `.length);
1122
1154
  const result = parseTriggerSet(args);
1123
1155
  if (!result.ok)
1124
1156
  return `❌ ${result.error}`;
@@ -1131,9 +1163,9 @@ export class CommandHandler {
1131
1163
  return `❌ 未知子命令。用法:
1132
1164
  /trigger — 查看活跃触发器
1133
1165
  /trigger list [--all] — 查看所有触发器
1134
- /trigger set <参数> — 注册触发器
1166
+ /trigger create <参数> — 创建触发器(set 为兼容别名)
1135
1167
  /trigger update <名称|ID> <参数> — 修改触发器
1136
- 常用参数:--delay/--at/--cron/--every、--prompt、--model、--effort、--permission(省略则继承;update 可用 inherit 清除覆盖)
1168
+ 常用参数:--exec script|trigger-session|target-session(create 必填)、--delay/--at/--cron/--every、--prompt、--model、--effort、--permission(省略则继承;update 可用 inherit 清除覆盖)
1137
1169
  /trigger enable <名称|ID> — 启用触发器
1138
1170
  /trigger disable <名称|ID> — 暂停触发器
1139
1171
  /trigger show <名称|ID> — 查看触发器详情
@@ -1416,11 +1448,11 @@ export class CommandHandler {
1416
1448
  * Agent managed CLI config entrypoint. Identity and relation are resolved
1417
1449
  * from the daemon-owned session rather than caller-provided flags.
1418
1450
  */
1419
- async handleConfigOperation(argv, sessionId, delegationToken) {
1451
+ async handleConfigOperation(argv, sessionId, delegationToken, delegationCommandHash) {
1420
1452
  if (!this.agentDelegationRegistry) {
1421
1453
  return { ok: false, code: 'DELEGATION_REQUIRED', error: 'Task delegation is not configured' };
1422
1454
  }
1423
- const delegation = this.agentDelegationRegistry.validate(delegationToken, sessionId);
1455
+ const delegation = this.agentDelegationRegistry.validate(delegationToken, sessionId, delegationCommandHash);
1424
1456
  if (!delegation.ok)
1425
1457
  return { ok: false, code: delegation.code, error: delegation.reason };
1426
1458
  const session = await this.sessionManager.getSessionById(sessionId);
@@ -1477,6 +1509,66 @@ export class CommandHandler {
1477
1509
  ? { ok: true, result }
1478
1510
  : { ok: false, code: result.code, error: result.error, ...(result.data !== undefined ? { data: result.data } : {}) };
1479
1511
  }
1512
+ /** Agent-managed contact CLI entrypoint. The daemon owns actor resolution and authorization. */
1513
+ async handleContactOperation(argv, sessionId, delegationToken, delegationCommandHash) {
1514
+ if (!this.agentDelegationRegistry) {
1515
+ return { ok: false, code: 'DELEGATION_REQUIRED', error: 'Task delegation is not configured' };
1516
+ }
1517
+ const delegation = this.agentDelegationRegistry.validate(delegationToken, sessionId, delegationCommandHash);
1518
+ if (!delegation.ok)
1519
+ return { ok: false, code: delegation.code, error: delegation.reason };
1520
+ const session = await this.sessionManager.getSessionById(sessionId);
1521
+ if (!session)
1522
+ return { ok: false, code: 'INVALID_SESSION', error: 'Invalid session' };
1523
+ const grant = delegation.grant;
1524
+ let conversationId;
1525
+ try {
1526
+ conversationId = parsePeerKey(grant.peerKey).channelId;
1527
+ }
1528
+ catch {
1529
+ return { ok: false, code: 'INVALID_DELEGATION', error: 'Delegation contains an invalid relation key' };
1530
+ }
1531
+ const resolved = resolveContactCommand(argv);
1532
+ if (!resolved.ok)
1533
+ return { ok: false, code: resolved.code, error: resolved.reason };
1534
+ if (resolved.command.selfAid !== grant.selfAid) {
1535
+ return { ok: false, code: 'SCOPE_MISMATCH', error: 'Only the current Agent contact book can be targeted' };
1536
+ }
1537
+ const subject = buildAuthSubject({
1538
+ selfAid: grant.selfAid,
1539
+ actorId: grant.actorId,
1540
+ channel: grant.channel,
1541
+ channelType: grant.channelType,
1542
+ channelId: conversationId,
1543
+ chatType: grant.chatType,
1544
+ conversationId,
1545
+ processOwners: loadDaemonConfig().owners ?? [],
1546
+ });
1547
+ const decision = authorizeOperation({
1548
+ source: 'agent-tool',
1549
+ subject,
1550
+ intent: {
1551
+ operation: resolved.command.operationId,
1552
+ scope: 'agent',
1553
+ source: 'agent-tool',
1554
+ args: {
1555
+ self: resolved.command.selfAid,
1556
+ ...('primaryId' in resolved.command ? { primaryId: resolved.command.primaryId } : {}),
1557
+ },
1558
+ },
1559
+ auditMetadata: {
1560
+ argvHash: hashArgv(resolved.command.canonicalArgv),
1561
+ taskId: grant.taskId,
1562
+ messageId: grant.messageId,
1563
+ },
1564
+ });
1565
+ if (!decision.allow)
1566
+ return { ok: false, code: decision.code, error: decision.reason };
1567
+ const result = await executeResolvedContactCommand(resolved.command, grant.actorId);
1568
+ return result.ok
1569
+ ? { ok: true, result }
1570
+ : { ok: false, code: result.code, error: result.error, ...(result.data ? { data: result.data } : {}) };
1571
+ }
1480
1572
  setAgentDelegationRegistry(registry) {
1481
1573
  this.agentDelegationRegistry = registry;
1482
1574
  }