evolcore 0.0.24 → 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 (112) hide show
  1. package/CHANGELOG.md +59 -0
  2. package/bin/ec.js +12 -1
  3. package/bin/install-codex-managed-hooks.mjs +28 -1
  4. package/dist/agents/baseagent.js +96 -46
  5. package/dist/agents/claude-runner.js +54 -13
  6. package/dist/agents/codex/app-server-client.js +13 -5
  7. package/dist/agents/codex/catalog.js +1 -1
  8. package/dist/agents/codex/diagnostics.js +34 -1
  9. package/dist/agents/codex-runner.js +128 -42
  10. package/dist/agents/ecagent-runner.js +14 -18
  11. package/dist/agents/gemini-runner.js +24 -9
  12. package/dist/agents/runner-types.js +25 -17
  13. package/dist/aun/aid/agentmd.js +135 -10
  14. package/dist/aun/aid/client.js +1 -1
  15. package/dist/aun/aid/identity.js +1 -1
  16. package/dist/aun/aid/index.js +2 -1
  17. package/dist/aun/aid/managed-operation.js +53 -1
  18. package/dist/aun/aid/store.js +17 -13
  19. package/dist/aun/menu-client.js +74 -0
  20. package/dist/aun/msg/group.js +1 -1
  21. package/dist/aun/msg/managed-operation.js +1 -1
  22. package/dist/aun/msg/p2p.js +6 -1
  23. package/dist/aun/msg/upload.js +30 -22
  24. package/dist/aun/storage/client.js +4 -1
  25. package/dist/aun/storage/index.js +1 -1
  26. package/dist/channels/aun.js +329 -94
  27. package/dist/cli/agent-command.js +125 -5
  28. package/dist/cli/agent-create.js +151 -11
  29. package/dist/cli/agent.js +119 -6
  30. package/dist/cli/aun-commands.js +74 -7
  31. package/dist/cli/config.js +27 -8
  32. package/dist/cli/contact.js +63 -6
  33. package/dist/cli/daemon-commands.js +90 -25
  34. package/dist/cli/diag.js +31 -0
  35. package/dist/cli/handoff-command.js +35 -3
  36. package/dist/cli/index.js +7 -1
  37. package/dist/cli/managed-command-guard.js +3 -0
  38. package/dist/cli/menu-client.js +38 -23
  39. package/dist/cli/restart-monitor.js +57 -10
  40. package/dist/config/builtin-roles.js +2 -0
  41. package/dist/config/config-manager.js +15 -0
  42. package/dist/config/config-operation-service.js +165 -3
  43. package/dist/config/contact-operation-service.js +96 -7
  44. package/dist/config/resolved-config-op.js +66 -8
  45. package/dist/core/agent-application-service.js +2 -1
  46. package/dist/core/auth/authorization-audit.js +20 -12
  47. package/dist/core/auth/operation-authorizer.js +87 -8
  48. package/dist/core/auth/operation-catalog.js +51 -4
  49. package/dist/core/auth/remote-access-store.js +40 -1
  50. package/dist/core/bootstrap-service.js +1 -1
  51. package/dist/core/command/agent-control.js +54 -25
  52. package/dist/core/command/cli-intent-parser.js +18 -0
  53. package/dist/core/command/command-handler.js +152 -20
  54. package/dist/core/command/diag-service.js +118 -0
  55. package/dist/core/command/menu-catalog.js +13 -3
  56. package/dist/core/command/menu-handler.js +88 -31
  57. package/dist/core/command/menu-protocol.js +6 -0
  58. package/dist/core/command/menu-token-store.js +41 -8
  59. package/dist/core/command/slash-gate.js +34 -4
  60. package/dist/core/command/slash-handler.js +77 -12
  61. package/dist/core/event-catalog.js +5 -0
  62. package/dist/core/evolagent.js +3 -3
  63. package/dist/core/handoff/runtime.js +205 -27
  64. package/dist/core/handoff/store.js +193 -4
  65. package/dist/core/message/im-renderer.js +72 -21
  66. package/dist/core/message/message-bridge.js +69 -9
  67. package/dist/core/message/message-queue.js +26 -0
  68. package/dist/core/message/message-utils.js +1 -0
  69. package/dist/core/message/response-engine.js +48 -23
  70. package/dist/core/model/catalog-service.js +79 -49
  71. package/dist/core/model/context-window-suffix.js +33 -0
  72. package/dist/core/model/model-catalog.js +1 -1
  73. package/dist/core/model/model-permission.js +12 -3
  74. package/dist/core/permission/ec-command-parser.js +51 -12
  75. package/dist/core/permission/sandbox-runtime.js +36 -14
  76. package/dist/core/permission/tool-policy.js +47 -7
  77. package/dist/index.js +195 -31
  78. package/dist/ipc.js +54 -2
  79. package/dist/response-system/engines/v1/proactive-flow.js +59 -2
  80. package/dist/stats/billing.js +41 -15
  81. package/dist/stats/price-resolver.js +28 -10
  82. package/dist/utils/avatar-upload.js +282 -23
  83. package/dist/utils/cross-platform.js +30 -1
  84. package/dist/utils/error-utils.js +21 -0
  85. package/dist/utils/evolcore-version.js +129 -0
  86. package/dist/utils/logger.js +3 -1
  87. package/dist/utils/model-prices.jsonl +3 -3
  88. package/dist/utils/npm-ops.js +176 -35
  89. package/kits/docs/context-assembly.md +1 -0
  90. package/kits/docs/evolcore/INDEX.md +3 -1
  91. package/kits/docs/evolcore/agent-create.md +30 -11
  92. package/kits/docs/evolcore/agent.md +18 -8
  93. package/kits/docs/evolcore/aid.md +6 -4
  94. package/kits/docs/evolcore/codex-windows-sandbox.md +146 -0
  95. package/kits/docs/evolcore/config.md +7 -0
  96. package/kits/docs/evolcore/contact.md +12 -5
  97. package/kits/docs/evolcore/menu-client.md +11 -3
  98. package/kits/docs/evolcore/msg.md +1 -0
  99. package/kits/docs/prompt-loading-architecture.md +1 -0
  100. package/kits/docs/venues/group.md +12 -0
  101. package/kits/eck_manifest.json +12 -0
  102. package/kits/rules/03-identity.md +3 -1
  103. package/kits/templates/message-fragments/handoff-request-to-target.md +3 -3
  104. package/kits/templates/roles/admin.json +14 -2
  105. package/kits/templates/roles/member.json +3 -2
  106. package/kits/templates/roles/visitor.json +4 -3
  107. package/kits/templates/system-fragments/bootstrap.md +16 -5
  108. package/kits/templates/system-fragments/channel.md +1 -1
  109. package/kits/templates/system-fragments/session.md +1 -1
  110. package/package.json +6 -4
  111. package/skills/eclink/SKILL.md +0 -84
  112. package/skills/eclink/agents/openai.yaml +0 -4
@@ -1,7 +1,11 @@
1
+ import crypto from 'node:crypto';
1
2
  import fs from 'fs';
2
3
  import path from 'path';
3
4
  import { getPackageRoot } from '../paths.js';
4
5
  let cachedVersion;
6
+ let cachedFlavor;
7
+ let cachedStartStamp;
8
+ let cachedStartedAtIso;
5
9
  export function readInstalledEvolcoreVersion() {
6
10
  if (cachedVersion !== undefined)
7
11
  return cachedVersion;
@@ -16,6 +20,131 @@ export function readInstalledEvolcoreVersion() {
16
20
  cachedVersion = version;
17
21
  return version;
18
22
  }
23
+ /**
24
+ * 区分源码仓库运行与全局安装包。`package.json` 的 `files` 白名单不含 `src/`,
25
+ * 所以 `src/index.ts` 的存在性可以稳定区分两者:同一个版本号在 dev 下跑的是
26
+ * 未发布的工作副本,排查时必须能看出来。
27
+ */
28
+ export function readEvolcoreRuntimeFlavor() {
29
+ if (cachedFlavor !== undefined)
30
+ return cachedFlavor;
31
+ let flavor;
32
+ try {
33
+ flavor = fs.existsSync(path.join(getPackageRoot(), 'src', 'index.ts')) ? 'dev' : 'install';
34
+ }
35
+ catch {
36
+ flavor = 'install';
37
+ }
38
+ cachedFlavor = flavor;
39
+ return flavor;
40
+ }
41
+ function pad2(n) {
42
+ return String(n).padStart(2, '0');
43
+ }
44
+ /**
45
+ * 本进程启动时刻。用 `process.uptime()` 反推而非模块加载时的 `Date.now()`:
46
+ * 后者受 import 链耗时影响,且本模块可能被惰性加载。无 syscall,只算一次。
47
+ */
48
+ function resolveStartedAt() {
49
+ return new Date(Date.now() - Math.round(process.uptime() * 1000));
50
+ }
51
+ /** 本进程启动时刻的 ISO 串,供结构化字段使用。 */
52
+ export function readEvolcoreStartedAtIso() {
53
+ if (cachedStartedAtIso !== undefined)
54
+ return cachedStartedAtIso;
55
+ cachedStartedAtIso = resolveStartedAt().toISOString();
56
+ return cachedStartedAtIso;
57
+ }
58
+ /**
59
+ * 错误标记里的启动时刻,`MMDD-HHMM` 本地时间。
60
+ *
61
+ * 用本地时间而非 UTC:读的人是对着自己的墙上时钟判断"这进程是我改完之后起的吗"。
62
+ * 精度到分钟——秒级对判断重启无意义,只会占长度。
63
+ */
64
+ export function readEvolcoreStartStamp() {
65
+ if (cachedStartStamp !== undefined)
66
+ return cachedStartStamp;
67
+ const d = resolveStartedAt();
68
+ cachedStartStamp = `${pad2(d.getMonth() + 1)}${pad2(d.getDate())}-${pad2(d.getHours())}${pad2(d.getMinutes())}`;
69
+ return cachedStartStamp;
70
+ }
71
+ /**
72
+ * 用户可见错误文案的版本标记,形如 `[EC 0.0.24 dev 0915-1230]`。
73
+ *
74
+ * 带启动时刻是因为版本号不足以定位问题:dev 模式下同一个 `0.0.24` 可能是任意一份
75
+ * 工作副本,改完代码不重启就仍在跑旧代码——启动时刻能直接把这种情况暴露出来。
76
+ */
77
+ export function evolcoreErrorTag() {
78
+ return `[EC ${readInstalledEvolcoreVersion()} ${readEvolcoreRuntimeFlavor()} ${readEvolcoreStartStamp()}]`;
79
+ }
80
+ const ERROR_TAG_PATTERN = /\[EC\s+[^\]]*\]/;
81
+ /**
82
+ * 前导 emoji / 变体选择符 / 空白:标记要插在它们之后。U+FE0F 单独列出,
83
+ * 它跟在 emoji 之后且不属于 Extended_Pictographic,漏掉会把 `⚠️` 拆开。
84
+ */
85
+ const LEADING_DECORATION = /^(?:\p{Extended_Pictographic}|️|\s)+/u;
86
+ /**
87
+ * 给用户可见的错误文案打版本标记。标记走前缀而非后缀:渠道前端会截断长文案
88
+ * (错误卡片只显示前两行),后缀会被直接截掉。
89
+ *
90
+ * 幂等,且在整串任意位置发现既有标记就跳过——错误文案会被
91
+ * `formatRetryableErrorFinalMessage` 之类的包装器嵌进 `原因:…` 中段,
92
+ * 只检查开头会导致同一个错误被打两次标。
93
+ */
94
+ export function tagErrorTextWithEvolcoreVersion(text) {
95
+ if (typeof text !== 'string' || !text.trim())
96
+ return text;
97
+ if (ERROR_TAG_PATTERN.test(text))
98
+ return text;
99
+ const decoration = text.match(LEADING_DECORATION)?.[0] ?? '';
100
+ return `${decoration}${evolcoreErrorTag()} ${text.slice(decoration.length)}`;
101
+ }
19
102
  export function _resetInstalledEvolcoreVersionForTests() {
20
103
  cachedVersion = undefined;
104
+ cachedFlavor = undefined;
105
+ cachedStartStamp = undefined;
106
+ cachedStartedAtIso = undefined;
107
+ }
108
+ function collectFiles(root, current = root, out = []) {
109
+ let entries;
110
+ try {
111
+ entries = fs.readdirSync(current, { withFileTypes: true });
112
+ }
113
+ catch {
114
+ return out;
115
+ }
116
+ for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
117
+ const full = path.join(current, entry.name);
118
+ if (entry.isDirectory())
119
+ collectFiles(root, full, out);
120
+ else if (entry.isFile())
121
+ out.push(path.relative(root, full));
122
+ }
123
+ return out;
124
+ }
125
+ /** Hash the complete compiled tree so a changed dependency module changes the ID too. */
126
+ export function computeBuildId(distDir) {
127
+ if (!fs.existsSync(distDir))
128
+ return null;
129
+ try {
130
+ const hash = crypto.createHash('sha256');
131
+ for (const relative of collectFiles(distDir)) {
132
+ const full = path.join(distDir, relative);
133
+ hash.update(relative.replaceAll(path.sep, '/'));
134
+ hash.update('\0');
135
+ hash.update(fs.readFileSync(full));
136
+ hash.update('\0');
137
+ }
138
+ return `sha256:${hash.digest('hex').slice(0, 24)}`;
139
+ }
140
+ catch {
141
+ return null;
142
+ }
143
+ }
144
+ export function readBuildIdentity(distDir, version) {
145
+ return {
146
+ id: computeBuildId(distDir),
147
+ entry: path.join(distDir, 'index.js'),
148
+ ...(version ? { version } : {}),
149
+ };
21
150
  }
@@ -152,7 +152,9 @@ export function normalizeStructuredLog(data) {
152
152
  export function localTimestamp() {
153
153
  const d = new Date();
154
154
  const pad = (n) => String(n).padStart(2, '0');
155
- return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}T${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}.${String(d.getMilliseconds()).padStart(3, '0')}`;
155
+ const offset = -d.getTimezoneOffset();
156
+ const zone = `${offset >= 0 ? '+' : '-'}${pad(Math.floor(Math.abs(offset) / 60))}:${pad(Math.abs(offset) % 60)}`;
157
+ return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}T${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}.${String(d.getMilliseconds()).padStart(3, '0')}${zone}`;
156
158
  }
157
159
  function log(level, ...args) {
158
160
  if (!shouldLog(level))
@@ -18,6 +18,6 @@
18
18
  {"model":"glm-5","effective_from":0,"billing_fn":"per_token_v1","currency":"CNY","price_input":5,"price_output":20,"price_cache_creation":0,"price_cache_read":0,"context_window":128000,"max_input_tokens":120000,"max_output_tokens":8192}
19
19
  {"model":"glm-4.7","effective_from":0,"billing_fn":"per_token_v1","currency":"CNY","price_input":1,"price_output":5,"price_cache_creation":0,"price_cache_read":0,"context_window":128000,"max_input_tokens":120000,"max_output_tokens":8192}
20
20
  {"model":"MiniMax-M2.7","effective_from":0,"billing_fn":"per_token_v1","currency":"CNY","price_input":1,"price_output":5,"price_cache_creation":0,"price_cache_read":0,"context_window":128000,"max_input_tokens":120000,"max_output_tokens":8192}
21
- {"model":"claude-opus-5","effective_from":0,"context_window":1000000,"max_input_tokens":900000,"max_output_tokens":131072,"supports_cache":true,"supports_vision":true}
22
- {"model":"claude-sonnet-5","effective_from":0,"context_window":1000000,"max_input_tokens":900000,"max_output_tokens":131072,"supports_cache":true,"supports_vision":true}
23
- {"model":"claude-fable-5","effective_from":0,"context_window":1000000,"max_input_tokens":900000,"max_output_tokens":131072,"supports_cache":true,"supports_vision":true}
21
+ {"model":"claude-opus-5","effective_from":0,"billing_fn":"per_token_v1","currency":"USD","price_input":5,"price_output":25,"price_cache_creation":6.25,"price_cache_read":0.5,"context_window":1000000,"max_input_tokens":900000,"max_output_tokens":131072,"supports_cache":true,"supports_vision":true}
22
+ {"model":"claude-sonnet-5","effective_from":0,"billing_fn":"per_token_v1","currency":"USD","price_input":2,"price_output":10,"price_cache_creation":2.5,"price_cache_read":0.2,"context_window":1000000,"max_input_tokens":900000,"max_output_tokens":131072,"supports_cache":true,"supports_vision":true}
23
+ {"model":"claude-fable-5","effective_from":0,"billing_fn":"per_token_v1","currency":"USD","price_input":10,"price_output":50,"price_cache_creation":12.5,"price_cache_read":1,"context_window":1000000,"max_input_tokens":900000,"max_output_tokens":131072,"supports_cache":true,"supports_vision":true}
@@ -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);
@@ -290,6 +290,7 @@ coding 场景(无 channel/无身份)下,`chatType`、`channel`、`selfAid`
290
290
  | 10 | rules | 目录 `$KITS_RULES` | always | ✗ |
291
291
  | 20 | identity-layer | fragment | chatType≠null | ✓ |
292
292
  | 21 | persona | `$PERSONAL_DIR/persona.md` | chatType≠null | ✗ |
293
+ | 21.5 | personal-profile | `$PERSONAL_DIR/personal.md` | chatType≠null | ✗ |
293
294
  | 22 | working-memory | `$PERSONAL_DIR/memory/working.md` | chatType≠null | ✗ |
294
295
  | 30 | relation-layer | fragment | chatType∈{private,group} | ✓ |
295
296
  | 35 | peer-profile | `$RELATIONS_DIR/{{peerKey}}/profile.md` | peerKey≠null | ✗ |
@@ -24,6 +24,8 @@
24
24
  | `ec logs` | 查看日志(`--level` `--module` `--raw` `--no-color`) | 日志/查看日志 |
25
25
  | `ec diagnose` | 诊断启动环境 | 诊断/环境诊断 |
26
26
 
27
+ Windows 上 Codex 托管任务出现 `helper_sandbox_lock_failed`、`SetNamedSecurityInfoW ... 5`、`unelevated ... cannot enforce deny-read` 或 daemon 管道 `EPERM` 时,参见 `codex-windows-sandbox.md`。
28
+
27
29
  ## 对外通信 / 网络类(连 AUN,以 AID 为发送者)
28
30
 
29
31
  | 命令集 | 用途 | 触发词 | 适用场景 | 文档 |
@@ -37,7 +39,7 @@
37
39
  | `ec agent` | EvolAgent 生命周期 | 创建/启停/热重载/改配置 | 管理员(owner/admin) | `agent.md`;完整创建与 Bootstrap 见 `agent-create.md` |
38
40
  | `ec contact` | Contact Book 与访问控制 | 申请添加、拉黑/解除拉黑、查拉黑 | Agent 当前私聊可申请添加;管理写操作按角色限制 | `contact.md` |
39
41
  | `ec rpc` | 底层 AUN RPC(逃生通道) | 直接调协议方法 | 高级/兜底 | `rpc.md` |
40
- | `ec menu-client` | 远程 Daemon Menu 请求 | 远程诊断/daemon 日志/监控/原始配置 | 非托管 eclink/运维主机 | `menu-client.md` |
42
+ | `ec menu-client` | 远程 Daemon Menu 请求 | 远程诊断/daemon 日志/监控/原始配置 | 运维主机或具备 `ec.menu.request` 权限的托管 Agent | `menu-client.md` |
41
43
 
42
44
  ## 会话自管理类(不连 AUN,操作当前会话)
43
45
 
@@ -1,11 +1,15 @@
1
1
  # 创建 EvolAgent:从创建到 Bootstrap 完成
2
2
 
3
- 本手册用于创建一个能正常工作的 EvolAgent。创建者负责创建和观察状态;新 Agent 在自己的 Bootstrap 会话中完成首次设定。不要把创建流水线完成误认为 Agent 已激活。
3
+ 本手册用于创建一个能正常工作的 EvolAgent。创建可以由人类直接发起,也可以由已有 Agent 代办:已有 Agent 负责收集信息、创建配置并观察状态;新 Agent 仍在自己的 Bootstrap 会话中修改名片并执行 `ready`。不要把创建流水线完成误认为 Agent 已激活。
4
4
 
5
5
  ### AID 与 Owner 约定
6
6
 
7
7
  - AID 必须是 `{name}.{issuer}`,例如 `helper.agentid.cn`;不要使用四级域名(如 `helper.team.agentid.cn`),也不要把显示名当作 AID。
8
- - `--owner` 填写需求提出者或实际责任 Owner 的完整 AID,而不是默认填写当前执行者。创建者只是代办创建流程时,仍应保留需求提出者的 Owner 权限。
8
+ - `--owner` 保留现有单个 Owner 参数。
9
+ - `--owners` 是有序 Owner 数组;Owner AID 必须使用完整 AID。代办创建时,当前创建 Agent 必须放在第一位,再把它自己的 Owner 放在后面;第一位 Owner 接收新 Agent 的 Bootstrap 首条消息。
10
+ - 通过托管委托创建时,创建者必须显式把原始需求 Owner 写入 `--owners`;若需要创建 Agent 继续代办 Bootstrap,应将创建 Agent 放在 `owners[0]`,原始需求 Owner 放在后续位置。代码不会自动补齐 Owner。
11
+ - `--owner` 与 `--owners` 互斥;未指定 `--owners` 时,现有 `--owner` 行为保持不变。
12
+ - `--handoff <上下文>` 只附着在当前创建 Agent 的任务上,用于把已收集的 Bootstrap 信息注入该 Agent 后续处理新 Agent Bootstrap 首条消息的上下文。它不是发给新 Agent 的消息,也不要求新 Agent 返回或绑定任何 handoff。
9
13
 
10
14
  ## 生命周期与完成标准
11
15
 
@@ -25,23 +29,31 @@ active
25
29
 
26
30
  多个 Agent 必须串行创建和观察。不要并行启动多个 `ec agent new`:证书、热加载和 Bootstrap 会竞争连接与资源,容易造成超时、锁等待或把失败误判为未创建。
27
31
 
28
- 在独立终端可以使用交互模式:
32
+ 在人类直接创建时,可以使用交互模式:
29
33
 
30
34
  ```bash
31
35
  ec agent new helper.agentid.pub
32
36
  ```
33
37
 
34
- 在 EvolCore 托管 Agent 会话等自动化环境中,必须使用非交互模式:
38
+ 在 EvolCore 托管 Agent 会话等自动化环境中,必须使用非交互模式。代办创建时,先按照 Bootstrap 要求向自己的 Owner 收集显示名 `name`、一到两句话的 `description`、3 到 6 个非空 `tags`,再把结果放入 `--handoff`:
35
39
 
36
40
  ```bash
37
41
  ec agent new helper.agentid.pub --non-interactive \
38
42
  --project /home/user/helper-project \
39
43
  --baseagent codex \
40
- --owner owner.agentid.pub \
44
+ --owners '["helper-creator.agentid.pub","owner.agentid.pub"]' \
41
45
  --name "Helper" \
42
- --description "协助处理项目开发任务"
46
+ --description "协助处理项目开发任务" \
47
+ --tags '["coding","diagnostics","logs"]' \
48
+ --handoff '供当前创建 Agent 使用的 Bootstrap 上下文:已向 Owner 确认 name=Helper;description=协助处理项目开发任务;tags=coding, maintenance, typescript。目标 Agent 不会看到此上下文;创建 Agent 收到其 Bootstrap 首条消息时,以这些信息作为 Owner 回复并完成验收。'
43
49
  ```
44
50
 
51
+ Bootstrap 完成前,Owner 可选地提供 persona 内容:直接发送文本,或发送 `.md`/`.txt` 文件。新 Agent 在确认后使用 `ec agent persona --text '<内容>'` 或 `ec agent persona --file '<路径>'` 写入自己的 `$PERSONAL_DIR/persona.md`;未提供 persona 不影响 `ready`。创建者不能借此替代 Bootstrap 中对 `agent.md` 的 Owner 确认。
52
+
53
+ 如果新 Agent 向 Agent/AI Owner 发送 Bootstrap 消息时收到“不是联系人”或联系人准入拒绝,应只发送一次 `/request <申请说明>` 申请添加联系人,说明这是新 Agent 的 Bootstrap 首次设定,并等待 Owner 审核;不要反复重试、修改全局联系人策略或联系其它对象。Owner 直接向新 Agent 发消息时,按该入站消息继续 Bootstrap。
54
+
55
+ `--owners` 只用于需要多个 Owner 的创建;普通多 Owner 创建可按实际责任关系排列顺序。使用 `--handoff` 代办创建时,当前创建 Agent 必须是 `owners[0]`。`--handoff` 的内容只在创建 Agent 的当前任务中保存和注入,目标 Agent 不会看到该参数。
56
+
45
57
  建议先用 dry-run 检查最终参数:
46
58
 
47
59
  ```bash
@@ -61,6 +73,8 @@ ec agent get helper.agentid.pub lifecycle
61
73
  ec agent show helper.agentid.pub
62
74
  ```
63
75
 
76
+ 创建成功后,若 Owner 是本机托管 Agent,创建流程会把新 Agent AID 自动写入该 Owner 的联系人簿;远程或人类 Owner 没有本机联系人簿时跳过。创建者仍应核对首位 Owner 的联系人状态,确保 Bootstrap 回复可达。
77
+
64
78
  如果是 menu 创建,`accepted=true` 只表示请求已受理。继续轮询 `createProgress.status` 直到 `ready` 或 `failed`。这里的 `createProgress.status=ready` **只表示后台创建流水线结束,不等于 `lifecycle=active`**。
65
79
 
66
80
  创建者应维护一个最小状态账本(可放在当前任务记录中),每个 Agent 至少记录:`aid`、需求 Owner、项目路径、创建请求是否受理、`createProgress.status`、`lifecycle`、本地 `agent.md` 是否存在/字段是否完整、名片是否已发布、最近错误、下一步和最后检查时间。重启或超时恢复时先按账本逐项核对实际状态,再决定续做,不按“上次命令是否返回”推断结果。
@@ -75,13 +89,13 @@ ec agent show helper.agentid.pub
75
89
 
76
90
  ## 3. Owner 与新 Agent 确认名片信息
77
91
 
78
- 以下步骤发生在**新 Agent 自己的 Bootstrap 会话**中,不由创建者代替执行。
92
+ 以下步骤发生在**新 Agent 自己的 Bootstrap 会话**中,不由创建者代替执行。若第一位 Owner 是代办创建的 Agent,它可以使用当前任务中注入的 `--handoff` 上下文代替人类回复;这不改变 Bootstrap 的确认规则。
79
93
 
80
94
  新 Agent 先读取系统注入路径指向的本地 `agent.md`,把其中格式有效的字段作为现有候选值:
81
95
 
82
96
  - `name`:简短清晰的显示名
83
97
  - `description`:一句到两句话描述职责或定位
84
- - `tags`:3 到 6 个非空标签
98
+ - `tags`:3 到 6 个非空标签;每个标签只允许 Unicode 字母、数字和连字符(`-`),最长 50 字符
85
99
 
86
100
  交互规则:
87
101
 
@@ -131,8 +145,8 @@ ec agent ready helper.agentid.pub
131
145
 
132
146
  1. 将 `lifecycle` 切换为 `active`。
133
147
  2. 结束当前 Bootstrap 上下文。
134
- 3. 创建全新的主会话。
135
- 4. 发送正式欢迎消息。
148
+ 3. 为每位 Owner 创建全新的主会话。
149
+ 4. 向全部 Owner 发送正式欢迎消息,并为各 Owner 绑定对应的新会话。
136
150
 
137
151
  新 Agent 在 `ready` 成功后不要从旧 Bootstrap 上下文再输出一段完成说明,也不要继续处理先前夹带的普通任务;后续工作从新主会话开始。
138
152
 
@@ -147,10 +161,15 @@ ec agent show helper.agentid.pub
147
161
 
148
162
  验证其它 Agent 的生命周期、配置和创建进度统一使用 `ec agent get/show/list`;`ec aid` 只用于身份、证书和名片操作,不要用 `ec aid` 代替 Agent 状态验证。
149
163
 
164
+ Bootstrap 首条消息只发送给 `owners[0]`;`ready` 成功后,正式欢迎消息发送给配置中的全部 Owner。
165
+
166
+ 使用 `--handoff` 代办创建时,创建 Agent 在与新 Agent 的会话中完成验收后,执行 `ec handoff return --content '<验收结果>'`,将结果回流至自己与原需求 Owner 的会话;该步骤不由新 Agent 执行。
167
+
150
168
  验收标准:
151
169
 
152
- - `lifecycle` 为 `active`。
170
+ - `lifecycle` 为 `active`;`ec agent show` 的 `lifecycle` 字段也必须为 `active`。
153
171
  - `agent.md` 已成功签名发布。
172
+ - 本地与远端 `agent.md` 的 `name`、`description`、`tags` 与 Owner 确认值一致;创建流程 skill 必须在发布后复读核验,不以初始上传响应代替。
154
173
  - 系统已创建新的主会话并发送正式欢迎消息。
155
174
  - 新 Agent 能在新主会话中处理普通任务。
156
175