evolcore 0.0.10 → 0.0.12

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 (131) hide show
  1. package/CHANGELOG.md +44 -0
  2. package/README.md +3 -3
  3. package/bin/codex-managed-hook.mjs +80 -0
  4. package/dist/agents/baseagent.js +4 -0
  5. package/dist/agents/claude-runner.js +158 -44
  6. package/dist/agents/codex-app-server-client.js +153 -23
  7. package/dist/agents/codex-runner.js +331 -43
  8. package/dist/agents/ecagent-runner.js +53 -6
  9. package/dist/agents/gemini-runner.js +1 -0
  10. package/dist/agents/request-identity.js +55 -0
  11. package/dist/aun/aid/client.js +11 -21
  12. package/dist/aun/aid/managed-operation.js +107 -0
  13. package/dist/aun/msg/group.js +81 -12
  14. package/dist/aun/msg/managed-operation.js +804 -0
  15. package/dist/aun/msg/mention-schema.js +20 -0
  16. package/dist/aun/msg/p2p.js +7 -0
  17. package/dist/aun/outbox.js +28 -31
  18. package/dist/channels/aun.js +418 -235
  19. package/dist/channels/daemon.js +13 -0
  20. package/dist/cli/agent-command.js +72 -17
  21. package/dist/cli/agent.js +249 -22
  22. package/dist/cli/aun-commands.js +270 -69
  23. package/dist/cli/bench.js +12 -3
  24. package/dist/cli/daemon-commands.js +52 -33
  25. package/dist/cli/fs-command.js +60 -0
  26. package/dist/cli/handoff-command.js +3 -0
  27. package/dist/cli/index.js +106 -63
  28. package/dist/cli/init-cancel.js +208 -0
  29. package/dist/cli/init-channel.js +343 -195
  30. package/dist/cli/init.js +21 -9
  31. package/dist/cli/managed-command-guard.js +41 -0
  32. package/dist/cli/model.js +11 -4
  33. package/dist/cli/queue-command.js +11 -1
  34. package/dist/cli/response.js +71 -0
  35. package/dist/cli/restart-monitor.js +4 -18
  36. package/dist/cli/trigger-command.js +4 -0
  37. package/dist/config/builtin-roles.js +5 -0
  38. package/dist/config/contact-book-store.js +11 -3
  39. package/dist/config/contact-request-service.js +161 -15
  40. package/dist/config/gateway-config.js +26 -10
  41. package/dist/config/mention-mode.js +8 -24
  42. package/dist/core/agent-reload-coordinator.js +53 -0
  43. package/dist/core/auth/agent-delegation.js +0 -1
  44. package/dist/core/auth/operation-authorizer.js +62 -154
  45. package/dist/core/auth/operation-catalog.js +264 -22
  46. package/dist/core/bootstrap-messages.js +68 -0
  47. package/dist/core/bootstrap-service.js +144 -10
  48. package/dist/core/capability/providers/claude-capability-provider.js +9 -31
  49. package/dist/core/capability/providers/codex-capability-provider.js +4 -15
  50. package/dist/core/capability/skill-discovery.js +55 -0
  51. package/dist/core/channel-loader.js +24 -7
  52. package/dist/core/command/agent-control.js +14 -11
  53. package/dist/core/command/cli-intent-parser.js +9 -0
  54. package/dist/core/command/command-handler.js +467 -6
  55. package/dist/core/command/connect-menu.js +15 -0
  56. package/dist/core/command/group-menu.js +4 -4
  57. package/dist/core/command/menu-catalog.js +366 -0
  58. package/dist/core/command/menu-handler.js +213 -110
  59. package/dist/core/command/menu-protocol.js +71 -0
  60. package/dist/core/command/slash-gate.js +5 -2
  61. package/dist/core/command/slash-handler.js +45 -25
  62. package/dist/core/daemon-file-cache.js +2 -1
  63. package/dist/core/data-migration.js +9 -2
  64. package/dist/core/evolagent-registry.js +125 -35
  65. package/dist/core/evolagent.js +12 -5
  66. package/dist/core/handoff/dispatcher.js +5 -2
  67. package/dist/core/handoff/runtime.js +145 -21
  68. package/dist/core/handoff/store.js +24 -0
  69. package/dist/core/handoff/types.js +1 -0
  70. package/dist/core/inference/text-inference.js +38 -4
  71. package/dist/core/message/logical-queue-bridge.js +1 -0
  72. package/dist/core/message/mention-schema.js +126 -0
  73. package/dist/core/message/message-bridge.js +237 -79
  74. package/dist/core/message/message-log.js +23 -0
  75. package/dist/core/message/message-queue.js +20 -4
  76. package/dist/core/message/response-engine.js +54 -36
  77. package/dist/core/model/model-catalog.js +143 -24
  78. package/dist/core/model/model-diagnostics.js +28 -10
  79. package/dist/core/permission/ec-command-parser.js +156 -10
  80. package/dist/core/permission/index.js +1 -0
  81. package/dist/core/permission/readonly-shell-query.js +532 -0
  82. package/dist/core/permission/sandbox-runtime.js +22 -1
  83. package/dist/core/permission/shell-environment.js +46 -0
  84. package/dist/core/permission/tool-policy.js +292 -111
  85. package/dist/core/protected-paths.js +17 -9
  86. package/dist/core/runner-reload-transaction.js +57 -0
  87. package/dist/core/session/session-manager.js +21 -2
  88. package/dist/eck/manifest-engine.js +39 -13
  89. package/dist/index.js +644 -180
  90. package/dist/ipc.js +246 -19
  91. package/dist/response-system/coordinator.js +6 -4
  92. package/dist/response-system/engines/v1/proactive-flow.js +26 -8
  93. package/dist/response-system/modes/single-session/index.js +14 -1
  94. package/dist/response-system/selector.js +6 -5
  95. package/dist/trigger/anomaly-store.js +4 -0
  96. package/dist/trigger/script-executor.js +1 -0
  97. package/dist/utils/aid-bind.js +3 -8
  98. package/dist/utils/codex-app-server-registry.js +90 -0
  99. package/dist/utils/codex-cli.js +5 -1
  100. package/dist/utils/cross-platform.js +15 -0
  101. package/dist/utils/log-writer.js +6 -10
  102. package/dist/utils/logger.js +5 -5
  103. package/dist/utils/npm-ops.js +5 -12
  104. package/dist/utils/tool-summary.js +11 -0
  105. package/kits/docs/channels/aun.md +1 -1
  106. package/kits/docs/context-assembly.md +2 -2
  107. package/kits/docs/evolcore/agent.md +11 -6
  108. package/kits/docs/evolcore/aid.md +14 -0
  109. package/kits/docs/evolcore/msg.md +13 -0
  110. package/kits/docs/prompt-loading-architecture.md +1 -2
  111. package/kits/docs/venues/group.md +1 -1
  112. package/kits/eck_manifest.json +0 -12
  113. package/kits/rules/01-overview.md +9 -0
  114. package/kits/rules/04-relation.md +1 -1
  115. package/kits/rules/05-venue.md +2 -2
  116. package/kits/schemas/agent-config.schema.3.json +1 -1
  117. package/kits/schemas/agent-config.schema.4.json +1 -1
  118. package/kits/schemas/relation-config.schema.2.json +1 -1
  119. package/kits/schemas/role-config.schema.1.json +1 -1
  120. package/kits/schemas/single-session.schema.1.json +3 -3
  121. package/kits/schemas/single-session.schema.2.json +3 -3
  122. package/kits/templates/roles/admin.json +27 -0
  123. package/kits/templates/roles/member.json +44 -0
  124. package/kits/templates/roles/visitor.json +40 -2
  125. package/kits/templates/system-fragments/bootstrap.md +12 -6
  126. package/kits/templates/system-fragments/channel.md +6 -0
  127. package/kits/templates/system-fragments/session.md +2 -0
  128. package/package.json +3 -2
  129. package/skills/eclink/SKILL.md +15 -3
  130. package/skills/eclink/agents/openai.yaml +3 -3
  131. package/dist/core/command/evol-menu-version-gate.js +0 -39
@@ -9,6 +9,7 @@ import { isManagementRole } from '../../config/builtin-roles.js';
9
9
  import { formatPeerKey } from '../relation/peer-identity.js';
10
10
  import crypto from 'crypto';
11
11
  import path from 'path';
12
+ import fs from 'fs';
12
13
  import { parseDuration, parseTriggerSet, parseTriggerUpdate } from '../../trigger/parser.js';
13
14
  import { applyTriggerPatch, replaceEditableTriggerDefinition } from '../../trigger/patch.js';
14
15
  import { definitionRevision } from '../../trigger/validation.js';
@@ -19,14 +20,19 @@ import { resolveConfigCommand } from '../../config/resolved-config-op.js';
19
20
  import { AUTHENTICATED_CONFIG_ROLE_ENV, executeResolvedConfigCommand } from '../../config/config-operation-service.js';
20
21
  import { hashArgv } from '../auth/authorization-audit.js';
21
22
  import { executeResolvedContactCommand, resolveContactCommand } from '../../config/contact-operation-service.js';
23
+ import { executeManagedAidOperation, ManagedAidOperationError, resolveManagedAidOperation, } from '../../aun/aid/managed-operation.js';
24
+ import { executeManagedGroupOperation, isManagedGroupRelationRead, ManagedGroupOperationError, resolveManagedGroupOperation, executeManagedMsgOperation, ManagedMsgOperationError, resolveManagedMsgOperation, executeManagedFsOperation, ManagedFsOperationError, resolveManagedFsOperation, } from '../../aun/msg/managed-operation.js';
22
25
  import { authorizeOperation, buildAuthSubject } from '../auth/auth-gateway.js';
26
+ import { isHClassPath, isLClassPath, isSameOrDescendant } from '../protected-paths.js';
23
27
  import { validateModelSelectionForRole } from '../model/model-permission.js';
28
+ import { getModelInfo } from '../model/model-catalog.js';
24
29
  import { constrainRuntimePermissionMode, validateRuntimeStringFieldOverride } from '../role/runtime-policy.js';
25
30
  import { parsePeerKey } from '../relation/peer-identity.js';
26
31
  import { isQuickCommand } from './slash-gate.js';
27
32
  import { handleSlashCommand } from './slash-handler.js';
28
33
  import { resolveChatMode } from '../message/peer-mode.js';
29
34
  import { execMenuAction as menuExecMenuAction, execMenuForEcweb as menuExecMenuForEcweb, execMenuForControl as menuExecMenuForControl, execMenuQuery as menuExecMenuQuery, execMenuUpdate as menuExecMenuUpdate, getMenuItems as menuGetMenuItems, getSubMenuItems as menuGetSubMenuItems, } from './menu-handler.js';
35
+ import { getMenuCatalog, execMenuValueBatch, validateMenuValueBatchArgs } from './menu-catalog.js';
30
36
  export { isProcessLevelOwner } from './menu-handler.js';
31
37
  const CLI_EXEC_TIMEOUT_MS = 15_000;
32
38
  const CLI_EXEC_MAX_OUTPUT = 128 * 1024;
@@ -93,6 +99,7 @@ export class CommandHandler {
93
99
  triggerSchedulerFallbackAid;
94
100
  daemonStatusProvider;
95
101
  agentDelegationRegistry;
102
+ handoffRuntime;
96
103
  /**
97
104
  * Get the runner for a (channel, baseagent) pair.
98
105
  *
@@ -675,6 +682,9 @@ export class CommandHandler {
675
682
  setMessageQueue(messageQueue) {
676
683
  this.messageQueue = messageQueue;
677
684
  }
685
+ setHandoffRuntime(handoffRuntime) {
686
+ this.handoffRuntime = handoffRuntime;
687
+ }
678
688
  setPermissionGateway(gateway) {
679
689
  this.permissionGateway = gateway;
680
690
  }
@@ -818,6 +828,29 @@ export class CommandHandler {
818
828
  getMenuItems(role, chatType = 'private', scope = 'agent', authSubject) {
819
829
  return menuGetMenuItems.call(this, role, chatType, scope, authSubject);
820
830
  }
831
+ async getMenuCatalog(channel, channelId, subject, request, channelScope = 'agent') {
832
+ return await getMenuCatalog({
833
+ subject,
834
+ channelType: this.resolveChannelType(channel),
835
+ channelScope,
836
+ channelId,
837
+ request,
838
+ });
839
+ }
840
+ async execMenuValueBatch(channel, channelId, subject, args, request, channelScope = 'agent') {
841
+ validateMenuValueBatchArgs(args);
842
+ return await execMenuValueBatch({
843
+ subject,
844
+ channelType: this.resolveChannelType(channel),
845
+ channelScope,
846
+ channelId,
847
+ request,
848
+ items: args?.items,
849
+ schemaVersion: args?.schemaVersion,
850
+ consistency: args?.consistency,
851
+ execQuery: async (cmd, resolvedChannelId, queryArgs, context) => this.execMenuQuery(cmd, channel, resolvedChannelId, context.subject.actorId, queryArgs, context.subject.chatType, context.subject.fromControlChannel, context.subject.identity, context.subject, channelScope === 'control' ? 'control' : 'menu'),
852
+ });
853
+ }
821
854
  /** 动态子菜单:根据 cmd 路径返回选项列表(供 menu.query + cmd 使用) */
822
855
  async getSubMenuItems(cmd, channel, channelId, userId, args, overrideIdentity, explicitChatType, fromControlChannel = false, authSubject, source = 'menu') {
823
856
  return await menuGetSubMenuItems.call(this, cmd, channel, channelId, userId, args, overrideIdentity, explicitChatType, fromControlChannel, authSubject, source);
@@ -1569,6 +1602,337 @@ export class CommandHandler {
1569
1602
  ? { ok: true, result }
1570
1603
  : { ok: false, code: result.code, error: result.error, ...(result.data ? { data: result.data } : {}) };
1571
1604
  }
1605
+ /** Agent-managed AID CLI entrypoint. Private key and CA access stay inside the daemon. */
1606
+ async handleAidOperation(argv, sessionId, delegationToken, delegationCommandHash) {
1607
+ if (!this.agentDelegationRegistry) {
1608
+ return { ok: false, code: 'DELEGATION_REQUIRED', error: 'Task delegation is not configured' };
1609
+ }
1610
+ const delegation = this.agentDelegationRegistry.validate(delegationToken, sessionId, delegationCommandHash);
1611
+ if (!delegation.ok)
1612
+ return { ok: false, code: delegation.code, error: delegation.reason };
1613
+ const session = await this.sessionManager.getSessionById(sessionId);
1614
+ if (!session)
1615
+ return { ok: false, code: 'INVALID_SESSION', error: 'Invalid session' };
1616
+ const parsed = resolveManagedAidOperation(argv);
1617
+ if (!parsed.ok)
1618
+ return { ok: false, code: parsed.code, error: parsed.reason };
1619
+ const grantSelfAid = delegation.grant.selfAid.replace(/^@/, '');
1620
+ const sessionSelfAid = session.selfAID?.replace(/^@/, '');
1621
+ if (sessionSelfAid && sessionSelfAid !== grantSelfAid) {
1622
+ return { ok: false, code: 'INVALID_DELEGATION', error: 'Delegation self agent does not match the active session' };
1623
+ }
1624
+ if (parsed.command.aid !== grantSelfAid) {
1625
+ return { ok: false, code: 'SCOPE_MISMATCH', error: 'Only the current Agent AID can be targeted' };
1626
+ }
1627
+ let conversationId;
1628
+ try {
1629
+ conversationId = parsePeerKey(delegation.grant.peerKey).channelId;
1630
+ }
1631
+ catch {
1632
+ return { ok: false, code: 'INVALID_DELEGATION', error: 'Delegation contains an invalid relation key' };
1633
+ }
1634
+ const subject = buildAuthSubject({
1635
+ selfAid: delegation.grant.selfAid,
1636
+ actorId: delegation.grant.actorId,
1637
+ channel: delegation.grant.channel,
1638
+ channelType: delegation.grant.channelType,
1639
+ channelId: conversationId,
1640
+ chatType: delegation.grant.chatType,
1641
+ conversationId,
1642
+ processOwners: loadDaemonConfig().owners ?? [],
1643
+ });
1644
+ const managementRole = isManagementRole(subject.role);
1645
+ const decision = authorizeOperation({
1646
+ source: 'agent-tool',
1647
+ subject,
1648
+ intent: {
1649
+ operation: parsed.command.operationId,
1650
+ scope: managementRole ? 'agent' : 'relation',
1651
+ source: 'agent-tool',
1652
+ args: { self: parsed.command.aid, targetId: parsed.command.aid },
1653
+ },
1654
+ auditMetadata: {
1655
+ argvHash: hashArgv(parsed.command.canonicalArgv),
1656
+ taskId: delegation.grant.taskId,
1657
+ messageId: delegation.grant.messageId,
1658
+ },
1659
+ });
1660
+ if (!decision.allow)
1661
+ return { ok: false, code: decision.code, error: decision.reason };
1662
+ try {
1663
+ return { ok: true, result: await executeManagedAidOperation(parsed.command) };
1664
+ }
1665
+ catch (error) {
1666
+ const message = error instanceof Error ? error.message : String(error);
1667
+ return {
1668
+ ok: false,
1669
+ code: error instanceof ManagedAidOperationError ? error.code : 'AID_OPERATION_FAILED',
1670
+ error: message,
1671
+ };
1672
+ }
1673
+ }
1674
+ /** Agent-managed group CLI entrypoint. AUN private material remains daemon-owned. */
1675
+ async handleGroupOperation(argv, sessionId, delegationToken, delegationCommandHash) {
1676
+ if (!this.agentDelegationRegistry) {
1677
+ return { ok: false, code: 'DELEGATION_REQUIRED', error: 'Task delegation is not configured' };
1678
+ }
1679
+ const delegation = this.agentDelegationRegistry.validate(delegationToken, sessionId, delegationCommandHash);
1680
+ if (!delegation.ok)
1681
+ return { ok: false, code: delegation.code, error: delegation.reason };
1682
+ const session = await this.sessionManager.getSessionById(sessionId);
1683
+ if (!session)
1684
+ return { ok: false, code: 'INVALID_SESSION', error: 'Invalid session' };
1685
+ const parsed = resolveManagedGroupOperation(argv);
1686
+ if (!parsed.ok)
1687
+ return { ok: false, code: parsed.code, error: parsed.reason };
1688
+ const grant = delegation.grant;
1689
+ const grantSelfAid = grant.selfAid.replace(/^@/, '');
1690
+ const sessionSelfAid = session.selfAID?.replace(/^@/, '');
1691
+ if (sessionSelfAid && sessionSelfAid !== grantSelfAid) {
1692
+ return { ok: false, code: 'INVALID_DELEGATION', error: 'Delegation self agent does not match the active session' };
1693
+ }
1694
+ if (parsed.command.from !== grantSelfAid) {
1695
+ return { ok: false, code: 'SCOPE_MISMATCH', error: 'Only the current Agent AID can perform managed group operations' };
1696
+ }
1697
+ let conversationId;
1698
+ try {
1699
+ conversationId = parsePeerKey(grant.peerKey).channelId;
1700
+ }
1701
+ catch {
1702
+ return { ok: false, code: 'INVALID_DELEGATION', error: 'Delegation contains an invalid relation key' };
1703
+ }
1704
+ const subject = buildAuthSubject({
1705
+ selfAid: grant.selfAid,
1706
+ actorId: grant.actorId,
1707
+ channel: grant.channel,
1708
+ channelType: grant.channelType,
1709
+ channelId: conversationId,
1710
+ chatType: grant.chatType,
1711
+ conversationId,
1712
+ processOwners: loadDaemonConfig().owners ?? [],
1713
+ });
1714
+ const managementRole = isManagementRole(subject.role);
1715
+ if (!managementRole && (grant.channelType !== 'aun' || !isManagedGroupRelationRead(parsed.command))) {
1716
+ return { ok: false, code: 'ROLE_ACCESS_DENIED', error: `Role ${subject.role} cannot manage Agent group membership or settings` };
1717
+ }
1718
+ if ((parsed.command.kind === 'dissolve' || parsed.command.kind === 'owner') && subject.role !== 'owner') {
1719
+ return { ok: false, code: 'ROLE_ACCESS_DENIED', error: `${parsed.command.kind} requires Agent owner permission` };
1720
+ }
1721
+ const decision = authorizeOperation({
1722
+ source: 'agent-tool',
1723
+ subject,
1724
+ intent: {
1725
+ operation: parsed.command.operationId,
1726
+ scope: managementRole ? 'agent' : 'relation',
1727
+ source: 'agent-tool',
1728
+ args: {
1729
+ self: parsed.command.from,
1730
+ ...(parsed.command.groupId ? {
1731
+ targetId: parsed.command.groupId,
1732
+ peer: parsed.command.groupId,
1733
+ peerKey: formatPeerKey(grant.channelType, parsed.command.groupId),
1734
+ } : {}),
1735
+ },
1736
+ },
1737
+ auditMetadata: {
1738
+ argvHash: hashArgv(parsed.command.canonicalArgv),
1739
+ taskId: grant.taskId,
1740
+ messageId: grant.messageId,
1741
+ },
1742
+ });
1743
+ if (!decision.allow)
1744
+ return { ok: false, code: decision.code, error: decision.reason };
1745
+ let rulesContent;
1746
+ if (parsed.command.kind === 'rules.set') {
1747
+ const filePath = String(parsed.command.filePath || '');
1748
+ const projectPath = path.resolve(session.projectPath);
1749
+ const resolvedPath = path.resolve(projectPath, filePath);
1750
+ if (!filePath || !isSameOrDescendant(resolvedPath, projectPath)) {
1751
+ return { ok: false, code: 'INVALID_RULES_PATH', error: 'Rules file must be inside the current session project' };
1752
+ }
1753
+ if (isHClassPath(resolvedPath) || isLClassPath(resolvedPath)) {
1754
+ return { ok: false, code: 'PROTECTED_RULES_PATH', error: 'Rules file cannot be read from a protected EvolCore path' };
1755
+ }
1756
+ try {
1757
+ const realProjectPath = fs.realpathSync(projectPath);
1758
+ const realPath = fs.realpathSync(resolvedPath);
1759
+ if (!isSameOrDescendant(realPath, realProjectPath)) {
1760
+ return { ok: false, code: 'INVALID_RULES_PATH', error: 'Rules file resolves outside the current session project' };
1761
+ }
1762
+ const stat = fs.statSync(realPath);
1763
+ if (!stat.isFile() || stat.size < 1 || stat.size > 4 * 1024) {
1764
+ return { ok: false, code: 'INVALID_RULES_CONTENT', error: 'Rules file must be a regular file from 1 to 4096 bytes' };
1765
+ }
1766
+ rulesContent = new TextDecoder('utf-8', { fatal: true }).decode(fs.readFileSync(realPath));
1767
+ }
1768
+ catch (error) {
1769
+ return {
1770
+ ok: false,
1771
+ code: 'INVALID_RULES_CONTENT',
1772
+ error: error instanceof Error ? error.message : String(error),
1773
+ };
1774
+ }
1775
+ }
1776
+ try {
1777
+ return { ok: true, result: await executeManagedGroupOperation(parsed.command, rulesContent) };
1778
+ }
1779
+ catch (error) {
1780
+ return {
1781
+ ok: false,
1782
+ code: error instanceof ManagedGroupOperationError ? error.code : 'GROUP_OPERATION_FAILED',
1783
+ error: error instanceof Error ? error.message : String(error),
1784
+ };
1785
+ }
1786
+ }
1787
+ /** Agent-managed private msg read entrypoint. AUN credentials and history roots remain daemon-owned. */
1788
+ async handleMsgOperation(argv, sessionId, delegationToken, delegationCommandHash) {
1789
+ if (!this.agentDelegationRegistry) {
1790
+ return { ok: false, code: 'DELEGATION_REQUIRED', error: 'Task delegation is not configured' };
1791
+ }
1792
+ const delegation = this.agentDelegationRegistry.validate(delegationToken, sessionId, delegationCommandHash);
1793
+ if (!delegation.ok)
1794
+ return { ok: false, code: delegation.code, error: delegation.reason };
1795
+ const session = await this.sessionManager.getSessionById(sessionId);
1796
+ if (!session)
1797
+ return { ok: false, code: 'INVALID_SESSION', error: 'Invalid session' };
1798
+ const parsed = resolveManagedMsgOperation(argv);
1799
+ if (!parsed.ok)
1800
+ return { ok: false, code: parsed.code, error: parsed.reason };
1801
+ const grant = delegation.grant;
1802
+ const grantSelfAid = grant.selfAid.replace(/^@/, '');
1803
+ const sessionSelfAid = session.selfAID?.replace(/^@/, '');
1804
+ if (sessionSelfAid && sessionSelfAid !== grantSelfAid) {
1805
+ return { ok: false, code: 'INVALID_DELEGATION', error: 'Delegation self agent does not match the active session' };
1806
+ }
1807
+ if (grant.channelType !== 'aun' || grant.chatType !== 'private') {
1808
+ return { ok: false, code: 'SCOPE_MISMATCH', error: 'Managed msg reads require the current AUN private relation' };
1809
+ }
1810
+ let peerAid;
1811
+ try {
1812
+ peerAid = parsePeerKey(grant.peerKey).channelId.replace(/^@/, '');
1813
+ }
1814
+ catch {
1815
+ return { ok: false, code: 'INVALID_DELEGATION', error: 'Delegation contains an invalid relation key' };
1816
+ }
1817
+ if (parsed.command.from !== grantSelfAid || parsed.command.target !== peerAid) {
1818
+ return { ok: false, code: 'SCOPE_MISMATCH', error: 'Only the current AUN private relation can be targeted' };
1819
+ }
1820
+ const subject = buildAuthSubject({
1821
+ selfAid: grant.selfAid,
1822
+ actorId: grant.actorId,
1823
+ channel: grant.channel,
1824
+ channelType: grant.channelType,
1825
+ channelId: peerAid,
1826
+ chatType: grant.chatType,
1827
+ conversationId: peerAid,
1828
+ processOwners: loadDaemonConfig().owners ?? [],
1829
+ });
1830
+ const decision = authorizeOperation({
1831
+ source: 'agent-tool',
1832
+ subject,
1833
+ intent: {
1834
+ operation: parsed.command.operationId,
1835
+ scope: 'relation',
1836
+ source: 'agent-tool',
1837
+ args: {
1838
+ self: parsed.command.from,
1839
+ targetId: parsed.command.target,
1840
+ peer: parsed.command.target,
1841
+ peerKey: formatPeerKey('aun', parsed.command.target),
1842
+ },
1843
+ },
1844
+ auditMetadata: {
1845
+ argvHash: hashArgv(parsed.command.canonicalArgv),
1846
+ taskId: grant.taskId,
1847
+ messageId: grant.messageId,
1848
+ },
1849
+ });
1850
+ if (!decision.allow)
1851
+ return { ok: false, code: decision.code, error: decision.reason };
1852
+ try {
1853
+ return { ok: true, result: await executeManagedMsgOperation(parsed.command) };
1854
+ }
1855
+ catch (error) {
1856
+ return {
1857
+ ok: false,
1858
+ code: error instanceof ManagedMsgOperationError ? error.code : 'MSG_OPERATION_FAILED',
1859
+ error: error instanceof Error ? error.message : String(error),
1860
+ };
1861
+ }
1862
+ }
1863
+ /** Agent-managed current-group filesystem read entrypoint. */
1864
+ async handleFsOperation(argv, sessionId, delegationToken, delegationCommandHash) {
1865
+ if (!this.agentDelegationRegistry) {
1866
+ return { ok: false, code: 'DELEGATION_REQUIRED', error: 'Task delegation is not configured' };
1867
+ }
1868
+ const delegation = this.agentDelegationRegistry.validate(delegationToken, sessionId, delegationCommandHash);
1869
+ if (!delegation.ok)
1870
+ return { ok: false, code: delegation.code, error: delegation.reason };
1871
+ const session = await this.sessionManager.getSessionById(sessionId);
1872
+ if (!session)
1873
+ return { ok: false, code: 'INVALID_SESSION', error: 'Invalid session' };
1874
+ const parsed = resolveManagedFsOperation(argv);
1875
+ if (!parsed.ok)
1876
+ return { ok: false, code: parsed.code, error: parsed.reason };
1877
+ const grant = delegation.grant;
1878
+ const actorAid = grant.selfAid.replace(/^@/, '');
1879
+ const sessionSelfAid = session.selfAID?.replace(/^@/, '');
1880
+ if (sessionSelfAid && sessionSelfAid !== actorAid) {
1881
+ return { ok: false, code: 'INVALID_DELEGATION', error: 'Delegation self agent does not match the active session' };
1882
+ }
1883
+ if (grant.channelType !== 'aun' || grant.chatType !== 'group') {
1884
+ return { ok: false, code: 'SCOPE_MISMATCH', error: 'Managed fs reads require the current AUN group relation' };
1885
+ }
1886
+ let currentGroup;
1887
+ try {
1888
+ currentGroup = parsePeerKey(grant.peerKey).channelId;
1889
+ }
1890
+ catch {
1891
+ return { ok: false, code: 'INVALID_DELEGATION', error: 'Delegation contains an invalid relation key' };
1892
+ }
1893
+ if ((parsed.command.actorAid && parsed.command.actorAid !== actorAid) || parsed.command.groupId !== currentGroup) {
1894
+ return { ok: false, code: 'SCOPE_MISMATCH', error: 'Only the current AUN group can be targeted' };
1895
+ }
1896
+ parsed.command.actorAid = actorAid;
1897
+ const subject = buildAuthSubject({
1898
+ selfAid: grant.selfAid,
1899
+ actorId: grant.actorId,
1900
+ channel: grant.channel,
1901
+ channelType: grant.channelType,
1902
+ channelId: currentGroup,
1903
+ chatType: grant.chatType,
1904
+ conversationId: currentGroup,
1905
+ processOwners: loadDaemonConfig().owners ?? [],
1906
+ });
1907
+ const decision = authorizeOperation({
1908
+ source: 'agent-tool',
1909
+ subject,
1910
+ intent: {
1911
+ operation: parsed.command.operationId,
1912
+ scope: 'relation',
1913
+ source: 'agent-tool',
1914
+ args: {
1915
+ self: actorAid,
1916
+ targetId: currentGroup,
1917
+ peer: currentGroup,
1918
+ peerKey: formatPeerKey('aun', currentGroup),
1919
+ },
1920
+ },
1921
+ auditMetadata: { argvHash: hashArgv(parsed.command.canonicalArgv), taskId: grant.taskId, messageId: grant.messageId },
1922
+ });
1923
+ if (!decision.allow)
1924
+ return { ok: false, code: decision.code, error: decision.reason };
1925
+ try {
1926
+ return { ok: true, result: await executeManagedFsOperation(parsed.command) };
1927
+ }
1928
+ catch (error) {
1929
+ return {
1930
+ ok: false,
1931
+ code: error instanceof ManagedFsOperationError ? error.code : 'FS_OPERATION_FAILED',
1932
+ error: error instanceof Error ? error.message : String(error),
1933
+ };
1934
+ }
1935
+ }
1572
1936
  setAgentDelegationRegistry(registry) {
1573
1937
  this.agentDelegationRegistry = registry;
1574
1938
  }
@@ -1576,7 +1940,7 @@ export class CommandHandler {
1576
1940
  * Agent ctl 入口:通过 IPC 接收 Agent 自主管理指令
1577
1941
  * 复用现有 slash cmd 逻辑,权限继承 session 用户角色
1578
1942
  */
1579
- async handleCtl(cmd, sessionId) {
1943
+ async handleCtl(cmd, sessionId, grant) {
1580
1944
  logger.info(`[ctl] cmd="${cmd}" sessionId=${sessionId}`);
1581
1945
  // 1. 白名单检查
1582
1946
  const inputCmd = cmd.split(' ')[0];
@@ -1588,9 +1952,56 @@ export class CommandHandler {
1588
1952
  if (!session) {
1589
1953
  return { ok: false, error: '无效的 session' };
1590
1954
  }
1955
+ if (!grant || grant.sessionId !== sessionId) {
1956
+ return { ok: false, code: 'DELEGATION_REQUIRED', error: 'An active task delegation grant is required' };
1957
+ }
1958
+ const sessionSelfAid = session.selfAID?.replace(/^@/, '');
1959
+ const grantSelfAid = grant.selfAid.replace(/^@/, '');
1960
+ if (sessionSelfAid && sessionSelfAid !== grantSelfAid) {
1961
+ return { ok: false, code: 'INVALID_DELEGATION', error: 'Delegation self agent does not match the active session' };
1962
+ }
1963
+ let conversationId;
1964
+ try {
1965
+ conversationId = parsePeerKey(grant.peerKey).channelId;
1966
+ }
1967
+ catch {
1968
+ return { ok: false, code: 'INVALID_DELEGATION', error: 'Delegation contains an invalid relation key' };
1969
+ }
1970
+ const ctlSubject = buildAuthSubject({
1971
+ selfAid: grant.selfAid,
1972
+ actorId: grant.actorId,
1973
+ channel: grant.channel,
1974
+ channelType: grant.channelType,
1975
+ channelId: conversationId,
1976
+ chatType: grant.chatType,
1977
+ conversationId,
1978
+ processOwners: loadDaemonConfig().owners ?? [],
1979
+ });
1980
+ const ctlIntent = this.resolveCtlIntent(cmd, grantSelfAid, grant.peerKey);
1981
+ if (!ctlIntent)
1982
+ return { ok: false, code: 'NOT_ALLOWED', error: `不允许的指令: ${inputCmd}` };
1983
+ const ctlDecision = authorizeOperation({
1984
+ source: 'agent-tool',
1985
+ subject: ctlSubject,
1986
+ intent: ctlIntent,
1987
+ auditMetadata: {
1988
+ argvHash: hashArgv(['ctl', ...cmd.slice(1).split(/\s+/).filter(Boolean)]),
1989
+ taskId: grant.taskId,
1990
+ messageId: grant.messageId,
1991
+ },
1992
+ });
1993
+ if (!ctlDecision.allow)
1994
+ return { ok: false, code: ctlDecision.code, error: ctlDecision.reason };
1995
+ if (cmd.startsWith('/model info ')) {
1996
+ const modelId = cmd.slice('/model info '.length).trim();
1997
+ if (!modelId || /\s/.test(modelId))
1998
+ return { ok: false, code: 'INVALID_ARGUMENT', error: 'model info requires one model id' };
1999
+ const info = await getModelInfo(modelId, grantSelfAid, session.baseagent);
2000
+ return { ok: true, result: JSON.stringify(info, null, 2), data: { ok: true, ...info } };
2001
+ }
1591
2002
  // 3. 从 session.metadata.peerId 获取 userId(用于权限判断)
1592
2003
  const userId = session.metadata?.peerId;
1593
- const ctlIdentity = this.resolveCtlIdentity(session, userId);
2004
+ const ctlIdentity = ctlSubject.identity;
1594
2005
  // 4. /send 文本消息:直接通过 adapter 主动发送,不走 handle()
1595
2006
  if (cmd.startsWith('/send ') || cmd === '/send') {
1596
2007
  // 解析 --encrypt 标志和消息文本
@@ -1625,9 +2036,9 @@ export class CommandHandler {
1625
2036
  const parts = sendArgs.split(/\s+/);
1626
2037
  const filePath = parts[parts.length - 1];
1627
2038
  if (filePath) {
1628
- const resolved = path.resolve(session.projectPath, filePath).replace(/\\/g, '/');
1629
- const projectPath = session.projectPath.replace(/\\/g, '/');
1630
- if (!resolved.startsWith(projectPath)) {
2039
+ const resolved = path.resolve(session.projectPath, filePath);
2040
+ const relative = path.relative(path.resolve(session.projectPath), resolved);
2041
+ if (relative.startsWith('..') || path.isAbsolute(relative)) {
1631
2042
  return { ok: false, error: '路径越界:只能发送项目目录下的文件' };
1632
2043
  }
1633
2044
  }
@@ -1640,7 +2051,7 @@ export class CommandHandler {
1640
2051
  // 6. 调用现有 handle(),不传 sendMessage 回调(结果直接返回)
1641
2052
  try {
1642
2053
  const result = await this._handleInternal(cmd, session.channel, session.channelId, undefined, // 不发送消息
1643
- userId, session.threadId || undefined, session.chatType, undefined, undefined, session.selfAID, ctlIdentity);
2054
+ userId, session.threadId || undefined, session.chatType, undefined, undefined, session.selfAID, ctlIdentity, ctlSubject);
1644
2055
  const text = typeof result === 'string' ? result : (result && 'text' in result ? result.text : '(无输出)');
1645
2056
  if (result && typeof result === 'object' && 'kind' in result && result.kind === 'command.error') {
1646
2057
  return { ok: false, error: text || '执行失败' };
@@ -1651,6 +2062,56 @@ export class CommandHandler {
1651
2062
  return { ok: false, error: err.message };
1652
2063
  }
1653
2064
  }
2065
+ resolveCtlIntent(cmd, selfAid, peerKey) {
2066
+ const [name = '', ...args] = cmd.trim().slice(1).trim().split(/\s+/);
2067
+ const relationArgs = { self: selfAid, peer: peerKey, peerKey };
2068
+ const intent = (operation, extra = {}) => ({
2069
+ operation,
2070
+ scope: 'relation',
2071
+ source: 'agent-tool',
2072
+ args: { ...relationArgs, ...extra },
2073
+ });
2074
+ if (name === 'send')
2075
+ return intent('ec.ctl.send');
2076
+ if (name === 'file')
2077
+ return intent('ec.ctl.file');
2078
+ if (name === 'status')
2079
+ return intent('session.current');
2080
+ if (name === 'pwd')
2081
+ return intent('project.current');
2082
+ if (name === 'name' || name === 'rename')
2083
+ return intent('session.rename');
2084
+ if (name === 'queue') {
2085
+ const action = args.includes('--clear') ? 'clear'
2086
+ : args.includes('--cancel') ? 'cancel'
2087
+ : args.includes('--interrupt') ? 'interrupt'
2088
+ : 'read';
2089
+ return intent(`ec.ctl.queue.${action}`);
2090
+ }
2091
+ if (name === 'model') {
2092
+ if (args.length === 0 || args[0] === 'list')
2093
+ return intent('model.list');
2094
+ if (args[0] === 'current')
2095
+ return intent('model.current');
2096
+ if (args[0] === 'info')
2097
+ return intent('model.info', { model: args[1] });
2098
+ if (args[0] === 'reset')
2099
+ return intent('model.reset');
2100
+ if (args[0] === 'check') {
2101
+ return { operation: 'model.check', scope: 'agent', source: 'agent-tool', args: { self: selfAid } };
2102
+ }
2103
+ return intent('model.use', { model: args[0] });
2104
+ }
2105
+ if (name === 'effort') {
2106
+ return args.length === 0
2107
+ ? intent('model.effort.current')
2108
+ : intent('model.effort', { effort: args[0] });
2109
+ }
2110
+ if (name === 'restart') {
2111
+ return { operation: 'system.restart', scope: 'process', source: 'agent-tool', args: { self: selfAid } };
2112
+ }
2113
+ return null;
2114
+ }
1654
2115
  // ── Queue command ──
1655
2116
  /** 提取命名参数值(如 --cancel msg123 → "msg123") */
1656
2117
  extractArg(args, flag) {
@@ -331,6 +331,21 @@ async function actionReviewRequest(req, self, context, decision) {
331
331
  decision,
332
332
  });
333
333
  if (reviewed.code === 'approved' || reviewed.code === 'rejected' || reviewed.code === 'blocked') {
334
+ if (reviewed.requestId && reviewed.expiresAt) {
335
+ try {
336
+ await context.notifyContactRequestReviewed?.({
337
+ applicantAid: primaryId,
338
+ requestId: reviewed.requestId,
339
+ expiresAt: reviewed.expiresAt,
340
+ status: reviewed.code === 'approved'
341
+ ? 'APPROVED'
342
+ : reviewed.code === 'rejected' ? 'REJECTED' : 'NOT_ALLOWED',
343
+ });
344
+ }
345
+ catch {
346
+ // Review state is authoritative; notification delivery is best-effort.
347
+ }
348
+ }
334
349
  return ok({
335
350
  self,
336
351
  action: `${decision}-request`,
@@ -1,6 +1,6 @@
1
1
  import crypto from 'crypto';
2
2
  import { ConfigTarget, mutateConfig, read as readConfig, resolveEffectiveFieldWithSource, } from '../../config/config-manager.js';
3
- import { dispatchToMentionMode } from '../../config/mention-mode.js';
3
+ import { normalizeMentionMode } from '../../config/mention-mode.js';
4
4
  import { readRolesConfig } from '../../config/roles.js';
5
5
  import { groupInfo, groupList } from '../../aun/msg/group.js';
6
6
  import { formatPeerKey, parsePeerKey } from '../relation/peer-identity.js';
@@ -156,9 +156,9 @@ async function resolveTarget(args, context) {
156
156
  scope,
157
157
  selector: { self: context.self, peerKey: formatPeerKey('aun', canonicalGroupId) },
158
158
  groupId: canonicalGroupId,
159
- aunMentionMode: dispatchToMentionMode(group.dispatch_mode
160
- ?? group.settings?.dispatch_mode
161
- ?? group.metadata?.dispatch_mode),
159
+ aunMentionMode: normalizeMentionMode(group.mention_mode)
160
+ ?? normalizeMentionMode(group.settings?.mention_mode)
161
+ ?? normalizeMentionMode(group.metadata?.mention_mode),
162
162
  };
163
163
  }
164
164
  function groupIdFromArgs(args, currentGroupId) {