evolcore 0.0.35 → 0.0.37

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 (224) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/README.md +6 -0
  3. package/bin/install-codex-managed-hooks.mjs +20 -3
  4. package/dist/agents/baseagent-availability.js +1 -1
  5. package/dist/agents/baseagent.js +88 -0
  6. package/dist/agents/claude-runner.js +59 -12
  7. package/dist/agents/codex/app-server-client.js +18 -1
  8. package/dist/agents/codex-runner.js +262 -21
  9. package/dist/agents/ecagent-runner.js +47 -11
  10. package/dist/agents/gemini-runner.js +36 -12
  11. package/dist/aun/aid/client.js +4 -3
  12. package/dist/aun/aid/control-aid.js +270 -8
  13. package/dist/aun/aid/identity.js +6 -3
  14. package/dist/aun/aid/ownership-operation.js +58 -0
  15. package/dist/aun/aid/store.js +1 -0
  16. package/dist/aun/group/fs-download.js +67 -2
  17. package/dist/aun/group/history.js +20 -5
  18. package/dist/aun/group/operations.js +8 -2
  19. package/dist/aun/menu/client.js +39 -2
  20. package/dist/aun/menu/transport.js +1 -1
  21. package/dist/aun/msg/history.js +43 -4
  22. package/dist/aun/msg/managed-operation.js +30 -53
  23. package/dist/aun/msg/p2p.js +51 -15
  24. package/dist/aun/outbox.js +4 -0
  25. package/dist/channels/aun.js +260 -73
  26. package/dist/channels/daemon.js +6 -4
  27. package/dist/channels/feishu.js +3 -0
  28. package/dist/cli/agent-command.js +161 -223
  29. package/dist/cli/agent-create.js +190 -223
  30. package/dist/cli/agent.js +3 -1
  31. package/dist/cli/aid-bind-command.js +53 -0
  32. package/dist/cli/aun-commands.js +384 -91
  33. package/dist/cli/bench.js +1 -1
  34. package/dist/cli/cli-argv.js +74 -0
  35. package/dist/cli/daemon-commands.js +105 -74
  36. package/dist/cli/diag.js +1 -1
  37. package/dist/cli/fs-command.js +18 -19
  38. package/dist/cli/getmeta-command.js +159 -6
  39. package/dist/cli/index.js +6 -12
  40. package/dist/cli/init-channel.js +8 -55
  41. package/dist/cli/init.js +125 -218
  42. package/dist/cli/queue-command.js +11 -8
  43. package/dist/cli/restart-monitor.js +177 -85
  44. package/dist/cli/session-command.js +1 -1
  45. package/dist/cli/stats.js +7 -5
  46. package/dist/cli/thread-command.js +103 -30
  47. package/dist/cli/trigger-command.js +1 -1
  48. package/dist/cli/watch-msg-diag.js +1 -0
  49. package/dist/config/access-policy.js +2 -2
  50. package/dist/config/boot-log.js +14 -11
  51. package/dist/config/config-field-policy.js +9 -1
  52. package/dist/config/config-manager.js +176 -90
  53. package/dist/config/config-operation-service.js +7 -14
  54. package/dist/config/contact-book-store.js +142 -26
  55. package/dist/config/contact-book.js +4 -2
  56. package/dist/config/lifecycle.js +1 -1
  57. package/dist/config/owner-config.js +96 -0
  58. package/dist/config/peer-role-resolver.js +12 -18
  59. package/dist/config/providers-config.js +382 -0
  60. package/dist/config/providers-import.js +74 -0
  61. package/dist/config/providers-management.js +328 -0
  62. package/dist/config/providers.js +22 -0
  63. package/dist/config/resolved-config-op.js +17 -14
  64. package/dist/config/snapshot.js +28 -16
  65. package/dist/config-store.js +18 -14
  66. package/dist/core/agent-ipc-registration.js +21 -63
  67. package/dist/core/agent-runtime.js +16 -5
  68. package/dist/core/auth/agent-delegation.js +199 -25
  69. package/dist/core/auth/auth-gateway.js +44 -2
  70. package/dist/core/auth/authorization-denial.js +1 -1
  71. package/dist/core/auth/debug-context.js +2 -1
  72. package/dist/core/auth/delegation-authorization.js +2 -2
  73. package/dist/core/auth/delegation-diagnostics.js +4 -1
  74. package/dist/core/auth/operation-authorizer.js +10 -1
  75. package/dist/core/auth/operation-catalog.js +19 -1
  76. package/dist/core/bootstrap-service.js +43 -20
  77. package/dist/core/channel-loader.js +0 -1
  78. package/dist/core/channel-runtime.js +30 -4
  79. package/dist/core/command/agent-control.js +196 -37
  80. package/dist/core/command/cli-intent-parser.js +60 -4
  81. package/dist/core/command/command-handler.js +212 -60
  82. package/dist/core/command/connect-menu.js +1 -0
  83. package/dist/core/command/diag-service.js +5 -4
  84. package/dist/core/command/file-search.js +368 -0
  85. package/dist/core/command/menu-catalog.js +2 -1
  86. package/dist/core/command/menu-handler.js +292 -107
  87. package/dist/core/command/menu-protocol.js +23 -0
  88. package/dist/core/command/role-menu.js +7 -19
  89. package/dist/core/command/skill-slash.js +130 -25
  90. package/dist/core/command/slash-handler.js +101 -53
  91. package/dist/core/command/topic-slash.js +16 -5
  92. package/dist/core/daemon-bootstrap.js +7 -1
  93. package/dist/core/daemon-control.js +19 -1
  94. package/dist/core/daemon-lifecycle.js +2 -1
  95. package/dist/core/event-catalog.js +2 -0
  96. package/dist/core/evolagent-registry.js +13 -2
  97. package/dist/core/evolagent.js +3 -6
  98. package/dist/core/handoff/ipc-registration.js +2 -2
  99. package/dist/core/handoff/runtime.js +40 -6
  100. package/dist/core/handoff/store.js +1 -0
  101. package/dist/core/inference/text-inference.js +95 -6
  102. package/dist/core/ipc-registration.js +43 -24
  103. package/dist/core/memory/consolidation-context.js +4 -1
  104. package/dist/core/memory/consolidation.js +25 -14
  105. package/dist/core/message/aun-ipc-sender.js +296 -88
  106. package/dist/core/message/aun-request-registry.js +226 -0
  107. package/dist/core/message/create-status.js +33 -4
  108. package/dist/core/message/message-bridge.js +62 -13
  109. package/dist/core/message/message-log.js +0 -1
  110. package/dist/core/message/message-queue.js +4 -1
  111. package/dist/core/message/message-utils.js +8 -1
  112. package/dist/core/message/peer-mode.js +17 -9
  113. package/dist/core/message/response-engine.js +266 -124
  114. package/dist/core/message/retry-scheduler.js +11 -8
  115. package/dist/core/model/catalog-service.js +60 -18
  116. package/dist/core/model/model-catalog.js +112 -9
  117. package/dist/core/model/model-fallback.js +94 -26
  118. package/dist/core/ownership/owner-resolver.js +687 -0
  119. package/dist/core/permission/approval-gateway.js +196 -62
  120. package/dist/core/permission/ec-command-parser.js +138 -25
  121. package/dist/core/permission/filesystem-approval-scope.js +141 -0
  122. package/dist/core/permission/protected-paths.js +14 -1
  123. package/dist/core/permission/sandbox-runtime.js +171 -18
  124. package/dist/core/permission/tool-policy.js +13 -2
  125. package/dist/core/role/runtime-policy.js +3 -4
  126. package/dist/core/session/session-idle-monitor.js +10 -8
  127. package/dist/core/session/session-manager.js +72 -45
  128. package/dist/core/session/session-mapper.js +18 -7
  129. package/dist/core/session/session-renew.js +5 -4
  130. package/dist/core/session/session-title.js +2 -1
  131. package/dist/core/session/session-turn-coordinator.js +4 -2
  132. package/dist/core/skill/index-generator.js +2 -16
  133. package/dist/core/task/task-recovery-service.js +15 -2
  134. package/dist/core/thread/command-parser.js +28 -13
  135. package/dist/core/thread/index.js +1 -0
  136. package/dist/core/thread/ipc-operation.js +11 -8
  137. package/dist/core/thread/thread-operation.js +79 -44
  138. package/dist/core/thread/thread-route.js +54 -6
  139. package/dist/core/thread/thread-service.js +236 -45
  140. package/dist/core/thread/topic-id.js +248 -0
  141. package/dist/eck/kit-renderer.js +1 -1
  142. package/dist/eck/message-renderer.js +12 -2
  143. package/dist/index.js +50 -27
  144. package/dist/ipc.js +59 -6
  145. package/dist/paths.js +1 -0
  146. package/dist/product.js +1 -0
  147. package/dist/response-system/engines/v1/proactive-flow.js +10 -3
  148. package/dist/stats/db.js +8 -0
  149. package/dist/stats/writer.js +26 -4
  150. package/dist/trigger/authorization.js +48 -3
  151. package/dist/trigger/feedback.js +28 -10
  152. package/dist/trigger/ipc-service.js +86 -10
  153. package/dist/trigger/local-files.js +55 -23
  154. package/dist/trigger/scheduler.js +57 -11
  155. package/dist/trigger/system-definitions.js +1 -0
  156. package/dist/trigger/validation.js +44 -15
  157. package/dist/utils/aid-bind.js +17 -297
  158. package/dist/utils/avatar-upload.js +16 -4
  159. package/dist/utils/cross-platform.js +115 -36
  160. package/dist/utils/ecweb-supervisor.js +1 -1
  161. package/dist/utils/ecweb-utils.js +8 -1
  162. package/dist/utils/error-utils.js +44 -6
  163. package/dist/utils/instance-registry.js +12 -2
  164. package/dist/utils/npm-ops.js +102 -27
  165. package/dist/utils/package-version-info.js +72 -27
  166. package/dist/utils/restart-safety.js +50 -0
  167. package/dist/utils/upgrade-jobs.js +304 -0
  168. package/dist/utils/windows-autostart.js +17 -10
  169. package/kits/docs/INDEX.md +5 -0
  170. package/kits/docs/aun/ABILITY_CALLING.md +239 -0
  171. package/kits/docs/evolcore/INDEX.md +14 -3
  172. package/kits/docs/evolcore/agent-create.md +55 -141
  173. package/kits/docs/evolcore/agent.md +38 -16
  174. package/kits/docs/evolcore/aid.md +34 -0
  175. package/kits/docs/evolcore/config.md +20 -1
  176. package/kits/docs/evolcore/event.md +2 -2
  177. package/kits/docs/evolcore/fs.md +18 -2
  178. package/kits/docs/evolcore/group-collaboration.md +13 -246
  179. package/kits/docs/evolcore/group.md +2 -1
  180. package/kits/docs/evolcore/memory-consolidation-batches.md +1 -1
  181. package/kits/docs/evolcore/menu-protocol.md +65 -1
  182. package/kits/docs/evolcore/msg.md +64 -2
  183. package/kits/docs/evolcore/skill.md +86 -0
  184. package/kits/docs/evolcore/stats.md +6 -0
  185. package/kits/docs/evolcore/task.md +2 -0
  186. package/kits/docs/evolcore/thread-chain.md +145 -51
  187. package/kits/docs/evolcore/trigger.md +48 -3
  188. package/kits/eck_manifest.json +21 -0
  189. package/kits/eck_message_manifest.json +4 -4
  190. package/kits/migrations/migrate-role-config-v5.mjs +20 -7
  191. package/kits/rules/01-overview.md +2 -1
  192. package/kits/rules/03-identity.md +3 -1
  193. package/kits/rules/04-relation.md +1 -1
  194. package/kits/rules/07-memory-and-thread.md +1 -1
  195. package/kits/schemas/_meta.json +16 -1
  196. package/kits/schemas/agent-config.schema.13.json +44 -2
  197. package/kits/schemas/contact-book.schema.3.json +5 -0
  198. package/kits/schemas/daemon.schema.6.json +3 -1
  199. package/kits/schemas/daemon.schema.8.json +329 -0
  200. package/kits/schemas/defaults.schema.5.json +39 -3
  201. package/kits/schemas/providers.schema.1.json +76 -0
  202. package/kits/schemas/relation-config.schema.9.json +5 -1
  203. package/kits/skills/.switches.json +28 -0
  204. package/kits/skills/aid-ability/SKILL.md +214 -0
  205. package/kits/skills/ec-agent-create/SKILL.md +162 -0
  206. package/kits/skills/ec-group/SKILL.md +2 -1
  207. package/kits/skills/ec-group-collaboration/SKILL.md +228 -0
  208. package/kits/skills/ec-identity/SKILL.md +143 -0
  209. package/kits/skills/ec-skill/scripts/cli.mjs +36 -3
  210. package/kits/skills/ec-skill/scripts/skill.mjs +202 -15
  211. package/kits/skills/ec-trigger/SKILL.md +167 -0
  212. package/kits/skills/ec-trigger/references/examples.md +153 -0
  213. package/kits/skills/thread-ops/SKILL.md +108 -420
  214. package/kits/templates/message-fragments/thread-context.md +1 -2
  215. package/kits/templates/message-fragments/trigger-thread-check.md +1 -1
  216. package/kits/templates/roles/member.json +1 -1
  217. package/kits/templates/roles/visitor.json +1 -0
  218. package/kits/templates/system-fragments/bootstrap.md +1 -0
  219. package/kits/templates/system-fragments/channel.md +2 -1
  220. package/kits/templates/system-fragments/commands.md +5 -4
  221. package/kits/templates/system-fragments/identity.md +2 -0
  222. package/kits/templates/system-fragments/session.md +1 -1
  223. package/kits/templates/system-fragments/venue.md +1 -1
  224. package/package.json +4 -3
@@ -1,4 +1,5 @@
1
1
  import fs from 'fs';
2
+ import { ipcQuery } from '../ipc.js';
2
3
  import path from 'path';
3
4
  import { spawn, execFile } from 'child_process';
4
5
  import { promisify } from 'util';
@@ -7,11 +8,12 @@ import { isRestartRecoveryEnabled, loadDaemonConfig } from '../config-store.js';
7
8
  import * as platform from '../utils/cross-platform.js';
8
9
  import { EventBus } from '../core/event-bus.js';
9
10
  import { applyCachedPackageUpgrades, recordPackageUpgrade } from '../utils/package-version-info.js';
11
+ import { acknowledgeUpgradeRestart, readUpgradeJob, updateUpgradeJob } from '../utils/upgrade-jobs.js';
10
12
  import { isValidAid } from '../aun/aid/index.js';
11
13
  import { msgSend } from '../aun/msg/index.js';
12
- import { scanInstances, cleanupInstances, writeRestartMonitor, removeRestartMonitor, isRestartMonitorWinner, findOrphanProcesses, killOrphans } from '../utils/instance-registry.js';
13
- import { MAIN_PACKAGE_NAME, WEB_PACKAGE_NAME } from '../product.js';
14
- import { shouldSuppressRealRestart } from '../utils/restart-safety.js';
14
+ import { scanInstances, cleanupInstances, writeRestartMonitor, removeRestartMonitor, isRestartMonitorWinner, findOrphanProcesses, killOrphans, updateRestartMonitorPhase } from '../utils/instance-registry.js';
15
+ import { MAIN_PACKAGE_NAME, RESTART_REQUEST_ID_ENV } from '../product.js';
16
+ import { readRestartRequest, updateRestartRequest, removeRestartRequest, shouldSuppressRealRestart } from '../utils/restart-safety.js';
15
17
  import { rotateStdoutLog } from '../utils/log-writer.js';
16
18
  import { applyDataMigration, finalizeDataMigration, planDataMigration, verifyDataMigration, } from '../core/data-migration.js';
17
19
  import { DAEMON_SHUTDOWN_TIMEOUT_MS, requestDaemonShutdown, stopCodexAppServerOrphans } from './daemon-commands.js';
@@ -112,6 +114,8 @@ export async function cmdRestartMonitor() {
112
114
  return;
113
115
  const p = resolvePaths();
114
116
  const restartLog = path.join(p.logs, 'restart.log');
117
+ const pendingFile = path.join(daemonControlDir(), 'restart-pending.json');
118
+ const requestId = process.env[RESTART_REQUEST_ID_ENV] || 'unknown';
115
119
  const MAX_HEAL_ATTEMPTS = 3;
116
120
  const READY_TIMEOUT = 30000; // 30s(AUN sidecar + 外部渠道连接预留)
117
121
  const HEAL_TIMEOUT = 30 * 60 * 1000; // 30 分钟,让 claude 自然结束
@@ -120,9 +124,24 @@ export async function cmdRestartMonitor() {
120
124
  const _d = new Date();
121
125
  const _p = (n) => String(n).padStart(2, '0');
122
126
  const ts = `${_d.getFullYear()}-${_p(_d.getMonth() + 1)}-${_p(_d.getDate())} ${_p(_d.getHours())}:${_p(_d.getMinutes())}:${_p(_d.getSeconds())}`;
123
- const line = `[${ts}] ${msg}\n`;
127
+ const line = `[${ts}] [requestId=${requestId}] ${msg}\n`;
124
128
  fs.appendFileSync(restartLog, line);
125
129
  };
130
+ const hasCorrelatedRequest = requestId !== 'unknown';
131
+ const upgradeJobId = hasCorrelatedRequest ? readRestartRequest(pendingFile, requestId)?.upgradeJobId : undefined;
132
+ const upgradeJob = typeof upgradeJobId === 'string' ? readUpgradeJob(upgradeJobId) : null;
133
+ if (upgradeJobId && (!upgradeJob || upgradeJob.restartRequestId !== requestId || upgradeJob.released || upgradeJob.deadline <= Date.now())) {
134
+ if (upgradeJob)
135
+ updateUpgradeJob(upgradeJob.jobId, { status: 'timeout', success: false, released: true, code: 'UPGRADE_TIMEOUT', reason: 'Update expired before restart preflight' }, '更新已超时,未执行重启');
136
+ process.exitCode = 2;
137
+ cleanupPendingFile(pendingFile, log, requestId);
138
+ return;
139
+ }
140
+ if (hasCorrelatedRequest && !readRestartRequest(pendingFile, requestId)) {
141
+ log('Restart request is missing or has been replaced; refusing to proceed');
142
+ process.exitCode = 2;
143
+ return;
144
+ }
126
145
  // 单实例保护:pre-check + post-write 自检(同 main 进程)
127
146
  {
128
147
  const pre = scanInstances();
@@ -133,8 +152,17 @@ export async function cmdRestartMonitor() {
133
152
  }
134
153
  }
135
154
  // 立即登记自己;exit 路径上自动清理 record
136
- writeRestartMonitor();
137
- process.on('exit', () => {
155
+ writeRestartMonitor(hasCorrelatedRequest ? requestId : undefined, 'starting');
156
+ process.on('exit', code => {
157
+ if (upgradeJob) {
158
+ const current = readUpgradeJob(upgradeJob.jobId);
159
+ if (current)
160
+ updateUpgradeJob(upgradeJob.jobId, {
161
+ released: true,
162
+ lifecycle: current.lifecycle ?? { status: code === 0 ? 'completed' : 'failed', at: new Date().toISOString(), ...(code !== 0 ? { reason: 'Restart lifecycle failed; inspect restart.log' } : {}) },
163
+ ...(['accepted', 'running'].includes(current.status) ? { status: 'failed', success: false, code: 'RESTART_ABORTED', reason: 'Restart monitor exited before entering restart' } : {}),
164
+ }, ['accepted', 'running'].includes(current.status) ? '重启未能开始,可重新发起' : undefined);
165
+ }
138
166
  removeRestartMonitor();
139
167
  // 安装包模式下本进程跑在 stage 副本里。cwd 不在其中(继承自调用方),
140
168
  // 所以可以安全自删;留下来只会占磁盘。
@@ -162,21 +190,56 @@ export async function cmdRestartMonitor() {
162
190
  const s = scanInstances();
163
191
  return s.mains.some(m => m.alive);
164
192
  };
165
- log('Restart monitor started');
193
+ const markRequestReady = () => {
194
+ if (!hasCorrelatedRequest)
195
+ return;
196
+ const requesterPid = readRestartRequest(pendingFile, requestId)?.requesterPid;
197
+ const daemonPid = scanInstances().mains.find(item => item.alive && item.record.pid !== requesterPid)?.record.pid;
198
+ if (daemonPid && updateRestartRequest(pendingFile, requestId, { status: 'ready', daemonPid })) {
199
+ if (upgradeJob && !readUpgradeJob(upgradeJob.jobId)?.lifecycle)
200
+ updateUpgradeJob(upgradeJob.jobId, { lifecycle: { status: 'ready', at: new Date().toISOString() } });
201
+ log(`Restart request marked ready (daemonPid=${daemonPid})`);
202
+ }
203
+ else {
204
+ log('WARN: could not mark correlated restart request ready');
205
+ }
206
+ };
166
207
  // Keep this path available for a failed read-only preflight so a stale
167
208
  // restart request cannot be replayed by a later daemon startup.
168
- const pendingFile = path.join(daemonControlDir(), 'restart-pending.json');
209
+ log(`Restart monitor started (requestId=${requestId})`);
169
210
  // 升级成功时记下版本变化,回执里要告诉 agent 实际跑的是哪个版本。
170
211
  let upgradeVersions;
212
+ if (upgradeJob)
213
+ updateUpgradeJob(upgradeJob.jobId, { phase: 'preflight', monitorPid: process.pid }, '正在执行原有迁移预检');
171
214
  const migrationPreflight = preflightDataMigration(p.root, log);
172
215
  if (!migrationPreflight.ok) {
173
216
  const message = `重启前用户数据迁移预检失败:${migrationPreflight.reason ?? 'unknown error'}`;
174
217
  log(message);
175
218
  console.error(`❌ ${message}`);
176
- cleanupPendingFile(pendingFile, log);
219
+ if (upgradeJob)
220
+ updateUpgradeJob(upgradeJob.jobId, { status: 'failed', success: false, code: 'PREFLIGHT_FAILED', reason: message }, '迁移预检失败,已中断重启');
221
+ cleanupPendingFile(pendingFile, log, hasCorrelatedRequest ? requestId : undefined);
177
222
  process.exitCode = 2;
178
223
  return;
179
224
  }
225
+ if (hasCorrelatedRequest) {
226
+ if (upgradeJob && (readUpgradeJob(upgradeJob.jobId)?.status !== 'running' || upgradeJob.deadline <= Date.now())) {
227
+ cleanupPendingFile(pendingFile, log, requestId);
228
+ process.exitCode = 2;
229
+ return;
230
+ }
231
+ if (!updateRestartRequest(pendingFile, requestId, { status: 'accepted', monitorPid: process.pid })) {
232
+ log('Restart request changed before acceptance; refusing to stop the daemon');
233
+ process.exitCode = 2;
234
+ return;
235
+ }
236
+ }
237
+ updateRestartMonitorPhase(process.pid, 'accepted');
238
+ log('Restart monitor accepted after migration preflight and monitor election');
239
+ if (upgradeJob) {
240
+ updateUpgradeJob(upgradeJob.jobId, { status: 'restart-started', success: true, phase: 'restart' }, '预检通过,已进入重启执行;不代表新进程已就绪');
241
+ await acknowledgeUpgradeRestart(upgradeJob.jobId);
242
+ }
180
243
  // Test daemons live in one temporary HOME per test, so the current HOME's
181
244
  // instance registry cannot see them. Their strict environment markers make
182
245
  // them safe to reap globally on every real restart. Keep one process-table
@@ -281,7 +344,7 @@ export async function cmdRestartMonitor() {
281
344
  if (exited.some(result => !result)) {
282
345
  log('❌ Existing daemon process could not be stopped; refusing to spawn a replacement');
283
346
  await notifyDaemonOwners(p, '❌ 旧 EvolCore 进程未能退出,已停止重启,避免重复连接 AUN。', log);
284
- cleanupPendingFile(pendingFile, log);
347
+ cleanupPendingFile(pendingFile, log, hasCorrelatedRequest ? requestId : undefined);
285
348
  process.exit(1);
286
349
  }
287
350
  // waitForProcessExit above already observed the old daemon exit. A fixed
@@ -295,7 +358,7 @@ export async function cmdRestartMonitor() {
295
358
  const remaining = findOrphanProcesses();
296
359
  if (scanInstances().mains.some(item => item.alive) || remaining.some(belongsToRuntime)) {
297
360
  log('Restart aborted: a runtime process appeared or survived shutdown');
298
- cleanupPendingFile(pendingFile, log);
361
+ cleanupPendingFile(pendingFile, log, hasCorrelatedRequest ? requestId : undefined);
299
362
  process.exit(1);
300
363
  }
301
364
  const otherHome = remaining.filter(o => !o.confirmedTestDaemon && !belongsToRuntime(o));
@@ -314,7 +377,10 @@ export async function cmdRestartMonitor() {
314
377
  // 升级会原地替换整包,先留一个可回退的产物副本。备份放 EVOLCORE_HOME 而非包目录:
315
378
  // Linux/macOS 的全局目录常常不可写,而 HOME 一定可写。
316
379
  const upgradeBackup = await backupDistForUpgrade(p, log);
317
- const [upgrade, ecwebUpgrade] = await applyCachedPackageUpgrades(undefined, { log });
380
+ const [upgrade] = await applyCachedPackageUpgrades(undefined, {
381
+ log,
382
+ ...(upgradeJob ? { target: upgradeJob.target, distTag: upgradeJob.distTag, npmConfig: upgradeJob.npmConfig } : {}),
383
+ });
318
384
  let upgradeRolledBack;
319
385
  switch (upgrade.status) {
320
386
  case 'upgraded':
@@ -355,19 +421,6 @@ export async function cmdRestartMonitor() {
355
421
  await notifyDaemonOwners(p, `⚠️ evolcore 升级失败,使用当前版本继续\n${upgrade.error ?? ''}`.trim(), log);
356
422
  break;
357
423
  }
358
- // EC Web independently applies a valid cached result under the install lock.
359
- switch (ecwebUpgrade.status) {
360
- case 'upgraded':
361
- log(`✅ ${WEB_PACKAGE_NAME} upgraded: ${ecwebUpgrade.from} → ${ecwebUpgrade.to}`);
362
- await notifyDaemonOwners(p, `📦 ${WEB_PACKAGE_NAME} 已升级 ${ecwebUpgrade.from} → ${ecwebUpgrade.to}`, log);
363
- break;
364
- case 'no-update':
365
- break;
366
- case 'failed':
367
- log(`⚠ ${WEB_PACKAGE_NAME} upgrade failed (${ecwebUpgrade.from} → ${ecwebUpgrade.to}): ${ecwebUpgrade.error}`);
368
- await notifyDaemonOwners(p, `⚠️ ${WEB_PACKAGE_NAME} 升级失败,使用当前版本继续\n${ecwebUpgrade.error ?? ''}`.trim(), log);
369
- break;
370
- }
371
424
  // 编译在 daemon 停止之后、拉起之前:此时没有活进程占着 dist/,
372
425
  // 失败还能回滚到上一份可用产物,因此这一步整体是可撤销的。
373
426
  const buildOutcome = await buildSourcesTransactionally(log);
@@ -375,7 +428,7 @@ export async function cmdRestartMonitor() {
375
428
  const message = `服务未重启:${buildOutcome.reason ?? '构建失败'}`;
376
429
  log(`❌ ${message}`);
377
430
  await notifyDaemonOwners(p, `❌ ${message}`, log);
378
- markRestartOutcome(pendingFile, 'build-failed', buildOutcome.reason, log);
431
+ markRestartOutcome(pendingFile, 'build-failed', buildOutcome.reason, log, hasCorrelatedRequest ? requestId : undefined);
379
432
  process.exit(1);
380
433
  }
381
434
  if (buildOutcome.status === 'rolled-back') {
@@ -386,8 +439,8 @@ export async function cmdRestartMonitor() {
386
439
  const migration = applyPendingDataMigration(p.root, log);
387
440
  if (!migration.ok) {
388
441
  const message = `服务未重启:用户数据迁移失败(${migration.reason ?? 'unknown error'})`;
389
- markRestartOutcome(pendingFile, 'migration-failed', migration.reason, log);
390
- cleanupPendingFile(pendingFile, log);
442
+ markRestartOutcome(pendingFile, 'migration-failed', migration.reason, log, hasCorrelatedRequest ? requestId : undefined);
443
+ cleanupPendingFile(pendingFile, log, hasCorrelatedRequest ? requestId : undefined);
391
444
  process.exit(1);
392
445
  }
393
446
  // ECWeb is owned by the daemon. Starting it here used to race with the
@@ -404,22 +457,23 @@ export async function cmdRestartMonitor() {
404
457
  }, log);
405
458
  }
406
459
  if (upgradeRolledBack) {
407
- markRestartOutcome(pendingFile, 'rolled-back', upgradeRolledBack, log);
460
+ markRestartOutcome(pendingFile, 'rolled-back', upgradeRolledBack, log, hasCorrelatedRequest ? requestId : undefined);
408
461
  log(`✓ Service restarted after upgrade rollback`);
409
462
  }
410
463
  else if (buildOutcome.status === 'rolled-back') {
411
464
  // 让发起方的 agent 知道改动没生效,而不是以为重启成功了。
412
- markRestartOutcome(pendingFile, 'rolled-back', buildOutcome.reason, log);
465
+ markRestartOutcome(pendingFile, 'rolled-back', buildOutcome.reason, log, hasCorrelatedRequest ? requestId : undefined);
413
466
  log('✓ Service restarted on the previous build');
414
467
  }
415
468
  else {
416
469
  dropStaleBackup(log);
417
470
  dropUpgradeBackup(upgradeBackup, log);
418
471
  if (upgradeVersions?.to) {
419
- markRestartOutcome(pendingFile, 'upgraded', `已升级 ${upgradeVersions.from} → ${upgradeVersions.to}`, log);
472
+ markRestartOutcome(pendingFile, 'upgraded', `已升级 ${upgradeVersions.from} → ${upgradeVersions.to}`, log, hasCorrelatedRequest ? requestId : undefined);
420
473
  }
421
474
  log('✓ Service restarted successfully');
422
475
  }
476
+ markRequestReady();
423
477
  archiveSelfHealLog(p, log);
424
478
  // 发起会话内的“重启成功”通知由新进程自行发送,此处只负责失败/自愈告警。
425
479
  await finishSuccessfulRestart();
@@ -438,7 +492,8 @@ export async function cmdRestartMonitor() {
438
492
  ...upgradeVersions, at: new Date().toISOString(), status: 'rolled-back', error: reason,
439
493
  }, log);
440
494
  log(`✓ ${reason}`);
441
- markRestartOutcome(pendingFile, 'rolled-back', reason, log);
495
+ markRestartOutcome(pendingFile, 'rolled-back', reason, log, hasCorrelatedRequest ? requestId : undefined);
496
+ markRequestReady();
442
497
  await notifyDaemonOwners(p, `⚠️ ${reason}。当前运行旧版本,请检查后重试。`, log);
443
498
  archiveSelfHealLog(p, log);
444
499
  process.exit(0);
@@ -455,7 +510,8 @@ export async function cmdRestartMonitor() {
455
510
  if (started) {
456
511
  const reason = '新编译产物启动失败,已回滚到上一个可用版本';
457
512
  log(`✓ ${reason}`);
458
- markRestartOutcome(pendingFile, 'rolled-back', reason, log);
513
+ markRestartOutcome(pendingFile, 'rolled-back', reason, log, hasCorrelatedRequest ? requestId : undefined);
514
+ markRequestReady();
459
515
  await notifyDaemonOwners(p, `⚠️ ${reason}。改动未生效,请检查代码后重试。`, log);
460
516
  archiveSelfHealLog(p, log);
461
517
  process.exit(0);
@@ -467,21 +523,21 @@ export async function cmdRestartMonitor() {
467
523
  if (p.root.startsWith('/tmp/') || process.env.EVOLCORE_TEST === '1') {
468
524
  log('❌ Service failed to start (test environment detected, skipping self-heal)');
469
525
  await notifyDaemonOwners(p, '❌ 服务启动失败(测试环境,已跳过自动修复)', log);
470
- cleanupPendingFile(pendingFile, log);
526
+ cleanupPendingFile(pendingFile, log, hasCorrelatedRequest ? requestId : undefined);
471
527
  process.exit(1);
472
528
  }
473
529
  if (hasStartupResourceConflict(p)) {
474
530
  const message = '服务启动失败:IPC 管道或端口仍被占用,已跳过自动修复,避免重复连接 AUN。';
475
531
  log(message);
476
532
  await notifyDaemonOwners(p, `❌ ${message}`, log);
477
- cleanupPendingFile(pendingFile, log);
533
+ cleanupPendingFile(pendingFile, log, hasCorrelatedRequest ? requestId : undefined);
478
534
  process.exit(1);
479
535
  }
480
536
  if (!isRestartRecoveryEnabled()) {
481
537
  const message = '服务启动失败,restartRecovery.enabled 已关闭,已跳过 Claude 自动修复。';
482
538
  log(`⚠️ ${message}`);
483
539
  await notifyDaemonOwners(p, `⚠️ ${message}`, log);
484
- cleanupPendingFile(pendingFile, log);
540
+ cleanupPendingFile(pendingFile, log, hasCorrelatedRequest ? requestId : undefined);
485
541
  process.exit(1);
486
542
  }
487
543
  // 启动失败,进入 self-heal 循环
@@ -493,17 +549,19 @@ export async function cmdRestartMonitor() {
493
549
  const message = '找不到 claude CLI,已停止自动修复;请检查后台进程 PATH。';
494
550
  log(message);
495
551
  await notifyDaemonOwners(p, `❌ ${message}`, log);
496
- cleanupPendingFile(pendingFile, log);
552
+ cleanupPendingFile(pendingFile, log, hasCorrelatedRequest ? requestId : undefined);
497
553
  process.exit(1);
498
554
  }
499
555
  for (let attempt = 1; attempt <= MAX_HEAL_ATTEMPTS; attempt++) {
500
556
  // 前置检查:服务可能已被上一轮 claude 修复并启动
501
557
  if (isServiceAlive()) {
502
558
  log(`✓ Service already running before attempt ${attempt}, skipping`);
559
+ markRequestReady();
503
560
  await sendHealSummary(p, attempt - 1, log);
504
561
  eventBus.publish({ type: 'self-heal:completed', success: true, attempts: attempt - 1 });
505
562
  archiveSelfHealLog(p, log);
506
- cleanupPendingFile(pendingFile, log);
563
+ if (!hasCorrelatedRequest)
564
+ cleanupPendingFile(pendingFile, log);
507
565
  process.exit(0);
508
566
  }
509
567
  log(`Self-heal attempt ${attempt}/${MAX_HEAL_ATTEMPTS}`);
@@ -513,10 +571,12 @@ export async function cmdRestartMonitor() {
513
571
  // 后置检查:不管 invokeClaude 返回什么,都检查服务实际状态
514
572
  if (isServiceAlive()) {
515
573
  log(`✓ Service is running after attempt ${attempt}`);
574
+ markRequestReady();
516
575
  await sendHealSummary(p, attempt, log);
517
576
  eventBus.publish({ type: 'self-heal:completed', success: true, attempts: attempt });
518
577
  archiveSelfHealLog(p, log);
519
- cleanupPendingFile(pendingFile, log);
578
+ if (!hasCorrelatedRequest)
579
+ cleanupPendingFile(pendingFile, log);
520
580
  process.exit(0);
521
581
  }
522
582
  if (!healed) {
@@ -527,10 +587,12 @@ export async function cmdRestartMonitor() {
527
587
  started = await spawnAndWaitReady(p, log, READY_TIMEOUT);
528
588
  if (started) {
529
589
  log(`✓ Self-heal succeeded on attempt ${attempt}`);
590
+ markRequestReady();
530
591
  await sendHealSummary(p, attempt, log);
531
592
  eventBus.publish({ type: 'self-heal:completed', success: true, attempts: attempt });
532
593
  archiveSelfHealLog(p, log);
533
- cleanupPendingFile(pendingFile, log);
594
+ if (!hasCorrelatedRequest)
595
+ cleanupPendingFile(pendingFile, log);
534
596
  process.exit(0);
535
597
  }
536
598
  log(`Attempt ${attempt}: still failing after fix`);
@@ -538,16 +600,18 @@ export async function cmdRestartMonitor() {
538
600
  // 全部失败 — 最后再检查一次
539
601
  if (isServiceAlive()) {
540
602
  log('✓ Service recovered during final check');
603
+ markRequestReady();
541
604
  await sendHealSummary(p, MAX_HEAL_ATTEMPTS, log);
542
605
  eventBus.publish({ type: 'self-heal:completed', success: true, attempts: MAX_HEAL_ATTEMPTS });
543
606
  archiveSelfHealLog(p, log);
544
- cleanupPendingFile(pendingFile, log);
607
+ if (!hasCorrelatedRequest)
608
+ cleanupPendingFile(pendingFile, log);
545
609
  process.exit(0);
546
610
  }
547
611
  log(`❌ All ${MAX_HEAL_ATTEMPTS} self-heal attempts failed`);
548
612
  eventBus.publish({ type: 'self-heal:completed', success: false, attempts: MAX_HEAL_ATTEMPTS });
549
613
  await notifyDaemonOwners(p, `❌ ${MAX_HEAL_ATTEMPTS} 次自动修复均失败,需要人工介入。\n修复记录:${p.selfHealLog}`, log);
550
- cleanupPendingFile(pendingFile, log);
614
+ cleanupPendingFile(pendingFile, log, hasCorrelatedRequest ? requestId : undefined);
551
615
  process.exit(1);
552
616
  }
553
617
  /**
@@ -803,11 +867,15 @@ function runBuildCommand(log) {
803
867
  * code change took effect. Older daemons ignore unknown fields, so writing this
804
868
  * stays safe when the rolled-back artifact predates the field.
805
869
  */
806
- function markRestartOutcome(pendingFile, outcome, reason, log) {
870
+ function markRestartOutcome(pendingFile, outcome, reason, log, requestId) {
807
871
  try {
808
872
  if (!fs.existsSync(pendingFile))
809
873
  return;
810
874
  const info = JSON.parse(fs.readFileSync(pendingFile, 'utf-8'));
875
+ if (requestId && info.requestId !== requestId)
876
+ return;
877
+ if (typeof info.upgradeJobId === 'string')
878
+ updateUpgradeJob(info.upgradeJobId, { lifecycle: { status: outcome, reason, at: new Date().toISOString() } });
811
879
  info.outcome = outcome;
812
880
  if (reason)
813
881
  info.outcomeReason = reason;
@@ -920,9 +988,25 @@ function dropUpgradeBackup(backupDir, log) {
920
988
  log('WARN: could not remove upgrade backup');
921
989
  }
922
990
  }
923
- function cleanupPendingFile(filePath, log) {
991
+ function cleanupPendingFile(filePath, log, requestId) {
924
992
  try {
925
993
  if (fs.existsSync(filePath)) {
994
+ if (requestId) {
995
+ const request = readRestartRequest(filePath, requestId);
996
+ if (!request)
997
+ return;
998
+ if (request.status === 'ready') {
999
+ log('Preserved restart-pending.json until the new daemon sends its completion notice');
1000
+ return;
1001
+ }
1002
+ removeRestartRequest(filePath, requestId);
1003
+ try {
1004
+ fs.rmSync(`${filePath}_`, { force: true });
1005
+ }
1006
+ catch { }
1007
+ log('Cleaned up correlated restart-pending.json');
1008
+ return;
1009
+ }
926
1010
  fs.unlinkSync(filePath);
927
1011
  log('Cleaned up restart-pending.json');
928
1012
  }
@@ -961,6 +1045,7 @@ async function spawnAndWaitReady(p, log, timeout) {
961
1045
  ...process.env,
962
1046
  EVOLCORE_HOME: p.root,
963
1047
  EVOLCORE_LAUNCHED_BY: 'restart-monitor',
1048
+ [RESTART_REQUEST_ID_ENV]: '',
964
1049
  LOG_LEVEL: process.env.LOG_LEVEL || 'INFO',
965
1050
  MESSAGE_LOG: process.env.MESSAGE_LOG || 'true',
966
1051
  EVENT_LOG: process.env.EVENT_LOG || 'true',
@@ -1099,50 +1184,57 @@ function archiveSelfHealLog(p, log) {
1099
1184
  log(`Archived self-heal log to ${archivePath}`);
1100
1185
  }
1101
1186
  async function notifyDaemonOwners(p, message, log) {
1102
- const cfg = loadDaemonConfig();
1103
- const daemonAid = typeof cfg.aid === 'string' ? cfg.aid.trim() : '';
1104
- if (!daemonAid) {
1105
- log('Daemon owner notification skipped: daemon.json.aid not configured');
1106
- return;
1107
- }
1108
- if (!isValidAid(daemonAid)) {
1109
- log(`Daemon owner notification skipped: invalid daemon.json.aid ${daemonAid}`);
1110
- return;
1111
- }
1112
- const owners = Array.from(new Set((Array.isArray(cfg.owners) ? cfg.owners : [])
1113
- .map(owner => typeof owner === 'string' ? owner.trim() : '')
1114
- .filter(Boolean)));
1115
- if (owners.length === 0) {
1116
- log('Daemon owner notification skipped: daemon.json.owners not configured');
1117
- return;
1118
- }
1119
- const validOwners = owners.filter(owner => {
1120
- const valid = isValidAid(owner);
1121
- if (!valid)
1122
- log(`Daemon owner notification skipped invalid owner AID: ${owner}`);
1123
- return valid;
1124
- });
1125
- if (validOwners.length === 0)
1126
- return;
1127
- const text = formatDaemonOwnerNotice(p, message);
1128
- for (const owner of validOwners) {
1129
- try {
1130
- const result = await withTimeout(msgSend({
1131
- from: daemonAid,
1132
- to: owner,
1133
- body: { mode: 'text', text },
1134
- slotId: 'restart-monitor',
1135
- }), OWNER_NOTIFICATION_TIMEOUT_MS);
1136
- if (!result.ok) {
1137
- log(`Daemon owner notification failed for ${owner}: ${result.error}`);
1138
- continue;
1139
- }
1140
- log(`Daemon owner notification sent to ${owner}: ${result.message_id}`);
1187
+ try {
1188
+ const cfg = loadDaemonConfig();
1189
+ const daemonAid = typeof cfg.aid === 'string' ? cfg.aid.trim() : '';
1190
+ if (!daemonAid) {
1191
+ log('Daemon owner notification skipped: daemon.json.aid not configured');
1192
+ return;
1141
1193
  }
1142
- catch (error) {
1143
- log(`Daemon owner notification failed for ${owner}: ${error.message?.slice(0, 200) || error}`);
1194
+ if (!isValidAid(daemonAid)) {
1195
+ log(`Daemon owner notification skipped: invalid daemon.json.aid ${daemonAid}`);
1196
+ return;
1197
+ }
1198
+ const response = await ipcQuery(p.socket, { type: 'aid.ownership', argv: ['aid', 'bind', 'status'] }, OWNER_NOTIFICATION_TIMEOUT_MS).catch(() => null);
1199
+ const state = response?.ok ? response.state : undefined;
1200
+ const owners = state?.subjectAid === daemonAid && state?.consensusStatus === 'consensus'
1201
+ && typeof state.ownerAid === 'string' && isValidAid(state.ownerAid) ? [state.ownerAid] : [];
1202
+ if (owners.length === 0) {
1203
+ log('Daemon owner notification skipped: fresh protocol consensus unavailable');
1204
+ return;
1205
+ }
1206
+ const validOwners = owners.filter(owner => {
1207
+ const valid = isValidAid(owner);
1208
+ if (!valid)
1209
+ log(`Daemon owner notification skipped invalid owner AID: ${owner}`);
1210
+ return valid;
1211
+ });
1212
+ if (validOwners.length === 0)
1213
+ return;
1214
+ const text = formatDaemonOwnerNotice(p, message);
1215
+ for (const owner of validOwners) {
1216
+ try {
1217
+ const result = await withTimeout(msgSend({
1218
+ from: daemonAid,
1219
+ to: owner,
1220
+ body: { mode: 'text', text },
1221
+ slotId: 'restart-monitor',
1222
+ }), OWNER_NOTIFICATION_TIMEOUT_MS);
1223
+ if (!result.ok) {
1224
+ log(`Daemon owner notification failed for ${owner}: ${result.error}`);
1225
+ continue;
1226
+ }
1227
+ log(`Daemon owner notification sent to ${owner}: ${result.message_id}`);
1228
+ }
1229
+ catch (error) {
1230
+ log(`Daemon owner notification failed for ${owner}: ${error.message?.slice(0, 200) || error}`);
1231
+ }
1144
1232
  }
1145
1233
  }
1234
+ catch (error) {
1235
+ const reason = error instanceof Error ? error.message : String(error);
1236
+ log(`WARN: Daemon owner notification failed: ${reason.slice(0, 200)}`);
1237
+ }
1146
1238
  }
1147
1239
  function formatDaemonOwnerNotice(p, message) {
1148
1240
  return [
@@ -149,7 +149,7 @@ async function cmdFind(args) {
149
149
  console.log(`ec 会话 id: ${f.sessionId}`);
150
150
  console.log(` agentSessionId: ${f.agentSessionId ?? '(空)'}`);
151
151
  console.log(` baseagent: ${f.baseagent ?? '(未知)'}`);
152
- console.log(` threadId: ${f.threadId ? f.threadId : '(主会话)'}`);
152
+ console.log(` threadId: ${f.threadId || '(主会话)'}`);
153
153
  if (f.name)
154
154
  console.log(` 名称: ${f.name}`);
155
155
  if (f.chatType)
package/dist/cli/stats.js CHANGED
@@ -41,8 +41,8 @@ Usage: ec stats [options]
41
41
  --groups [--limit N] 群聊列表(同上)
42
42
  --summary 指定时间范围总消耗汇总(token/USD/CNY)
43
43
  --peer-detail <id> 指定对端 AID 或 peer_key,按天返回消耗明细
44
- --task-calls <taskId> 一个 task 的逐次大模型调用明细
45
- --session-calls <id> 一个会话的逐次大模型调用明细
44
+ --task-calls <taskId> 一个 task 的模型用量明细(Codex 为回合/子 agent 汇总)
45
+ --session-calls <id> 一个会话的模型用量明细(Codex 为回合/子 agent 汇总)
46
46
 
47
47
  输出格式:
48
48
  --format json JSON 输出
@@ -417,7 +417,7 @@ export async function handleStats(args) {
417
417
  console.log();
418
418
  return;
419
419
  }
420
- // 逐次大模型调用明细
420
+ // 模型用量明细
421
421
  if ((flags['task-calls'] && typeof flags['task-calls'] === 'string') ||
422
422
  (flags['session-calls'] && typeof flags['session-calls'] === 'string')) {
423
423
  const byTask = typeof flags['task-calls'] === 'string';
@@ -431,13 +431,15 @@ export async function handleStats(args) {
431
431
  console.log('No model calls found.');
432
432
  return;
433
433
  }
434
- console.log(`\n${BOLD}📊 大模型调用明细 — ${key}${RESET}\n`);
435
- const headers = ['#', 'Model', 'Input', 'Output', 'CacheR', 'CacheW', 'Task', 'Deg'];
434
+ console.log(`\n${BOLD}📊 模型用量明细 — ${key}${RESET}\n`);
435
+ const headers = ['#', 'Model', 'Source', 'Input', 'Output', 'CacheR', 'CacheW', 'Provider Thread', 'Task', 'Deg'];
436
436
  const tableRows = rows.map(r => [
437
437
  String(r.call_index),
438
438
  r.model.split('-').slice(0, 3).join('-'),
439
+ r.source,
439
440
  fmtTokens(r.input_tokens), fmtTokens(r.output_tokens),
440
441
  fmtTokens(r.cache_read_tokens), fmtTokens(r.cache_creation_tokens),
442
+ r.provider_thread_id ?? '',
441
443
  r.task_id, r.degraded ? 'Y' : '',
442
444
  ]);
443
445
  printTable(headers, tableRows);