evolcore 0.0.25 → 0.0.27

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (304) hide show
  1. package/CHANGELOG.md +632 -565
  2. package/LICENSE +21 -21
  3. package/README.md +455 -455
  4. package/bin/codex-managed-acl.ps1 +203 -160
  5. package/bin/codex-managed-hook.mjs +208 -208
  6. package/bin/ec-safe-output.js +327 -327
  7. package/bin/ec.js +40 -29
  8. package/bin/install-codex-managed-hooks.mjs +582 -555
  9. package/dist/agents/baseagent-availability.js +68 -0
  10. package/dist/agents/baseagent.js +96 -46
  11. package/dist/agents/claude-runner.js +172 -33
  12. package/dist/agents/codex/app-server-client.js +76 -31
  13. package/dist/agents/codex/catalog.js +1 -1
  14. package/dist/agents/codex/diagnostics.js +34 -1
  15. package/dist/agents/codex-runner.js +434 -446
  16. package/dist/agents/ecagent-runner.js +174 -34
  17. package/dist/agents/gemini-runner.js +8 -7
  18. package/dist/agents/runner-types.js +75 -19
  19. package/dist/aun/aid/agentmd.js +151 -36
  20. package/dist/aun/aid/client.js +1 -1
  21. package/dist/aun/aid/control-aid.js +180 -1
  22. package/dist/aun/aid/identity.js +4 -4
  23. package/dist/aun/aid/index.js +1 -1
  24. package/dist/aun/aid/store.js +17 -13
  25. package/dist/aun/group/history.js +303 -0
  26. package/dist/aun/group/identity.js +43 -0
  27. package/dist/aun/group/index.js +5 -0
  28. package/dist/aun/{msg/group.js → group/operations.js} +162 -39
  29. package/dist/aun/{menu-client.js → menu/client.js} +85 -6
  30. package/dist/aun/menu/index.js +2 -0
  31. package/dist/aun/menu/transport.js +49 -0
  32. package/dist/aun/msg/index.js +2 -1
  33. package/dist/aun/msg/managed-operation.js +85 -19
  34. package/dist/aun/msg/mention-schema.js +51 -0
  35. package/dist/aun/msg/p2p.js +19 -9
  36. package/dist/aun/msg/upload.js +43 -80
  37. package/dist/aun/outbox.js +29 -7
  38. package/dist/aun/rpc/client.js +1 -0
  39. package/dist/aun/storage/client.js +39 -50
  40. package/dist/channels/aun.js +842 -318
  41. package/dist/channels/daemon.js +10 -6
  42. package/dist/cli/agent-command.js +212 -89
  43. package/dist/cli/agent-create.js +626 -11
  44. package/dist/cli/agent.js +125 -13
  45. package/dist/cli/aun-commands.js +487 -337
  46. package/dist/cli/bench.js +20 -20
  47. package/dist/cli/config.js +55 -33
  48. package/dist/cli/contact.js +72 -15
  49. package/dist/cli/ctl-command.js +24 -24
  50. package/dist/cli/daemon-commands.js +140 -63
  51. package/dist/cli/diag.js +31 -0
  52. package/dist/cli/fs-command.js +70 -70
  53. package/dist/cli/handoff-command.js +46 -14
  54. package/dist/cli/index.js +91 -79
  55. package/dist/cli/init.js +34 -1
  56. package/dist/cli/link-rules.js +21 -21
  57. package/dist/cli/managed-command-guard.js +5 -1
  58. package/dist/cli/menu-client.js +51 -32
  59. package/dist/cli/model.js +3 -3
  60. package/dist/cli/net-check.js +22 -22
  61. package/dist/cli/queue-command.js +31 -31
  62. package/dist/cli/response.js +32 -32
  63. package/dist/cli/restart-monitor.js +103 -42
  64. package/dist/cli/stats.js +51 -51
  65. package/dist/cli/task-command.js +59 -0
  66. package/dist/cli/task-context.js +97 -49
  67. package/dist/cli/trigger-command.js +74 -64
  68. package/dist/config/builtin-roles.js +11 -0
  69. package/dist/config/config-field-policy.js +11 -0
  70. package/dist/config/config-manager.js +32 -4
  71. package/dist/config/config-operation-service.js +160 -0
  72. package/dist/config/contact-book-store.js +1 -1
  73. package/dist/config/contact-book.js +1 -1
  74. package/dist/config/contact-operation-service.js +97 -8
  75. package/dist/config/peer-role-resolver.js +1 -1
  76. package/dist/config/resolved-config-op.js +30 -7
  77. package/dist/config-store.js +1 -1
  78. package/dist/core/agent-application-service.js +7 -1
  79. package/dist/core/audit/event-key.js +17 -2
  80. package/dist/core/audit/log-integrity.js +34 -8
  81. package/dist/core/auth/agent-delegation.js +48 -14
  82. package/dist/core/auth/auth-gateway.js +2 -0
  83. package/dist/core/auth/authorization-audit.js +53 -49
  84. package/dist/core/auth/operation-authorizer.js +90 -7
  85. package/dist/core/auth/operation-catalog.js +145 -4
  86. package/dist/core/auth/remote-access-store.js +155 -10
  87. package/dist/core/bootstrap-messages.js +66 -7
  88. package/dist/core/bootstrap-service.js +111 -20
  89. package/dist/core/command/agent-control.js +88 -7
  90. package/dist/core/command/agent-storage-menu.js +443 -0
  91. package/dist/core/command/cli-intent-parser.js +19 -0
  92. package/dist/core/command/command-handler.js +159 -65
  93. package/dist/core/command/data-root-resolver.js +273 -0
  94. package/dist/core/command/diag-service.js +118 -0
  95. package/dist/core/command/group-menu.js +7 -2
  96. package/dist/core/command/menu-catalog.js +46 -7
  97. package/dist/core/command/menu-handler.js +392 -100
  98. package/dist/core/command/menu-protocol.js +45 -6
  99. package/dist/core/command/menu-token-store.js +41 -8
  100. package/dist/core/command/role-menu.js +2 -2
  101. package/dist/core/command/slash-gate.js +34 -4
  102. package/dist/core/command/slash-handler.js +123 -43
  103. package/dist/core/event-catalog.js +4 -0
  104. package/dist/core/evolagent-registry.js +28 -2
  105. package/dist/core/group/invite-service.js +924 -0
  106. package/dist/core/handoff/runtime.js +205 -27
  107. package/dist/core/handoff/store.js +193 -4
  108. package/dist/core/interaction-router.js +22 -8
  109. package/dist/core/message/im-renderer.js +146 -34
  110. package/dist/core/message/message-bridge.js +163 -20
  111. package/dist/core/message/message-log.js +5 -3
  112. package/dist/core/message/message-queue.js +99 -41
  113. package/dist/core/message/message-utils.js +1 -0
  114. package/dist/core/message/response-engine.js +755 -144
  115. package/dist/core/model/catalog-service.js +79 -49
  116. package/dist/core/model/context-window-suffix.js +33 -0
  117. package/dist/core/model/model-catalog.js +1 -1
  118. package/dist/core/model/model-permission.js +12 -3
  119. package/dist/core/permission/approval-gateway.js +36 -3
  120. package/dist/core/permission/ec-command-parser.js +112 -41
  121. package/dist/core/permission/protected-paths.js +134 -16
  122. package/dist/core/permission/sandbox-runtime.js +108 -17
  123. package/dist/core/permission/tool-error-code.js +15 -0
  124. package/dist/core/permission/tool-policy.js +9 -8
  125. package/dist/core/relation/peer-identity.js +57 -6
  126. package/dist/core/relation/shared-data.js +106 -0
  127. package/dist/core/role/runtime-policy.js +64 -0
  128. package/dist/core/runtime-lock.js +10 -26
  129. package/dist/core/session/runner-task-history.js +170 -0
  130. package/dist/core/session/session-manager.js +34 -6
  131. package/dist/core/session/session-mapper.js +41 -0
  132. package/dist/core/session/session-turn-coordinator.js +37 -7
  133. package/dist/core/session/session-turns.js +48 -7
  134. package/dist/core/task/task-recovery-service.js +176 -0
  135. package/dist/core/task/task-resume-operation.js +32 -0
  136. package/dist/eck/group-rules-sync.js +5 -3
  137. package/dist/eck/kit-renderer.js +1 -0
  138. package/dist/index.js +588 -217
  139. package/dist/ipc.js +193 -15
  140. package/dist/paths.js +17 -4
  141. package/dist/product.js +2 -0
  142. package/dist/response-system/engines/v1/proactive-flow.js +59 -2
  143. package/dist/stats/billing.js +41 -15
  144. package/dist/stats/db.js +159 -159
  145. package/dist/stats/eck-vars.js +9 -9
  146. package/dist/stats/price-resolver.js +28 -10
  147. package/dist/stats/query.js +139 -139
  148. package/dist/stats/role-budget.js +6 -6
  149. package/dist/stats/writer.js +51 -51
  150. package/dist/trigger/event-source.js +19 -0
  151. package/dist/trigger/feedback.js +233 -40
  152. package/dist/trigger/parser.js +55 -5
  153. package/dist/trigger/patch.js +9 -0
  154. package/dist/trigger/scheduler.js +30 -4
  155. package/dist/trigger/validation.js +20 -3
  156. package/dist/utils/cross-platform.js +30 -1
  157. package/dist/utils/ecweb-utils.js +1 -0
  158. package/dist/utils/error-dict.json +160 -160
  159. package/dist/utils/error-utils.js +61 -2
  160. package/dist/utils/evolcore-version.js +129 -0
  161. package/dist/utils/logger.js +26 -14
  162. package/dist/utils/macos-autostart.js +22 -22
  163. package/dist/utils/model-prices.jsonl +23 -23
  164. package/dist/utils/npm-ops.js +176 -35
  165. package/dist/utils/tool-summary.js +59 -3
  166. package/dist/utils/welcome.js +196 -23
  167. package/ecagent/LICENSE +21 -21
  168. package/ecagent/NOTICE +11 -11
  169. package/ecagent/README.md +61 -61
  170. package/ecagent/dist/harness/agent-harness.js +3 -0
  171. package/ecagent/dist/harness/compaction/branch-summarization.js +30 -30
  172. package/ecagent/dist/harness/compaction/compaction.js +83 -83
  173. package/ecagent/dist/harness/messages.js +7 -7
  174. package/ecagent/dist/harness/session/jsonl-storage.js +21 -2
  175. package/kits/docs/GUIDE.md +20 -20
  176. package/kits/docs/INDEX.md +60 -66
  177. package/kits/docs/aun/CHEATSHEET.md +19 -19
  178. package/kits/docs/aun/SYNC_PROTOCOL.md +15 -15
  179. package/kits/docs/channels/aun.md +69 -56
  180. package/kits/docs/channels/feishu.md +56 -56
  181. package/kits/docs/context-assembly.md +379 -367
  182. package/kits/docs/eck_templates/GUIDE.template.md +22 -22
  183. package/kits/docs/eck_templates/INDEX.template.md +28 -28
  184. package/kits/docs/eck_templates/path-registry.template.md +33 -33
  185. package/kits/docs/eck_templates/runtime.template.md +19 -19
  186. package/kits/docs/evolcore/INDEX.md +76 -71
  187. package/kits/docs/evolcore/agent-create.md +202 -165
  188. package/kits/docs/evolcore/agent.md +99 -67
  189. package/kits/docs/evolcore/aid.md +68 -60
  190. package/kits/docs/evolcore/codex-windows-sandbox.md +146 -146
  191. package/kits/docs/evolcore/config.md +241 -195
  192. package/kits/docs/evolcore/contact.md +75 -68
  193. package/kits/docs/evolcore/ctl.md +46 -46
  194. package/kits/docs/evolcore/event.md +216 -216
  195. package/kits/docs/evolcore/fs-architecture.md +1215 -1215
  196. package/kits/docs/evolcore/fs.md +110 -110
  197. package/kits/docs/evolcore/group-collaboration.md +244 -244
  198. package/kits/docs/evolcore/group-fs.md +17 -17
  199. package/kits/docs/evolcore/group-rules.md +251 -251
  200. package/kits/docs/evolcore/group.md +267 -152
  201. package/kits/docs/evolcore/menu-client.md +58 -34
  202. package/kits/docs/evolcore/model.md +50 -50
  203. package/kits/docs/evolcore/msg.md +169 -165
  204. package/kits/docs/evolcore/response.md +75 -75
  205. package/kits/docs/evolcore/rpc.md +37 -37
  206. package/kits/docs/evolcore/self-summary.md +29 -29
  207. package/kits/docs/evolcore/stats.md +83 -83
  208. package/kits/docs/evolcore/storage.md +53 -50
  209. package/kits/docs/evolcore/task.md +14 -0
  210. package/kits/docs/evolcore/trigger.md +623 -575
  211. package/kits/docs/identity/AID_PROFILE_SPEC.md +26 -26
  212. package/kits/docs/identity/PATH_OPS.md +16 -16
  213. package/kits/docs/identity/ROLE_DETAIL.md +25 -25
  214. package/kits/docs/identity/identity-tools.md +26 -26
  215. package/kits/docs/path-registry.md +62 -62
  216. package/kits/docs/prompt-loading-architecture.md +269 -269
  217. package/kits/docs/venues/aun-group.md +45 -45
  218. package/kits/docs/venues/aun-private.md +10 -10
  219. package/kits/docs/venues/client-desktop.md +10 -10
  220. package/kits/docs/venues/client-mobile.md +10 -10
  221. package/kits/docs/venues/feishu-group.md +13 -13
  222. package/kits/docs/venues/feishu-private.md +9 -9
  223. package/kits/docs/venues/group.md +42 -25
  224. package/kits/docs/venues/private.md +10 -10
  225. package/kits/eck_manifest.auxiliary.json +43 -43
  226. package/kits/eck_manifest.json +217 -190
  227. package/kits/eck_message_manifest.json +74 -74
  228. package/kits/migrations/README-role-config-v4.md +32 -32
  229. package/kits/migrations/migrate-contact-book-v2.mjs +748 -748
  230. package/kits/migrations/migrate-role-config-v4.mjs +614 -614
  231. package/kits/migrations/migrate-role-config-v5.mjs +365 -365
  232. package/kits/migrations/rename-config-file.mjs +99 -99
  233. package/kits/rules/01-overview.md +166 -162
  234. package/kits/rules/02-navigation.md +96 -96
  235. package/kits/rules/03-identity.md +36 -36
  236. package/kits/rules/04-relation.md +76 -75
  237. package/kits/rules/05-venue.md +60 -60
  238. package/kits/rules/06-channel.md +65 -63
  239. package/kits/schemas/_meta.json +53 -53
  240. package/kits/schemas/agent-config.schema.1.json +177 -177
  241. package/kits/schemas/agent-config.schema.10.json +395 -395
  242. package/kits/schemas/agent-config.schema.11.json +421 -421
  243. package/kits/schemas/agent-config.schema.12.json +451 -427
  244. package/kits/schemas/agent-config.schema.2.json +239 -239
  245. package/kits/schemas/agent-config.schema.3.json +119 -119
  246. package/kits/schemas/agent-config.schema.4.json +208 -208
  247. package/kits/schemas/agent-config.schema.5.json +326 -326
  248. package/kits/schemas/agent-config.schema.6.json +322 -322
  249. package/kits/schemas/agent-config.schema.7.json +303 -303
  250. package/kits/schemas/agent-config.schema.8.json +304 -304
  251. package/kits/schemas/agent-config.schema.9.json +364 -364
  252. package/kits/schemas/contact-book.schema.1.json +36 -36
  253. package/kits/schemas/contact-book.schema.2.json +43 -43
  254. package/kits/schemas/contact-book.schema.3.json +67 -67
  255. package/kits/schemas/daemon.schema.1.json +91 -91
  256. package/kits/schemas/daemon.schema.2.json +101 -101
  257. package/kits/schemas/daemon.schema.3.json +123 -123
  258. package/kits/schemas/daemon.schema.4.json +136 -136
  259. package/kits/schemas/daemon.schema.5.json +135 -135
  260. package/kits/schemas/daemon.schema.6.json +131 -131
  261. package/kits/schemas/defaults.schema.1.json +81 -81
  262. package/kits/schemas/defaults.schema.2.json +85 -85
  263. package/kits/schemas/defaults.schema.3.json +73 -73
  264. package/kits/schemas/defaults.schema.4.json +107 -107
  265. package/kits/schemas/defaults.schema.5.json +119 -119
  266. package/kits/schemas/menu-exec-schema-commands.md +208 -208
  267. package/kits/schemas/migrations/README.md +30 -30
  268. package/kits/schemas/relation-config.schema.1.json +158 -158
  269. package/kits/schemas/relation-config.schema.2.json +73 -73
  270. package/kits/schemas/relation-config.schema.3.json +50 -50
  271. package/kits/schemas/relation-config.schema.4.json +47 -47
  272. package/kits/schemas/relation-config.schema.5.json +47 -47
  273. package/kits/schemas/relation-config.schema.6.json +46 -46
  274. package/kits/schemas/relation-config.schema.7.json +59 -59
  275. package/kits/schemas/relation-config.schema.8.json +103 -103
  276. package/kits/schemas/role-config.schema.1.json +215 -201
  277. package/kits/schemas/role-registry.schema.1.json +35 -35
  278. package/kits/schemas/single-session.schema.1.json +31 -31
  279. package/kits/schemas/single-session.schema.2.json +57 -57
  280. package/kits/schemas/single-session.schema.3.json +32 -32
  281. package/kits/templates/bootstrap-welcome.md +17 -17
  282. package/kits/templates/message-fragments/handoff-context-to-target.md +9 -9
  283. package/kits/templates/message-fragments/handoff-request-to-target.md +19 -19
  284. package/kits/templates/message-fragments/handoff-response-to-origin.md +9 -9
  285. package/kits/templates/message-fragments/inject-default.md +2 -2
  286. package/kits/templates/message-fragments/item.md +2 -2
  287. package/kits/templates/roles/admin.json +92 -92
  288. package/kits/templates/roles/member.json +101 -96
  289. package/kits/templates/roles/owner.json +9 -9
  290. package/kits/templates/roles/visitor.json +90 -85
  291. package/kits/templates/system-fragments/baseagent.md +14 -14
  292. package/kits/templates/system-fragments/bootstrap.md +53 -34
  293. package/kits/templates/system-fragments/channel.md +28 -64
  294. package/kits/templates/system-fragments/commands.md +36 -28
  295. package/kits/templates/system-fragments/group-unread-history.md +7 -0
  296. package/kits/templates/system-fragments/identity.md +13 -13
  297. package/kits/templates/system-fragments/relation.md +19 -19
  298. package/kits/templates/system-fragments/reply.md +56 -0
  299. package/kits/templates/system-fragments/session.md +59 -59
  300. package/kits/templates/system-fragments/venue.md +31 -31
  301. package/package.json +104 -98
  302. package/dist/aun/group-identity.js +0 -9
  303. /package/dist/aun/{group-fs-download.js → group/fs-download.js} +0 -0
  304. /package/dist/aun/{msg/group-index.js → group/index-rpc.js} +0 -0
@@ -8,11 +8,119 @@
8
8
  */
9
9
  import fs from 'fs';
10
10
  import path from 'path';
11
- import { execFile, execSync } from 'child_process';
11
+ import { execFile, execFileSync } from 'child_process';
12
12
  import { promisify } from 'util';
13
13
  import { getPackageRoot } from '../paths.js';
14
14
  import { isWindows } from './cross-platform.js';
15
15
  const execFileAsync = promisify(execFile);
16
+ /** The preferred public registry for update checks. */
17
+ export const DEFAULT_NPM_REGISTRY = 'https://registry.npmjs.org';
18
+ function normalizeRegistry(registry = DEFAULT_NPM_REGISTRY) {
19
+ const value = registry.trim();
20
+ return (value || DEFAULT_NPM_REGISTRY).replace(/\/+$/, '');
21
+ }
22
+ /**
23
+ * Resolve npm from the same Node installation that is running EvolCore.
24
+ * This prevents a daemon launched by one NVM installation from querying or
25
+ * mutating another installation selected by the shell's PATH.
26
+ */
27
+ function resolveNpmInvocation() {
28
+ const nodeDir = path.dirname(process.execPath);
29
+ const sibling = path.join(nodeDir, isWindows ? 'npm.cmd' : 'npm');
30
+ const cliCandidates = [
31
+ path.join(nodeDir, 'node_modules', 'npm', 'bin', 'npm-cli.js'),
32
+ ];
33
+ if (fs.existsSync(sibling)) {
34
+ try {
35
+ const target = fs.realpathSync(sibling);
36
+ if (target.endsWith('.js'))
37
+ cliCandidates.unshift(target);
38
+ }
39
+ catch { /* fall through to the known npm layouts */ }
40
+ }
41
+ const npmCli = cliCandidates.find(candidate => fs.existsSync(candidate));
42
+ if (npmCli)
43
+ return { command: process.execPath, argsPrefix: [npmCli] };
44
+ if (isWindows) {
45
+ return {
46
+ command: process.env.ComSpec || path.join(process.env.SystemRoot || 'C:\\Windows', 'System32', 'cmd.exe'),
47
+ argsPrefix: ['/c', 'npm'],
48
+ };
49
+ }
50
+ return { command: fs.existsSync(sibling) ? sibling : 'npm', argsPrefix: [] };
51
+ }
52
+ /**
53
+ * Read the effective registry from the npm belonging to the current Node.
54
+ * An empty value means npm has no explicit registry and uses the public
55
+ * default. A command failure is handled separately by using the public source
56
+ * directly, so a broken npm config cannot leave the upgrade without a retry.
57
+ */
58
+ async function resolveUpgradeRegistry(signal) {
59
+ const npm = resolveNpmInvocation();
60
+ try {
61
+ const result = await execFileAsync(npm.command, [...npm.argsPrefix, 'config', 'get', 'registry'], {
62
+ timeout: 5000,
63
+ windowsHide: isWindows,
64
+ signal,
65
+ });
66
+ const value = String(result.stdout ?? '').trim();
67
+ if (!value || value === 'undefined' || value === 'null') {
68
+ return { registry: DEFAULT_NPM_REGISTRY, useNpmCli: false };
69
+ }
70
+ return { registry: normalizeRegistry(value), useNpmCli: true };
71
+ }
72
+ catch {
73
+ return { registry: DEFAULT_NPM_REGISTRY, useNpmCli: false };
74
+ }
75
+ }
76
+ async function checkLatestVersionViaNpm(pkg, timeoutMs, signal, registry) {
77
+ const npm = resolveNpmInvocation();
78
+ try {
79
+ const result = await execFileAsync(npm.command, [
80
+ ...npm.argsPrefix,
81
+ 'view',
82
+ `${pkg}@latest`,
83
+ 'version',
84
+ '--json',
85
+ '--registry',
86
+ registry,
87
+ ], {
88
+ timeout: timeoutMs ?? 10000,
89
+ windowsHide: isWindows,
90
+ signal,
91
+ });
92
+ const raw = String(result.stdout ?? '').trim();
93
+ if (!raw)
94
+ return null;
95
+ const parsed = JSON.parse(raw);
96
+ if (typeof parsed === 'string' && parsed)
97
+ return parsed;
98
+ if (Array.isArray(parsed)) {
99
+ const latest = parsed.at(-1);
100
+ return typeof latest === 'string' && latest ? latest : null;
101
+ }
102
+ return null;
103
+ }
104
+ catch {
105
+ return null;
106
+ }
107
+ }
108
+ // Registry checks can run concurrently, but two package upgrades must never
109
+ // mutate npm's global tree at the same time. A tiny in-process queue keeps the
110
+ // fast path parallel without changing upgrade ordering or failure semantics.
111
+ let globalInstallTail = Promise.resolve();
112
+ async function withGlobalInstallLock(action) {
113
+ const previous = globalInstallTail;
114
+ let release;
115
+ globalInstallTail = new Promise(resolve => { release = resolve; });
116
+ await previous;
117
+ try {
118
+ await action();
119
+ }
120
+ finally {
121
+ release();
122
+ }
123
+ }
16
124
  // ── npm install -g (shared) ────────────────────────────────────────────────
17
125
  /**
18
126
  * Can we run sudo without a password prompt?
@@ -35,20 +143,11 @@ async function canSudoNonInteractively() {
35
143
  }
36
144
  }
37
145
  export async function npmInstallGlobal(pkg, options = {}) {
38
- let cmd;
39
- let args;
40
- // Windows: run via cmd /c to avoid shell:true deprecation warning on Node 22.
41
- // Unix: npm directly, no shell needed.
42
- if (isWindows) {
43
- cmd = process.env.ComSpec || path.join(process.env.SystemRoot || 'C:\\Windows', 'System32', 'cmd.exe');
44
- args = ['/c', 'npm', 'install', '-g', pkg];
45
- }
46
- else {
47
- cmd = 'npm';
48
- args = ['install', '-g', pkg];
49
- }
146
+ const npm = resolveNpmInvocation();
147
+ const registry = options.registry ? normalizeRegistry(options.registry) : undefined;
148
+ const npmArgs = ['install', '-g', pkg, ...(registry ? ['--registry', registry] : [])];
50
149
  try {
51
- await execFileAsync(cmd, args, {
150
+ await execFileAsync(npm.command, [...npm.argsPrefix, ...npmArgs], {
52
151
  timeout: options.timeoutMs ?? 180000,
53
152
  windowsHide: isWindows,
54
153
  signal: options.signal,
@@ -62,9 +161,10 @@ export async function npmInstallGlobal(pkg, options = {}) {
62
161
  throw new Error('权限不足。请以管理员身份运行 PowerShell 或 CMD,然后重试');
63
162
  }
64
163
  if (!await canSudoNonInteractively()) {
65
- throw new Error(`权限不足,且 sudo 需要密码(当前进程无法交互输入)。请手动执行: sudo npm install -g ${pkg}`);
164
+ const registryArg = registry ? ` --registry ${registry}` : '';
165
+ throw new Error(`权限不足,且 sudo 需要密码(当前进程无法交互输入)。请使用当前 Node 对应的 npm 手动执行: sudo npm install -g ${pkg}${registryArg}`);
66
166
  }
67
- await execFileAsync('sudo', ['-n', 'npm', 'install', '-g', pkg], {
167
+ await execFileAsync('sudo', ['-n', npm.command, ...npm.argsPrefix, ...npmArgs], {
68
168
  timeout: options.timeoutMs ?? 180000,
69
169
  windowsHide: false,
70
170
  signal: options.signal,
@@ -132,10 +232,8 @@ export function getLocalVersion() {
132
232
  */
133
233
  export function resolveGlobalPkg(pkgName) {
134
234
  try {
135
- // 命令完全静态(npm root -g,无用户输入),用 execSync 传完整命令字符串,
136
- // 避免 "args 数组 + shell:true" 组合触发 Node DeprecationWarning,且无注入风险。
137
- const npmCmd = isWindows ? 'npm.cmd' : 'npm';
138
- const globalRoot = execSync(`${npmCmd} root -g`, {
235
+ const npm = resolveNpmInvocation();
236
+ const globalRoot = execFileSync(npm.command, [...npm.argsPrefix, 'root', '-g'], {
139
237
  encoding: 'utf-8', timeout: 10000, windowsHide: true,
140
238
  }).trim();
141
239
  const pkgPath = path.join(globalRoot, ...pkgName.split('/'), 'package.json');
@@ -152,7 +250,7 @@ export function resolveGlobalPkg(pkgName) {
152
250
  * 使用 HTTP fetch 直接查 registry API,不依赖 npm CLI。
153
251
  * 超时 10 秒,失败返回 null。
154
252
  */
155
- export async function checkLatestVersion(pkg = 'evolcore', timeoutMs = 10000, signal) {
253
+ async function fetchLatestVersion(pkg = 'evolcore', timeoutMs = 10000, signal, registry = DEFAULT_NPM_REGISTRY) {
156
254
  const controller = new AbortController();
157
255
  const abort = () => controller.abort();
158
256
  if (signal?.aborted)
@@ -161,14 +259,14 @@ export async function checkLatestVersion(pkg = 'evolcore', timeoutMs = 10000, si
161
259
  signal?.addEventListener('abort', abort, { once: true });
162
260
  const timer = setTimeout(abort, timeoutMs);
163
261
  try {
164
- const res = await fetch(`https://registry.npmjs.org/${pkg}/latest`, {
262
+ const res = await fetch(`${normalizeRegistry(registry)}/${encodeURIComponent(pkg)}/latest`, {
165
263
  signal: controller.signal,
166
264
  headers: { 'Accept': 'application/json' },
167
265
  });
168
266
  if (!res.ok)
169
267
  return null;
170
268
  const data = await res.json();
171
- return data.version || null;
269
+ return typeof data.version === 'string' && data.version ? data.version : null;
172
270
  }
173
271
  catch {
174
272
  return null;
@@ -178,6 +276,31 @@ export async function checkLatestVersion(pkg = 'evolcore', timeoutMs = 10000, si
178
276
  signal?.removeEventListener('abort', abort);
179
277
  }
180
278
  }
279
+ /**
280
+ * Read the current npm registry first. If that read succeeds, npm itself is
281
+ * used for the version query so proxy/CA/auth settings are preserved. If the
282
+ * read fails, retry once against the official HTTP endpoint. The selected
283
+ * registry is returned so installation uses the same source.
284
+ */
285
+ async function checkLatestVersionWithFallback(pkg, timeoutMs, signal, explicitRegistry) {
286
+ if (explicitRegistry) {
287
+ const registry = normalizeRegistry(explicitRegistry);
288
+ const version = await fetchLatestVersion(pkg, timeoutMs, signal, registry);
289
+ return version ? { version, registry } : null;
290
+ }
291
+ const selection = await resolveUpgradeRegistry(signal);
292
+ const version = selection.useNpmCli
293
+ ? await checkLatestVersionViaNpm(pkg, timeoutMs, signal, selection.registry)
294
+ : await fetchLatestVersion(pkg, timeoutMs, signal, selection.registry);
295
+ return version ? { version, registry: selection.registry } : null;
296
+ }
297
+ /**
298
+ * Query the registry selected from the running Node/npm configuration, or the
299
+ * official registry when that configuration cannot be read.
300
+ */
301
+ export async function checkLatestVersion(pkg = 'evolcore', timeoutMs = 10000, signal, registry) {
302
+ return (await checkLatestVersionWithFallback(pkg, timeoutMs, signal, registry))?.version ?? null;
303
+ }
181
304
  /**
182
305
  * 完整升级流程:检查 → 比较 → 安装(失败重试一次)
183
306
  */
@@ -188,12 +311,13 @@ export async function tryUpgrade(options = {}) {
188
311
  }
189
312
  const localVer = getLocalVersion();
190
313
  // 查询 registry
191
- const remoteVer = await checkLatestVersion('evolcore', options.checkTimeoutMs, options.signal);
192
- if (!remoteVer) {
314
+ const checked = await checkLatestVersionWithFallback('evolcore', options.checkTimeoutMs, options.signal, options.registry);
315
+ if (!checked) {
193
316
  return options.signal?.aborted
194
317
  ? { status: 'failed', from: localVer, error: 'Upgrade cancelled or timed out' }
195
318
  : { status: 'skipped', error: 'Failed to check remote version' };
196
319
  }
320
+ const { version: remoteVer, registry } = checked;
197
321
  // 版本比较
198
322
  if (compareVersions(localVer, remoteVer) >= 0) {
199
323
  return { status: 'no-update', from: localVer };
@@ -202,11 +326,19 @@ export async function tryUpgrade(options = {}) {
202
326
  let lastError;
203
327
  for (let attempt = 0; attempt < 2; attempt++) {
204
328
  try {
205
- await npmInstallGlobal('evolcore@latest', {
206
- timeoutMs: options.installTimeoutMs,
207
- signal: options.signal,
329
+ let installedVer;
330
+ await withGlobalInstallLock(async () => {
331
+ await npmInstallGlobal(`evolcore@${remoteVer}`, {
332
+ timeoutMs: options.installTimeoutMs,
333
+ signal: options.signal,
334
+ registry,
335
+ });
336
+ installedVer = getLocalVersion();
208
337
  });
209
- return { status: 'upgraded', from: localVer, to: remoteVer };
338
+ if (!installedVer || compareVersions(installedVer, remoteVer) < 0) {
339
+ throw new Error(`npm install completed but evolcore is still ${installedVer ?? 'not installed'} (target ${remoteVer})`);
340
+ }
341
+ return { status: 'upgraded', from: localVer, to: installedVer };
210
342
  }
211
343
  catch (e) {
212
344
  lastError = e.stderr || e.message || String(e);
@@ -230,23 +362,32 @@ export async function tryUpgradeGlobalPkg(resolveInstalled, pkgName, options = {
230
362
  return { status: 'skipped' }; // not installed, skip
231
363
  }
232
364
  const localVer = installed.version;
233
- const remoteVer = await checkLatestVersion(pkgName, options.checkTimeoutMs, options.signal);
234
- if (!remoteVer) {
365
+ const checked = await checkLatestVersionWithFallback(pkgName, options.checkTimeoutMs, options.signal, options.registry);
366
+ if (!checked) {
235
367
  return options.signal?.aborted
236
368
  ? { status: 'failed', from: localVer, error: 'Upgrade cancelled or timed out' }
237
369
  : { status: 'skipped', error: 'Failed to check remote version' };
238
370
  }
371
+ const { version: remoteVer, registry } = checked;
239
372
  if (compareVersions(localVer, remoteVer) >= 0) {
240
373
  return { status: 'no-update', from: localVer };
241
374
  }
242
375
  let lastError;
243
376
  for (let attempt = 0; attempt < 2; attempt++) {
244
377
  try {
245
- await npmInstallGlobal(`${pkgName}@latest`, {
246
- timeoutMs: options.installTimeoutMs,
247
- signal: options.signal,
378
+ let installedVer;
379
+ await withGlobalInstallLock(async () => {
380
+ await npmInstallGlobal(`${pkgName}@${remoteVer}`, {
381
+ timeoutMs: options.installTimeoutMs,
382
+ signal: options.signal,
383
+ registry,
384
+ });
385
+ installedVer = resolveInstalled()?.version;
248
386
  });
249
- return { status: 'upgraded', from: localVer, to: remoteVer };
387
+ if (!installedVer || compareVersions(installedVer, remoteVer) < 0) {
388
+ throw new Error(`npm install completed but ${pkgName} is still ${installedVer ?? 'not installed'} (target ${remoteVer})`);
389
+ }
390
+ return { status: 'upgraded', from: localVer, to: installedVer };
250
391
  }
251
392
  catch (e) {
252
393
  lastError = e.stderr || e.message || String(e);
@@ -51,19 +51,75 @@ function displayShellCommand(command) {
51
51
  const carrier = resolveCodexShellCarrierString(command);
52
52
  return carrier?.dialect === 'powershell' ? carrier.command : undefined;
53
53
  }
54
+ /** Hide an initial cd through its first unquoted && or ;, regardless of the directory. */
55
+ function displayBashCommand(command) {
56
+ const prefix = command.match(/^[ \t]*cd(?=[ \t;]|&&)/);
57
+ if (!prefix)
58
+ return command;
59
+ let quote;
60
+ for (let index = prefix[0].length; index < command.length; index++) {
61
+ const char = command[index];
62
+ if (quote === "'") {
63
+ if (char === quote)
64
+ quote = undefined;
65
+ continue;
66
+ }
67
+ if (char === '\\') {
68
+ index++;
69
+ continue;
70
+ }
71
+ // Nested shell expressions need a full shell parser; keep them visible.
72
+ if (char === '`' || (char === '$' && command[index + 1] === '('))
73
+ return command;
74
+ if (quote === '"') {
75
+ if (char === quote)
76
+ quote = undefined;
77
+ continue;
78
+ }
79
+ if (char === "'" || char === '"') {
80
+ quote = char;
81
+ continue;
82
+ }
83
+ if (char === ';' || (char === '&' && command[index + 1] === '&')) {
84
+ if (char === ';' && /[;&]/.test(command[index + 1] ?? ''))
85
+ return command;
86
+ const body = command.slice(index + (char === ';' ? 1 : 2)).trimStart();
87
+ return body || command;
88
+ }
89
+ // Do not skip over other commands, redirections, or shell comments.
90
+ if (/[\r\n|&<>()]/.test(char) || (char === '#' && /[ \t]/.test(command[index - 1])))
91
+ return command;
92
+ }
93
+ return command;
94
+ }
54
95
  /**
55
96
  * Build the tool arguments projected into activity/thought payloads.
56
97
  * Execution, permission checks, and audit logging keep using the original
57
- * input; only the human-facing Shell command is de-wrapped here.
98
+ * input; only the human-facing command is simplified here.
58
99
  */
59
100
  export function toolInputForDisplay(toolName, input) {
60
- if (!input || toolName !== 'Shell')
101
+ if (!input || typeof input.command !== 'string')
102
+ return input;
103
+ let command;
104
+ if (toolName === 'Shell') {
105
+ command = displayShellCommand(input.command) ?? input.command;
106
+ // Keep explicit shell invocations, but omit standard executable directories.
107
+ command = command.replace(/^(\s*)(['"]?)\/(?:usr\/)?bin\/(bash|sh)\2(?=\s|$)/, '$1$2$3$2');
108
+ }
109
+ else if (toolName === 'Bash') {
110
+ command = displayBashCommand(input.command);
111
+ }
112
+ else {
61
113
  return input;
62
- const command = displayShellCommand(input.command);
114
+ }
63
115
  if (!command || command === input.command)
64
116
  return input;
65
117
  return { ...input, command };
66
118
  }
119
+ /** Activity summaries use the same command projection as displayed arguments. */
120
+ export function summarizeToolInputForDisplay(toolName, input) {
121
+ return summarizeToolInput(toolName, toolInputForDisplay(toolName, input) ?? {});
122
+ }
67
123
  /**
68
124
  * 工具输入摘要(提取工具调用的可读描述,供权限审批和消息展示使用)
69
125
  */
@@ -8,10 +8,154 @@
8
8
  * - 所有 IM 渠道(Feishu/WeChat/DingTalk/QQBot/WeCom):首次交互时发送给交互用户(被动响应)
9
9
  */
10
10
  import fs from 'fs';
11
- import { channelStatePath } from '../paths.js';
11
+ import path from 'path';
12
+ import { createHash } from 'crypto';
13
+ import { parseDocument } from 'yaml';
14
+ import { agentWelcomePath, channelStatePath, resolveRoot } from '../paths.js';
15
+ import { isValidAid } from '../aun/aid/validation.js';
12
16
  import { loadAgent, saveAgent } from '../config-store.js';
13
17
  import { resolveAgentLifecycle, withLifecycleForWrite } from '../config/lifecycle.js';
18
+ import { renderTemplate } from '../eck/manifest-engine.js';
19
+ import { atomicWriteText } from './atomic-write.js';
14
20
  import { logger } from './logger.js';
21
+ export const MAX_WELCOME_TEMPLATE_BYTES = 4 * 1024;
22
+ export const MAX_RENDERED_WELCOME_BYTES = 8 * 1024;
23
+ const WELCOME_TEMPLATE_VARS = new Set([
24
+ 'agentName', 'agentDescription', 'agentAid', 'agentTags', 'agentTagsText',
25
+ 'ownerName', 'ownerAid', 'channel',
26
+ ]);
27
+ /** Validate the small, data-only template language used by Bootstrap welcome text. */
28
+ export function validateWelcomeTemplate(template) {
29
+ const errors = [];
30
+ if (typeof template !== 'string' || !template.trim()) {
31
+ return { valid: false, errors: ['welcome template must be non-empty'] };
32
+ }
33
+ if (template.includes('\0') || Buffer.from(template, 'utf8').toString('utf8') !== template) {
34
+ errors.push('welcome template must contain valid UTF-8 text only');
35
+ }
36
+ if (Buffer.byteLength(template, 'utf8') > MAX_WELCOME_TEMPLATE_BYTES) {
37
+ return { valid: false, errors: [`welcome template must be at most ${MAX_WELCOME_TEMPLATE_BYTES} bytes`] };
38
+ }
39
+ const stack = [];
40
+ const tokenPattern = /\{\{|\}\}/g;
41
+ let match;
42
+ while ((match = tokenPattern.exec(template)) !== null) {
43
+ const end = template.indexOf('}}', tokenPattern.lastIndex);
44
+ if (match[0] !== '{{' || end < 0) {
45
+ errors.push('unmatched template delimiter');
46
+ break;
47
+ }
48
+ const token = template.slice(tokenPattern.lastIndex, end);
49
+ tokenPattern.lastIndex = end + 2;
50
+ if (token === '#each agentTags') {
51
+ if (stack.includes('each'))
52
+ errors.push('nested tag loops are not supported');
53
+ stack.push('each');
54
+ }
55
+ else if (token === '/each' || token === '/') {
56
+ const expected = token === '/each' ? 'each' : 'condition';
57
+ if (stack.pop() !== expected)
58
+ errors.push(`unexpected {{${token}}}`);
59
+ }
60
+ else if (/^\?\w+$/.test(token) && WELCOME_TEMPLATE_VARS.has(token.slice(1))) {
61
+ stack.push('condition');
62
+ }
63
+ else if ((token === '.' || token === '@index') && stack.includes('each')) {
64
+ // Loop-local variables are valid only inside the tag loop.
65
+ }
66
+ else if (!WELCOME_TEMPLATE_VARS.has(token)) {
67
+ errors.push(`unsupported template variable or block: ${token}`);
68
+ }
69
+ if (stack.length > 8) {
70
+ errors.push('template blocks are nested too deeply');
71
+ break;
72
+ }
73
+ }
74
+ if (stack.includes('each'))
75
+ errors.push('unclosed {{#each}} block');
76
+ if (stack.includes('condition'))
77
+ errors.push('unclosed condition block');
78
+ return { valid: errors.length === 0, errors };
79
+ }
80
+ function checkedWelcomePath(aid) {
81
+ if (!isValidAid(aid))
82
+ throw new Error('invalid Agent AID');
83
+ const target = agentWelcomePath(aid);
84
+ const root = path.resolve(resolveRoot());
85
+ for (let cursor = path.resolve(target); cursor !== root; cursor = path.dirname(cursor)) {
86
+ try {
87
+ if (fs.lstatSync(cursor).isSymbolicLink())
88
+ throw new Error('welcome path must not contain symbolic links');
89
+ }
90
+ catch (error) {
91
+ if (error.code !== 'ENOENT')
92
+ throw error;
93
+ }
94
+ }
95
+ return target;
96
+ }
97
+ export function saveAgentWelcomeTemplate(aid, template) {
98
+ const validation = validateWelcomeTemplate(template);
99
+ if (!validation.valid)
100
+ throw new Error(`invalid welcome template: ${validation.errors.join('; ')}`);
101
+ const target = checkedWelcomePath(aid);
102
+ const staging = target + '.tmp.' + process.pid;
103
+ try {
104
+ if (fs.lstatSync(staging).isSymbolicLink())
105
+ throw new Error('invalid welcome staging path');
106
+ }
107
+ catch (error) {
108
+ if (error.code !== 'ENOENT')
109
+ throw error;
110
+ }
111
+ try {
112
+ atomicWriteText(target, template);
113
+ }
114
+ finally {
115
+ fs.rmSync(staging, { force: true });
116
+ }
117
+ return { path: target, bytes: Buffer.byteLength(template, 'utf8'), sha256: createHash('sha256').update(template).digest('hex') };
118
+ }
119
+ export function readAgentWelcomeTemplate(aid) {
120
+ const target = checkedWelcomePath(aid);
121
+ let stat;
122
+ try {
123
+ stat = fs.lstatSync(target);
124
+ }
125
+ catch (error) {
126
+ if (error.code === 'ENOENT')
127
+ return null;
128
+ throw error;
129
+ }
130
+ if (!stat.isFile() || stat.size > MAX_WELCOME_TEMPLATE_BYTES)
131
+ throw new Error('invalid welcome template file');
132
+ const template = new TextDecoder('utf-8', { fatal: true, ignoreBOM: true }).decode(fs.readFileSync(target));
133
+ const validation = validateWelcomeTemplate(template);
134
+ if (!validation.valid)
135
+ throw new Error(`invalid welcome template at ${target}: ${validation.errors.join('; ')}`);
136
+ return template;
137
+ }
138
+ export function parseAgentWelcomeMetadata(content) {
139
+ const frontmatter = content.match(/^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/)?.[1];
140
+ if (!frontmatter)
141
+ return { tags: [] };
142
+ try {
143
+ const document = parseDocument(frontmatter);
144
+ if (document.errors.length)
145
+ return { tags: [] };
146
+ const value = document.toJS();
147
+ if (!value || typeof value !== 'object' || Array.isArray(value))
148
+ return { tags: [] };
149
+ return {
150
+ name: typeof value.name === 'string' ? value.name.trim() : undefined,
151
+ description: typeof value.description === 'string' ? value.description.trim() : undefined,
152
+ tags: Array.isArray(value.tags) ? value.tags.filter((tag) => typeof tag === 'string').map(tag => tag.trim()) : [],
153
+ };
154
+ }
155
+ catch {
156
+ return { tags: [] };
157
+ }
158
+ }
15
159
  // ============================================================================
16
160
  // 欢迎消息生成
17
161
  // ============================================================================
@@ -31,25 +175,54 @@ export function generateWelcomeMessage(options) {
31
175
  const bindingNote = options.includeBindingNote
32
176
  ? `\n\n🔐 **绑定说明**:\n- 这条消息表示您的设备已和此 Agent 绑定\n- 只有您能看到和使用这个 Agent\n- AUN 提供端到端加密和跨设备同步`
33
177
  : '';
34
- return `🎉 欢迎使用 EvolCore!
35
-
36
- ${greeting}
37
-
38
- ${agentIntro}${channelInfo}
39
-
40
- 📋 **日常使用方法**:
41
-
42
- 1. **查看帮助**:发送 \`/help\` 查看所有可用命令
43
- 2. **查看状态**:发送 \`/status\` 查看当前会话状态
44
- 3. **会话管理**:发送 \`/session\` 查看和切换会话
45
-
46
- 💡 **提示**:
47
- - 直接发送消息即可与 Claude/Codex 对话
48
- - 支持多会话管理,每个会话独立上下文
49
- - 所有命令以 \`/\` 开头${channelSpecificNote}${bindingNote}
50
-
178
+ return `🎉 欢迎使用 EvolCore!
179
+
180
+ ${greeting}
181
+
182
+ ${agentIntro}${channelInfo}
183
+
184
+ 📋 **日常使用方法**:
185
+
186
+ 1. **查看帮助**:发送 \`/help\` 查看所有可用命令
187
+ 2. **查看状态**:发送 \`/status\` 查看当前会话状态
188
+ 3. **会话管理**:发送 \`/session\` 查看和切换会话
189
+
190
+ 💡 **提示**:
191
+ - 直接发送消息即可与 Claude/Codex 对话
192
+ - 支持多会话管理,每个会话独立上下文
193
+ - 所有命令以 \`/\` 开头${channelSpecificNote}${bindingNote}
194
+
51
195
  现在就可以开始工作了!`;
52
196
  }
197
+ export function renderPostBootstrapWelcomeMessage(options) {
198
+ const validation = validateWelcomeTemplate(options.template);
199
+ if (!validation.valid)
200
+ throw new Error(`invalid welcome template: ${validation.errors.join('; ')}`);
201
+ // ECK renders loop bodies recursively. Mask data so names/descriptions containing
202
+ // template syntax remain literal even when inserted inside a loop or condition.
203
+ const values = [];
204
+ const literal = (value) => {
205
+ if (!value)
206
+ return '';
207
+ values.push(value);
208
+ return `\0WELCOME${values.length - 1}\0`;
209
+ };
210
+ const rendered = renderTemplate(options.template, {
211
+ agentName: literal(options.agentName),
212
+ agentDescription: literal(options.agentDescription),
213
+ agentAid: literal(options.agentAid),
214
+ agentTags: (options.agentTags ?? []).map(literal),
215
+ agentTagsText: literal((options.agentTags ?? []).join(', ')),
216
+ ownerName: literal(options.ownerName),
217
+ ownerAid: literal(options.ownerAid),
218
+ channel: literal('aun'),
219
+ }, false).replace(/\0WELCOME(\d+)\0/g, (_match, index) => values[Number(index)]);
220
+ if (!rendered.trim())
221
+ throw new Error('welcome template rendered to an empty message');
222
+ if (Buffer.byteLength(rendered, 'utf8') > MAX_RENDERED_WELCOME_BYTES)
223
+ throw new Error('rendered welcome exceeds 8192 bytes');
224
+ return rendered;
225
+ }
53
226
  /**
54
227
  * 生成简化版欢迎消息(用于快速场景)
55
228
  */
@@ -65,25 +238,25 @@ export function generateInitSuccessMessage(channelType, hasOwner) {
65
238
  const channelName = getChannelDescription(channelType);
66
239
  // AUN 在 connect 时向 owner 发送欢迎消息
67
240
  if (hasOwner && channelType === 'aun') {
68
- return `\n💡 提示:首次启动时会向 owner 发送欢迎消息
241
+ return `\n💡 提示:首次启动时会向 owner 发送欢迎消息
69
242
  启动服务: ec start`;
70
243
  }
71
244
  // Feishu 虽然有 owner,但已改为首次交互发送(与其他 IM 统一)
72
245
  if (hasOwner && channelType === 'feishu') {
73
- return `\n💡 提示:首次用户交互时会发送欢迎消息
246
+ return `\n💡 提示:首次用户交互时会发送欢迎消息
74
247
  启动服务: ec start`;
75
248
  }
76
249
  // 其他 IM 渠道:首次交互发送
77
250
  if (hasOwner) {
78
- return `\n💡 提示:${channelName}渠道已配置
251
+ return `\n💡 提示:${channelName}渠道已配置
79
252
  启动服务: ec start`;
80
253
  }
81
254
  const ownerHint = channelType === 'aun'
82
255
  ? ` 配置 owner: ec init ${channelType}`
83
256
  : ' 配置 owner: 在 agent config.json 的 owners 中写入 AID;非 AUN 渠道身份写入 contact.json';
84
257
  // 无 owner 的渠道
85
- return `\n💡 提示:${channelName}渠道已配置
86
- 启动服务: ec start
258
+ return `\n💡 提示:${channelName}渠道已配置
259
+ 启动服务: ec start
87
260
  ${ownerHint}`;
88
261
  }
89
262
  /**