evolcore 0.0.33 → 0.0.35-test.0

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 (152) hide show
  1. package/CHANGELOG.md +59 -0
  2. package/bin/ec.js +3 -2
  3. package/bin/install-codex-managed-hooks.mjs +38 -4
  4. package/dist/agents/baseagent-availability.js +20 -36
  5. package/dist/agents/claude-runner.js +93 -14
  6. package/dist/agents/codex/diagnostics.js +79 -22
  7. package/dist/agents/codex-runner.js +245 -47
  8. package/dist/agents/ecagent-runner.js +90 -27
  9. package/dist/agents/runner-types.js +5 -6
  10. package/dist/aun/aid/agentmd.js +13 -2
  11. package/dist/aun/aid/client.js +1 -1
  12. package/dist/aun/aid/identity.js +1 -1
  13. package/dist/aun/msg/managed-operation.js +16 -0
  14. package/dist/aun/msg/p2p.js +18 -2
  15. package/dist/channels/aun.js +123 -7
  16. package/dist/channels/daemon.js +6 -3
  17. package/dist/cli/aun-commands.js +39 -14
  18. package/dist/cli/ctl-command.js +3 -1
  19. package/dist/cli/daemon-commands.js +130 -23
  20. package/dist/cli/fs-command.js +2 -1
  21. package/dist/cli/getmeta-command.js +211 -0
  22. package/dist/cli/index.js +26 -6
  23. package/dist/cli/integrity-command.js +196 -0
  24. package/dist/cli/managed-command-guard.js +3 -0
  25. package/dist/cli/memory-command.js +200 -156
  26. package/dist/cli/thread-command.js +17 -7
  27. package/dist/cli/trigger-command.js +6 -1
  28. package/dist/config/config-field-policy.js +4 -0
  29. package/dist/config/config-manager.js +19 -0
  30. package/dist/config/contact-book-store.js +5 -0
  31. package/dist/config/resolved-config-op.js +25 -8
  32. package/dist/config/role-service.js +13 -0
  33. package/dist/config/runtime-env.js +16 -0
  34. package/dist/config-store.js +30 -0
  35. package/dist/core/audit/log-integrity.js +3 -1
  36. package/dist/core/auth/agent-delegation.js +108 -35
  37. package/dist/core/auth/delegation-diagnostics.js +414 -0
  38. package/dist/core/auth/operation-authorizer.js +22 -3
  39. package/dist/core/auth/operation-catalog.js +16 -0
  40. package/dist/core/channel-runtime.js +1 -1
  41. package/dist/core/command/agent-control.js +18 -26
  42. package/dist/core/command/command-handler.js +145 -5
  43. package/dist/core/command/daemon-diagnostics.js +1 -0
  44. package/dist/core/command/menu-catalog.js +3 -0
  45. package/dist/core/command/menu-handler.js +24 -7
  46. package/dist/core/command/menu-protocol.js +1 -0
  47. package/dist/core/command/slash-handler.js +2 -0
  48. package/dist/core/command/topic-slash.js +4 -7
  49. package/dist/core/daemon-bootstrap.js +4 -7
  50. package/dist/core/event-catalog.js +14 -0
  51. package/dist/core/file-integrity/read.js +61 -0
  52. package/dist/core/file-integrity/store.js +262 -0
  53. package/dist/core/ipc-registration.js +47 -2
  54. package/dist/core/memory/batch-meta.js +133 -0
  55. package/dist/core/memory/consolidation-context.js +132 -28
  56. package/dist/core/memory/consolidation-ops-records.js +14 -16
  57. package/dist/core/memory/consolidation-trigger.js +15 -7
  58. package/dist/core/memory/consolidation.js +146 -26
  59. package/dist/core/memory/index-manager.js +19 -28
  60. package/dist/core/memory/memory-admin.js +19 -14
  61. package/dist/core/memory/memory-document.js +53 -9
  62. package/dist/core/memory/memory-writer.js +50 -19
  63. package/dist/core/message/aun-ipc-sender.js +19 -1
  64. package/dist/core/message/im-renderer.js +20 -4
  65. package/dist/core/message/message-bridge.js +5 -4
  66. package/dist/core/message/message-log.js +1 -0
  67. package/dist/core/message/peer-mode.js +17 -0
  68. package/dist/core/message/response-engine.js +598 -50
  69. package/dist/core/permission/ec-command-parser.js +169 -44
  70. package/dist/core/permission/protected-paths.js +5 -1
  71. package/dist/core/permission/readonly-shell-query.js +43 -5
  72. package/dist/core/permission/session-path-grants.js +334 -0
  73. package/dist/core/permission/tool-canonical.js +226 -0
  74. package/dist/core/permission/tool-policy.js +60 -62
  75. package/dist/core/relation/peer-identity.js +87 -12
  76. package/dist/core/role/runtime-policy.js +21 -10
  77. package/dist/core/session/session-fs-store.js +123 -8
  78. package/dist/core/session/session-manager.js +131 -6
  79. package/dist/core/session/session-title.js +26 -1
  80. package/dist/core/skill/index-generator.js +17 -3
  81. package/dist/core/system-session/grants.js +40 -0
  82. package/dist/core/system-session/memory.js +289 -0
  83. package/dist/core/system-session/registry.js +74 -0
  84. package/dist/core/system-session/types.js +14 -0
  85. package/dist/core/thread/command-parser.js +6 -1
  86. package/dist/core/thread/ipc-operation.js +33 -16
  87. package/dist/core/thread/thread-operation.js +62 -24
  88. package/dist/core/thread/thread-service.js +258 -51
  89. package/dist/eck/kit-renderer.js +7 -2
  90. package/dist/eck/manifest-engine.js +24 -4
  91. package/dist/index.js +11 -2
  92. package/dist/ipc.js +51 -40
  93. package/dist/paths.js +70 -0
  94. package/dist/stats/billing.js +20 -8
  95. package/dist/trigger/feedback.js +6 -1
  96. package/dist/trigger/parser.js +8 -1
  97. package/dist/trigger/system-definitions.js +3 -3
  98. package/dist/utils/cross-platform.js +17 -5
  99. package/dist/utils/error-dict.json +18 -4
  100. package/dist/utils/error-utils.js +121 -0
  101. package/dist/utils/instance-registry.js +13 -8
  102. package/dist/utils/log-writer.js +12 -0
  103. package/dist/utils/process-introspect.js +11 -0
  104. package/kits/docs/context-assembly.md +0 -1
  105. package/kits/docs/evolcore/agent-create.md +5 -4
  106. package/kits/docs/evolcore/agent.md +2 -2
  107. package/kits/docs/evolcore/config.md +2 -2
  108. package/kits/docs/evolcore/ctl.md +2 -2
  109. package/kits/docs/evolcore/ec-capabilities.md +1 -1
  110. package/kits/docs/evolcore/fs-architecture.md +9 -10
  111. package/kits/docs/evolcore/fs.md +2 -2
  112. package/kits/docs/evolcore/group-collaboration.md +3 -3
  113. package/kits/docs/evolcore/group.md +14 -12
  114. package/kits/docs/evolcore/memory-architecture.md +4 -5
  115. package/kits/docs/evolcore/memory-consolidation.md +19 -16
  116. package/kits/docs/evolcore/memory-tags.md +3 -1
  117. package/kits/docs/evolcore/memory.md +24 -12
  118. package/kits/docs/evolcore/model.md +16 -0
  119. package/kits/docs/evolcore/msg.md +7 -7
  120. package/kits/docs/evolcore/stats.md +1 -1
  121. package/kits/docs/evolcore/thread-chain.md +30 -14
  122. package/kits/docs/prompt-loading-architecture.md +0 -1
  123. package/kits/eck_manifest.auxiliary.json +9 -0
  124. package/kits/eck_manifest.json +20 -20
  125. package/kits/eck_manifest.memory.json +20 -12
  126. package/kits/rules/01-overview.md +11 -0
  127. package/kits/rules/03-identity.md +6 -3
  128. package/kits/rules/06-channel.md +1 -1
  129. package/kits/rules/07-memory-and-thread.md +13 -16
  130. package/kits/schemas/_meta.json +7 -1
  131. package/kits/schemas/agent-config.schema.13.json +1 -1
  132. package/kits/schemas/daemon.schema.7.json +140 -0
  133. package/kits/skills/ec-group/SKILL.md +8 -5
  134. package/kits/skills/ec-skill/scripts/cli.mjs +1 -1
  135. package/kits/skills/ec-skill/scripts/skill.mjs +1 -1
  136. package/kits/skills/thread-ops/SKILL.md +18 -18
  137. package/kits/templates/memory-consolidation/forget.md +2 -2
  138. package/kits/templates/memory-consolidation/generalize.md +4 -4
  139. package/kits/templates/memory-consolidation/notes.md +12 -12
  140. package/kits/templates/memory-consolidation/profile.md +3 -3
  141. package/kits/templates/memory-consolidation/promote.md +3 -3
  142. package/kits/templates/memory-consolidation/workflow.md +42 -7
  143. package/kits/templates/message-fragments/handoff-request-to-target.md +1 -1
  144. package/kits/templates/system-fragments/bash-description.md +9 -0
  145. package/kits/templates/system-fragments/bootstrap.md +4 -3
  146. package/kits/templates/system-fragments/channel.md +2 -2
  147. package/kits/templates/system-fragments/memory.md +16 -9
  148. package/kits/templates/system-fragments/reply.md +6 -6
  149. package/kits/templates/system-fragments/session.md +1 -1
  150. package/package.json +4 -3
  151. package/dist/core/memory/memory-session-policy.js +0 -41
  152. package/dist/core/memory/memory-signature.js +0 -257
@@ -2,6 +2,7 @@ import { appendExpireEntry, recordNextLaunchCleanup, cleanupPreviousByKind, runE
2
2
  import { ExecutionTimeoutError } from '../utils/error-utils.js';
3
3
  import { normalizeExecutionMode } from '../trigger/validation.js';
4
4
  import { resolvePeerRoleDetail } from '../config/peer-role-resolver.js';
5
+ import { assertSystemSessionLaunchable } from '../core/system-session/registry.js';
5
6
  const DEFAULT_WATCHDOG_MS = 11 * 60 * 1000;
6
7
  export class DaemonChannel {
7
8
  sessionManager;
@@ -253,6 +254,9 @@ export class DaemonChannel {
253
254
  // 记忆整理会话:把会话原型与整理模式写进 metadata,供渲染层选 manifest / 手册,
254
255
  // 供 ipc 侧校验写命令的 --scope。
255
256
  const { memoryMode, memoryScope, consolidationBatchDir, consolidationChatDir, consolidationMemoryDir } = trigger.execution;
257
+ // 系统会话:类型 / subtype / runner 校验不过直接拒绝(Gemini 不支持),不降级成普通会话
258
+ if (memoryMode)
259
+ assertSystemSessionLaunchable({ type: 'memory', subtype: memoryMode }, baseagent);
256
260
  // 反馈目标与触发方式(consolidation.ts 在 normalize 后补挂的字段):
257
261
  // 收尾时据此投递反馈,并判断是否该反馈(定时触发不反馈)。
258
262
  const { feedbackOrigin, consolidationTriggeredBy } = trigger.execution;
@@ -260,7 +264,7 @@ export class DaemonChannel {
260
264
  channelKey: this.channelKey,
261
265
  peerId: `trigger:${trigger.id}`,
262
266
  peerName: trigger.name,
263
- ...(memoryMode ? { sessionType: 'memory', memoryMode } : {}),
267
+ ...(memoryMode ? { systemSession: { type: 'memory', subtype: memoryMode }, sessionType: 'memory', memoryMode } : {}),
264
268
  ...(memoryScope ? { memoryScope } : {}),
265
269
  ...(consolidationBatchDir ? { consolidationBatchDir } : {}),
266
270
  ...(consolidationChatDir ? { consolidationChatDir } : {}),
@@ -570,8 +574,6 @@ export class DaemonChannel {
570
574
  peerId: `bg:${spec.kind}`,
571
575
  peerName: spec.kind,
572
576
  peerType: 'system',
573
- bgKind: spec.kind,
574
- bgInstanceId: spec.instanceId,
575
577
  ...(spec.metadata ?? {}),
576
578
  }, spec.kind, undefined, 'private', spec.baseagent, spec.agentAid, this.channelName, 'system');
577
579
  const promise = new Promise((resolve) => {
@@ -586,6 +588,7 @@ export class DaemonChannel {
586
588
  terminated: false,
587
589
  cleanupPolicy,
588
590
  agentAid: spec.agentAid,
591
+ runtimeDir: spec.runtimeDir,
589
592
  watchdogTimer: setTimeout(() => {
590
593
  this.finishBgSession(runId, slot, 'error', 'bg-session watchdog timeout');
591
594
  }, this.watchdogMs),
@@ -259,14 +259,18 @@ async function runManagedMsg(args, formatJson) {
259
259
  }
260
260
  }
261
261
  else {
262
- if (runtime && (runtime.channel !== 'aun' || runtime.chatType !== 'private')) {
262
+ // `--thread` 是话题作用域的读取:目标对端由话题推断,数据是本 agent 自己的
263
+ // 消息日志,因此既不受"当前必须是私聊"限制(群会话里也要能查 A↔C 的话题),
264
+ // 也不受"只能查当前对端"限制。不带 --thread 时保持原有边界。
265
+ const threadScoped = parsed.command.kind === 'history' && !!parsed.command.threadId;
266
+ if (runtime && (runtime.channel !== 'aun' || runtime.chatType !== 'private') && !threadScoped) {
263
267
  failManagedGroup(formatJson, 'SCOPE_MISMATCH', 'ec msg history/online 只能读取当前 AUN 私聊关系');
264
268
  }
265
269
  // owner/admin 可以跨对端读历史(每次查询仍只针对一个对端)。这里只是快速
266
270
  // 预检:角色未知时放行交给 daemon 判定,daemon 侧 handleMsgOperation 才是
267
271
  // 权威关卡,member 由 currentRelationOnly 约束、visitor 直接拒绝。
268
272
  const mayReadCrossPeer = parsed.command.kind === 'history'
269
- && (runtime?.peerRole === undefined || isManagementRole(runtime.peerRole));
273
+ && (threadScoped || runtime?.peerRole === undefined || isManagementRole(runtime.peerRole));
270
274
  if (!mayReadCrossPeer && runtime?.channelId && parsed.command.target !== runtime.channelId.replace(/^@/, '')) {
271
275
  failManagedGroup(formatJson, 'SCOPE_MISMATCH', `只能读取当前 AUN 私聊对端:${runtime.channelId}`);
272
276
  }
@@ -2065,10 +2069,15 @@ Options:
2065
2069
  // ==================== Group ====================
2066
2070
  const GROUP_FROM_AID_FORMAT_EXAMPLE = 'sender.example.invalid';
2067
2071
  const GROUP_AID_FORMAT_EXAMPLE = '12345.example.invalid';
2068
- function printGroupSendAidHint() {
2069
- console.error('正确格式: ec group send <发送方AID> <真实群AID> <正文或文件参数>');
2070
- console.error(`仅格式示例(.example.invalid 是保留的虚构域,不是真实 AID): ec group send ${GROUP_FROM_AID_FORMAT_EXAMPLE} ${GROUP_AID_FORMAT_EXAMPLE} "hello"`);
2071
- console.error('查询真实群 AID: ec group list <发送方AID>');
2072
+ function printGroupArgsHint(sub) {
2073
+ if (sub === 'send') {
2074
+ console.error('正确格式: ec group send <发送方AID> <真实群AID> <正文或文件参数>');
2075
+ }
2076
+ else {
2077
+ console.error(`正确格式: ec group ${sub} <发送方AID> <真实群AID> [选项]`);
2078
+ }
2079
+ console.error(`仅格式示例(.example.invalid 是保留的虚构域,不是真实 AID): ec group ${sub} ${GROUP_FROM_AID_FORMAT_EXAMPLE} ${GROUP_AID_FORMAT_EXAMPLE}`);
2080
+ console.error('顺序固定为「先发送方 AID,后群 AID」;查询真实群 AID: ec group list <发送方AID>');
2072
2081
  }
2073
2082
  function printGroupRulesUsage() {
2074
2083
  console.log(`用法:
@@ -2173,7 +2182,8 @@ Options:
2173
2182
  无值时默认 5m;Trigger 名:check-<目标threadId>-from-<来源threadId>-<唯一后缀>
2174
2183
  取消/调整用返回的 trigger_id(trig_…),show/update/cancel 只认 id
2175
2184
  --trigger-prompt <text> 自定义复查 Trigger prompt 中的用户段(仅 --check-every)
2176
- --mention <aid> 发送时 @ 某个成员(可多次,或用逗号分隔多个 aid)
2185
+ --mention <...> 发送时 @ 某个成员。单引号包住、逗号分隔多个 aid
2186
+ (如 --mention 'alice.agentid.pub,bob.agentid.pub'),可多次传入
2177
2187
  --mention-all 发送时 @ 所有人
2178
2188
  --content-type <mime> 显式覆盖 MIME(仅 --file 模式)
2179
2189
  --as <type> 附件类型:image|video|voice|file(仅 --file 模式)
@@ -2187,7 +2197,7 @@ Options:
2187
2197
  格式示例(.example.invalid 是保留的虚构域,以下 AID 均不是真实地址):
2188
2198
  ec group create sender.example.invalid "Dev Team" --visibility private
2189
2199
  ec group send sender.example.invalid 12345.example.invalid "hello team"
2190
- ec group send sender.example.invalid 12345.example.invalid "@member 看下 PR" --mention member.example.invalid
2200
+ ec group send sender.example.invalid 12345.example.invalid '@member 看下 PR' --mention 'member.example.invalid'
2191
2201
  ec group send sender.example.invalid 12345.example.invalid --file ./arch.png
2192
2202
  ec group send sender.example.invalid g-12345.example.invalid "进度?" --thread thread-xxx --check-every 30m
2193
2203
  ec group rules sender.example.invalid 12345.example.invalid set ./rules.md
@@ -2207,13 +2217,28 @@ g- 是名称的一部分,会完整保留;默认命名格式为 g-10000 至 g
2207
2217
  console.error('❌ 缺少 <from-aid> 参数');
2208
2218
  process.exit(1);
2209
2219
  }
2220
+ // 第 1 个位置参数必须是「发送方 AID」。群 ID 与私聊 AID 语法相同,isValidAid
2221
+ // 会放行,随后在加载证书时抛 CERT_NOT_FOUND——这条路径既不说明原因也不给出
2222
+ // 正确写法。因此先用明确的群 ID 形态判定,把"参数写反了"当场指出来。
2223
+ // 例外:invites 是多值标志/邀请列表,第 1 个位置参数不是 from;create/list 的
2224
+ // 形态不同,也在下面各自的用法里处理,这里只拦截"以群 ID 打头"这一种错位。
2210
2225
  const { isValidAid } = await import('../aun/aid/index.js');
2226
+ if (isExplicitGroupId(from)) {
2227
+ const second = args[2];
2228
+ console.error(`❌ 第 1 个位置参数 <from-aid> 无效:收到了群 ID: ${from}`);
2229
+ if (second && isValidAid(second) && !isExplicitGroupId(second)) {
2230
+ console.error(`检测到参数顺序写反了:第 2 个参数 ${second} 才是发送方 AID。`);
2231
+ console.error(`请改成: ec group ${sub} ${second} ${from}` + (sub === 'send' ? ' <正文>' : ''));
2232
+ }
2233
+ else {
2234
+ console.error('当前命令缺少发送方 AID,后续参数发生了错位。');
2235
+ }
2236
+ printGroupArgsHint(sub);
2237
+ process.exit(1);
2238
+ }
2211
2239
  if (!isValidAid(from)) {
2212
2240
  console.error(`❌ 第 1 个位置参数 <from-aid> 无效: ${from}`);
2213
- if (isExplicitGroupId(from)) {
2214
- console.error('检测到这里填写的是群 ID;当前命令缺少发送方 AID,后续参数发生了错位。');
2215
- }
2216
- printGroupSendAidHint();
2241
+ printGroupArgsHint(sub);
2217
2242
  process.exit(1);
2218
2243
  }
2219
2244
  if ((process.env.EVOLCORE_SESSION_ID || (sub === 'history' && readDelegationCredential())) && sub !== 'send') {
@@ -2285,7 +2310,7 @@ g- 是名称的一部分,会完整保留;默认命名格式为 g-10000 至 g
2285
2310
  const gid = args[2];
2286
2311
  if (!gid) {
2287
2312
  console.error('❌ 缺少第 2 个位置参数 <group-aid>');
2288
- printGroupSendAidHint();
2313
+ printGroupArgsHint(sub);
2289
2314
  process.exit(1);
2290
2315
  }
2291
2316
  try {
@@ -2293,7 +2318,7 @@ g- 是名称的一部分,会完整保留;默认命名格式为 g-10000 至 g
2293
2318
  }
2294
2319
  catch (error) {
2295
2320
  console.error(`❌ 第 2 个位置参数 <group-aid> 不是有效群 AID: ${gid}: ${error.message}`);
2296
- printGroupSendAidHint();
2321
+ printGroupArgsHint(sub);
2297
2322
  process.exit(1);
2298
2323
  }
2299
2324
  };
@@ -25,7 +25,9 @@ const CTL_HELP = `用法: ec ctl <command> [args...]
25
25
  ec ctl name "新会话名称"
26
26
  ec ctl send "任务已完成"
27
27
  ec ctl file report.md
28
- ec ctl queue --full`;
28
+ ec ctl queue --full
29
+ ec ctl path list
30
+ ec ctl path grant "D:/shared/data" --access read`;
29
31
  export async function cmdCtl(args) {
30
32
  if (args.length === 0) {
31
33
  console.error(CTL_HELP);
@@ -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 { MAIN_CLI_BIN, RUNTIME_HOME_ENV, WEB_CLI_BIN, WEB_PACKAGE_LATEST, WEB_PACKAGE_NAME } from '../product.js';
28
+ import { MAIN_CLI_BIN, RUNTIME_HOME_ENV, TARGET_PACKAGE_ROOT_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';
@@ -79,7 +79,7 @@ export async function ensureCodexHookBeforeLifecycle(onRepaired) {
79
79
  });
80
80
  if (!usesCodex)
81
81
  return true;
82
- const packageRoot = getPackageRoot();
82
+ const packageRoot = process.env[TARGET_PACKAGE_ROOT_ENV] || getPackageRoot();
83
83
  const result = await ensureCodexManagedHook({
84
84
  packageRoot,
85
85
  repairDecision: state => confirmCodexHookRebind(state, packageRoot),
@@ -328,6 +328,11 @@ async function launchPackageLifecycle(source, log, options = {}) {
328
328
  const result = await platform.launchMonitorAndConfirm(args, env, pid => scanInstances().restartMonitors.some(item => item.alive && item.record.pid === pid), pid => scanInstances().restartMonitors.some(item => item.alive && item.record.pid !== pid));
329
329
  if (!result.ok && launch.stageDir)
330
330
  platform.cleanupMonitorStage(launch.stageDir);
331
+ if (!result.ok) {
332
+ for (const line of (result.stderrTail ?? '').split(/\r?\n/).filter(Boolean).slice(-8)) {
333
+ console.error(` | ${line}`);
334
+ }
335
+ }
331
336
  return { ok: result.ok, ...(!result.ok ? { reason: result.reason } : {}) };
332
337
  }
333
338
  function requireCompletedDataMigration(root) {
@@ -458,7 +463,73 @@ function resolveRunningDuration(pid, uptime) {
458
463
  const instance = scanInstances().mains.find(entry => entry.alive && entry.record.pid === pid);
459
464
  return instance ? formatRunningDuration(Date.now() - instance.record.startedAt) : '未知';
460
465
  }
461
- function printStartupInfo(opts = {}) {
466
+ /**
467
+ * 渲染"运行模式"那一行。
468
+ *
469
+ * `daemonMode` 有值时用 daemon 实例记录里的模式(`ec status` 看的是正在跑的进程);
470
+ * 没有时按当前进程判断(`ec start` 时就是这一份 CLI 即将拉起的模式)。
471
+ */
472
+ /**
473
+ * CLI 侧按包路径判断模式。**不能用 isDevMode()**:它首次求值会读走并删除
474
+ * `--as-mode` 覆盖文件,那份文件只该由 daemon 消费。
475
+ */
476
+ function packageIsDevMode() {
477
+ return !getPackageRoot().includes('node_modules');
478
+ }
479
+ export function describeRunMode(daemonMode, cliIsDevMode = packageIsDevMode()) {
480
+ const effective = daemonMode ?? (cliIsDevMode ? 'dev' : 'install');
481
+ const label = effective === 'install' ? '安装模式' : '开发模式';
482
+ const note = effective === 'install'
483
+ ? '(校验随包文件,篡改自动恢复)'
484
+ : '(不校验随包文件,可随意改动)';
485
+ return `运行模式: ${label}${note}`;
486
+ }
487
+ /**
488
+ * 把本次 `--as-mode` 写进一次性覆盖文件,daemon 启动时读走并删除。
489
+ *
490
+ * 不走环境变量:环境变量会被 `ec restart` 全量继承,模式会在操作者不知情的
491
+ * 情况下被带到新进程。文件是一次性的,daemon 读过即删;若 daemon 最终没起来,
492
+ * 文件还留着,会在下一次启动时被读到——所以启动真实失败时此处清理。
493
+ */
494
+ function writeAsModeOverride(asMode) {
495
+ const file = asModeOverridePath();
496
+ if (!asMode) {
497
+ // 没带 --as-mode:清掉上次失败启动可能残留的文件,保证这次按包路径判断
498
+ clearAsModeOverride(file);
499
+ return null;
500
+ }
501
+ fs.mkdirSync(path.dirname(file), { recursive: true });
502
+ fs.writeFileSync(file, `${asMode}\n`, 'utf-8');
503
+ return file;
504
+ }
505
+ function clearAsModeOverride(file) {
506
+ if (!file)
507
+ return;
508
+ try {
509
+ fs.rmSync(file, { force: true });
510
+ }
511
+ catch { /* ignore */ }
512
+ }
513
+ /** daemon 已在运行时 `--as-mode` 不会生效,明说,别让人以为切过去了。 */
514
+ function warnAsModeNotApplied(asMode) {
515
+ if (!asMode)
516
+ return;
517
+ console.log(` ⚠ --as-mode ${asMode} 未生效:daemon 已在运行,模式只在启动时确定。`);
518
+ console.log(` 需要切换请先 ec stop,再 ec start --as-mode ${asMode}。`);
519
+ }
520
+ function printAsModeNotice(asMode) {
521
+ if (!asMode)
522
+ return;
523
+ console.log('');
524
+ console.log(`🧪 本次运行模式已设为${asMode === 'install' ? '安装模式' : '开发模式'}(ec start --as-mode ${asMode})`);
525
+ if (asMode === 'install') {
526
+ console.log(' 随包发布的 kits/技能文件将按台账校验,带外改动会被自动恢复。');
527
+ console.log(' 开发仓里请勿长期使用:本机 kits 文件会被登记进真实台账,');
528
+ console.log(' 之后再按开发模式修改它们,会在下次以安装模式启动时被回滚。');
529
+ }
530
+ console.log(' 只影响这一次启动;ec restart 不带该参数,会恢复按包路径判断。');
531
+ }
532
+ function printStartupInfo(opts = {}, asMode) {
462
533
  const pkgRoot = getPackageRoot();
463
534
  const isNpmInstall = pkgRoot.includes('node_modules');
464
535
  const cliRunsSource = !import.meta.url.includes('/dist/');
@@ -502,13 +573,18 @@ function printStartupInfo(opts = {}) {
502
573
  console.log(`${prefix}${version}${pidPart}${aunPart}`);
503
574
  console.log(` 包路径: ${pkgRoot}`);
504
575
  console.log(` 安装类型: ${isNpmInstall ? 'npm全局安装' : '开发仓(link)'}`);
576
+ // asMode 只是"本次请求",是否生效要看 daemon 是否真的由这次启动拉起,标清楚别和实际模式混淆
577
+ const modeLine = describeRunMode(opts.runMode ?? asMode, !isNpmInstall);
578
+ console.log(` ${asMode && !opts.runMode ? `${modeLine}(--as-mode 请求)` : modeLine}`);
505
579
  console.log(` CLI执行: ${cliRunsSource ? '源码(tsx)' : '编译产物(dist)'}`);
506
580
  console.log(` Daemon执行: ${daemonRunsDist ? '编译产物(dist)' : '未知'}`);
507
581
  console.log(` 代码时间: ${latestMtime ? formatLocalTime(latestMtime) : '?'}`);
508
582
  }
509
583
  export async function cmdStart(opts = {}) {
510
- const cmdStartedAt = Date.now();
511
- printStartupInfo();
584
+ // Measure from process start so the reported time includes CLI bootstrap
585
+ // (tsx compilation in a dev checkout), which is what the operator waits for.
586
+ const cmdStartedAt = Math.min(Date.now(), Math.round(performance.timeOrigin));
587
+ printStartupInfo(undefined, opts.asMode);
512
588
  const p = resolvePaths();
513
589
  const existingDaemon = await probeDaemon(p.socket);
514
590
  if (existingDaemon) {
@@ -521,6 +597,9 @@ export async function cmdStart(opts = {}) {
521
597
  return;
522
598
  }
523
599
  console.log(` EvolCore is already running (PID: ${existingDaemon.pid}, IPC reachable)`);
600
+ const runningRecord = scanInstances().mains.find(m => m.alive && m.record.pid === existingDaemon.pid)?.record;
601
+ console.log(` 当前 daemon ${describeRunMode(runningRecord?.runMode)}`);
602
+ warnAsModeNotApplied(opts.asMode);
524
603
  console.log(` 已在运行;已运行时长: ${resolveRunningDuration(existingDaemon.pid, existingDaemon.uptime)};部署/脚本可直接复用当前实例`);
525
604
  return;
526
605
  }
@@ -621,6 +700,8 @@ export async function cmdStart(opts = {}) {
621
700
  console.log(` EvolCore is already running (PID: ${aliveMains.map(m => m.record.pid).join(', ')})`);
622
701
  console.log(` 启动于: ${new Date(first.record.startedAtIso).toLocaleString()}`);
623
702
  console.log(` 启动方式: ${first.record.launchedBy}`);
703
+ console.log(` 当前 daemon ${describeRunMode(first.record.runMode)}`);
704
+ warnAsModeNotApplied(opts.asMode);
624
705
  // 报告 AID 状态
625
706
  if (status.aidLastActivity.size > 0) {
626
707
  console.log(' AID 状态:');
@@ -647,7 +728,9 @@ export async function cmdStart(opts = {}) {
647
728
  // On Windows/macOS the process environment may be unavailable, so an
648
729
  // unregistered process from this package is conservatively treated as a
649
730
  // blocker when its HOME cannot be identified.
650
- const orphans = findOrphanProcesses();
731
+ // No live main reached this point, so the registry snapshot above is still
732
+ // the authoritative "known PID" set; avoid a second scan.
733
+ const orphans = findOrphanProcesses(status);
651
734
  const blockingOrphans = orphans.filter(o => isRuntimeOrphan(o));
652
735
  if (blockingOrphans.length > 0) {
653
736
  console.error(`❌ 检测到未登记的 EvolCore 进程(PID: ${blockingOrphans.map(o => o.pid).join(', ')}),已停止启动以避免重复连接 AUN。`);
@@ -658,10 +741,13 @@ export async function cmdStart(opts = {}) {
658
741
  reportOrphans(orphans);
659
742
  if (versionInfo.some(result => result.hasUpdate)) {
660
743
  cleanEnv();
744
+ // 这条路由 restart-monitor 拉起 daemon,同样靠覆盖文件把 --as-mode 带过去
745
+ const overrideFile = writeAsModeOverride(opts.asMode);
661
746
  const launched = await launchPackageLifecycle('start', console.log, {
662
747
  prepared: true, foreground: opts.foreground, env: buildDaemonEnv(p, opts),
663
748
  });
664
749
  if (!launched.ok) {
750
+ clearAsModeOverride(overrideFile);
665
751
  console.error(`❌ 启动事务未能建立:${launched.reason}`);
666
752
  process.exitCode = 1;
667
753
  }
@@ -684,6 +770,7 @@ export async function cmdStart(opts = {}) {
684
770
  const appMain = path.join(getPackageRoot(), 'dist', 'index.js');
685
771
  const daemonEnv = buildDaemonEnv(p, opts);
686
772
  if (opts.foreground) {
773
+ const overrideFile = writeAsModeOverride(opts.asMode);
687
774
  const child = spawn('node', ['--no-warnings=ExperimentalWarning', appMain], {
688
775
  detached: false,
689
776
  stdio: 'inherit',
@@ -692,6 +779,7 @@ export async function cmdStart(opts = {}) {
692
779
  });
693
780
  const childPid = child.pid;
694
781
  if (!childPid) {
782
+ clearAsModeOverride(overrideFile);
695
783
  console.error('❌ Failed to start EvolCore (process creation failed)');
696
784
  process.exit(1);
697
785
  return;
@@ -710,16 +798,17 @@ export async function cmdStart(opts = {}) {
710
798
  console.log('🔧 调试模式已启用(EVOLCORE_DEBUG_MODE=1)');
711
799
  console.log(' 在宿主终端以任意 session 身份执行命令:');
712
800
  console.log('');
713
- console.log(' ec msg send "消息内容" --as-session <sessionId>');
801
+ console.log(` ec msg send '<消息内容>' --as-session <sessionId>`);
714
802
  console.log(' ec trigger list --as-session <sessionId>');
715
803
  console.log(' ec memory list --as-session <sessionId>');
716
804
  console.log('');
717
805
  console.log(' 查看可用会话(运行后将在此处列出):');
718
- console.log(' ec msg send "test" --as-session # 不带 sessionId 自动列出');
806
+ console.log(` ec msg send 'test' --as-session # 不带 sessionId 自动列出`);
719
807
  console.log('');
720
808
  console.log(' 注意:重启 daemon(ec restart)会清除调试模式,');
721
809
  console.log(' 需重新执行 ec start --debug 才能恢复。');
722
810
  }
811
+ printAsModeNotice(opts.asMode);
723
812
  void waitForExistingEcweb(p).then((ecweb) => {
724
813
  if (shouldPrintNoSelfAgentHints) {
725
814
  printNoSelfAgentHints({ daemonConfig: loadDaemonConfig(), ecwebStarted: ecweb !== null, skipped });
@@ -766,6 +855,7 @@ export async function cmdStart(opts = {}) {
766
855
  const stdoutLog = path.join(p.logs, 'stdout.log');
767
856
  const out = fs.openSync(stdoutLog, 'a');
768
857
  const err = fs.openSync(stdoutLog, 'a');
858
+ const overrideFile = writeAsModeOverride(opts.asMode);
769
859
  const child = spawn('node', ['--no-warnings=ExperimentalWarning', appMain], {
770
860
  detached: true,
771
861
  stdio: ['ignore', out, err],
@@ -773,6 +863,12 @@ export async function cmdStart(opts = {}) {
773
863
  env: daemonEnv,
774
864
  });
775
865
  const childPid = child.pid;
866
+ // daemon 已在启动时读走该文件;若未能起来(pid 丢失/提前退出),清掉避免影响下次启动
867
+ child.once('error', () => clearAsModeOverride(overrideFile));
868
+ // Track liveness from the spawn handle instead of launching tasklist on
869
+ // every poll; the poll interval below is short enough that it matters.
870
+ let childExited = false;
871
+ child.once('exit', () => { childExited = true; });
776
872
  child.unref();
777
873
  // 等待 ready signal(最多 30 秒,AUN sidecar 超时 15s + 其他通道连接)
778
874
  const startTime = Date.now();
@@ -823,25 +919,27 @@ export async function cmdStart(opts = {}) {
823
919
  printCodeStats(getPackageRoot(), p.logs);
824
920
  }
825
921
  console.log(`⏱ done in ${((Date.now() - cmdStartedAt) / 1000).toFixed(1)}s`);
922
+ printAsModeNotice(opts.asMode);
826
923
  if (opts.debug) {
827
924
  console.log('');
828
925
  console.log('🔧 调试模式已启用(EVOLCORE_DEBUG_MODE=1)');
829
926
  console.log(' 在宿主终端以任意 session 身份执行命令:');
830
927
  console.log('');
831
- console.log(' ec msg send "消息内容" --as-session <sessionId>');
928
+ console.log(` ec msg send '<消息内容>' --as-session <sessionId>`);
832
929
  console.log(' ec trigger list --as-session <sessionId>');
833
930
  console.log(' ec memory list --as-session <sessionId>');
834
931
  console.log('');
835
932
  console.log(' 查看可用会话(运行后将在此处列出):');
836
- console.log(' ec msg send "test" --as-session # 不带 sessionId 自动列出');
933
+ console.log(` ec msg send 'test' --as-session # 不带 sessionId 自动列出`);
837
934
  console.log('');
838
935
  console.log(' 注意:重启 daemon(ec restart)会清除调试模式,');
839
936
  console.log(' 需重新执行 ec start --debug 才能恢复。');
840
937
  }
841
- // ECWeb lifecycle belongs to the daemon; the CLI only reports whether
842
- // the supervised instance became healthy.
843
- const ecwebStarted = await waitForExistingEcweb(p) !== null;
938
+ // ECWeb lifecycle belongs to the daemon; the CLI only needs its health
939
+ // for the no-agent hints. Waiting otherwise just holds the prompt for
940
+ // up to the probe timeout after the daemon is already ready.
844
941
  if (shouldPrintNoSelfAgentHints) {
942
+ const ecwebStarted = await waitForExistingEcweb(p) !== null;
845
943
  printNoSelfAgentHints({ daemonConfig: loadDaemonConfig(), ecwebStarted, skipped });
846
944
  }
847
945
  return;
@@ -860,7 +958,7 @@ export async function cmdStart(opts = {}) {
860
958
  return;
861
959
  }
862
960
  // 进程已退出且无 ready signal
863
- if (!platform.isProcessRunning(childPid)) {
961
+ if (childExited) {
864
962
  // 给进程一点时间写 ready signal(可能刚好在写入中)
865
963
  if (Date.now() - startTime > 3000) {
866
964
  console.log('❌ Failed to start EvolCore');
@@ -875,9 +973,9 @@ export async function cmdStart(opts = {}) {
875
973
  return;
876
974
  }
877
975
  }
878
- setTimeout(checkReady, 500);
976
+ setTimeout(checkReady, 200);
879
977
  };
880
- setTimeout(checkReady, 1000);
978
+ setTimeout(checkReady, 200);
881
979
  }
882
980
  function isPidReused(pid, expectedStartedAt) {
883
981
  if (expectedStartedAt === undefined)
@@ -890,7 +988,10 @@ function reportPidReused(pid) {
890
988
  }
891
989
  async function stopPid(pid, gracefulRequested = false, expectedStartedAt) {
892
990
  console.log(`🛑 Stopping EvolCore (PID: ${pid})...`);
893
- if (isPidReused(pid, expectedStartedAt)) {
991
+ // A graceful request was accepted by the daemon that answered for this exact
992
+ // PID over IPC, so its identity is already proven; the start-time probe (a
993
+ // PowerShell launch on Windows) only guards the kill path.
994
+ if (!gracefulRequested && isPidReused(pid, expectedStartedAt)) {
894
995
  reportPidReused(pid);
895
996
  return true;
896
997
  }
@@ -939,7 +1040,7 @@ function isRuntimeOrphan(orphan, currentHome = resolveRoot()) {
939
1040
  export async function cmdStop() {
940
1041
  const status = scanInstances();
941
1042
  const p = resolvePaths();
942
- const initialOrphans = findOrphanProcesses();
1043
+ const initialOrphans = findOrphanProcesses(status);
943
1044
  const runtimeOrphans = initialOrphans.filter(orphan => isRuntimeOrphan(orphan, p.root));
944
1045
  const ping = await probeDaemon(p.socket);
945
1046
  if (ping && !hasVisibleDaemonPid(status, ping) && runtimeOrphans.length === 0) {
@@ -964,7 +1065,8 @@ export async function cmdStop() {
964
1065
  process.exitCode = 1;
965
1066
  return;
966
1067
  }
967
- await sleep(500);
1068
+ // stopPid already observed every process leave the process table, so the
1069
+ // records can be cleaned immediately (same reasoning as restart-monitor).
968
1070
  cleanupInstances();
969
1071
  if (aliveMains.length > 1) {
970
1072
  console.log(`⚠ 停止了 ${aliveMains.length} 个 main 实例: ${aliveMains.map(m => m.record.pid).join(', ')}`);
@@ -973,6 +1075,9 @@ export async function cmdStop() {
973
1075
  export async function cmdRestart(opts = {}) {
974
1076
  const cmdStartedAt = Date.now();
975
1077
  console.log('🔄 Restarting EvolCore...');
1078
+ // 重启不带 --as-mode,模式按包路径重新判定;清掉可能残留的覆盖文件,并显示出来
1079
+ writeAsModeOverride(undefined);
1080
+ console.log(` ${describeRunMode()}`);
976
1081
  if (!await ensureCodexHookBeforeLifecycle())
977
1082
  return;
978
1083
  const initialStatus = scanInstances();
@@ -1186,7 +1291,8 @@ export async function cmdStatus() {
1186
1291
  console.log('');
1187
1292
  }
1188
1293
  if (pid) {
1189
- printStartupInfo({ pid, running: true });
1294
+ const mainRecord = aliveMains.find(m => m.record.pid === pid)?.record;
1295
+ printStartupInfo({ pid, running: true, runMode: mainRecord?.runMode });
1190
1296
  if (!ping) {
1191
1297
  console.log('⚠ 仅确认 daemon PID 存活,IPC 不可用,不能据此判断服务健康。');
1192
1298
  if (ipcFailure)
@@ -3203,12 +3309,13 @@ export async function cmdDiagnose(args = []) {
3203
3309
  console.log('[diagnose] - Codex managed hook: 跳过(没有 Agent 使用 Codex)');
3204
3310
  }
3205
3311
  else {
3206
- const hook = inspectCodexManagedHook({ packageRoot: getPackageRoot() });
3312
+ const packageRoot = process.env[TARGET_PACKAGE_ROOT_ENV] || getPackageRoot();
3313
+ const hook = inspectCodexManagedHook({ packageRoot });
3207
3314
  if (hook.status === 'installed') {
3208
3315
  console.log(`[diagnose] ✓ Codex managed hook 已安装: ${hook.hookPath}`);
3209
3316
  if (runHookLaunchProbe) {
3210
3317
  try {
3211
- const launch = runCodexHookLaunchDiagnostic({ packageRoot: getPackageRoot() });
3318
+ const launch = runCodexHookLaunchDiagnostic({ packageRoot });
3212
3319
  if (launch.status === 'ok') {
3213
3320
  console.log('[diagnose] ✓ Codex managed hook 启动探针报告: ' + launch.output);
3214
3321
  for (const result of launch.results) {
@@ -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 虚拟卷来源