evolcore 0.0.25 → 0.0.26

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 (231) hide show
  1. package/CHANGELOG.md +607 -565
  2. package/LICENSE +21 -21
  3. package/README.md +455 -455
  4. package/bin/codex-managed-acl.ps1 +160 -160
  5. package/bin/codex-managed-hook.mjs +208 -208
  6. package/bin/ec-safe-output.js +327 -327
  7. package/bin/ec.js +40 -29
  8. package/bin/install-codex-managed-hooks.mjs +582 -555
  9. package/dist/agents/baseagent.js +96 -46
  10. package/dist/agents/claude-runner.js +50 -12
  11. package/dist/agents/codex/app-server-client.js +13 -5
  12. package/dist/agents/codex/catalog.js +1 -1
  13. package/dist/agents/codex/diagnostics.js +34 -1
  14. package/dist/agents/codex-runner.js +121 -38
  15. package/dist/agents/ecagent-runner.js +4 -9
  16. package/dist/agents/gemini-runner.js +2 -7
  17. package/dist/agents/runner-types.js +25 -17
  18. package/dist/aun/aid/agentmd.js +81 -8
  19. package/dist/aun/aid/client.js +1 -1
  20. package/dist/aun/aid/identity.js +1 -1
  21. package/dist/aun/aid/index.js +1 -1
  22. package/dist/aun/aid/store.js +17 -13
  23. package/dist/aun/menu-client.js +74 -0
  24. package/dist/aun/msg/group.js +1 -1
  25. package/dist/aun/msg/managed-operation.js +1 -1
  26. package/dist/aun/msg/p2p.js +6 -1
  27. package/dist/aun/msg/upload.js +30 -22
  28. package/dist/channels/aun.js +329 -94
  29. package/dist/cli/agent-command.js +105 -84
  30. package/dist/cli/agent-create.js +112 -10
  31. package/dist/cli/agent.js +16 -5
  32. package/dist/cli/aun-commands.js +297 -296
  33. package/dist/cli/bench.js +20 -20
  34. package/dist/cli/config.js +47 -28
  35. package/dist/cli/contact.js +70 -13
  36. package/dist/cli/ctl-command.js +22 -22
  37. package/dist/cli/daemon-commands.js +105 -40
  38. package/dist/cli/diag.js +31 -0
  39. package/dist/cli/fs-command.js +67 -67
  40. package/dist/cli/handoff-command.js +41 -9
  41. package/dist/cli/index.js +83 -77
  42. package/dist/cli/link-rules.js +21 -21
  43. package/dist/cli/managed-command-guard.js +3 -0
  44. package/dist/cli/menu-client.js +46 -31
  45. package/dist/cli/net-check.js +22 -22
  46. package/dist/cli/queue-command.js +29 -29
  47. package/dist/cli/response.js +30 -30
  48. package/dist/cli/restart-monitor.js +87 -40
  49. package/dist/cli/stats.js +51 -51
  50. package/dist/cli/trigger-command.js +56 -56
  51. package/dist/config/builtin-roles.js +1 -0
  52. package/dist/config/config-operation-service.js +145 -0
  53. package/dist/config/contact-operation-service.js +96 -7
  54. package/dist/config/resolved-config-op.js +27 -7
  55. package/dist/core/agent-application-service.js +2 -1
  56. package/dist/core/auth/authorization-audit.js +20 -12
  57. package/dist/core/auth/operation-authorizer.js +25 -4
  58. package/dist/core/auth/operation-catalog.js +35 -4
  59. package/dist/core/auth/remote-access-store.js +40 -1
  60. package/dist/core/bootstrap-service.js +1 -1
  61. package/dist/core/command/command-handler.js +83 -26
  62. package/dist/core/command/diag-service.js +118 -0
  63. package/dist/core/command/menu-catalog.js +12 -3
  64. package/dist/core/command/menu-handler.js +47 -26
  65. package/dist/core/command/menu-protocol.js +5 -0
  66. package/dist/core/command/menu-token-store.js +41 -8
  67. package/dist/core/command/slash-gate.js +34 -4
  68. package/dist/core/command/slash-handler.js +86 -21
  69. package/dist/core/handoff/runtime.js +205 -27
  70. package/dist/core/handoff/store.js +193 -4
  71. package/dist/core/message/im-renderer.js +72 -21
  72. package/dist/core/message/message-bridge.js +69 -9
  73. package/dist/core/message/message-queue.js +26 -0
  74. package/dist/core/message/message-utils.js +1 -0
  75. package/dist/core/message/response-engine.js +47 -28
  76. package/dist/core/model/catalog-service.js +79 -49
  77. package/dist/core/model/context-window-suffix.js +33 -0
  78. package/dist/core/model/model-catalog.js +1 -1
  79. package/dist/core/model/model-permission.js +12 -3
  80. package/dist/core/permission/ec-command-parser.js +51 -12
  81. package/dist/core/permission/sandbox-runtime.js +36 -14
  82. package/dist/core/permission/tool-policy.js +2 -1
  83. package/dist/index.js +280 -163
  84. package/dist/ipc.js +54 -2
  85. package/dist/response-system/engines/v1/proactive-flow.js +59 -2
  86. package/dist/stats/billing.js +41 -15
  87. package/dist/stats/db.js +159 -159
  88. package/dist/stats/eck-vars.js +9 -9
  89. package/dist/stats/price-resolver.js +28 -10
  90. package/dist/stats/query.js +139 -139
  91. package/dist/stats/role-budget.js +6 -6
  92. package/dist/stats/writer.js +51 -51
  93. package/dist/utils/cross-platform.js +30 -1
  94. package/dist/utils/error-dict.json +160 -160
  95. package/dist/utils/error-utils.js +21 -0
  96. package/dist/utils/evolcore-version.js +129 -0
  97. package/dist/utils/logger.js +3 -1
  98. package/dist/utils/macos-autostart.js +22 -22
  99. package/dist/utils/model-prices.jsonl +23 -23
  100. package/dist/utils/npm-ops.js +176 -35
  101. package/dist/utils/welcome.js +22 -22
  102. package/ecagent/LICENSE +21 -21
  103. package/ecagent/NOTICE +11 -11
  104. package/ecagent/README.md +61 -61
  105. package/ecagent/dist/harness/compaction/branch-summarization.js +30 -30
  106. package/ecagent/dist/harness/compaction/compaction.js +83 -83
  107. package/ecagent/dist/harness/messages.js +7 -7
  108. package/kits/docs/GUIDE.md +20 -20
  109. package/kits/docs/INDEX.md +66 -66
  110. package/kits/docs/aun/CHEATSHEET.md +19 -19
  111. package/kits/docs/aun/SYNC_PROTOCOL.md +15 -15
  112. package/kits/docs/channels/aun.md +56 -56
  113. package/kits/docs/channels/feishu.md +56 -56
  114. package/kits/docs/context-assembly.md +364 -364
  115. package/kits/docs/eck_templates/GUIDE.template.md +22 -22
  116. package/kits/docs/eck_templates/INDEX.template.md +28 -28
  117. package/kits/docs/eck_templates/path-registry.template.md +33 -33
  118. package/kits/docs/eck_templates/runtime.template.md +19 -19
  119. package/kits/docs/evolcore/INDEX.md +71 -71
  120. package/kits/docs/evolcore/agent-create.md +170 -165
  121. package/kits/docs/evolcore/agent.md +66 -66
  122. package/kits/docs/evolcore/aid.md +60 -60
  123. package/kits/docs/evolcore/codex-windows-sandbox.md +146 -146
  124. package/kits/docs/evolcore/config.md +202 -195
  125. package/kits/docs/evolcore/contact.md +75 -68
  126. package/kits/docs/evolcore/ctl.md +46 -46
  127. package/kits/docs/evolcore/event.md +216 -216
  128. package/kits/docs/evolcore/fs-architecture.md +1215 -1215
  129. package/kits/docs/evolcore/fs.md +110 -110
  130. package/kits/docs/evolcore/group-collaboration.md +243 -243
  131. package/kits/docs/evolcore/group-fs.md +17 -17
  132. package/kits/docs/evolcore/group-rules.md +251 -251
  133. package/kits/docs/evolcore/group.md +152 -152
  134. package/kits/docs/evolcore/menu-client.md +42 -34
  135. package/kits/docs/evolcore/model.md +50 -50
  136. package/kits/docs/evolcore/msg.md +166 -165
  137. package/kits/docs/evolcore/response.md +75 -75
  138. package/kits/docs/evolcore/rpc.md +37 -37
  139. package/kits/docs/evolcore/self-summary.md +29 -29
  140. package/kits/docs/evolcore/stats.md +83 -83
  141. package/kits/docs/evolcore/storage.md +50 -50
  142. package/kits/docs/evolcore/trigger.md +575 -575
  143. package/kits/docs/identity/AID_PROFILE_SPEC.md +26 -26
  144. package/kits/docs/identity/PATH_OPS.md +16 -16
  145. package/kits/docs/identity/ROLE_DETAIL.md +25 -25
  146. package/kits/docs/identity/identity-tools.md +26 -26
  147. package/kits/docs/path-registry.md +62 -62
  148. package/kits/docs/prompt-loading-architecture.md +269 -269
  149. package/kits/docs/venues/aun-group.md +45 -45
  150. package/kits/docs/venues/aun-private.md +10 -10
  151. package/kits/docs/venues/client-desktop.md +10 -10
  152. package/kits/docs/venues/client-mobile.md +10 -10
  153. package/kits/docs/venues/feishu-group.md +13 -13
  154. package/kits/docs/venues/feishu-private.md +9 -9
  155. package/kits/docs/venues/group.md +37 -25
  156. package/kits/docs/venues/private.md +10 -10
  157. package/kits/eck_manifest.auxiliary.json +43 -43
  158. package/kits/eck_manifest.json +190 -190
  159. package/kits/eck_message_manifest.json +74 -74
  160. package/kits/migrations/README-role-config-v4.md +32 -32
  161. package/kits/migrations/migrate-contact-book-v2.mjs +748 -748
  162. package/kits/migrations/migrate-role-config-v4.mjs +614 -614
  163. package/kits/migrations/migrate-role-config-v5.mjs +365 -365
  164. package/kits/migrations/rename-config-file.mjs +99 -99
  165. package/kits/rules/01-overview.md +162 -162
  166. package/kits/rules/02-navigation.md +96 -96
  167. package/kits/rules/03-identity.md +36 -36
  168. package/kits/rules/04-relation.md +75 -75
  169. package/kits/rules/05-venue.md +60 -60
  170. package/kits/rules/06-channel.md +63 -63
  171. package/kits/schemas/_meta.json +53 -53
  172. package/kits/schemas/agent-config.schema.1.json +177 -177
  173. package/kits/schemas/agent-config.schema.10.json +395 -395
  174. package/kits/schemas/agent-config.schema.11.json +421 -421
  175. package/kits/schemas/agent-config.schema.12.json +427 -427
  176. package/kits/schemas/agent-config.schema.2.json +239 -239
  177. package/kits/schemas/agent-config.schema.3.json +119 -119
  178. package/kits/schemas/agent-config.schema.4.json +208 -208
  179. package/kits/schemas/agent-config.schema.5.json +326 -326
  180. package/kits/schemas/agent-config.schema.6.json +322 -322
  181. package/kits/schemas/agent-config.schema.7.json +303 -303
  182. package/kits/schemas/agent-config.schema.8.json +304 -304
  183. package/kits/schemas/agent-config.schema.9.json +364 -364
  184. package/kits/schemas/contact-book.schema.1.json +36 -36
  185. package/kits/schemas/contact-book.schema.2.json +43 -43
  186. package/kits/schemas/contact-book.schema.3.json +67 -67
  187. package/kits/schemas/daemon.schema.1.json +91 -91
  188. package/kits/schemas/daemon.schema.2.json +101 -101
  189. package/kits/schemas/daemon.schema.3.json +123 -123
  190. package/kits/schemas/daemon.schema.4.json +136 -136
  191. package/kits/schemas/daemon.schema.5.json +135 -135
  192. package/kits/schemas/daemon.schema.6.json +131 -131
  193. package/kits/schemas/defaults.schema.1.json +81 -81
  194. package/kits/schemas/defaults.schema.2.json +85 -85
  195. package/kits/schemas/defaults.schema.3.json +73 -73
  196. package/kits/schemas/defaults.schema.4.json +107 -107
  197. package/kits/schemas/defaults.schema.5.json +119 -119
  198. package/kits/schemas/menu-exec-schema-commands.md +208 -208
  199. package/kits/schemas/migrations/README.md +30 -30
  200. package/kits/schemas/relation-config.schema.1.json +158 -158
  201. package/kits/schemas/relation-config.schema.2.json +73 -73
  202. package/kits/schemas/relation-config.schema.3.json +50 -50
  203. package/kits/schemas/relation-config.schema.4.json +47 -47
  204. package/kits/schemas/relation-config.schema.5.json +47 -47
  205. package/kits/schemas/relation-config.schema.6.json +46 -46
  206. package/kits/schemas/relation-config.schema.7.json +59 -59
  207. package/kits/schemas/relation-config.schema.8.json +103 -103
  208. package/kits/schemas/role-config.schema.1.json +201 -201
  209. package/kits/schemas/role-registry.schema.1.json +35 -35
  210. package/kits/schemas/single-session.schema.1.json +31 -31
  211. package/kits/schemas/single-session.schema.2.json +57 -57
  212. package/kits/schemas/single-session.schema.3.json +32 -32
  213. package/kits/templates/bootstrap-welcome.md +17 -17
  214. package/kits/templates/message-fragments/handoff-context-to-target.md +9 -9
  215. package/kits/templates/message-fragments/handoff-request-to-target.md +19 -19
  216. package/kits/templates/message-fragments/handoff-response-to-origin.md +9 -9
  217. package/kits/templates/message-fragments/inject-default.md +2 -2
  218. package/kits/templates/message-fragments/item.md +2 -2
  219. package/kits/templates/roles/admin.json +92 -92
  220. package/kits/templates/roles/member.json +96 -96
  221. package/kits/templates/roles/owner.json +9 -9
  222. package/kits/templates/roles/visitor.json +85 -85
  223. package/kits/templates/system-fragments/baseagent.md +14 -14
  224. package/kits/templates/system-fragments/bootstrap.md +31 -29
  225. package/kits/templates/system-fragments/channel.md +64 -64
  226. package/kits/templates/system-fragments/commands.md +28 -28
  227. package/kits/templates/system-fragments/identity.md +13 -13
  228. package/kits/templates/system-fragments/relation.md +19 -19
  229. package/kits/templates/system-fragments/session.md +59 -59
  230. package/kits/templates/system-fragments/venue.md +31 -31
  231. package/package.json +100 -98
@@ -4,6 +4,20 @@ import { agentDataDir } from '../../paths.js';
4
4
  import { atomicReadJson, atomicWriteJson } from '../../utils/atomic-write.js';
5
5
  export const DEFAULT_MENU_TOKEN_IDLE_TIMEOUT_SECONDS = 30 * 24 * 60 * 60;
6
6
  export const MAX_MENU_TOKENS_PER_AGENT = 1_000;
7
+ function validInstallationId(value) {
8
+ return value === undefined || (typeof value === 'string' && value.length <= 256
9
+ && !/[\u0000-\u001f\u007f]/.test(value));
10
+ }
11
+ function normalizeInstallation(value) {
12
+ if (!value || typeof value !== 'object' || !validInstallationId(value.deviceId)
13
+ || !validInstallationId(value.slotId))
14
+ return null;
15
+ const deviceId = value.deviceId?.trim() || '';
16
+ const slotId = value.slotId?.trim() || '';
17
+ if (slotId && !deviceId)
18
+ return null;
19
+ return { deviceId, slotId };
20
+ }
7
21
  function hashToken(token) {
8
22
  return createHash('sha256').update(token).digest('hex');
9
23
  }
@@ -13,6 +27,9 @@ function isTokenRecord(value) {
13
27
  const record = value;
14
28
  return typeof record.peerAid === 'string'
15
29
  && record.peerAid.trim().length > 0
30
+ && validInstallationId(record.peerDeviceId)
31
+ && validInstallationId(record.peerSlotId)
32
+ && (!record.peerSlotId?.trim() || !!record.peerDeviceId?.trim())
16
33
  && typeof record.tokenHash === 'string'
17
34
  && /^[a-f0-9]{64}$/.test(record.tokenHash)
18
35
  && typeof record.requestedAt === 'number'
@@ -28,6 +45,10 @@ function isTokenRecord(value) {
28
45
  function isExpired(record, now) {
29
46
  return now - record.lastUsedAt >= record.idleTimeout * 1000;
30
47
  }
48
+ function installationMatches(record, installation) {
49
+ return (record.peerDeviceId?.trim() || '') === (installation.deviceId?.trim() || '')
50
+ && (record.peerSlotId?.trim() || '') === (installation.slotId?.trim() || '');
51
+ }
31
52
  function hashesEqual(left, right) {
32
53
  const leftBytes = Buffer.from(left, 'hex');
33
54
  const rightBytes = Buffer.from(right, 'hex');
@@ -41,10 +62,14 @@ export class MenuTokenStore {
41
62
  constructor(maxRecords = MAX_MENU_TOKENS_PER_AGENT) {
42
63
  this.maxRecords = maxRecords;
43
64
  }
44
- issue(selfAid, peerAid, metadata = {}, now = Date.now()) {
65
+ issue(selfAid, peerAid, metadata = {}, installationOrNow = {}, now = Date.now()) {
66
+ const installation = normalizeInstallation(typeof installationOrNow === 'number' ? {} : installationOrNow);
67
+ if (!installation)
68
+ return null;
69
+ const issuedAt = typeof installationOrNow === 'number' ? installationOrNow : now;
45
70
  const file = this.read(selfAid);
46
71
  const idleTimeout = DEFAULT_MENU_TOKEN_IDLE_TIMEOUT_SECONDS;
47
- const tokens = file.tokens.filter(record => record.peerAid !== peerAid && !isExpired(record, now));
72
+ const tokens = file.tokens.filter(record => (record.peerAid !== peerAid || !installationMatches(record, installation)) && !isExpired(record, issuedAt));
48
73
  if (tokens.length >= this.maxRecords) {
49
74
  if (tokens.length !== file.tokens.length)
50
75
  this.write(selfAid, tokens);
@@ -53,9 +78,11 @@ export class MenuTokenStore {
53
78
  const menuToken = `mt_${randomBytes(32).toString('base64url')}`;
54
79
  tokens.push({
55
80
  peerAid,
81
+ ...(installation.deviceId?.trim() ? { peerDeviceId: installation.deviceId.trim() } : {}),
82
+ ...(installation.slotId?.trim() ? { peerSlotId: installation.slotId.trim() } : {}),
56
83
  tokenHash: hashToken(menuToken),
57
- requestedAt: now,
58
- lastUsedAt: now,
84
+ requestedAt: issuedAt,
85
+ lastUsedAt: issuedAt,
59
86
  idleTimeout,
60
87
  ...(metadata.clientName ? { clientName: metadata.clientName } : {}),
61
88
  ...(metadata.clientVersion ? { clientVersion: metadata.clientVersion } : {}),
@@ -64,21 +91,27 @@ export class MenuTokenStore {
64
91
  this.write(selfAid, tokens);
65
92
  return { menuToken, idleTimeout };
66
93
  }
67
- use(selfAid, peerAid, menuToken, now = Date.now()) {
94
+ use(selfAid, peerAid, menuToken, installationOrNow = {}, now = Date.now()) {
95
+ const installation = normalizeInstallation(typeof installationOrNow === 'number' ? {} : installationOrNow);
96
+ if (!installation)
97
+ return false;
98
+ const usedAt = typeof installationOrNow === 'number' ? installationOrNow : now;
68
99
  const file = this.read(selfAid);
69
- const activeTokens = file.tokens.filter(record => !isExpired(record, now));
100
+ const activeTokens = file.tokens.filter(record => !isExpired(record, usedAt));
70
101
  const tokenHash = typeof menuToken === 'string' && menuToken.length > 0 && menuToken.length <= 256
71
102
  ? hashToken(menuToken)
72
103
  : undefined;
73
104
  const matchingIndex = tokenHash
74
- ? activeTokens.findIndex(record => record.peerAid === peerAid && hashesEqual(record.tokenHash, tokenHash))
105
+ ? activeTokens.findIndex(record => record.peerAid === peerAid
106
+ && installationMatches(record, installation)
107
+ && hashesEqual(record.tokenHash, tokenHash))
75
108
  : -1;
76
109
  if (matchingIndex < 0) {
77
110
  if (activeTokens.length !== file.tokens.length)
78
111
  this.write(selfAid, activeTokens);
79
112
  return false;
80
113
  }
81
- activeTokens[matchingIndex] = { ...activeTokens[matchingIndex], lastUsedAt: now };
114
+ activeTokens[matchingIndex] = { ...activeTokens[matchingIndex], lastUsedAt: usedAt };
82
115
  this.write(selfAid, activeTokens);
83
116
  return true;
84
117
  }
@@ -45,7 +45,8 @@ export function probeSessionActivity(evidence, probes) {
45
45
  // 支持的命令列表
46
46
  const commands = ['/new', '/pwd', '/help', '/evolhelp', '/status', '/restart', '/reload', '/model', '/effort', '/baseagent', '/slist', '/session', '/rename', '/stop', '/pause', '/compact', '/renew', '/repair', '/fork', '/del', '/perm', '/file', '/check', '/rewind', '/activity', '/observable', '/chatmode', '/mentionmode', '/ask', '/resume', '/aid', '/rpc', '/storage', '/agent', '/trigger', '/upgrade', '/pair'];
47
47
  const deprecatedCommands = ['/clear'];
48
- const exactBoundaryCommands = new Set(['/pause', '/resume', '/stop', '/pair']);
48
+ commands.push('/diag');
49
+ const exactBoundaryCommands = new Set(['/pause', '/resume', '/stop', '/pair', '/diag']);
49
50
  /** Parse the execution command without treating it as a quick/result command. */
50
51
  export function parseFullAccessCommand(content) {
51
52
  const match = content.match(/^\s*(\/fa|\/fullaccess)(?=\s|$)([\s\S]*)$/);
@@ -64,8 +65,37 @@ const aliases = {
64
65
  '/rw': '/rewind',
65
66
  '/base': '/baseagent',
66
67
  };
68
+ const GROUP_READ_ONLY_COMMANDS = new Set(['/mentionmode', '/model', '/effort', '/perm', '/baseagent']);
69
+ const GROUP_TASK_COMMANDS = new Set([
70
+ '/help', '/evolhelp', '/status', '/check', '/ask', '/pause', '/resume', '/stop', '/compact', '/renew',
71
+ ]);
72
+ /** Group slash policy applies to every role, including old command-card clicks. */
73
+ export function guardGroupSlashCommand(content, chatType) {
74
+ if (chatType !== 'group' || !content.trim().startsWith('/'))
75
+ return undefined;
76
+ const [inputCommand, ...args] = content.trim().split(/\s+/);
77
+ const command = aliases[inputCommand] ?? inputCommand;
78
+ if (GROUP_READ_ONLY_COMMANDS.has(command)) {
79
+ if (!args.length || (command === '/model' && args.length === 1 && ['current', 'list', 'check'].includes(args[0]))) {
80
+ return undefined;
81
+ }
82
+ return {
83
+ kind: 'command.error',
84
+ text: `❌ 群聊中 ${command} 仅支持查询,请在私聊或管理界面操作`,
85
+ reason: 'GROUP_COMMAND_READ_ONLY',
86
+ };
87
+ }
88
+ if (GROUP_TASK_COMMANDS.has(command))
89
+ return undefined;
90
+ return {
91
+ kind: 'command.error',
92
+ text: `❌ 群聊中不支持 ${inputCommand},输入 /help 查看可用命令`,
93
+ reason: 'GROUP_COMMAND_UNSUPPORTED',
94
+ };
95
+ }
67
96
  // 命令快速路径前缀(所有命令都不进入消息队列)
68
97
  const quickCommandPrefixes = ['/new', '/pwd', '/help', '/evolhelp', '/status', '/restart', '/reload', '/model', '/effort', '/baseagent', '/slist', '/session', '/rename', '/repair', '/fork', '/stop', '/pause', '/clear', '/compact', '/renew', '/del', '/perm', '/file', '/check', '/s ', '/name', '/rewind', '/rw', '/rw ', '/activity', '/observable', '/chatmode', '/mentionmode', '/ask', '/resume', '/base ', '/aid', '/rpc', '/storage', '/agent', '/trigger', '/upgrade', '/pair'];
98
+ quickCommandPrefixes.push('/diag');
69
99
  /**
70
100
  * 计算两个字符串的 Levenshtein 距离(编辑距离)
71
101
  */
@@ -95,7 +125,7 @@ function levenshteinDistance(str1, str2) {
95
125
  return matrix[len1][len2];
96
126
  }
97
127
  export function isQuickCommand(content) {
98
- return content === '/s' || quickCommandPrefixes.some(cmd => exactBoundaryCommands.has(cmd)
128
+ return content === '/s' || content === '/base' || quickCommandPrefixes.some(cmd => exactBoundaryCommands.has(cmd)
99
129
  ? content === cmd || content.startsWith(cmd + ' ')
100
130
  : content.startsWith(cmd));
101
131
  }
@@ -131,7 +161,7 @@ export function guardRoleCommand(content, activeChatType, isAdmin) {
131
161
  const userGroupCommands = [
132
162
  '/status', '/help', '/evolhelp', '/check', '/chatmode', '/mentionmode',
133
163
  '/model', '/effort', '/baseagent', '/perm', '/activity', '/stop', '/pause',
134
- '/resume', '/trigger', '/file',
164
+ '/resume', '/trigger', '/file', '/ask',
135
165
  ];
136
166
  const userCommands = activeChatType === 'group' && !isAdmin
137
167
  ? userGroupCommands
@@ -146,7 +176,7 @@ export function guardRoleCommand(content, activeChatType, isAdmin) {
146
176
  return {
147
177
  kind: 'command.error',
148
178
  text: activeChatType === 'group'
149
- ? '❌ 无权限:当前群聊仅支持 /status 和 /help'
179
+ ? '❌ 无权限:此命令仅限管理员使用,输入 /help 查看可用命令'
150
180
  : '❌ 无权限:此命令仅限管理员使用',
151
181
  };
152
182
  }
@@ -28,7 +28,7 @@ import { isSystemControlChannel } from '../system-channels.js';
28
28
  import { launchMonitorAndConfirm, resolveMonitorLaunch, cleanupMonitorStage } from '../../utils/cross-platform.js';
29
29
  import { scanInstances } from '../../utils/instance-registry.js';
30
30
  import { inspectDataMigrationRequirement } from '../data-migration.js';
31
- import { guardIdleCommand, guardKnownCommand, guardRoleCommand, guardThreadCommand, isRecognizedSlashCommand, normalizeSlashContent, probeSessionActivity, } from './slash-gate.js';
31
+ import { guardIdleCommand, guardGroupSlashCommand, guardKnownCommand, guardRoleCommand, guardThreadCommand, isRecognizedSlashCommand, normalizeSlashContent, probeSessionActivity, } from './slash-gate.js';
32
32
  const allEfforts = ['none', 'minimal', 'low', 'medium', 'high', 'xhigh', 'max'];
33
33
  const PERMISSION_MODE_KEYS = PUBLIC_PERMISSION_MODES;
34
34
  function defaultSlashChatmode(field) {
@@ -368,7 +368,7 @@ async function getGitWorkingDirInfo(projectPath) {
368
368
  return null; // git 命令失败时静默返回 null
369
369
  }
370
370
  }
371
- export async function handleSlashCommand(content, channel, channelId, sendMessage, userId, threadId, chatType, source, messageId, selfAID, overrideIdentity, overrideSubject, replyContext) {
371
+ export async function handleSlashCommand(content, channel, channelId, sendMessage, userId, threadId, chatType, source, messageId, selfAID, overrideIdentity, overrideSubject, replyContext, applyGroupSlashPolicy = false) {
372
372
  // 卡片回调的 chatType 不可靠(飞书 bot 单聊 chatId 也是 oc_ 前缀),
373
373
  // 不应覆盖 session 中已有的正确值
374
374
  if (source === 'card-trigger')
@@ -434,9 +434,18 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
434
434
  logger.warn(`[CommandHandler] topic session lookup failed: thread=${threadId}: ${error instanceof Error ? error.message : String(error)}`);
435
435
  }
436
436
  }
437
- const activeChatType = topicAuthSession?.chatType
437
+ const activeChatType = (applyGroupSlashPolicy
438
+ ? replyContext?.delivery?.chatType || overrideSubject?.chatType || chatType
439
+ : undefined)
440
+ || topicAuthSession?.chatType
438
441
  || activeSession?.chatType
439
442
  || (chatType === 'group' ? 'group' : 'private');
443
+ const groupSlash = applyGroupSlashPolicy && activeChatType === 'group';
444
+ if (applyGroupSlashPolicy) {
445
+ const groupGuard = guardGroupSlashCommand(normalizedContent, activeChatType);
446
+ if (groupGuard)
447
+ return groupGuard;
448
+ }
440
449
  const getExistingSessionForCommand = async () => {
441
450
  if (threadId) {
442
451
  if (topicAuthSessionLookupFailed)
@@ -485,7 +494,8 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
485
494
  || normalizedContent === '/upgrade'
486
495
  || normalizedContent === '/reload'
487
496
  || normalizedContent.startsWith('/reload ')
488
- || normalizedContent === '/pair';
497
+ || normalizedContent === '/pair'
498
+ || /^\/diag(?:\s|$)/.test(normalizedContent);
489
499
  const roleGuard = guardRoleCommand(normalizedContent, activeChatType, isAdmin || (isDaemonOwner && isProcessLevelSlash));
490
500
  if (roleGuard)
491
501
  return roleGuard;
@@ -617,6 +627,30 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
617
627
  };
618
628
  // /help 命令不需要会话
619
629
  if (normalizedContent === '/help') {
630
+ if (groupSlash) {
631
+ const lines = [
632
+ '群聊可用命令:',
633
+ ' /help - 显示帮助',
634
+ ' /status - 显示会话状态',
635
+ ' /check - 检查 Agent 健康',
636
+ ' /model [current|list|check] - 查询模型或检查网关',
637
+ ' /effort - 查看推理强度',
638
+ ' /baseagent - 查看 Agent 后端',
639
+ ' /perm - 查看权限模式',
640
+ ' /mentionmode - 查看群聊 @ 处理模式',
641
+ ' /ask <选项> - 回答待处理问题',
642
+ ' /pause - 在下一次工具调用前暂停任务',
643
+ ' /resume - 继续已暂停的任务',
644
+ ' /stop - 中断当前任务',
645
+ ...(isAdmin ? [
646
+ ' /compact - 压缩会话上下文',
647
+ ...(threadId ? [' /renew - 轮换当前话题的后端会话'] : []),
648
+ ] : []),
649
+ '',
650
+ '模型、推理强度、后端、权限与 @ 模式在群聊中仅支持查询。',
651
+ ];
652
+ return { kind: 'command.result', text: lines.join('\n') };
653
+ }
620
654
  const canReadModel = canAccessSlashOperation('model.current', 'relation')
621
655
  || canAccessSlashOperation('model.list', 'relation');
622
656
  const canUseModel = canAccessSlashOperation('model.use', 'relation');
@@ -633,7 +667,7 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
633
667
  const appendProcessCommands = (lines) => {
634
668
  if (!isDaemonOwner)
635
669
  return;
636
- lines.push('', '🛠️ 进程级运维:', ' /restart - 重启服务', ' /upgrade - 检查版本更新', ' /reload [aid] - 热重载 Agent 配置', ' /fa <提示词> - 单次使用宿主级 fullaccess 执行(别名: /fullaccess)', ' /pair - 获取远程控制配对码');
670
+ lines.push('', '🛠️ 进程级运维:', ' /restart - 重启服务', ' /upgrade - 检查版本更新', ' /reload [aid] - 热重载 Agent 配置', ' /fa <提示词> - 单次使用宿主级 fullaccess 执行(别名: /fullaccess)', ' /pair - 获取远程控制配对码', ' /diag <问题描述> - 邀请远程诊断;/diag stop 撤销授权');
637
671
  };
638
672
  if (!isAdmin && activeChatType === 'group') {
639
673
  const lines = [
@@ -773,6 +807,14 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
773
807
  ];
774
808
  return { kind: 'command.result', text: lines.join('\n') };
775
809
  }
810
+ if (/^\/diag(?:\s|$)/.test(normalizedContent)) {
811
+ if (!isDaemonOwner || !userId || !this.diagService)
812
+ return { kind: 'command.error', text: '❌ /diag 需要本机 DaemonOwner 和可用的诊断入口' };
813
+ const problem = content.trim().slice(5).trim();
814
+ const result = await this.diagService.execute(problem.toLowerCase() === 'stop'
815
+ ? { action: 'stop' } : { action: 'start', problem }, userId);
816
+ return { kind: result.ok ? 'command.result' : 'command.error', text: result.message };
817
+ }
776
818
  // /pair is also recognized on an Agent channel so it cannot fall through
777
819
  // into model input. The daemon control channel has its own fast path; this
778
820
  // branch preserves the same daemon-owner boundary for ordinary AUN chats.
@@ -792,6 +834,26 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
792
834
  }
793
835
  // /evolhelp 命令:返回 JSON 格式的命令列表(供程序解析)
794
836
  if (normalizedContent === '/evolhelp') {
837
+ if (groupSlash) {
838
+ const commands = [
839
+ { command: '/help', description: '显示帮助信息', category: '帮助', roles: ['visitor', 'member', 'admin', 'owner'] },
840
+ { command: '/status', description: '显示会话状态', category: '运维', roles: ['visitor', 'member', 'admin', 'owner'] },
841
+ { command: '/check', description: '检查 EvolAgent 实例健康', category: '运维', roles: ['visitor', 'member', 'admin', 'owner'] },
842
+ { command: '/model', args: '[current|list|check]', description: '查询模型或检查模型网关', category: 'Agent 与模型', roles: [identity.role] },
843
+ { command: '/effort', description: '查看推理强度', category: 'Agent 与模型', roles: [identity.role] },
844
+ { command: '/baseagent', description: '查看 Agent 后端', category: 'Agent 与模型', roles: [identity.role] },
845
+ { command: '/perm', description: '查看当前权限模式', category: '权限管理', roles: [identity.role] },
846
+ { command: '/mentionmode', description: '查看群聊 @ 处理模式', category: '聊天设置', roles: [identity.role] },
847
+ { command: '/ask', args: '<选项>', description: '回答 Agent 的交互式问题', category: '运维', roles: ['visitor', 'member', 'admin', 'owner'] },
848
+ { command: '/pause', description: '在下一次工具调用前暂停当前任务', category: '运维', roles: ['admin', 'owner'] },
849
+ { command: '/resume', description: '继续已暂停的当前任务', category: '运维', roles: ['admin', 'owner'] },
850
+ { command: '/stop', description: '中断当前任务', category: '运维', roles: ['admin', 'owner'] },
851
+ ];
852
+ if (isAdmin) {
853
+ commands.push({ command: '/compact', description: '压缩会话上下文', category: '会话管理', roles: ['admin', 'owner'] }, ...(threadId ? [{ command: '/renew', description: '轮换当前话题 backend', category: '会话管理', roles: ['admin', 'owner'] }] : []));
854
+ }
855
+ return { kind: 'command.result', text: JSON.stringify({ commands, categories: [...new Set(commands.map(c => c.category))] }) };
856
+ }
795
857
  const cmds = [];
796
858
  const canReadModel = canAccessSlashOperation('model.current', 'relation')
797
859
  || canAccessSlashOperation('model.list', 'relation');
@@ -851,6 +913,7 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
851
913
  cmds.push({ command: '/upgrade', description: '检查版本更新', category: '运维', roles: ['daemon-owner'] });
852
914
  cmds.push({ command: '/reload', args: '[aid]', description: '热重载 Agent 配置', category: '运维', roles: ['daemon-owner'] });
853
915
  cmds.push({ command: '/pair', description: '获取远程控制配对码', category: '运维', roles: ['daemon-owner'] });
916
+ cmds.push({ command: '/diag', args: '<问题描述>|stop', description: '邀请远程诊断或撤销授权', category: '运维', roles: ['daemon-owner'] });
854
917
  }
855
918
  if (!isDaemonOwner && isAdmin) {
856
919
  cmds.push({ command: '/reload', description: '热重载当前 Agent 配置', category: '运维', roles: ['admin', 'owner'] });
@@ -1063,7 +1126,7 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
1063
1126
  if (!args) {
1064
1127
  const activeBaseagent = owningAgent?.baseagent || this.parseDefaultBaseagent();
1065
1128
  // 尝试发送 CommandCard 卡片
1066
- if (this.interactionRouter && available.length > 1) {
1129
+ if (!groupSlash && this.interactionRouter && available.length > 1) {
1067
1130
  const baseagentSession = await getExistingSessionForCommand();
1068
1131
  const interaction = {
1069
1132
  type: 'interaction',
@@ -1090,7 +1153,7 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
1090
1153
  }
1091
1154
  // 降级:文本
1092
1155
  const list = available.map((a) => `${a === activeBaseagent ? ' ✓' : ' '} ${a}`).join('\n');
1093
- const canSwitchAgent = isOwner;
1156
+ const canSwitchAgent = isOwner && !groupSlash;
1094
1157
  if (canSwitchAgent) {
1095
1158
  return { kind: 'command.result', text: `当前 Baseagent: ${activeBaseagent}\n\n可用:\n${list}\n用法: /baseagent <name>` };
1096
1159
  }
@@ -1348,7 +1411,7 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
1348
1411
  isDaemonOwner,
1349
1412
  });
1350
1413
  // 尝试发送 CommandCard 卡片
1351
- if (!args && canReadModelList && this.interactionRouter && models.length > 0) {
1414
+ if (!groupSlash && !args && canReadModelList && this.interactionRouter && models.length > 0) {
1352
1415
  const interaction = {
1353
1416
  type: 'interaction',
1354
1417
  id: `model-${Date.now()}-${crypto.randomBytes(4).toString('hex')}`,
@@ -1379,7 +1442,7 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
1379
1442
  const catalogHint = `[${catalog.source}/${catalog.freshness}] ${catalog.diagnostics.map(d => d.message).join('; ')}`;
1380
1443
  const modelList = models.map((m) => ` ${modelMatches(modelAgent, m, currentModel) ? '✓' : ' '} ${modelDisplayLabel(modelAgent, m)}`).join('\n');
1381
1444
  const effortHint = efforts.length > 0
1382
- ? `\n推理强度: ${currentEffort === 'auto' ? 'auto (SDK默认)' : currentEffort} (使用 /effort 调整)`
1445
+ ? `\n推理强度: ${currentEffort === 'auto' ? 'auto (SDK默认)' : currentEffort}${groupSlash ? '' : ' (使用 /effort 调整)'}`
1383
1446
  : '';
1384
1447
  if (args === 'current') {
1385
1448
  return {
@@ -1389,7 +1452,7 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
1389
1452
  };
1390
1453
  }
1391
1454
  if (canReadModelList) {
1392
- const usage = !args && isAdmin ? '\n\n用法: /model <模型>' : '';
1455
+ const usage = !groupSlash && !args && isAdmin ? '\n\n用法: /model <模型>' : '';
1393
1456
  return {
1394
1457
  kind: 'command.result',
1395
1458
  text: `当前模型: ${modelDisplayLabel(modelAgent, currentModel)}${effortHint}${rawModels.includes(currentModel) ? '' : ' [当前配置,未列入目录]'}\n${catalogHint}\n\n可用模型:\n${modelList}${usage}`,
@@ -1659,7 +1722,7 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
1659
1722
  return { kind: 'command.error', text: '⚠️ 当前模型不支持推理强度设置' };
1660
1723
  }
1661
1724
  // /effort(无参数):显示当前推理强度 + 发送 CommandCard 卡片
1662
- if (this.interactionRouter) {
1725
+ if (!groupSlash && this.interactionRouter) {
1663
1726
  const allItems = [...efforts, 'auto'];
1664
1727
  const interaction = {
1665
1728
  type: 'interaction',
@@ -1687,7 +1750,7 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
1687
1750
  // 降级:文本
1688
1751
  const effortDisplay = currentEffort === 'auto' ? 'auto (SDK默认)' : currentEffort;
1689
1752
  const effortOptions = [...efforts, 'auto'].join(' / ');
1690
- if (isAdmin) {
1753
+ if (isAdmin && !groupSlash) {
1691
1754
  return { kind: 'command.result', text: `推理强度: ${effortDisplay} 可选: ${effortOptions} 用法: /effort <level>` };
1692
1755
  }
1693
1756
  return { kind: 'command.result', text: `推理强度: ${effortDisplay}` };
@@ -2109,6 +2172,8 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
2109
2172
  const currentMode = readSlashMentionMode(mentionTarget, mentionFallback);
2110
2173
  if (!arg) {
2111
2174
  const displayMode = currentMode ?? '未设置(跟随群设置)';
2175
+ if (groupSlash)
2176
+ return { kind: 'command.result', text: `@ 处理模式: ${displayMode}` };
2112
2177
  // 尝试发送 CommandCard 卡片
2113
2178
  if (isAdmin) {
2114
2179
  const modes = [
@@ -3223,10 +3288,10 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
3223
3288
  // /slist cli — 仅 CLI 会话(未导入的)
3224
3289
  if (normalizedContent === '/slist' || normalizedContent === '/slist cli') {
3225
3290
  if (!session) {
3226
- return { kind: 'command.error', text: `❌ 当前没有活跃会话
3227
-
3228
- 请先执行以下操作之一:
3229
- 1. 发送任意消息 - 自动创建新会话
3291
+ return { kind: 'command.error', text: `❌ 当前没有活跃会话
3292
+
3293
+ 请先执行以下操作之一:
3294
+ 1. 发送任意消息 - 自动创建新会话
3230
3295
  2. /new [名称] - 创建命名会话` };
3231
3296
  }
3232
3297
  const showCliOnly = normalizedContent === '/slist cli';
@@ -3497,11 +3562,11 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
3497
3562
  if (!newName)
3498
3563
  return { kind: 'command.result', text: '用法: /name <新名称> 或 /rename <新名称>' };
3499
3564
  if (!session) {
3500
- return { kind: 'command.error', text: `❌ 当前没有活跃会话
3501
-
3502
- 请先执行以下操作之一:
3503
- 1. 发送任意消息 - 自动创建新会话
3504
- 2. /new [名称] - 创建命名会话
3565
+ return { kind: 'command.error', text: `❌ 当前没有活跃会话
3566
+
3567
+ 请先执行以下操作之一:
3568
+ 1. 发送任意消息 - 自动创建新会话
3569
+ 2. /new [名称] - 创建命名会话
3505
3570
  3. /session <名称> - 切换到已有会话` };
3506
3571
  }
3507
3572
  const existing = await this.sessionManager.getSessionByName(channel, channelId, newName);