evolcore 0.0.38 → 0.0.40

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 (125) hide show
  1. package/CHANGELOG.md +64 -0
  2. package/README.md +31 -20
  3. package/bin/ec-safe-output.js +14 -2
  4. package/dist/agents/baseagent-availability.js +1 -0
  5. package/dist/agents/claude-runner.js +32 -46
  6. package/dist/agents/codex-runner.js +54 -30
  7. package/dist/agents/ecagent-runner.js +18 -7
  8. package/dist/agents/gemini-runner.js +1 -2
  9. package/dist/agents/runner-types.js +28 -6
  10. package/dist/aun/aid/agentmd.js +21 -0
  11. package/dist/aun/aid/client.js +1 -1
  12. package/dist/aun/aid/control-aid.js +23 -98
  13. package/dist/aun/aid/ownership-operation.js +35 -12
  14. package/dist/aun/group/invite-service.js +6 -0
  15. package/dist/aun/msg/p2p.js +8 -12
  16. package/dist/channels/aun.js +66 -48
  17. package/dist/channels/dingtalk.js +8 -2
  18. package/dist/channels/feishu.js +6 -1
  19. package/dist/channels/wecom.js +7 -3
  20. package/dist/cli/agent-create.js +30 -6
  21. package/dist/cli/agent.js +30 -37
  22. package/dist/cli/aid-bind-command.js +16 -2
  23. package/dist/cli/aun-commands.js +31 -23
  24. package/dist/cli/config.js +15 -18
  25. package/dist/cli/contact.js +9 -12
  26. package/dist/cli/ctl-command.js +3 -3
  27. package/dist/cli/daemon-commands.js +242 -17
  28. package/dist/cli/diag.js +7 -3
  29. package/dist/cli/fs-command.js +5 -4
  30. package/dist/cli/handoff-command.js +11 -11
  31. package/dist/cli/help.js +18 -0
  32. package/dist/cli/index.js +25 -13
  33. package/dist/cli/init-channel.js +2 -1
  34. package/dist/cli/memory-command.js +47 -27
  35. package/dist/cli/model.js +5 -4
  36. package/dist/cli/queue-command.js +3 -3
  37. package/dist/cli/response.js +5 -4
  38. package/dist/cli/restart-monitor.js +35 -13
  39. package/dist/cli/skill-command.js +13 -13
  40. package/dist/cli/task-command.js +3 -3
  41. package/dist/cli/task-context.js +1 -0
  42. package/dist/cli/thread-command.js +5 -3
  43. package/dist/cli/trigger-command.js +3 -3
  44. package/dist/cli/version.js +6 -2
  45. package/dist/config/config-field-policy.js +1 -0
  46. package/dist/contracts/ecweb-service-protocol.js +44 -0
  47. package/dist/core/agent-runtime.js +6 -0
  48. package/dist/core/auth/agent-delegation.js +7 -4
  49. package/dist/core/auth/auth-gateway.js +3 -1
  50. package/dist/core/auth/authorization-audit.js +5 -1
  51. package/dist/core/auth/debug-context.js +5 -1
  52. package/dist/core/auth/operation-catalog.js +17 -1
  53. package/dist/core/bootstrap-service.js +20 -12
  54. package/dist/core/command/cli-intent-parser.js +5 -2
  55. package/dist/core/command/command-handler.js +20 -5
  56. package/dist/core/command/menu-catalog.js +48 -12
  57. package/dist/core/command/menu-handler.js +74 -6
  58. package/dist/core/command/menu-protocol.js +20 -1
  59. package/dist/core/command/slash-gate.js +17 -3
  60. package/dist/core/command/slash-handler.js +90 -7
  61. package/dist/core/event-catalog.js +2 -0
  62. package/dist/core/interaction-router.js +2 -1
  63. package/dist/core/ipc-registration.js +26 -7
  64. package/dist/core/memory/batch-meta.js +2 -0
  65. package/dist/core/memory/consolidation-context.js +42 -51
  66. package/dist/core/memory/consolidation-ops-records.js +39 -7
  67. package/dist/core/memory/consolidation-trigger.js +71 -64
  68. package/dist/core/memory/consolidation.js +226 -20
  69. package/dist/core/memory/memory-writer.js +160 -34
  70. package/dist/core/memory/relation-idle-monitor.js +287 -0
  71. package/dist/core/memory/relation-sweep.js +206 -0
  72. package/dist/core/message/aun-ipc-sender.js +5 -0
  73. package/dist/core/message/message-bridge.js +64 -55
  74. package/dist/core/message/message-log.js +17 -0
  75. package/dist/core/message/message-queue.js +6 -0
  76. package/dist/core/message/outbound.js +70 -0
  77. package/dist/core/message/peer-mode.js +7 -2
  78. package/dist/core/message/response-engine.js +341 -73
  79. package/dist/core/ownership/owner-resolver.js +465 -140
  80. package/dist/core/permission/ec-command-parser.js +19 -3
  81. package/dist/core/permission/protected-paths.js +51 -0
  82. package/dist/core/permission/tool-policy.js +7 -7
  83. package/dist/core/role/runtime-policy.js +64 -0
  84. package/dist/core/session/session-manager.js +25 -5
  85. package/dist/core/system-session/memory.js +3 -18
  86. package/dist/ecweb/basic-settings-service.js +709 -0
  87. package/dist/ecweb/providers-service.js +172 -0
  88. package/dist/index.js +92 -4
  89. package/dist/ipc.js +78 -11
  90. package/dist/stats/db.js +33 -0
  91. package/dist/stats/index.js +2 -2
  92. package/dist/stats/query.js +246 -0
  93. package/dist/stats/writer.js +41 -1
  94. package/dist/trigger/application-service.js +12 -0
  95. package/dist/trigger/authorization.js +2 -2
  96. package/dist/trigger/history.js +36 -0
  97. package/dist/trigger/scheduler.js +3 -0
  98. package/dist/utils/ecweb-supervisor.js +31 -10
  99. package/dist/utils/error-utils.js +8 -4
  100. package/dist/utils/evolcore-version.js +3 -2
  101. package/dist/utils/host-info.js +52 -0
  102. package/dist/utils/welcome.js +18 -24
  103. package/ecagent/dist/harness/agent-harness.js +4 -1
  104. package/kits/docs/evolcore/INDEX.md +1 -1
  105. package/kits/docs/evolcore/agent.md +1 -1
  106. package/kits/docs/evolcore/aid.md +58 -8
  107. package/kits/docs/evolcore/config.md +9 -7
  108. package/kits/docs/evolcore/memory-architecture.md +3 -4
  109. package/kits/docs/evolcore/memory-consolidation.md +28 -7
  110. package/kits/docs/evolcore/memory-tags.md +1 -1
  111. package/kits/docs/evolcore/memory.md +1 -1
  112. package/kits/docs/evolcore/menu-client.md +1 -1
  113. package/kits/docs/evolcore/menu-protocol.md +6 -0
  114. package/kits/docs/evolcore/msg.md +1 -1
  115. package/kits/eck_manifest.json +3 -3
  116. package/kits/rules/04-relation.md +3 -2
  117. package/kits/schemas/agent-config.schema.13.json +5 -1
  118. package/kits/templates/memory-consolidation/generalize.md +32 -4
  119. package/kits/templates/memory-consolidation/notes.md +2 -2
  120. package/kits/templates/memory-consolidation/promote.md +10 -2
  121. package/kits/templates/system-fragments/baseagent.md +5 -0
  122. package/kits/templates/system-fragments/memory.md +12 -3
  123. package/kits/templates/system-fragments/session.md +1 -1
  124. package/kits/{rules/07-memory-and-thread.md → templates/system-fragments/thread.md} +17 -80
  125. package/package.json +3 -3
package/dist/cli/diag.js CHANGED
@@ -1,4 +1,4 @@
1
- import { ipcQuery } from '../ipc.js';
1
+ import { daemonUnavailableMessage, queryDaemon } from './help.js';
2
2
  import { resolvePaths } from '../paths.js';
3
3
  import { readDelegationCredential } from '../core/auth/agent-delegation.js';
4
4
  import { parseDiagArgs } from '../core/command/diag-service.js';
@@ -13,13 +13,17 @@ export async function cmdDiag(args) {
13
13
  let result;
14
14
  try {
15
15
  parseDiagArgs(args);
16
- result = await ipcQuery(resolvePaths().socket, {
16
+ const { response, transportError } = await queryDaemon(resolvePaths().socket, {
17
17
  type: 'diag', argv,
18
18
  ...(process.env.EVOLCORE_SESSION_ID ? {
19
19
  sessionId: process.env.EVOLCORE_SESSION_ID,
20
20
  delegationToken: readDelegationCredential(),
21
21
  } : {}),
22
- }, 30_000) ?? { ok: false, code: 'DAEMON_UNAVAILABLE', message: 'daemon 未返回结果,请检查其运行状态;发送结果尚未确认' };
22
+ }, 30_000);
23
+ result = response ?? {
24
+ ok: false, code: 'DAEMON_UNAVAILABLE',
25
+ message: daemonUnavailableMessage(transportError, 'daemon 未返回结果,请检查其运行状态;发送结果尚未确认'),
26
+ };
23
27
  }
24
28
  catch (error) {
25
29
  result = { ok: false, code: 'INVALID_ARGUMENT', message: error.message };
@@ -3,7 +3,7 @@ import path from 'path';
3
3
  import { TextDecoder } from 'util';
4
4
  import { getArgValue, isHelpFlag, wantsHelp } from './help.js';
5
5
  import { agentmdGet, getAidStore, isValidAid, loadClient, SLOT } from '../aun/aid/index.js';
6
- import { ipcQuery } from '../ipc.js';
6
+ import { daemonUnavailableMessage, queryDaemon } from './help.js';
7
7
  import { resolvePaths } from '../paths.js';
8
8
  import { AGENT_DELEGATION_TOKEN_ENV, readDelegationCredential } from '../core/auth/agent-delegation.js';
9
9
  import { DEFAULT_FS_CAT_BYTES, MAX_FS_CAT_BYTES, FsReadBudgetError, downloadGroupFsBytes, readPersonalFsBytesWithinBudget, } from '../aun/group/fs-download.js';
@@ -222,14 +222,15 @@ async function runManagedFs(args, formatJson) {
222
222
  fail('NO_SESSION', 'EVOLCORE_SESSION_ID is not set');
223
223
  if (!delegationToken)
224
224
  fail('DELEGATION_REQUIRED', `${AGENT_DELEGATION_TOKEN_ENV} is not set for the active task`);
225
- const response = await ipcQuery(resolvePaths().socket, {
225
+ const { response, transportError } = await queryDaemon(resolvePaths().socket, {
226
226
  type: 'fs.op',
227
227
  argv: ['fs', ...args],
228
228
  sessionId,
229
229
  delegationToken,
230
230
  }, 30_000);
231
- if (!response)
232
- return fail('DAEMON_UNAVAILABLE', 'Unable to connect to the evolcore daemon');
231
+ if (!response) {
232
+ return fail('DAEMON_UNAVAILABLE', daemonUnavailableMessage(transportError, 'Unable to connect to the evolcore daemon'));
233
+ }
233
234
  if (!response.ok || !response.result)
234
235
  fail(String(response.code || 'FS_OPERATION_FAILED'), response.error || 'Filesystem operation failed');
235
236
  const result = response.result;
@@ -1,6 +1,6 @@
1
1
  import fs from 'fs';
2
2
  import { resolvePaths } from '../paths.js';
3
- import { ipcQuery } from '../ipc.js';
3
+ import { daemonUnavailableMessage, queryDaemon } from './help.js';
4
4
  import { HANDOFF_QUERY_DEFAULT_LIMIT, HANDOFF_QUERY_MAX_LIMIT, HANDOFF_STATES } from '../core/handoff/types.js';
5
5
  import { HANDOFF_ID_RE } from '../core/handoff/store.js';
6
6
  import { isHelpFlag, wantsHelp } from './help.js';
@@ -191,7 +191,7 @@ Commands:
191
191
  process.exitCode = 1;
192
192
  return;
193
193
  }
194
- const result = await ipcQuery(resolvePaths().socket, {
194
+ const { response: result, transportError } = await queryDaemon(resolvePaths().socket, {
195
195
  type: 'handoff-return',
196
196
  argv: ['handoff', ...args],
197
197
  sessionId: process.env.EVOLCORE_SESSION_ID,
@@ -200,7 +200,7 @@ Commands:
200
200
  delegationToken: readDelegationCredential(),
201
201
  }, 5000);
202
202
  if (!result) {
203
- console.error('✗ daemon 暂时不可用');
203
+ console.error(`✗ ${daemonUnavailableMessage(transportError, 'daemon 暂时不可用')}`);
204
204
  process.exitCode = 1;
205
205
  return;
206
206
  }
@@ -222,7 +222,7 @@ Commands:
222
222
  process.exitCode = 1;
223
223
  return;
224
224
  }
225
- const result = await ipcQuery(resolvePaths().socket, {
225
+ const { response: result, transportError } = await queryDaemon(resolvePaths().socket, {
226
226
  type: 'handoff-recover-return',
227
227
  argv: ['handoff', ...args],
228
228
  sessionId: process.env.EVOLCORE_SESSION_ID,
@@ -231,7 +231,7 @@ Commands:
231
231
  delegationToken: readDelegationCredential(),
232
232
  }, 5000);
233
233
  if (!result) {
234
- console.error('✗ daemon 暂时不可用');
234
+ console.error(`✗ ${daemonUnavailableMessage(transportError, 'daemon 暂时不可用')}`);
235
235
  process.exitCode = 1;
236
236
  return;
237
237
  }
@@ -245,13 +245,13 @@ Commands:
245
245
  process.exitCode = 1;
246
246
  return;
247
247
  }
248
- const result = await ipcQuery(resolvePaths().socket, {
248
+ const { response: result, transportError } = await queryDaemon(resolvePaths().socket, {
249
249
  type: 'handoff-status', sessionId: process.env.EVOLCORE_SESSION_ID, handoffId,
250
250
  argv: ['handoff', ...args],
251
251
  delegationToken: readDelegationCredential(),
252
252
  }, 5000);
253
253
  if (!result) {
254
- console.error('✗ daemon 暂时不可用');
254
+ console.error(`✗ ${daemonUnavailableMessage(transportError, 'daemon 暂时不可用')}`);
255
255
  process.exitCode = 1;
256
256
  return;
257
257
  }
@@ -273,7 +273,7 @@ Commands:
273
273
  process.exitCode = 1;
274
274
  return;
275
275
  }
276
- const result = await ipcQuery(resolvePaths().socket, {
276
+ const { response: result, transportError } = await queryDaemon(resolvePaths().socket, {
277
277
  type: 'handoff-list',
278
278
  argv: ['handoff', ...args],
279
279
  sessionId: process.env.EVOLCORE_SESSION_ID,
@@ -284,7 +284,7 @@ Commands:
284
284
  delegationToken: readDelegationCredential(),
285
285
  }, 5000);
286
286
  if (!result) {
287
- console.error('✗ daemon 暂时不可用');
287
+ console.error(`✗ ${daemonUnavailableMessage(transportError, 'daemon 暂时不可用')}`);
288
288
  process.exitCode = 1;
289
289
  return;
290
290
  }
@@ -318,7 +318,7 @@ Commands:
318
318
  process.exitCode = 1;
319
319
  return;
320
320
  }
321
- const result = await ipcQuery(resolvePaths().socket, {
321
+ const { response: result, transportError } = await queryDaemon(resolvePaths().socket, {
322
322
  type: 'handoff-trace',
323
323
  argv: ['handoff', ...args],
324
324
  sessionId: process.env.EVOLCORE_SESSION_ID,
@@ -328,7 +328,7 @@ Commands:
328
328
  delegationToken: readDelegationCredential(),
329
329
  }, 5000);
330
330
  if (!result) {
331
- console.error('✗ daemon 暂时不可用');
331
+ console.error(`✗ ${daemonUnavailableMessage(transportError, 'daemon 暂时不可用')}`);
332
332
  process.exitCode = 1;
333
333
  return;
334
334
  }
package/dist/cli/help.js CHANGED
@@ -11,7 +11,25 @@
11
11
  * - 单层命令(如 cmdLinkRules、cmdAid 的具体 sub 处理块内)用 wantsHelp(args),
12
12
  * 支持 option 风格帮助,同时不把 `--` 后的正文误判为帮助。
13
13
  */
14
+ import { IpcTransportError, ipcQuery } from '../ipc.js';
14
15
  const HELP_TOKENS = new Set(['help', '--help', '-h']);
16
+ /** Query a daemon-backed command while preserving concrete transport failures. */
17
+ export async function queryDaemon(socketPath, command, timeoutMs = 3000) {
18
+ try {
19
+ return {
20
+ response: await ipcQuery(socketPath, command, timeoutMs, { throwOnError: true }),
21
+ };
22
+ }
23
+ catch (error) {
24
+ if (error instanceof IpcTransportError) {
25
+ return { response: null, transportError: error };
26
+ }
27
+ throw error;
28
+ }
29
+ }
30
+ export function daemonUnavailableMessage(transportError, fallback) {
31
+ return transportError?.message ?? fallback;
32
+ }
15
33
  export function isHelpFlag(token) {
16
34
  return token !== undefined && HELP_TOKENS.has(token);
17
35
  }
package/dist/cli/index.js CHANGED
@@ -82,7 +82,7 @@ function printLifecycleHelp(command) {
82
82
  start: `用法: ec start [--foreground] [--diagnose|-d] [--debug] [--as-mode dev|install]\n\n --foreground 前台运行,适合 systemd 或容器直接管理\n --diagnose, -d 以诊断模式启动 daemon\n --debug 调试模式:注册调试 IPC,可在宿主终端以任意 session 身份发命令\n --as-mode 覆盖运行模式判定(仅本次启动生效,不写配置):\n install = 按安装模式(校验随包文件完整性,篡改自动恢复)\n dev = 按开发模式(不校验随包文件)`,
83
83
  stop: '用法: ec stop\n\n停止当前 EVOLCORE_HOME 下运行的 EvolCore 服务。',
84
84
  restart: `用法: ec restart [--clear] [--diagnose|-d]\n\n --clear 同时清理跨 HOME 残留的 EvolCore 主进程\n --diagnose, -d 以诊断模式启动新 daemon`,
85
- status: '用法: ec status\n\n查看 daemon、agent、渠道与会话运行状态。',
85
+ status: '用法: ec status [--format json]\n\n查看 daemon、Agent、渠道与队列运行状态。JSON 模式输出结构化状态快照。',
86
86
  logs: `用法: ec logs [选项]\n\n --level <error|warn|info|debug> 最低日志级别\n --module <name> 只显示指定模块\n --raw 原始输出\n --no-color 禁用颜色`,
87
87
  watch: `用法: ec watch [logs|log|aid|msg|diag|web|session]\n\n ec watch 打开监控选择菜单\n ec watch logs 选择日志类型后监控\n ec watch log <类型...> 直接监控指定日志类型\n ec watch aid 监控 AID 连接\n ec watch msg 打开消息 TUI\n ec watch diag 实时诊断消息生命周期(AUN→daemon→任务)\n ec watch web 安装/升级并启动 EC Web\n ec watch session ec watch web 的兼容别名`,
88
88
  'restart-monitor': '用法: ec restart-monitor\n\n运行内 daemon 重启与升级监控器。',
@@ -266,21 +266,33 @@ export async function main(args) {
266
266
  printLifecycleHelp('status');
267
267
  break;
268
268
  }
269
- if (process.env.EVOLCORE_SESSION_ID) {
270
- const sessionId = process.env.EVOLCORE_SESSION_ID;
271
- const delegationToken = readDelegationCredential();
272
- const result = await ipcQuery(resolvePaths().socket, {
273
- type: 'status.auth', sessionId, delegationToken,
274
- });
275
- if (!result?.ok) {
276
- console.error(`❌ ${result?.error || 'status is not allowed in this task'}`);
269
+ {
270
+ const statusArgs = strippedArgs.slice(1);
271
+ const formatValue = getArgValue(statusArgs, '--format');
272
+ const validStatusArgs = statusArgs.length === 0
273
+ || (statusArgs.length === 2 && statusArgs[0] === '--format' && formatValue === 'json');
274
+ if (!validStatusArgs) {
275
+ console.error('用法: ec status [--format json]');
277
276
  process.exitCode = 1;
278
277
  break;
279
278
  }
280
- await cmdStatus();
281
- }
282
- else {
283
- await cmdStatus();
279
+ const format = formatValue;
280
+ if (process.env.EVOLCORE_SESSION_ID) {
281
+ const sessionId = process.env.EVOLCORE_SESSION_ID;
282
+ const delegationToken = readDelegationCredential();
283
+ const result = await ipcQuery(resolvePaths().socket, {
284
+ type: 'status.auth', sessionId, delegationToken,
285
+ });
286
+ if (!result?.ok) {
287
+ console.error(`❌ ${result?.error || 'status is not allowed in this task'}`);
288
+ process.exitCode = 1;
289
+ break;
290
+ }
291
+ await cmdStatus({ format });
292
+ }
293
+ else {
294
+ await cmdStatus({ format });
295
+ }
284
296
  }
285
297
  break;
286
298
  case 'logs':
@@ -8,6 +8,7 @@
8
8
  import readline from 'readline';
9
9
  import { cmdAidBind } from './aid-bind-command.js';
10
10
  import { getOwners } from '../core/ownership/owner-resolver.js';
11
+ import { MAIN_CLI_BIN } from '../product.js';
11
12
  import crypto from 'crypto';
12
13
  import { selectInstance } from './init.js';
13
14
  import { loadAllAgents, loadAgent, loadDaemonConfig } from '../config-store.js';
@@ -695,7 +696,7 @@ export async function cmdInitWechat() {
695
696
  // ==================== AUN ====================
696
697
  export async function cmdInitAun() {
697
698
  console.log('AUN 协议 Owner 统一使用 ec aid bind <ownerAid> [--subject <subjectAid>]');
698
- console.log('状态查询: ec aid bind status [--subject <subjectAid>];Evol App 扫码绑定留待二期。');
699
+ console.log(`状态查询: ${MAIN_CLI_BIN} aid bind status [--subject <subjectAid>];邀约审批: ${MAIN_CLI_BIN} aid bind accept|reject <operationId>。`);
699
700
  }
700
701
  async function pickAunOwnerBindTarget(rl, daemonAid) {
701
702
  const { agents } = loadAllAgents();
@@ -13,7 +13,7 @@
13
13
  */
14
14
  import fs from 'node:fs';
15
15
  import { resolvePaths } from '../paths.js';
16
- import { ipcQuery } from '../ipc.js';
16
+ import { daemonUnavailableMessage, queryDaemon } from './help.js';
17
17
  import { isHelpFlag, wantsHelp, getArgValue } from './help.js';
18
18
  import { readDelegationCredential } from '../core/auth/agent-delegation.js';
19
19
  import { canonicalizeEvolcoreCommandArgv } from '../core/permission/ec-command-parser.js';
@@ -43,11 +43,12 @@ ec memory note drop <slug>
43
43
  ec memory note list
44
44
 
45
45
  ec memory create --scope <relation|agent|owner> --slug <slug> --desc '<description>' \\
46
- --tags '<tag1>,<tag2>' [--keywords '<kw1>,<kw2>'] [--replaces '<path1>,<path2>'] '<content>'
46
+ --tags '<tag1>,<tag2>' [--keywords '<kw1>,<kw2>'] [--replaces '<path1>,<path2>'] \\
47
+ [--peer <peerKey>] [--reason '<why>'] [--generalized-from <path>] [--promoted-from <path>] '<content>'
47
48
 
48
- ec memory update <path> [--tags '<tag1>,<tag2>'] [--touch]
49
+ ec memory update <path> [--tags '<tag1>,<tag2>'] [--touch] [--reason '<why>']
49
50
 
50
- ec memory archive <path...>
51
+ ec memory archive <path...> [--reason '<why>']
51
52
 
52
53
  ec memory consolidate [--mode <notes|generalize|promote|profile|forget>]
53
54
 
@@ -168,8 +169,9 @@ export function resolveMemoryCommandArgv(argv) {
168
169
  const kind = noteSub ? `note.${noteSub}` : argv[1];
169
170
  const known = {
170
171
  note: ['--slug', '--tags', '--speakers'], 'note.update': ['--slug', '--tags'],
171
- 'note.drop': [], 'note.list': [], create: ['--scope', '--slug', '--desc', '--tags', '--keywords', '--replaces', '--peer'],
172
- update: ['--tags', '--touch'], archive: [], demote: ['--scope'], consolidate: ['--mode'],
172
+ 'note.drop': [], 'note.list': [],
173
+ create: ['--scope', '--slug', '--desc', '--tags', '--keywords', '--replaces', '--peer', '--reason', '--generalized-from', '--promoted-from'],
174
+ update: ['--tags', '--touch', '--reason'], archive: ['--reason'], demote: ['--scope'], consolidate: ['--mode'],
173
175
  };
174
176
  // feedback 已移除:旧会话/旧文档可能还在调用,给一句能照做的说明而不是泛化的非法命令。
175
177
  if (kind === 'feedback') {
@@ -209,17 +211,19 @@ export function resolveMemoryCommandArgv(argv) {
209
211
  throw new Error('--peer requires relation scope');
210
212
  return { type, scope, slug: required(flags.get('--slug'), '--slug'), description: required(flags.get('--desc'), '--desc'),
211
213
  content: required(content, 'content'), tags: parseTags(flags.get('--tags')), keywords: parseTags(flags.get('--keywords')),
212
- replaces: parseTags(flags.get('--replaces')), targetPeerKey: flags.get('--peer') };
214
+ replaces: parseTags(flags.get('--replaces')), targetPeerKey: flags.get('--peer'),
215
+ reason: flags.get('--reason'), generalizedFrom: flags.get('--generalized-from'), promotedFrom: flags.get('--promoted-from') };
213
216
  }
214
217
  if (kind === 'update') {
215
218
  if (!flags.has('--tags') && !flags.has('--touch'))
216
219
  throw new Error('No memory changes supplied');
217
- return { type, filePath: required(positional[0], 'path'), tags: parseTags(flags.get('--tags')), touch: flags.has('--touch') };
220
+ return { type, filePath: required(positional[0], 'path'), tags: parseTags(flags.get('--tags')), touch: flags.has('--touch'),
221
+ reason: flags.get('--reason') };
218
222
  }
219
223
  if (kind === 'archive') {
220
224
  if (!positional.length)
221
225
  throw new Error('Memory paths are required');
222
- return { type, filePaths: positional };
226
+ return { type, filePaths: positional, reason: flags.get('--reason') };
223
227
  }
224
228
  if (kind === 'demote') {
225
229
  const scope = flags.get('--scope') ?? 'agent';
@@ -240,9 +244,9 @@ const USAGE = {
240
244
  note: "ec memory note --slug <slug> [--tags '<tag1>,<tag2>'] [--speakers '<aid1>,<aid2>'] '<正文>'",
241
245
  noteUpdate: "ec memory note update <slug> [--slug <新slug>] [--tags '<tag1>,<tag2>'] ['<新正文>']",
242
246
  noteDrop: 'ec memory note drop <slug>',
243
- create: "ec memory create --scope <relation|agent|owner> --slug <slug> --desc '<描述>' --tags '<tag1>,<tag2>' [--keywords '<kw1>,<kw2>'] [--replaces '<路径1>,<路径2>'] [--peer <peerKey>] '<正文>'",
244
- update: "ec memory update <路径> [--tags '<tag1>,<tag2>'] [--touch](至少给一项)",
245
- archive: 'ec memory archive <路径...>',
247
+ create: "ec memory create --scope <relation|agent|owner> --slug <slug> --desc '<描述>' --tags '<tag1>,<tag2>' [--keywords '<kw1>,<kw2>'] [--replaces '<路径1>,<路径2>'] [--peer <peerKey>] [--reason '<为什么>'] [--generalized-from <路径>] [--promoted-from <路径>] '<正文>'",
248
+ update: "ec memory update <路径> [--tags '<tag1>,<tag2>'] [--touch] [--reason '<为什么>'](至少给一项)",
249
+ archive: "ec memory archive <路径...> [--reason '<为什么>']",
246
250
  };
247
251
  /**
248
252
  * 统一失败出口。JSON 模式与 `ec fs` / `ec response` 一致:stdout 输出 `{ ok: false, code, error }`;
@@ -337,7 +341,7 @@ async function cmdMemoryNoteCreate(args, json) {
337
341
  if (!content)
338
342
  failWithUsage(json, '必须提供记忆内容(content)', USAGE.note);
339
343
  const { socketPath, ...session } = requireManagedSession('ec memory note', flags, json);
340
- const result = await ipcQuery(socketPath, {
344
+ const result = await queryDaemon(socketPath, {
341
345
  type: 'memory.note', argv: ['memory', 'note', ...args], ...session, slug, content, tags,
342
346
  speakers: speakers && speakers.length > 0 ? speakers : undefined,
343
347
  }, 15_000);
@@ -359,7 +363,7 @@ async function cmdMemoryNoteUpdate(args, json) {
359
363
  failWithUsage(json, '至少提供一项要改的内容(--slug / --tags / 正文)', USAGE.noteUpdate);
360
364
  }
361
365
  const { socketPath, ...session } = requireManagedSession('ec memory note update', flags, json);
362
- const result = await ipcQuery(socketPath, {
366
+ const result = await queryDaemon(socketPath, {
363
367
  type: 'memory.note.update', argv: ['memory', 'note', 'update', ...args], ...session, slug,
364
368
  newSlug, tags, tagsProvided: tagsRaw !== undefined,
365
369
  content: content || undefined,
@@ -375,7 +379,7 @@ async function cmdMemoryNoteDrop(args, json) {
375
379
  if (!slug)
376
380
  failWithUsage(json, '必须提供要撤销的 slug', USAGE.noteDrop);
377
381
  const { socketPath, ...session } = requireManagedSession('ec memory note drop', flags, json);
378
- const result = await ipcQuery(socketPath, {
382
+ const result = await queryDaemon(socketPath, {
379
383
  type: 'memory.note.drop', argv: ['memory', 'note', 'drop', ...args], ...session, slug,
380
384
  }, 15_000);
381
385
  reportResult(result, json, (r) => {
@@ -385,7 +389,7 @@ async function cmdMemoryNoteDrop(args, json) {
385
389
  async function cmdMemoryNoteList(args, json) {
386
390
  const { flags } = parseCliArgs(args, [], json);
387
391
  const { socketPath, ...session } = requireManagedSession('ec memory note list', flags, json);
388
- const result = await ipcQuery(socketPath, {
392
+ const result = await queryDaemon(socketPath, {
389
393
  type: 'memory.note.list', argv: ['memory', 'note', 'list', ...args], ...session,
390
394
  }, 15_000);
391
395
  reportResult(result, json, (r) => {
@@ -406,6 +410,7 @@ async function cmdMemoryNoteList(args, json) {
406
410
  async function cmdMemoryCreate(args, json) {
407
411
  const { flags, positional } = parseCliArgs(args, [
408
412
  '--scope', '--slug', '--desc', '--tags', '--keywords', '--replaces', '--peer',
413
+ '--reason', '--generalized-from', '--promoted-from',
409
414
  ], json);
410
415
  const content = positional.join(' ').trim();
411
416
  const scope = flags.get('--scope');
@@ -415,6 +420,9 @@ async function cmdMemoryCreate(args, json) {
415
420
  const keywordsRaw = flags.get('--keywords');
416
421
  const replacesRaw = flags.get('--replaces');
417
422
  const targetPeerKey = flags.get('--peer');
423
+ const reason = flags.get('--reason');
424
+ const generalizedFrom = flags.get('--generalized-from');
425
+ const promotedFrom = flags.get('--promoted-from');
418
426
  if (!scope || !['agent', 'relation', 'owner'].includes(scope)) {
419
427
  failWithUsage(json, '--scope 必须是 agent / relation / owner 之一', USAGE.create);
420
428
  }
@@ -429,14 +437,23 @@ async function cmdMemoryCreate(args, json) {
429
437
  if (targetPeerKey !== undefined && scope !== 'relation') {
430
438
  failWithUsage(json, '--peer 只在 --scope relation 时有效', USAGE.create);
431
439
  }
440
+ if (generalizedFrom !== undefined && scope !== 'agent') {
441
+ failWithUsage(json, '--generalized-from 只在 --scope agent 时有效', USAGE.create);
442
+ }
443
+ if (promotedFrom !== undefined && scope !== 'owner') {
444
+ failWithUsage(json, '--promoted-from 只在 --scope owner 时有效', USAGE.create);
445
+ }
432
446
  const { socketPath, ...session } = requireManagedSession('ec memory create', flags, json);
433
- const result = await ipcQuery(socketPath, {
447
+ const result = await queryDaemon(socketPath, {
434
448
  type: 'memory.create', argv: ['memory', 'create', ...args], ...session, scope, slug, description: desc,
435
449
  content,
436
450
  tags: parseTags(tagsRaw),
437
451
  keywords: parseTags(keywordsRaw),
438
452
  replaces: replacesRaw ? replacesRaw.split(',').map(s => s.trim()).filter(Boolean) : undefined,
439
453
  targetPeerKey: targetPeerKey ?? undefined,
454
+ reason: reason ?? undefined,
455
+ generalizedFrom: generalizedFrom ?? undefined,
456
+ promotedFrom: promotedFrom ?? undefined,
440
457
  }, 15_000);
441
458
  reportResult(result, json, (r) => {
442
459
  if (r.filePath)
@@ -447,30 +464,32 @@ async function cmdMemoryCreate(args, json) {
447
464
  });
448
465
  }
449
466
  async function cmdMemoryConsolidatedUpdate(args, json) {
450
- const { flags, positional } = parseCliArgs(args, ['--tags', '--touch'], json);
467
+ const { flags, positional } = parseCliArgs(args, ['--tags', '--touch', '--reason'], json);
451
468
  const filePath = positional[0];
452
469
  const tagsRaw = flags.get('--tags');
453
470
  const touch = flags.has('--touch');
471
+ const reason = flags.get('--reason');
454
472
  if (!filePath)
455
473
  failWithUsage(json, '必须提供要修改的记忆文件路径', USAGE.update);
456
474
  if (tagsRaw === undefined && !touch)
457
475
  failWithUsage(json, '至少提供 --tags 或 --touch', USAGE.update);
458
476
  const { socketPath, ...session } = requireManagedSession('ec memory update', flags, json);
459
- const result = await ipcQuery(socketPath, {
477
+ const result = await queryDaemon(socketPath, {
460
478
  type: 'memory.update', argv: ['memory', 'update', ...args], ...session, filePath,
461
- tags: parseTags(tagsRaw), touch,
479
+ tags: parseTags(tagsRaw), touch, reason: reason ?? undefined,
462
480
  }, 15_000);
463
481
  reportResult(result, json, () => {
464
482
  console.log(`✓ 已更新: ${filePath}`);
465
483
  });
466
484
  }
467
485
  async function cmdMemoryArchive(args, json) {
468
- const { flags, positional } = parseCliArgs(args, [], json);
486
+ const { flags, positional } = parseCliArgs(args, ['--reason'], json);
469
487
  if (positional.length === 0)
470
488
  failWithUsage(json, '必须提供至少一个要归档的文件路径', USAGE.archive);
471
489
  const { socketPath, ...session } = requireManagedSession('ec memory archive', flags, json);
472
- const result = await ipcQuery(socketPath, {
490
+ const result = await queryDaemon(socketPath, {
473
491
  type: 'memory.archive', argv: ['memory', 'archive', ...args], ...session, filePaths: positional,
492
+ reason: flags.get('--reason') ?? undefined,
474
493
  }, 15_000);
475
494
  reportResult(result, json, (r) => {
476
495
  const archived = r.archived ?? positional;
@@ -496,7 +515,7 @@ async function cmdMemoryDemote(args, json) {
496
515
  fail(json, 'INVALID_ARGUMENT', '--scope 必须是 agent 或 owner', { text: '--scope 必须是 agent 或 owner' });
497
516
  }
498
517
  const { socketPath, ...session } = requireManagedSession('ec memory demote', flags, json);
499
- const result = await ipcQuery(socketPath, {
518
+ const result = await queryDaemon(socketPath, {
500
519
  type: 'memory.demote', argv: ['memory', 'demote', ...args], ...session, slug, scope,
501
520
  }, 15_000);
502
521
  reportResult(result, json, (r) => {
@@ -528,7 +547,7 @@ async function cmdMemoryConsolidate(args, json) {
528
547
  // JSON 模式下 stdout 只留结果对象
529
548
  if (!json)
530
549
  console.log(`正在触发记忆整理(mode=${mode})...`);
531
- const result = await ipcQuery(socketPath, {
550
+ const result = await queryDaemon(socketPath, {
532
551
  type: 'memory.consolidate', argv: ['memory', 'consolidate', ...args], ...session, mode,
533
552
  }, 30_000);
534
553
  reportResult(result, json, (r) => {
@@ -551,10 +570,11 @@ async function cmdMemoryConsolidate(args, json) {
551
570
  }
552
571
  // ── 结果输出 ───────────────────────────────────────────────────────────────
553
572
  function reportResult(result, json, ok) {
554
- if (!result) {
555
- fail(json, 'DAEMON_UNAVAILABLE', '无法连接 evolcore 服务', { text: '错误: 无法连接 evolcore 服务' });
573
+ if (!result.response) {
574
+ const message = daemonUnavailableMessage(result.transportError, '无法连接 evolcore 服务');
575
+ fail(json, 'DAEMON_UNAVAILABLE', message, { text: `错误: ${message}` });
556
576
  }
557
- const r = result;
577
+ const r = result.response;
558
578
  if (!r.ok) {
559
579
  const message = r.error || '操作失败';
560
580
  fail(json, r.code || 'MEMORY_OPERATION_FAILED', message, { text: message });
package/dist/cli/model.js CHANGED
@@ -16,7 +16,7 @@ import { resolveEffectiveFieldWithSource } from '../config/config-manager.js';
16
16
  import { getCatalog, getModelInfo, catalogEfforts, resolveCatalogModel, assertCatalogSelectable } from '../core/model/model-catalog.js';
17
17
  import { constrainResolvedModelForRole, filterModelsForRole, validateModelSelectionForRole, } from '../core/model/model-permission.js';
18
18
  import { resolvePaths } from '../paths.js';
19
- import { ipcQuery } from '../ipc.js';
19
+ import { daemonUnavailableMessage, queryDaemon } from './help.js';
20
20
  import { parsePeerKey } from '../core/relation/peer-identity.js';
21
21
  import { resolvePeerRoleDetail } from '../config/peer-role-resolver.js';
22
22
  import { AGENT_DELEGATION_TOKEN_ENV, readDelegationCredential } from '../core/auth/agent-delegation.js';
@@ -50,11 +50,12 @@ async function runManagedCliOperation(args, formatJson, timeoutMs = 10_000) {
50
50
  const delegationToken = readDelegationCredential();
51
51
  if (!delegationToken)
52
52
  fail(formatJson, 'DELEGATION_REQUIRED', `${AGENT_DELEGATION_TOKEN_ENV} 未设置`);
53
- const response = await ipcQuery(resolvePaths().socket, {
53
+ const { response, transportError } = await queryDaemon(resolvePaths().socket, {
54
54
  type: 'ctl', argv: args, sessionId, delegationToken,
55
55
  }, timeoutMs);
56
- if (!response)
57
- fail(formatJson, 'DAEMON_UNAVAILABLE', '无法连接 evolcore 服务');
56
+ if (!response) {
57
+ fail(formatJson, 'DAEMON_UNAVAILABLE', daemonUnavailableMessage(transportError, '无法连接 evolcore 服务'));
58
+ }
58
59
  if (!response.ok)
59
60
  fail(formatJson, response.code || 'CTL_FAILED', response.error || '命令执行失败');
60
61
  printCtlResult({ text: response.result || '', data: response.data }, formatJson);
@@ -1,5 +1,5 @@
1
1
  import { resolvePaths } from '../paths.js';
2
- import { ipcQuery } from '../ipc.js';
2
+ import { daemonUnavailableMessage, queryDaemon } from './help.js';
3
3
  import { readDelegationCredential } from '../core/auth/agent-delegation.js';
4
4
  import { getArgValue, isHelpFlag, wantsHelp } from './help.js';
5
5
  import { canonicalizeEvolcoreCommandArgv } from '../core/permission/ec-command-parser.js';
@@ -94,7 +94,7 @@ JSON 时间字段:
94
94
  : undefined;
95
95
  const socketPath = resolvePaths().socket;
96
96
  const managed = process.env.EVOLCORE_SESSION_ID;
97
- const result = await ipcQuery(socketPath, managed ? {
97
+ const { response: result, transportError } = await queryDaemon(socketPath, managed ? {
98
98
  type: 'queue.op',
99
99
  argv: ['queue', ...args],
100
100
  agent,
@@ -115,7 +115,7 @@ JSON 时间字段:
115
115
  before,
116
116
  });
117
117
  if (!result) {
118
- console.error('错误: 无法连接 evolcore 服务');
118
+ console.error(`错误: ${daemonUnavailableMessage(transportError, '无法连接 evolcore 服务')}`);
119
119
  process.exit(1);
120
120
  }
121
121
  const r = result;
@@ -15,7 +15,7 @@ import { readField, writeField, determineFieldScope, normalizePeer, ModelScopeEr
15
15
  import { readFieldWithSource, resolveAgentConfig, ConfigTarget } from '../config/config-manager.js';
16
16
  import { ResponseModeRegistry } from '../response-system/registry.js';
17
17
  import { registerBuiltinModes } from '../response-system/modes/index.js';
18
- import { ipcQuery } from '../ipc.js';
18
+ import { daemonUnavailableMessage, queryDaemon } from './help.js';
19
19
  import { resolvePaths } from '../paths.js';
20
20
  import { AGENT_DELEGATION_TOKEN_ENV, readDelegationCredential } from '../core/auth/agent-delegation.js';
21
21
  const RESPONSE_MODE_FIELD = 'responseMode';
@@ -64,11 +64,12 @@ async function runManagedResponse(args, formatJson) {
64
64
  fail(formatJson, 'NO_SESSION', 'EVOLCORE_SESSION_ID is not set');
65
65
  if (!delegationToken)
66
66
  fail(formatJson, 'DELEGATION_REQUIRED', `${AGENT_DELEGATION_TOKEN_ENV} is not set`);
67
- const response = await ipcQuery(resolvePaths().socket, {
67
+ const { response, transportError } = await queryDaemon(resolvePaths().socket, {
68
68
  type: 'ctl', argv: ['response', ...args], sessionId, delegationToken,
69
69
  }, 10_000);
70
- if (!response)
71
- fail(formatJson, 'DAEMON_UNAVAILABLE', 'Unable to connect to the evolcore daemon');
70
+ if (!response) {
71
+ fail(formatJson, 'DAEMON_UNAVAILABLE', daemonUnavailableMessage(transportError, 'Unable to connect to the evolcore daemon'));
72
+ }
72
73
  if (!response.ok)
73
74
  fail(formatJson, response.code || 'CTL_FAILED', response.error || 'response operation failed');
74
75
  emit(formatJson, response.data ?? { ok: true, result: response.result }, () => response.result || 'ok');
@@ -130,6 +130,18 @@ export async function cmdRestartMonitor() {
130
130
  const hasCorrelatedRequest = requestId !== 'unknown';
131
131
  const upgradeJobId = hasCorrelatedRequest ? readRestartRequest(pendingFile, requestId)?.upgradeJobId : undefined;
132
132
  const upgradeJob = typeof upgradeJobId === 'string' ? readUpgradeJob(upgradeJobId) : null;
133
+ let configuredAutoUpgrade = true;
134
+ if (!upgradeJob && !process.argv.includes('--apply-package-upgrades') && !process.argv.includes('--skip-package-upgrades')) {
135
+ try {
136
+ configuredAutoUpgrade = loadDaemonConfig().upgrade?.autoOnStart !== false;
137
+ }
138
+ catch {
139
+ configuredAutoUpgrade = false;
140
+ }
141
+ }
142
+ const applyPackageUpgrades = !!upgradeJob
143
+ || process.argv.includes('--apply-package-upgrades')
144
+ || (!process.argv.includes('--skip-package-upgrades') && configuredAutoUpgrade);
133
145
  if (upgradeJobId && (!upgradeJob || upgradeJob.restartRequestId !== requestId || upgradeJob.released || upgradeJob.deadline <= Date.now())) {
134
146
  if (upgradeJob)
135
147
  updateUpgradeJob(upgradeJob.jobId, { status: 'timeout', success: false, released: true, code: 'UPGRADE_TIMEOUT', reason: 'Update expired before restart preflight' }, '更新已超时,未执行重启');
@@ -371,16 +383,24 @@ export async function cmdRestartMonitor() {
371
383
  const stoppedCodex = stopCodexAppServerOrphans();
372
384
  if (stoppedCodex)
373
385
  log(`Cleaned ${stoppedCodex} EvolCore codex app-server orphan(s)`);
374
- // Apply only the preflight cache. There is no registry VERSION query after
375
- // shutdown (npm still downloads the exact cached package when needed).
376
- log('Applying cached version information...');
377
- // 升级会原地替换整包,先留一个可回退的产物副本。备份放 EVOLCORE_HOME 而非包目录:
378
- // Linux/macOS 的全局目录常常不可写,而 HOME 一定可写。
379
- const upgradeBackup = await backupDistForUpgrade(p, log);
380
- const [upgrade] = await applyCachedPackageUpgrades(undefined, {
381
- log,
382
- ...(upgradeJob ? { target: upgradeJob.target, distTag: upgradeJob.distTag, npmConfig: upgradeJob.npmConfig } : {}),
383
- });
386
+ let upgradeBackup = null;
387
+ let upgrade;
388
+ if (applyPackageUpgrades) {
389
+ // Apply only the preflight cache. There is no registry VERSION query after
390
+ // shutdown (npm still downloads the exact cached package when needed).
391
+ log('Applying cached version information...');
392
+ // 升级会原地替换整包,先留一个可回退的产物副本。备份放 EVOLCORE_HOME 而非包目录:
393
+ // Linux/macOS 的全局目录常常不可写,而 HOME 一定可写。
394
+ upgradeBackup = await backupDistForUpgrade(p, log);
395
+ [upgrade] = await applyCachedPackageUpgrades(undefined, {
396
+ log,
397
+ ...(upgradeJob ? { target: upgradeJob.target, distTag: upgradeJob.distTag, npmConfig: upgradeJob.npmConfig } : {}),
398
+ });
399
+ }
400
+ else {
401
+ log('Skipping cached package upgrade because upgrade.autoOnStart=false');
402
+ upgrade = { status: 'skipped', error: 'Automatic package upgrades disabled by autoOnStart' };
403
+ }
384
404
  let upgradeRolledBack;
385
405
  switch (upgrade.status) {
386
406
  case 'upgraded':
@@ -405,9 +425,11 @@ export async function cmdRestartMonitor() {
405
425
  log(`Already up to date (${upgrade.from})`);
406
426
  break;
407
427
  case 'skipped':
408
- log(upgrade.error
409
- ? 'Skipped upgrade (network unavailable)'
410
- : 'Skipped upgrade check (dev mode)');
428
+ log(upgrade.error === 'Automatic package upgrades disabled by autoOnStart'
429
+ ? 'Skipped package upgrade (autoOnStart=false)'
430
+ : upgrade.error
431
+ ? 'Skipped upgrade (network unavailable)'
432
+ : 'Skipped upgrade check (dev mode)');
411
433
  break;
412
434
  case 'failed':
413
435
  log(`⚠ Upgrade failed (${upgrade.from} → ${upgrade.to}): ${upgrade.error}`);