evolcore 0.0.32 → 0.0.34

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 (214) hide show
  1. package/CHANGELOG.md +61 -0
  2. package/bin/ec.js +3 -2
  3. package/bin/install-codex-managed-hooks.mjs +135 -37
  4. package/dist/agents/baseagent-availability.js +20 -36
  5. package/dist/agents/claude-runner.js +111 -15
  6. package/dist/agents/codex/app-server-client.js +3 -0
  7. package/dist/agents/codex/diagnostics.js +112 -54
  8. package/dist/agents/codex-runner.js +241 -63
  9. package/dist/agents/ecagent-runner.js +78 -33
  10. package/dist/agents/runner-types.js +5 -6
  11. package/dist/aun/aid/identity.js +1 -1
  12. package/dist/aun/group/operations.js +0 -1
  13. package/dist/aun/msg/history.js +1 -0
  14. package/dist/aun/msg/p2p.js +0 -1
  15. package/dist/aun/outbox.js +50 -1
  16. package/dist/channels/aun.js +44 -8
  17. package/dist/channels/daemon.js +261 -3
  18. package/dist/cli/agent-command.js +58 -24
  19. package/dist/cli/agent.js +1 -1
  20. package/dist/cli/aun-commands.js +368 -31
  21. package/dist/cli/contact.js +8 -2
  22. package/dist/cli/ctl-command.js +3 -1
  23. package/dist/cli/daemon-commands.js +197 -27
  24. package/dist/cli/debug-mode.js +154 -0
  25. package/dist/cli/fs-command.js +2 -1
  26. package/dist/cli/handoff-command.js +3 -0
  27. package/dist/cli/index.js +98 -67
  28. package/dist/cli/init.js +2 -2
  29. package/dist/cli/integrity-command.js +196 -0
  30. package/dist/cli/managed-command-guard.js +5 -0
  31. package/dist/cli/memory-command.js +166 -146
  32. package/dist/cli/queue-command.js +9 -4
  33. package/dist/cli/session-command.js +194 -0
  34. package/dist/cli/skill-command.js +78 -2
  35. package/dist/cli/task-context.js +1 -0
  36. package/dist/cli/thread-command.js +622 -0
  37. package/dist/cli/trigger-command.js +10 -3
  38. package/dist/config/builtin-roles.js +1 -0
  39. package/dist/config/config-field-policy.js +6 -3
  40. package/dist/config/config-manager.js +26 -2
  41. package/dist/config/contact-book-store.js +5 -0
  42. package/dist/config/resolved-config-op.js +3 -0
  43. package/dist/config/role-service.js +13 -0
  44. package/dist/config/runtime-env.js +16 -0
  45. package/dist/config-store.js +45 -0
  46. package/dist/core/audit/log-integrity.js +3 -1
  47. package/dist/core/auth/agent-delegation.js +133 -36
  48. package/dist/core/auth/debug-context.js +139 -0
  49. package/dist/core/auth/delegation-diagnostics.js +414 -0
  50. package/dist/core/auth/operation-authorizer.js +25 -2
  51. package/dist/core/auth/operation-catalog.js +49 -0
  52. package/dist/core/background-session/session-cleanup.js +212 -0
  53. package/dist/core/background-session/types.js +10 -0
  54. package/dist/core/bootstrap-messages.js +1 -1
  55. package/dist/core/bootstrap-service.js +6 -3
  56. package/dist/core/command/agent-control.js +18 -26
  57. package/dist/core/command/command-handler.js +197 -27
  58. package/dist/core/command/daemon-diagnostics.js +1 -0
  59. package/dist/core/command/memory-slash.js +6 -2
  60. package/dist/core/command/menu-catalog.js +51 -1
  61. package/dist/core/command/menu-handler.js +114 -23
  62. package/dist/core/command/menu-protocol.js +25 -1
  63. package/dist/core/command/skill-slash.js +322 -0
  64. package/dist/core/command/slash-gate.js +6 -4
  65. package/dist/core/command/slash-handler.js +195 -13
  66. package/dist/core/command/topic-slash.js +528 -0
  67. package/dist/core/daemon-bootstrap.js +4 -7
  68. package/dist/core/evolagent-registry.js +20 -5
  69. package/dist/core/file-integrity/read.js +61 -0
  70. package/dist/core/file-integrity/store.js +262 -0
  71. package/dist/core/handoff/runtime.js +5 -11
  72. package/dist/core/handoff/store.js +5 -0
  73. package/dist/core/ipc-registration.js +175 -3
  74. package/dist/core/memory/batch-meta.js +132 -0
  75. package/dist/core/memory/consolidation-context.js +83 -29
  76. package/dist/core/memory/consolidation-ops-records.js +49 -16
  77. package/dist/core/memory/consolidation-queue-format.js +21 -0
  78. package/dist/core/memory/consolidation.js +264 -205
  79. package/dist/core/memory/index-manager.js +19 -28
  80. package/dist/core/memory/memory-admin.js +19 -14
  81. package/dist/core/memory/memory-document.js +53 -9
  82. package/dist/core/memory/memory-writer.js +30 -15
  83. package/dist/core/message/aun-ipc-sender.js +161 -6
  84. package/dist/core/message/im-renderer.js +169 -10
  85. package/dist/core/message/message-bridge.js +15 -4
  86. package/dist/core/message/message-log.js +1 -0
  87. package/dist/core/message/peer-mode.js +6 -1
  88. package/dist/core/message/response-engine.js +929 -110
  89. package/dist/core/message/subtask-tool-projection.js +140 -0
  90. package/dist/core/model/catalog-service.js +17 -12
  91. package/dist/core/permission/ec-command-parser.js +217 -9
  92. package/dist/core/permission/protected-paths.js +5 -1
  93. package/dist/core/permission/readonly-shell-query.js +43 -5
  94. package/dist/core/permission/session-path-grants.js +334 -0
  95. package/dist/core/permission/tool-canonical.js +226 -0
  96. package/dist/core/permission/tool-policy.js +30 -62
  97. package/dist/core/role/runtime-policy.js +21 -10
  98. package/dist/core/session/session-fs-store.js +123 -8
  99. package/dist/core/session/session-idle-monitor.js +249 -22
  100. package/dist/core/session/session-manager.js +129 -14
  101. package/dist/core/skill/command-parser.js +13 -0
  102. package/dist/core/skill/index-generator.js +21 -4
  103. package/dist/core/skill/startup.js +41 -0
  104. package/dist/core/skill/switch-resolver.js +55 -29
  105. package/dist/core/system-session/grants.js +40 -0
  106. package/dist/core/system-session/memory.js +222 -0
  107. package/dist/core/system-session/registry.js +74 -0
  108. package/dist/core/system-session/types.js +14 -0
  109. package/dist/core/thread/command-parser.js +125 -0
  110. package/dist/core/thread/index.js +1 -0
  111. package/dist/core/thread/ipc-operation.js +95 -0
  112. package/dist/core/thread/thread-context-storage.js +131 -0
  113. package/dist/core/thread/thread-operation.js +258 -0
  114. package/dist/core/thread/thread-route.js +33 -0
  115. package/dist/core/thread/thread-service.js +770 -0
  116. package/dist/core/thread/types.js +1 -0
  117. package/dist/eck/kit-renderer.js +7 -2
  118. package/dist/eck/manifest-engine.js +24 -4
  119. package/dist/eck/message-renderer.js +35 -0
  120. package/dist/index.js +19 -7
  121. package/dist/ipc.js +140 -50
  122. package/dist/paths.js +93 -0
  123. package/dist/response-system/engines/v1/proactive-flow.js +169 -80
  124. package/dist/stats/billing.js +20 -8
  125. package/dist/trigger/authorization.js +57 -28
  126. package/dist/trigger/feedback.js +13 -2
  127. package/dist/trigger/parser.js +70 -1
  128. package/dist/trigger/system-definitions.js +3 -3
  129. package/dist/trigger/validation.js +14 -0
  130. package/dist/utils/cross-platform.js +11 -2
  131. package/dist/utils/error-dict.json +18 -4
  132. package/dist/utils/error-utils.js +143 -1
  133. package/dist/utils/instance-registry.js +13 -8
  134. package/dist/utils/log-writer.js +12 -0
  135. package/dist/utils/process-introspect.js +11 -0
  136. package/kits/docs/context-assembly.md +1 -1
  137. package/kits/docs/evolcore/INDEX.md +1 -0
  138. package/kits/docs/evolcore/agent-create.md +40 -28
  139. package/kits/docs/evolcore/agent.md +21 -6
  140. package/kits/docs/evolcore/config.md +2 -2
  141. package/kits/docs/evolcore/ctl.md +2 -2
  142. package/kits/docs/evolcore/ec-capabilities.md +5 -1
  143. package/kits/docs/evolcore/fs-architecture.md +4 -5
  144. package/kits/docs/evolcore/fs.md +1 -1
  145. package/kits/docs/evolcore/group-collaboration.md +4 -6
  146. package/kits/docs/evolcore/group.md +11 -12
  147. package/kits/docs/evolcore/memory-architecture.md +4 -5
  148. package/kits/docs/evolcore/memory-consolidation.md +19 -15
  149. package/kits/docs/evolcore/memory-tags.md +3 -1
  150. package/kits/docs/evolcore/memory.md +24 -12
  151. package/kits/docs/evolcore/menu-exec-schema-commands.md +2 -2
  152. package/kits/docs/evolcore/menu-protocol.md +7 -0
  153. package/kits/docs/evolcore/model.md +16 -0
  154. package/kits/docs/evolcore/msg.md +8 -11
  155. package/kits/docs/evolcore/thread-chain.md +267 -0
  156. package/kits/docs/prompt-loading-architecture.md +1 -1
  157. package/kits/eck_manifest.json +55 -40
  158. package/kits/eck_manifest.memory.json +0 -12
  159. package/kits/eck_message_manifest.json +169 -10
  160. package/kits/rules/01-overview.md +11 -0
  161. package/kits/rules/03-identity.md +6 -3
  162. package/kits/rules/05-venue.md +1 -1
  163. package/kits/rules/06-channel.md +1 -1
  164. package/kits/rules/07-memory-and-thread.md +199 -0
  165. package/kits/schemas/_meta.json +7 -1
  166. package/kits/schemas/agent-config.schema.13.json +1 -1
  167. package/kits/schemas/daemon.schema.7.json +140 -0
  168. package/kits/schemas/relation-config.schema.9.json +18 -3
  169. package/kits/schemas/role-config.schema.1.json +6 -6
  170. package/kits/schemas/single-session.schema.1.json +6 -6
  171. package/kits/schemas/single-session.schema.2.json +6 -6
  172. package/kits/schemas/single-session.schema.3.json +6 -6
  173. package/kits/skills/.switches.json +12 -1
  174. package/kits/skills/ec-group/SKILL.md +234 -0
  175. package/kits/skills/ec-skill/SKILL.md +10 -1
  176. package/kits/skills/ec-skill/scripts/cli.mjs +16 -2
  177. package/kits/skills/ec-skill/scripts/skill.mjs +1 -1
  178. package/kits/skills/scrapling-article-fetch/SKILL.md +128 -0
  179. package/kits/skills/scrapling-article-fetch/scripts/check_python_env.py +54 -0
  180. package/kits/skills/scrapling-article-fetch/scripts/render_markdown_reply.py +35 -0
  181. package/kits/skills/scrapling-article-fetch/scripts/run_pipeline.sh +16 -0
  182. package/kits/skills/scrapling-article-fetch/scripts/scrapling_fetch.py +344 -0
  183. package/kits/skills/thread-ops/SKILL.md +492 -0
  184. package/kits/skills/wechat-article-media-download/SKILL.md +65 -0
  185. package/kits/skills/wechat-article-media-download/scripts/download.py +163 -0
  186. package/kits/skills/wechat-article-reader/SKILL.md +97 -0
  187. package/kits/skills/wechat-article-reader/scripts/fetch.mjs +256 -0
  188. package/kits/templates/bootstrap-welcome.md +7 -7
  189. package/kits/templates/memory-consolidation/forget.md +7 -2
  190. package/kits/templates/memory-consolidation/generalize.md +9 -4
  191. package/kits/templates/memory-consolidation/notes.md +11 -11
  192. package/kits/templates/memory-consolidation/profile.md +8 -3
  193. package/kits/templates/memory-consolidation/promote.md +8 -3
  194. package/kits/templates/memory-consolidation/workflow.md +6 -6
  195. package/kits/templates/message-fragments/handoff-request-to-target.md +1 -1
  196. package/kits/templates/message-fragments/item.md +2 -1
  197. package/kits/templates/message-fragments/thread-context.md +6 -0
  198. package/kits/templates/message-fragments/trigger-session.md +2 -0
  199. package/kits/templates/message-fragments/trigger-thread-check.md +2 -0
  200. package/kits/templates/roles/member.json +2 -0
  201. package/kits/templates/roles/visitor.json +2 -0
  202. package/kits/templates/system-fragments/bootstrap.md +67 -72
  203. package/kits/templates/system-fragments/channel.md +3 -6
  204. package/kits/templates/system-fragments/commands.md +1 -0
  205. package/kits/templates/system-fragments/managed-runtime.md +15 -0
  206. package/kits/templates/system-fragments/memory.md +16 -9
  207. package/kits/templates/system-fragments/reply.md +7 -6
  208. package/kits/templates/system-fragments/session.md +5 -14
  209. package/package.json +6 -3
  210. package/dist/core/memory/memory-session-policy.js +0 -41
  211. package/dist/core/memory/memory-signature.js +0 -257
  212. package/kits/docs/evolcore/group-fs.md +0 -17
  213. package/kits/docs/venues/aun-group.md +0 -45
  214. package/kits/docs/venues/group.md +0 -43
@@ -4,7 +4,7 @@ import os from 'os';
4
4
  import { spawn, execFile } from 'child_process';
5
5
  import { promisify } from 'util';
6
6
  import readline from 'readline';
7
- import { resolveRoot, resolvePaths, ensureDataDirs, getPackageRoot, agentMdPath } from '../paths.js';
7
+ import { resolveRoot, resolvePaths, ensureDataDirs, getPackageRoot, agentMdPath, asModeOverridePath } from '../paths.js';
8
8
  import { loadDefaults, loadAllAgents, loadDaemonConfig, saveDaemonConfig } from '../config-store.js';
9
9
  import { resolveEffective } from '../config/config-manager.js';
10
10
  import { resolveAnthropicConfig } from '../agents/baseagent.js';
@@ -25,7 +25,7 @@ import { isNonAgentSessionChannel } from '../core/system-channels.js';
25
25
  import { printCodeStats } from './code-stats.js';
26
26
  import { getProcessStartTime, startTimeMatches } from '../utils/process-introspect.js';
27
27
  import { AGENT_DELEGATION_TOKEN_ENV, readDelegationCredential } from '../core/auth/agent-delegation.js';
28
- import { RUNTIME_HOME_ENV, WEB_CLI_BIN, WEB_PACKAGE_LATEST, WEB_PACKAGE_NAME } from '../product.js';
28
+ import { MAIN_CLI_BIN, RUNTIME_HOME_ENV, WEB_CLI_BIN, WEB_PACKAGE_LATEST, WEB_PACKAGE_NAME } from '../product.js';
29
29
  import { rotateStdoutLog } from '../utils/log-writer.js';
30
30
  import { inspectDataMigrationRequirement } from '../core/data-migration.js';
31
31
  import { isInitCancelledError, printInitCancelHint } from './init-cancel.js';
@@ -34,8 +34,34 @@ import { listCodexAppServerProcesses, unregisterCodexAppServerProcess } from '..
34
34
  import { DEFAULT_ECWEB_PORT, ecwebService, setEcwebEnabled } from '../config/daemon-services.js';
35
35
  import { checkCodexProjectPath, inspectCodexManagedHook, ensureCodexManagedHook, runCodexHookLaunchDiagnostic, } from '../agents/codex/diagnostics.js';
36
36
  const execFileAsync = promisify(execFile);
37
+ export async function confirmCodexHookRebind(state, packageRoot) {
38
+ console.log('⚠️ Codex 受管 Hook 绑定的另一套 EvolCore/Node 路径仍存在,需确认是否切换:');
39
+ console.log(` 现有包: ${state.managedPackageRoot}`);
40
+ console.log(` 现有 Node: ${state.managedNodePath}`);
41
+ console.log(` 当前包: ${packageRoot}`);
42
+ console.log(` 当前 Node: ${process.execPath}`);
43
+ console.log(' 切换会更新机器级 Hook,影响共享此策略的 Codex 会话;不会执行数据迁移。');
44
+ if (!process.stdin.isTTY || !process.stdout.isTTY) {
45
+ console.warn(` 非交互模式不自动选择。请在交互式终端运行 ${MAIN_CLI_BIN} start 确认;也可使用现有安装的 CLI。`);
46
+ return false;
47
+ }
48
+ const terminal = readline.createInterface({ input: process.stdin, output: process.stdout });
49
+ return new Promise(resolve => {
50
+ let settled = false;
51
+ const finish = (confirmed) => {
52
+ if (settled)
53
+ return;
54
+ settled = true;
55
+ terminal.close();
56
+ resolve(confirmed);
57
+ };
58
+ terminal.once('close', () => finish(false));
59
+ terminal.once('SIGINT', () => finish(false));
60
+ terminal.question(' [1] 切换到当前包/Node 并修复 [2] 保留原绑定并取消本次启动 [1/2,默认 2]: ', answer => finish(answer.trim() === '1'));
61
+ });
62
+ }
37
63
  /** Run before launch, and before restart stops any existing daemon. */
38
- export function ensureCodexHookBeforeLifecycle(onRepaired) {
64
+ export async function ensureCodexHookBeforeLifecycle(onRepaired) {
39
65
  if (!platform.isWindows)
40
66
  return true;
41
67
  try {
@@ -53,19 +79,31 @@ export function ensureCodexHookBeforeLifecycle(onRepaired) {
53
79
  });
54
80
  if (!usesCodex)
55
81
  return true;
56
- const result = ensureCodexManagedHook({
57
- packageRoot: getPackageRoot(),
58
- onInstallRequired: () => console.log('🔧 Codex 运行组件缺失,正在自动安装;Windows 提示管理员授权时请选择“是”。'),
82
+ const packageRoot = getPackageRoot();
83
+ const result = await ensureCodexManagedHook({
84
+ packageRoot,
85
+ repairDecision: state => confirmCodexHookRebind(state, packageRoot),
86
+ onInstallRequired: state => console.log(state.status === 'stale'
87
+ ? '🔧 Codex 受管 Hook 路径已过期,正在修复;Windows 提示管理员授权时请选择“是”。'
88
+ : '🔧 Codex 运行组件缺失,正在自动安装;Windows 提示管理员授权时请选择“是”。'),
59
89
  });
60
90
  if (result.status === 'installed') {
61
91
  if (result.repaired) {
62
- console.log('✅ Codex Windows managed hook 已自动安装并验证');
92
+ console.log('✅ Codex Windows managed hook 已修复并验证');
63
93
  onRepaired?.();
64
94
  }
65
95
  return true;
66
96
  }
67
- console.error(`❌ Codex Windows managed hook 未就绪:${result.error || result.reason || result.status}`);
68
- if (result.status === 'missing') {
97
+ if (result.cancelled) {
98
+ console.warn('ℹ 未确认切换 Codex 受管 Hook,保留原绑定;本次启动/重启已取消,现有服务未停止。');
99
+ process.exitCode = 1;
100
+ return false;
101
+ }
102
+ console.error(`❌ Codex Windows managed hook 启动检查未通过:${result.error || result.reason || result.status}`);
103
+ if (result.status === 'stale') {
104
+ console.error(` 本次操作已停止,现有服务未停止。请在交互式终端运行 ${MAIN_CLI_BIN} start 确认修复,或使用原安装的 CLI。`);
105
+ }
106
+ else if (result.status === 'missing') {
69
107
  console.error(' 本次操作已停止。下次启动或重启会再次自动尝试安装,需要允许 Windows 管理员授权。');
70
108
  }
71
109
  else {
@@ -192,6 +230,7 @@ function buildDaemonEnv(p, opts) {
192
230
  EVENT_LOG: process.env.EVENT_LOG || 'true',
193
231
  ...(opts.diagnose ? { EVOLCORE_DIAGNOSE: '1' } : {}),
194
232
  ...(opts.bindBootstrap ? { EVOLCORE_BIND_BOOTSTRAP: '1' } : {}),
233
+ ...(opts.debug ? { EVOLCORE_DEBUG_MODE: '1' } : {}),
195
234
  };
196
235
  }
197
236
  function latestSourceMtime(dir) {
@@ -419,7 +458,73 @@ function resolveRunningDuration(pid, uptime) {
419
458
  const instance = scanInstances().mains.find(entry => entry.alive && entry.record.pid === pid);
420
459
  return instance ? formatRunningDuration(Date.now() - instance.record.startedAt) : '未知';
421
460
  }
422
- function printStartupInfo(opts = {}) {
461
+ /**
462
+ * 渲染"运行模式"那一行。
463
+ *
464
+ * `daemonMode` 有值时用 daemon 实例记录里的模式(`ec status` 看的是正在跑的进程);
465
+ * 没有时按当前进程判断(`ec start` 时就是这一份 CLI 即将拉起的模式)。
466
+ */
467
+ /**
468
+ * CLI 侧按包路径判断模式。**不能用 isDevMode()**:它首次求值会读走并删除
469
+ * `--as-mode` 覆盖文件,那份文件只该由 daemon 消费。
470
+ */
471
+ function packageIsDevMode() {
472
+ return !getPackageRoot().includes('node_modules');
473
+ }
474
+ export function describeRunMode(daemonMode, cliIsDevMode = packageIsDevMode()) {
475
+ const effective = daemonMode ?? (cliIsDevMode ? 'dev' : 'install');
476
+ const label = effective === 'install' ? '安装模式' : '开发模式';
477
+ const note = effective === 'install'
478
+ ? '(校验随包文件,篡改自动恢复)'
479
+ : '(不校验随包文件,可随意改动)';
480
+ return `运行模式: ${label}${note}`;
481
+ }
482
+ /**
483
+ * 把本次 `--as-mode` 写进一次性覆盖文件,daemon 启动时读走并删除。
484
+ *
485
+ * 不走环境变量:环境变量会被 `ec restart` 全量继承,模式会在操作者不知情的
486
+ * 情况下被带到新进程。文件是一次性的,daemon 读过即删;若 daemon 最终没起来,
487
+ * 文件还留着,会在下一次启动时被读到——所以启动真实失败时此处清理。
488
+ */
489
+ function writeAsModeOverride(asMode) {
490
+ const file = asModeOverridePath();
491
+ if (!asMode) {
492
+ // 没带 --as-mode:清掉上次失败启动可能残留的文件,保证这次按包路径判断
493
+ clearAsModeOverride(file);
494
+ return null;
495
+ }
496
+ fs.mkdirSync(path.dirname(file), { recursive: true });
497
+ fs.writeFileSync(file, `${asMode}\n`, 'utf-8');
498
+ return file;
499
+ }
500
+ function clearAsModeOverride(file) {
501
+ if (!file)
502
+ return;
503
+ try {
504
+ fs.rmSync(file, { force: true });
505
+ }
506
+ catch { /* ignore */ }
507
+ }
508
+ /** daemon 已在运行时 `--as-mode` 不会生效,明说,别让人以为切过去了。 */
509
+ function warnAsModeNotApplied(asMode) {
510
+ if (!asMode)
511
+ return;
512
+ console.log(` ⚠ --as-mode ${asMode} 未生效:daemon 已在运行,模式只在启动时确定。`);
513
+ console.log(` 需要切换请先 ec stop,再 ec start --as-mode ${asMode}。`);
514
+ }
515
+ function printAsModeNotice(asMode) {
516
+ if (!asMode)
517
+ return;
518
+ console.log('');
519
+ console.log(`🧪 本次运行模式已设为${asMode === 'install' ? '安装模式' : '开发模式'}(ec start --as-mode ${asMode})`);
520
+ if (asMode === 'install') {
521
+ console.log(' 随包发布的 kits/技能文件将按台账校验,带外改动会被自动恢复。');
522
+ console.log(' 开发仓里请勿长期使用:本机 kits 文件会被登记进真实台账,');
523
+ console.log(' 之后再按开发模式修改它们,会在下次以安装模式启动时被回滚。');
524
+ }
525
+ console.log(' 只影响这一次启动;ec restart 不带该参数,会恢复按包路径判断。');
526
+ }
527
+ function printStartupInfo(opts = {}, asMode) {
423
528
  const pkgRoot = getPackageRoot();
424
529
  const isNpmInstall = pkgRoot.includes('node_modules');
425
530
  const cliRunsSource = !import.meta.url.includes('/dist/');
@@ -463,25 +568,33 @@ function printStartupInfo(opts = {}) {
463
568
  console.log(`${prefix}${version}${pidPart}${aunPart}`);
464
569
  console.log(` 包路径: ${pkgRoot}`);
465
570
  console.log(` 安装类型: ${isNpmInstall ? 'npm全局安装' : '开发仓(link)'}`);
571
+ // asMode 只是"本次请求",是否生效要看 daemon 是否真的由这次启动拉起,标清楚别和实际模式混淆
572
+ const modeLine = describeRunMode(opts.runMode ?? asMode, !isNpmInstall);
573
+ console.log(` ${asMode && !opts.runMode ? `${modeLine}(--as-mode 请求)` : modeLine}`);
466
574
  console.log(` CLI执行: ${cliRunsSource ? '源码(tsx)' : '编译产物(dist)'}`);
467
575
  console.log(` Daemon执行: ${daemonRunsDist ? '编译产物(dist)' : '未知'}`);
468
576
  console.log(` 代码时间: ${latestMtime ? formatLocalTime(latestMtime) : '?'}`);
469
577
  }
470
578
  export async function cmdStart(opts = {}) {
471
- const cmdStartedAt = Date.now();
472
- printStartupInfo();
579
+ // Measure from process start so the reported time includes CLI bootstrap
580
+ // (tsx compilation in a dev checkout), which is what the operator waits for.
581
+ const cmdStartedAt = Math.min(Date.now(), Math.round(performance.timeOrigin));
582
+ printStartupInfo(undefined, opts.asMode);
473
583
  const p = resolvePaths();
474
584
  const existingDaemon = await probeDaemon(p.socket);
475
585
  if (existingDaemon) {
476
586
  let hookRepaired = false;
477
- if (!ensureCodexHookBeforeLifecycle(() => { hookRepaired = true; }))
587
+ if (!await ensureCodexHookBeforeLifecycle(() => { hookRepaired = true; }))
478
588
  return;
479
589
  if (hookRepaired) {
480
- console.log('🔄 Codex 运行组件已补齐,正在自动重启服务以加载策略...');
590
+ console.log('🔄 Codex 运行组件已修复,正在自动重启服务以加载策略...');
481
591
  await cmdRestart({ diagnose: opts.diagnose });
482
592
  return;
483
593
  }
484
594
  console.log(` EvolCore is already running (PID: ${existingDaemon.pid}, IPC reachable)`);
595
+ const runningRecord = scanInstances().mains.find(m => m.alive && m.record.pid === existingDaemon.pid)?.record;
596
+ console.log(` 当前 daemon ${describeRunMode(runningRecord?.runMode)}`);
597
+ warnAsModeNotApplied(opts.asMode);
485
598
  console.log(` 已在运行;已运行时长: ${resolveRunningDuration(existingDaemon.pid, existingDaemon.uptime)};部署/脚本可直接复用当前实例`);
486
599
  return;
487
600
  }
@@ -524,7 +637,7 @@ export async function cmdStart(opts = {}) {
524
637
  // The initialization path can create the first Codex agent, so perform the
525
638
  // machine-hook check after config migration/auto-init and before any daemon
526
639
  // process is launched.
527
- if (!ensureCodexHookBeforeLifecycle())
640
+ if (!await ensureCodexHookBeforeLifecycle())
528
641
  return;
529
642
  // 控制 AID 门禁:缺 aid 且交互式 → 只补全控制 AID + owners(不重走 baseagent 向导)。
530
643
  // 非 TTY(restart-monitor/systemd/管道)不补全(无法交互),只提示后继续启动,daemon 侧 warn 兜底。
@@ -582,6 +695,8 @@ export async function cmdStart(opts = {}) {
582
695
  console.log(` EvolCore is already running (PID: ${aliveMains.map(m => m.record.pid).join(', ')})`);
583
696
  console.log(` 启动于: ${new Date(first.record.startedAtIso).toLocaleString()}`);
584
697
  console.log(` 启动方式: ${first.record.launchedBy}`);
698
+ console.log(` 当前 daemon ${describeRunMode(first.record.runMode)}`);
699
+ warnAsModeNotApplied(opts.asMode);
585
700
  // 报告 AID 状态
586
701
  if (status.aidLastActivity.size > 0) {
587
702
  console.log(' AID 状态:');
@@ -608,7 +723,9 @@ export async function cmdStart(opts = {}) {
608
723
  // On Windows/macOS the process environment may be unavailable, so an
609
724
  // unregistered process from this package is conservatively treated as a
610
725
  // blocker when its HOME cannot be identified.
611
- const orphans = findOrphanProcesses();
726
+ // No live main reached this point, so the registry snapshot above is still
727
+ // the authoritative "known PID" set; avoid a second scan.
728
+ const orphans = findOrphanProcesses(status);
612
729
  const blockingOrphans = orphans.filter(o => isRuntimeOrphan(o));
613
730
  if (blockingOrphans.length > 0) {
614
731
  console.error(`❌ 检测到未登记的 EvolCore 进程(PID: ${blockingOrphans.map(o => o.pid).join(', ')}),已停止启动以避免重复连接 AUN。`);
@@ -619,10 +736,13 @@ export async function cmdStart(opts = {}) {
619
736
  reportOrphans(orphans);
620
737
  if (versionInfo.some(result => result.hasUpdate)) {
621
738
  cleanEnv();
739
+ // 这条路由 restart-monitor 拉起 daemon,同样靠覆盖文件把 --as-mode 带过去
740
+ const overrideFile = writeAsModeOverride(opts.asMode);
622
741
  const launched = await launchPackageLifecycle('start', console.log, {
623
742
  prepared: true, foreground: opts.foreground, env: buildDaemonEnv(p, opts),
624
743
  });
625
744
  if (!launched.ok) {
745
+ clearAsModeOverride(overrideFile);
626
746
  console.error(`❌ 启动事务未能建立:${launched.reason}`);
627
747
  process.exitCode = 1;
628
748
  }
@@ -645,6 +765,7 @@ export async function cmdStart(opts = {}) {
645
765
  const appMain = path.join(getPackageRoot(), 'dist', 'index.js');
646
766
  const daemonEnv = buildDaemonEnv(p, opts);
647
767
  if (opts.foreground) {
768
+ const overrideFile = writeAsModeOverride(opts.asMode);
648
769
  const child = spawn('node', ['--no-warnings=ExperimentalWarning', appMain], {
649
770
  detached: false,
650
771
  stdio: 'inherit',
@@ -653,6 +774,7 @@ export async function cmdStart(opts = {}) {
653
774
  });
654
775
  const childPid = child.pid;
655
776
  if (!childPid) {
777
+ clearAsModeOverride(overrideFile);
656
778
  console.error('❌ Failed to start EvolCore (process creation failed)');
657
779
  process.exit(1);
658
780
  return;
@@ -666,6 +788,22 @@ export async function cmdStart(opts = {}) {
666
788
  console.log(` EVOLCORE_HOME: ${resolveRoot()}`);
667
789
  console.log(` Logs: ${p.logs}/`);
668
790
  console.log(`⏱ ready in ${((Date.now() - cmdStartedAt) / 1000).toFixed(1)}s`);
791
+ if (opts.debug) {
792
+ console.log('');
793
+ console.log('🔧 调试模式已启用(EVOLCORE_DEBUG_MODE=1)');
794
+ console.log(' 在宿主终端以任意 session 身份执行命令:');
795
+ console.log('');
796
+ console.log(` ec msg send '<消息内容>' --as-session <sessionId>`);
797
+ console.log(' ec trigger list --as-session <sessionId>');
798
+ console.log(' ec memory list --as-session <sessionId>');
799
+ console.log('');
800
+ console.log(' 查看可用会话(运行后将在此处列出):');
801
+ console.log(` ec msg send 'test' --as-session # 不带 sessionId 自动列出`);
802
+ console.log('');
803
+ console.log(' 注意:重启 daemon(ec restart)会清除调试模式,');
804
+ console.log(' 需重新执行 ec start --debug 才能恢复。');
805
+ }
806
+ printAsModeNotice(opts.asMode);
669
807
  void waitForExistingEcweb(p).then((ecweb) => {
670
808
  if (shouldPrintNoSelfAgentHints) {
671
809
  printNoSelfAgentHints({ daemonConfig: loadDaemonConfig(), ecwebStarted: ecweb !== null, skipped });
@@ -712,6 +850,7 @@ export async function cmdStart(opts = {}) {
712
850
  const stdoutLog = path.join(p.logs, 'stdout.log');
713
851
  const out = fs.openSync(stdoutLog, 'a');
714
852
  const err = fs.openSync(stdoutLog, 'a');
853
+ const overrideFile = writeAsModeOverride(opts.asMode);
715
854
  const child = spawn('node', ['--no-warnings=ExperimentalWarning', appMain], {
716
855
  detached: true,
717
856
  stdio: ['ignore', out, err],
@@ -719,6 +858,12 @@ export async function cmdStart(opts = {}) {
719
858
  env: daemonEnv,
720
859
  });
721
860
  const childPid = child.pid;
861
+ // daemon 已在启动时读走该文件;若未能起来(pid 丢失/提前退出),清掉避免影响下次启动
862
+ child.once('error', () => clearAsModeOverride(overrideFile));
863
+ // Track liveness from the spawn handle instead of launching tasklist on
864
+ // every poll; the poll interval below is short enough that it matters.
865
+ let childExited = false;
866
+ child.once('exit', () => { childExited = true; });
722
867
  child.unref();
723
868
  // 等待 ready signal(最多 30 秒,AUN sidecar 超时 15s + 其他通道连接)
724
869
  const startTime = Date.now();
@@ -769,10 +914,27 @@ export async function cmdStart(opts = {}) {
769
914
  printCodeStats(getPackageRoot(), p.logs);
770
915
  }
771
916
  console.log(`⏱ done in ${((Date.now() - cmdStartedAt) / 1000).toFixed(1)}s`);
772
- // ECWeb lifecycle belongs to the daemon; the CLI only reports whether
773
- // the supervised instance became healthy.
774
- const ecwebStarted = await waitForExistingEcweb(p) !== null;
917
+ printAsModeNotice(opts.asMode);
918
+ if (opts.debug) {
919
+ console.log('');
920
+ console.log('🔧 调试模式已启用(EVOLCORE_DEBUG_MODE=1)');
921
+ console.log(' 在宿主终端以任意 session 身份执行命令:');
922
+ console.log('');
923
+ console.log(` ec msg send '<消息内容>' --as-session <sessionId>`);
924
+ console.log(' ec trigger list --as-session <sessionId>');
925
+ console.log(' ec memory list --as-session <sessionId>');
926
+ console.log('');
927
+ console.log(' 查看可用会话(运行后将在此处列出):');
928
+ console.log(` ec msg send 'test' --as-session # 不带 sessionId 自动列出`);
929
+ console.log('');
930
+ console.log(' 注意:重启 daemon(ec restart)会清除调试模式,');
931
+ console.log(' 需重新执行 ec start --debug 才能恢复。');
932
+ }
933
+ // ECWeb lifecycle belongs to the daemon; the CLI only needs its health
934
+ // for the no-agent hints. Waiting otherwise just holds the prompt for
935
+ // up to the probe timeout after the daemon is already ready.
775
936
  if (shouldPrintNoSelfAgentHints) {
937
+ const ecwebStarted = await waitForExistingEcweb(p) !== null;
776
938
  printNoSelfAgentHints({ daemonConfig: loadDaemonConfig(), ecwebStarted, skipped });
777
939
  }
778
940
  return;
@@ -791,7 +953,7 @@ export async function cmdStart(opts = {}) {
791
953
  return;
792
954
  }
793
955
  // 进程已退出且无 ready signal
794
- if (!platform.isProcessRunning(childPid)) {
956
+ if (childExited) {
795
957
  // 给进程一点时间写 ready signal(可能刚好在写入中)
796
958
  if (Date.now() - startTime > 3000) {
797
959
  console.log('❌ Failed to start EvolCore');
@@ -806,9 +968,9 @@ export async function cmdStart(opts = {}) {
806
968
  return;
807
969
  }
808
970
  }
809
- setTimeout(checkReady, 500);
971
+ setTimeout(checkReady, 200);
810
972
  };
811
- setTimeout(checkReady, 1000);
973
+ setTimeout(checkReady, 200);
812
974
  }
813
975
  function isPidReused(pid, expectedStartedAt) {
814
976
  if (expectedStartedAt === undefined)
@@ -821,7 +983,10 @@ function reportPidReused(pid) {
821
983
  }
822
984
  async function stopPid(pid, gracefulRequested = false, expectedStartedAt) {
823
985
  console.log(`🛑 Stopping EvolCore (PID: ${pid})...`);
824
- if (isPidReused(pid, expectedStartedAt)) {
986
+ // A graceful request was accepted by the daemon that answered for this exact
987
+ // PID over IPC, so its identity is already proven; the start-time probe (a
988
+ // PowerShell launch on Windows) only guards the kill path.
989
+ if (!gracefulRequested && isPidReused(pid, expectedStartedAt)) {
825
990
  reportPidReused(pid);
826
991
  return true;
827
992
  }
@@ -870,7 +1035,7 @@ function isRuntimeOrphan(orphan, currentHome = resolveRoot()) {
870
1035
  export async function cmdStop() {
871
1036
  const status = scanInstances();
872
1037
  const p = resolvePaths();
873
- const initialOrphans = findOrphanProcesses();
1038
+ const initialOrphans = findOrphanProcesses(status);
874
1039
  const runtimeOrphans = initialOrphans.filter(orphan => isRuntimeOrphan(orphan, p.root));
875
1040
  const ping = await probeDaemon(p.socket);
876
1041
  if (ping && !hasVisibleDaemonPid(status, ping) && runtimeOrphans.length === 0) {
@@ -895,7 +1060,8 @@ export async function cmdStop() {
895
1060
  process.exitCode = 1;
896
1061
  return;
897
1062
  }
898
- await sleep(500);
1063
+ // stopPid already observed every process leave the process table, so the
1064
+ // records can be cleaned immediately (same reasoning as restart-monitor).
899
1065
  cleanupInstances();
900
1066
  if (aliveMains.length > 1) {
901
1067
  console.log(`⚠ 停止了 ${aliveMains.length} 个 main 实例: ${aliveMains.map(m => m.record.pid).join(', ')}`);
@@ -904,7 +1070,10 @@ export async function cmdStop() {
904
1070
  export async function cmdRestart(opts = {}) {
905
1071
  const cmdStartedAt = Date.now();
906
1072
  console.log('🔄 Restarting EvolCore...');
907
- if (!ensureCodexHookBeforeLifecycle())
1073
+ // 重启不带 --as-mode,模式按包路径重新判定;清掉可能残留的覆盖文件,并显示出来
1074
+ writeAsModeOverride(undefined);
1075
+ console.log(` ${describeRunMode()}`);
1076
+ if (!await ensureCodexHookBeforeLifecycle())
908
1077
  return;
909
1078
  const initialStatus = scanInstances();
910
1079
  const socketPath = resolvePaths().socket;
@@ -1117,7 +1286,8 @@ export async function cmdStatus() {
1117
1286
  console.log('');
1118
1287
  }
1119
1288
  if (pid) {
1120
- printStartupInfo({ pid, running: true });
1289
+ const mainRecord = aliveMains.find(m => m.record.pid === pid)?.record;
1290
+ printStartupInfo({ pid, running: true, runMode: mainRecord?.runMode });
1121
1291
  if (!ping) {
1122
1292
  console.log('⚠ 仅确认 daemon PID 存活,IPC 不可用,不能据此判断服务健康。');
1123
1293
  if (ipcFailure)
@@ -0,0 +1,154 @@
1
+ /**
2
+ * debug-mode.ts — `--as-session <sessionId>` 调试路径
3
+ *
4
+ * 在外部终端(宿主 CC 终端)以任意 agent 身份执行托管命令。
5
+ * 调用方在检查 EVOLCORE_SESSION_ID 之前先调用 resolveDebugContext(args);
6
+ * 注入完成后 process.env.EVOLCORE_SESSION_ID 已设好,后续走统一托管路径。
7
+ */
8
+ import fs from 'fs';
9
+ import path from 'path';
10
+ import { resolvePaths, debugSessionsDir } from '../paths.js';
11
+ import { ipcQuery } from '../ipc.js';
12
+ import { AGENT_DELEGATION_TOKEN_ENV } from '../core/auth/agent-delegation.js';
13
+ import { buildTaskRuntimeEnv } from './task-context.js';
14
+ const AS_SESSION_FLAG = '--as-session';
15
+ function formatTimeAgo(ms) {
16
+ const s = Math.floor(ms / 1000);
17
+ if (s < 60)
18
+ return `${s}秒前`;
19
+ const m = Math.floor(s / 60);
20
+ if (m < 60)
21
+ return `${m}分钟前`;
22
+ const h = Math.floor(m / 60);
23
+ if (h < 24)
24
+ return `${h}小时前`;
25
+ return `${Math.floor(h / 24)}天前`;
26
+ }
27
+ function listAvailableSessions() {
28
+ const dir = debugSessionsDir();
29
+ if (!fs.existsSync(dir))
30
+ return { sessions: [], dirExists: false };
31
+ try {
32
+ const sessions = fs.readdirSync(dir)
33
+ .filter(f => f.endsWith('.json'))
34
+ .map(f => {
35
+ try {
36
+ return JSON.parse(fs.readFileSync(path.join(dir, f), 'utf-8'));
37
+ }
38
+ catch {
39
+ return null;
40
+ }
41
+ })
42
+ .filter((s) => s !== null && !!s.sessionId)
43
+ .sort((a, b) => b.writtenAt - a.writtenAt)
44
+ .slice(0, 20);
45
+ return { sessions, dirExists: true };
46
+ }
47
+ catch {
48
+ return { sessions: [], dirExists: true };
49
+ }
50
+ }
51
+ function printSessionList(result) {
52
+ const { sessions, dirExists } = result;
53
+ if (!dirExists) {
54
+ console.error('❌ 调试模式未启用。请先执行:ec start --debug');
55
+ return;
56
+ }
57
+ if (sessions.length === 0) {
58
+ console.error('ℹ️ 调试模式已启用,但暂无可用会话。');
59
+ console.error(' 请等待 agent 收到至少一条消息后再试。');
60
+ return;
61
+ }
62
+ console.error('\n可用会话(最近 20 个):');
63
+ for (const s of sessions) {
64
+ const age = formatTimeAgo(Date.now() - s.writtenAt);
65
+ console.error(` ${s.sessionId} ${s.selfAid} → ${s.channelId} ${age}`);
66
+ }
67
+ }
68
+ /**
69
+ * 检测命令行参数中是否包含 --as-session,若有则执行调试路径:
70
+ * 1. 互斥检查(托管会话中拒绝)
71
+ * 2. 读临时文件
72
+ * 3. 调 debug-issue-token IPC 现取 token
73
+ * 4. 注入 process.env,后续命令走统一托管路径
74
+ *
75
+ * 不带 --as-session 时直接返回,不做任何事。
76
+ */
77
+ export async function resolveDebugContext(args) {
78
+ const flagIdx = args.indexOf(AS_SESSION_FLAG);
79
+ if (flagIdx === -1)
80
+ return;
81
+ // 互斥检查:托管会话里不允许 --as-session
82
+ if (process.env.EVOLCORE_SESSION_ID) {
83
+ console.error('❌ --as-session 仅限外部终端使用,托管会话中不可用');
84
+ process.exit(1);
85
+ }
86
+ const sessionId = args[flagIdx + 1];
87
+ // 不带 sessionId 或 sessionId 以 - 开头(下一个 flag)
88
+ if (!sessionId || sessionId.startsWith('-')) {
89
+ console.error('❌ --as-session 需要指定 sessionId\n');
90
+ console.error(`使用:ec <命令> --as-session <sessionId>`);
91
+ printSessionList(listAvailableSessions());
92
+ process.exit(1);
93
+ }
94
+ if (!/^[A-Za-z0-9][A-Za-z0-9_.-]*$/.test(sessionId)) {
95
+ console.error('❌ 无效 sessionId');
96
+ process.exit(1);
97
+ }
98
+ // 读临时文件(session bind 时已写入)
99
+ const snapshotPath = path.join(debugSessionsDir(), `${sessionId}.json`);
100
+ if (!fs.existsSync(snapshotPath)) {
101
+ const result = listAvailableSessions();
102
+ if (!result.dirExists) {
103
+ console.error('❌ 调试模式未启用。请先执行:ec start --debug');
104
+ }
105
+ else {
106
+ console.error(`❌ 调试会话不存在:${sessionId}\n`);
107
+ printSessionList(result);
108
+ }
109
+ process.exit(1);
110
+ }
111
+ // 调 debug-issue-token IPC 现取 token
112
+ let response;
113
+ try {
114
+ response = await ipcQuery(resolvePaths().socket, { type: 'debug-issue-token', sessionId }, 5000);
115
+ }
116
+ catch (err) {
117
+ console.error(`❌ 无法连接 daemon:${err instanceof Error ? err.message : String(err)}`);
118
+ console.error('请确认 daemon 正在以 debug 模式运行:ec start --debug');
119
+ process.exit(1);
120
+ }
121
+ if (!response) {
122
+ console.error('❌ daemon 无响应,请确认 daemon 正在以 debug 模式运行:ec start --debug');
123
+ process.exit(1);
124
+ }
125
+ if (!response.ok) {
126
+ console.error(`❌ 无法获取调试 token:${response.error}`);
127
+ process.exit(1);
128
+ }
129
+ const { token, runtimeContext } = response;
130
+ // 注入 process.env,后续代码走统一托管路径
131
+ // 注意:去掉 daemonRuntimeEpoch——debug session 不是真实任务,
132
+ // 不应该受 ipc.ts 里的 daemon epoch 检查约束,否则 msg send --thread 后续命令会报
133
+ // "The task delegation token belongs to a previous daemon runtime"
134
+ const ctxWithoutEpoch = { ...runtimeContext, daemonRuntimeEpoch: undefined };
135
+ Object.assign(process.env, buildTaskRuntimeEnv(ctxWithoutEpoch));
136
+ process.env[AGENT_DELEGATION_TOKEN_ENV] = token;
137
+ // thread 命令从 EVOLCORE_SELF_AID 读 selfAid(不走 taskRuntimeContext),单独注入
138
+ if (runtimeContext.selfAid)
139
+ process.env.EVOLCORE_SELF_AID = runtimeContext.selfAid;
140
+ }
141
+ /** 从 args 中去掉 --as-session <sessionId> 两个元素,返回净化后的参数列表 */
142
+ export function stripAsSessionArgs(args) {
143
+ const result = [];
144
+ let i = 0;
145
+ while (i < args.length) {
146
+ if (args[i] === AS_SESSION_FLAG) {
147
+ i += 2; // 跳过 flag 和 sessionId
148
+ }
149
+ else {
150
+ result.push(args[i++]);
151
+ }
152
+ }
153
+ return result;
154
+ }
@@ -56,7 +56,8 @@ Options:
56
56
  --content-type <mime> 上传时指定内容类型
57
57
  --public 上传后公开可读(personal storage)
58
58
  --visibility <public|private> chmod 可见性
59
- --allow-roles <roles> chmod 角色约束,逗号分隔
59
+ --allow-roles <roles> chmod 角色约束,写法固定为单引号包住、逗号分隔,
60
+ 如 --allow-roles 'owner,admin'
60
61
  -m aid:<aid>:<perms> setfacl 新增/更新 ACL
61
62
  -x aid:<aid> setfacl 移除 ACL
62
63
  --source <AID>:<path> mount 虚拟卷来源
@@ -162,6 +162,9 @@ export async function cmdHandoff(args) {
162
162
  if (!sub || isHelpFlag(sub) || wantsHelp(args)) {
163
163
  console.log(`用法: ec handoff <command> [args]
164
164
 
165
+ ⚠ ec handoff 已废弃,请改用话题链(ec thread + ec msg send --thread)。
166
+ 本命令保留以兼容现有脚本,将在 P2 后续版本中移除。
167
+
165
168
  Commands:
166
169
  return [handoff-id] [content] 提交一个回流结果;required 可用同一 ID 追加多个结果
167
170
  return [handoff-id] --text-from-file <path>