evolcore 0.0.33 → 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 (129) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/bin/ec.js +3 -2
  3. package/bin/install-codex-managed-hooks.mjs +12 -1
  4. package/dist/agents/baseagent-availability.js +20 -36
  5. package/dist/agents/claude-runner.js +82 -13
  6. package/dist/agents/codex/diagnostics.js +37 -11
  7. package/dist/agents/codex-runner.js +218 -44
  8. package/dist/agents/ecagent-runner.js +73 -20
  9. package/dist/agents/runner-types.js +5 -6
  10. package/dist/aun/aid/identity.js +1 -1
  11. package/dist/channels/aun.js +16 -6
  12. package/dist/channels/daemon.js +6 -3
  13. package/dist/cli/aun-commands.js +3 -2
  14. package/dist/cli/ctl-command.js +3 -1
  15. package/dist/cli/daemon-commands.js +120 -19
  16. package/dist/cli/fs-command.js +2 -1
  17. package/dist/cli/index.js +20 -6
  18. package/dist/cli/integrity-command.js +196 -0
  19. package/dist/cli/memory-command.js +158 -145
  20. package/dist/cli/thread-command.js +5 -3
  21. package/dist/cli/trigger-command.js +6 -1
  22. package/dist/config/config-field-policy.js +4 -0
  23. package/dist/config/config-manager.js +19 -0
  24. package/dist/config/contact-book-store.js +5 -0
  25. package/dist/config/role-service.js +13 -0
  26. package/dist/config/runtime-env.js +16 -0
  27. package/dist/config-store.js +30 -0
  28. package/dist/core/audit/log-integrity.js +3 -1
  29. package/dist/core/auth/agent-delegation.js +108 -35
  30. package/dist/core/auth/delegation-diagnostics.js +414 -0
  31. package/dist/core/auth/operation-authorizer.js +1 -1
  32. package/dist/core/auth/operation-catalog.js +16 -0
  33. package/dist/core/command/agent-control.js +18 -26
  34. package/dist/core/command/command-handler.js +138 -4
  35. package/dist/core/command/daemon-diagnostics.js +1 -0
  36. package/dist/core/command/menu-catalog.js +3 -0
  37. package/dist/core/command/menu-handler.js +22 -7
  38. package/dist/core/command/menu-protocol.js +1 -0
  39. package/dist/core/command/slash-handler.js +2 -0
  40. package/dist/core/daemon-bootstrap.js +4 -7
  41. package/dist/core/file-integrity/read.js +61 -0
  42. package/dist/core/file-integrity/store.js +262 -0
  43. package/dist/core/ipc-registration.js +36 -1
  44. package/dist/core/memory/batch-meta.js +132 -0
  45. package/dist/core/memory/consolidation-context.js +35 -14
  46. package/dist/core/memory/consolidation-ops-records.js +14 -16
  47. package/dist/core/memory/consolidation.js +91 -26
  48. package/dist/core/memory/index-manager.js +19 -28
  49. package/dist/core/memory/memory-admin.js +19 -14
  50. package/dist/core/memory/memory-document.js +53 -9
  51. package/dist/core/memory/memory-writer.js +30 -15
  52. package/dist/core/message/im-renderer.js +20 -4
  53. package/dist/core/message/message-bridge.js +2 -1
  54. package/dist/core/message/response-engine.js +584 -47
  55. package/dist/core/permission/ec-command-parser.js +93 -4
  56. package/dist/core/permission/protected-paths.js +5 -1
  57. package/dist/core/permission/readonly-shell-query.js +43 -5
  58. package/dist/core/permission/session-path-grants.js +334 -0
  59. package/dist/core/permission/tool-canonical.js +226 -0
  60. package/dist/core/permission/tool-policy.js +30 -62
  61. package/dist/core/role/runtime-policy.js +21 -10
  62. package/dist/core/session/session-fs-store.js +123 -8
  63. package/dist/core/session/session-manager.js +75 -5
  64. package/dist/core/skill/index-generator.js +17 -3
  65. package/dist/core/system-session/grants.js +40 -0
  66. package/dist/core/system-session/memory.js +222 -0
  67. package/dist/core/system-session/registry.js +74 -0
  68. package/dist/core/system-session/types.js +14 -0
  69. package/dist/core/thread/thread-service.js +13 -20
  70. package/dist/eck/kit-renderer.js +7 -2
  71. package/dist/eck/manifest-engine.js +24 -4
  72. package/dist/index.js +11 -2
  73. package/dist/ipc.js +51 -40
  74. package/dist/paths.js +70 -0
  75. package/dist/stats/billing.js +20 -8
  76. package/dist/trigger/feedback.js +6 -1
  77. package/dist/trigger/parser.js +8 -1
  78. package/dist/trigger/system-definitions.js +3 -3
  79. package/dist/utils/cross-platform.js +11 -2
  80. package/dist/utils/error-dict.json +18 -4
  81. package/dist/utils/error-utils.js +121 -0
  82. package/dist/utils/instance-registry.js +13 -8
  83. package/dist/utils/log-writer.js +12 -0
  84. package/dist/utils/process-introspect.js +11 -0
  85. package/kits/docs/context-assembly.md +0 -1
  86. package/kits/docs/evolcore/agent-create.md +5 -4
  87. package/kits/docs/evolcore/agent.md +2 -2
  88. package/kits/docs/evolcore/config.md +2 -2
  89. package/kits/docs/evolcore/ctl.md +2 -2
  90. package/kits/docs/evolcore/ec-capabilities.md +1 -1
  91. package/kits/docs/evolcore/fs-architecture.md +4 -5
  92. package/kits/docs/evolcore/fs.md +1 -1
  93. package/kits/docs/evolcore/group-collaboration.md +2 -2
  94. package/kits/docs/evolcore/group.md +10 -10
  95. package/kits/docs/evolcore/memory-architecture.md +4 -5
  96. package/kits/docs/evolcore/memory-consolidation.md +18 -14
  97. package/kits/docs/evolcore/memory-tags.md +3 -1
  98. package/kits/docs/evolcore/memory.md +24 -12
  99. package/kits/docs/evolcore/model.md +16 -0
  100. package/kits/docs/evolcore/msg.md +7 -7
  101. package/kits/docs/evolcore/thread-chain.md +5 -5
  102. package/kits/docs/prompt-loading-architecture.md +0 -1
  103. package/kits/eck_manifest.json +0 -20
  104. package/kits/eck_manifest.memory.json +0 -12
  105. package/kits/rules/01-overview.md +11 -0
  106. package/kits/rules/03-identity.md +6 -3
  107. package/kits/rules/06-channel.md +1 -1
  108. package/kits/rules/07-memory-and-thread.md +12 -15
  109. package/kits/schemas/_meta.json +7 -1
  110. package/kits/schemas/agent-config.schema.13.json +1 -1
  111. package/kits/schemas/daemon.schema.7.json +140 -0
  112. package/kits/skills/ec-group/SKILL.md +4 -4
  113. package/kits/skills/ec-skill/scripts/cli.mjs +1 -1
  114. package/kits/skills/ec-skill/scripts/skill.mjs +1 -1
  115. package/kits/templates/memory-consolidation/forget.md +2 -2
  116. package/kits/templates/memory-consolidation/generalize.md +4 -4
  117. package/kits/templates/memory-consolidation/notes.md +11 -11
  118. package/kits/templates/memory-consolidation/profile.md +3 -3
  119. package/kits/templates/memory-consolidation/promote.md +3 -3
  120. package/kits/templates/memory-consolidation/workflow.md +6 -6
  121. package/kits/templates/message-fragments/handoff-request-to-target.md +1 -1
  122. package/kits/templates/system-fragments/bootstrap.md +4 -3
  123. package/kits/templates/system-fragments/channel.md +2 -2
  124. package/kits/templates/system-fragments/memory.md +16 -9
  125. package/kits/templates/system-fragments/reply.md +6 -6
  126. package/kits/templates/system-fragments/session.md +1 -1
  127. package/package.json +4 -3
  128. package/dist/core/memory/memory-session-policy.js +0 -41
  129. package/dist/core/memory/memory-signature.js +0 -257
@@ -3,9 +3,10 @@
3
3
  // when 求值、模板渲染、路径解析、manifest 加载/缓存原语,避免两套实现漂移。
4
4
  import fs from 'fs';
5
5
  import path from 'path';
6
- import { kitsDir, resolveRoot, getPackageRoot } from '../paths.js';
6
+ import { kitsDir, resolveRoot, getPackageRoot, isDevMode } from '../paths.js';
7
7
  import { logger } from '../utils/logger.js';
8
8
  import { fileCache } from '../core/daemon-file-cache.js';
9
+ import { readVerifiedText, isShippedPath } from '../core/file-integrity/read.js';
9
10
  // ── 限额默认值 ──
10
11
  /** 单目录段默认最多文件数 */
11
12
  export const DIR_MAX_FILES = 20;
@@ -365,12 +366,31 @@ function sectionCacheOptions(resolvedPath, vars) {
365
366
  }
366
367
  return { policy: 'mtime', group: 'eck-runtime' };
367
368
  }
369
+ /**
370
+ * 读取一个 kits 文档,安装模式下先过台账校验。
371
+ *
372
+ * 为什么校验包在 loader 里:这些内容走 FileCache 的 `on-reload` 策略,缓存命中时
373
+ * 不会碰磁盘,校验只有包在这里才与"实际注入的那份内容"对齐;被篡改且恢复不了时
374
+ * 返回 null,条目本身也不会写进缓存。开发模式下 kits 由 agent 直接改写,不校验。
375
+ */
376
+ function loadKitsDoc(resolved, vars) {
377
+ const opts = sectionCacheOptions(resolved, vars);
378
+ if (!isShippedPath(resolved)) {
379
+ return fileCache.getText(resolved, opts);
380
+ }
381
+ const verify = !isDevMode();
382
+ return fileCache.get(resolved, () => {
383
+ const checked = readVerifiedText(resolved, { verify, tag: 'eck' });
384
+ // 校验不过就不进缓存:返回 null → fileCache 存 null,该段被丢弃
385
+ return checked ? checked.text : null;
386
+ }, opts);
387
+ }
368
388
  /** 加载 loop 的 child 子模板文件内容(路径解析 + fileCache)。找不到返回 null。 */
369
389
  export function loadChildTemplate(childFile, vars) {
370
390
  const resolved = resolvePath(childFile, vars);
371
391
  if (!resolved)
372
392
  return null;
373
- return fileCache.getText(resolved, sectionCacheOptions(resolved, vars));
393
+ return loadKitsDoc(resolved, vars);
374
394
  }
375
395
  /**
376
396
  * 返回 [filePath, rawContent][]。kits 文件跨 session 缓存到 reload;运行时文件
@@ -401,7 +421,7 @@ function loadFileSection(filePath, vars, sessionCache) {
401
421
  const resolved = resolvePath(filePath, vars);
402
422
  if (!resolved)
403
423
  return null;
404
- const content = fileCache.getText(resolved, sectionCacheOptions(resolved, vars));
424
+ const content = loadKitsDoc(resolved, vars);
405
425
  return content === null ? null : [resolved, content];
406
426
  }
407
427
  /**
@@ -435,7 +455,7 @@ function readDirectoryFiles(dirPath, pattern, maxFiles, maxBytes, vars) {
435
455
  break;
436
456
  }
437
457
  const fp = path.join(dirPath, f);
438
- const content = fileCache.getText(fp, sectionCacheOptions(fp, vars));
458
+ const content = loadKitsDoc(fp, vars);
439
459
  if (content === null)
440
460
  continue;
441
461
  // 字节限额(至少加载一个文件,避免单个超大文件导致空目录)
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  // 【重要】最先加载环境变量,确保后续模块初始化时可用
2
2
  import dotenv from 'dotenv';
3
3
  import path from 'path';
4
- import { ensureDataDirs, resolvePaths, eckDebugDir, daemonControlDir, daemonQueuePath, daemonMessageDedupPath } from './paths.js';
4
+ import { ensureDataDirs, resolvePaths, eckDebugDir, daemonControlDir, daemonQueuePath, daemonMessageDedupPath, isDevMode } from './paths.js';
5
5
  // 立即加载 .env 文件(在其他模块导入之前)
6
6
  try {
7
7
  dotenv.config({ path: path.join(resolvePaths().root, '.env') });
@@ -57,6 +57,7 @@ import { StatsCollector } from './utils/stats.js';
57
57
  import { AidStatsCollector } from './utils/stats.js';
58
58
  import { initializeStatsPersistence } from './stats/writer.js';
59
59
  import { PermissionGateway } from './core/permission/approval-gateway.js';
60
+ import { releaseSessionPathGrants } from './core/permission/session-path-grants.js';
60
61
  import { InteractionRouter } from './core/interaction-router.js';
61
62
  import { GroupInviteReviewService } from './aun/group/invite-service.js';
62
63
  import { AgentDelegationRegistry, } from './core/auth/agent-delegation.js';
@@ -138,8 +139,10 @@ async function main() {
138
139
  }
139
140
  // 立即登记自己(让其他并发启动者能看见我)
140
141
  const launchedBy = process.env.EVOLCORE_LAUNCHED_BY || 'start';
142
+ // 本次运行模式在这里定下(首次求值时会读走并删除 `ec start --as-mode` 的覆盖文件)
143
+ const devMode = isDevMode();
141
144
  writeMain(launchedBy);
142
- logger.info(`✓ Instance record written: main-${process.pid}.json`);
145
+ logger.info(`✓ Instance record written: main-${process.pid}.json runMode=${devMode ? 'dev' : 'install'}`);
143
146
  // post-write 自检:写完 record 后再扫一次,发现并发对手时按 (startedAt, pid) 选赢家
144
147
  {
145
148
  const verdict = isMainWinner();
@@ -240,6 +243,12 @@ async function main() {
240
243
  if (event.nameChanged)
241
244
  agentRegistry?.invalidateAgentDisplayCache?.(event.aid);
242
245
  });
246
+ // 运行时路径授权以会话为归属:会话被删除后不再有身份去消费它,随之释放。
247
+ // 只挂 session:deleted——session:idle 只表示空闲,会话仍会继续服务。
248
+ eventBus.subscribe('session:deleted', (event) => {
249
+ if ('sessionId' in event && event.sessionId)
250
+ releaseSessionPathGrants(event.sessionId);
251
+ });
243
252
  // 统计收集器(近 1 小时滚动统计)
244
253
  const statsCollector = new StatsCollector(eventBus);
245
254
  // Per-AID 消息统计收集器(累计,供 watch aid 实时展示)
package/dist/ipc.js CHANGED
@@ -23,6 +23,7 @@ import { remoteAccessService } from './core/auth/remote-access-store.js';
23
23
  import { setModelCatalogDaemonOwner } from './core/model/model-catalog.js';
24
24
  import { RUNTIME_ENVIRONMENT_NAMES, setRuntimeEnvironmentOverride } from './config/runtime-env.js';
25
25
  import { MAIN_CLI_BIN } from './product.js';
26
+ import { delegationDiagnosticMessage, withDelegationDiagnosticContext, } from './core/auth/delegation-diagnostics.js';
26
27
  const isWindows = process.platform === 'win32';
27
28
  const isNamedPipe = (p) => isWindows && p.toLowerCase().startsWith('\\\\.\\pipe\\');
28
29
  const CPU_SAMPLE_INTERVAL_MS = 1000;
@@ -79,7 +80,7 @@ const LOCAL_CONTROL_COMMANDS = new Set([
79
80
  ]);
80
81
  const MEMORY_COMMAND_ENDPOINTS = new Set([
81
82
  'memory.note', 'memory.note.update', 'memory.note.drop', 'memory.note.list',
82
- 'memory.create', 'memory.update', 'memory.archive', 'memory.demote', 'memory.consolidate', 'memory.feedback',
83
+ 'memory.create', 'memory.update', 'memory.archive', 'memory.demote', 'memory.consolidate',
83
84
  ]);
84
85
  const DELEGATED_ARGV_ENDPOINTS = new Set([
85
86
  'agent.op', 'config.op', 'contact.op', 'aid.op', 'group.op', 'msg.op', 'fs.op',
@@ -194,6 +195,7 @@ export class IpcServer {
194
195
  debugTaskRuntimeContextProvider;
195
196
  debugIssueTokenExecutor;
196
197
  taskRuntimeContextAuthorizer;
198
+ systemSessionCommandGate;
197
199
  codexPreToolUseExecutor;
198
200
  aunMsgSender;
199
201
  diagExecutor;
@@ -218,6 +220,7 @@ export class IpcServer {
218
220
  wechatContactBindExecutor;
219
221
  controlToken;
220
222
  daemonRuntimeEpoch;
223
+ delegationFailureRecorder;
221
224
  remoteAccessService = remoteAccessService;
222
225
  /** Current daemon owner used to bind locally issued ECWeb pairing codes. */
223
226
  remoteAccessOwnerProvider;
@@ -288,6 +291,9 @@ export class IpcServer {
288
291
  setDaemonRuntimeEpoch(epoch) {
289
292
  this.daemonRuntimeEpoch = epoch;
290
293
  }
294
+ setDelegationFailureRecorder(recorder) {
295
+ this.delegationFailureRecorder = recorder;
296
+ }
291
297
  /** Inject menu.* executor (ECWeb Control proxies menu requests through this) */
292
298
  setMenuExecutor(executor) {
293
299
  this.menuExecutor = executor;
@@ -492,6 +498,9 @@ export class IpcServer {
492
498
  setTaskRuntimeContextAuthorizer(authorizer) {
493
499
  this.taskRuntimeContextAuthorizer = authorizer;
494
500
  }
501
+ setSystemSessionCommandGate(gate) {
502
+ this.systemSessionCommandGate = gate;
503
+ }
495
504
  setCodexPreToolUseExecutor(executor) {
496
505
  this.codexPreToolUseExecutor = executor;
497
506
  }
@@ -951,6 +960,27 @@ export class IpcServer {
951
960
  }
952
961
  }
953
962
  async handleCommand(cmd, requestSignal, receivedAtMono = performance.now()) {
963
+ return withDelegationDiagnosticContext({
964
+ sessionId: typeof cmd.sessionId === 'string' ? cmd.sessionId : undefined,
965
+ requestId: nextIpcQueryId(cmd),
966
+ correlationId: typeof cmd.correlationId === 'string' ? ipcLogToken(cmd.correlationId) : undefined,
967
+ operation: ipcLogToken(cmd.type),
968
+ }, () => this.dispatchCommand(cmd, requestSignal, receivedAtMono));
969
+ }
970
+ diagnosticRejection(cause, cmd) {
971
+ const failure = this.delegationFailureRecorder?.(cause, typeof (cmd.sessionId ?? cmd.originSessionId ?? cmd.actorSessionId) === 'string'
972
+ ? cmd.sessionId ?? cmd.originSessionId ?? cmd.actorSessionId : undefined, typeof cmd.delegationToken === 'string' ? cmd.delegationToken : undefined, typeof cmd.delegationCommandHash === 'string' ? cmd.delegationCommandHash : undefined);
973
+ const diagnosticCause = failure?.diagnosticCause ?? cause;
974
+ return {
975
+ ok: false,
976
+ code: diagnosticCause === 'missing_token' ? 'DELEGATION_REQUIRED'
977
+ : diagnosticCause === 'daemon_restarted' ? 'DAEMON_RESTARTED' : 'INVALID_DELEGATION',
978
+ error: failure?.reason ?? delegationDiagnosticMessage(diagnosticCause),
979
+ diagnosticCause,
980
+ ...(failure?.diagnosticEventId ? { diagnosticEventId: failure.diagnosticEventId } : {}),
981
+ };
982
+ }
983
+ async dispatchCommand(cmd, requestSignal, receivedAtMono = performance.now()) {
954
984
  if (requiresLocalControlToken(cmd) && !this.hasValidControlToken(cmd.controlToken)) {
955
985
  return { ok: false, code: 'INVALID_CONTROL_TOKEN', error: 'valid local control token is required' };
956
986
  }
@@ -959,11 +989,7 @@ export class IpcServer {
959
989
  && cmd.daemonRuntimeEpoch !== undefined
960
990
  && cmd.daemonRuntimeEpoch !== this.daemonRuntimeEpoch) {
961
991
  logger.warn(`[IPC] stale delegation rejected: session=${ipcLogToken(cmd.sessionId ?? cmd.originSessionId ?? cmd.actorSessionId)} type=${ipcLogToken(cmd.type)} cause=daemon_runtime_epoch_mismatch`);
962
- return {
963
- ok: false,
964
- code: 'DAEMON_RESTARTED',
965
- error: 'The task delegation token belongs to a previous daemon runtime; start a new task authorization and do not retry this command in the current task',
966
- };
992
+ return this.diagnosticRejection('daemon_restarted', cmd);
967
993
  }
968
994
  if (typeof cmd.delegationToken === 'string'
969
995
  && (DELEGATED_ARGV_ENDPOINTS.has(cmd.type) || (cmd.type === 'aun-msg-send' && cmd.argv !== undefined))) {
@@ -976,9 +1002,26 @@ export class IpcServer {
976
1002
  ...cmd.argv,
977
1003
  ]);
978
1004
  if (!expectedHash || cmd.delegationCommandHash !== expectedHash) {
979
- return { ok: false, code: 'INVALID_DELEGATION', error: 'Command hash does not match the submitted argv' };
1005
+ return this.diagnosticRejection('command_hash_mismatch', cmd);
980
1006
  }
981
1007
  }
1008
+ // 系统会话统一闸门:带系统会话 delegation 的请求只看模块 checkCommand,拒绝即返回
1009
+ if (this.systemSessionCommandGate
1010
+ && typeof cmd.delegationToken === 'string' && cmd.delegationToken
1011
+ && typeof cmd.sessionId === 'string' && cmd.sessionId) {
1012
+ const argv = Array.isArray(cmd.argv) && cmd.argv.every((value) => typeof value === 'string')
1013
+ ? cmd.argv
1014
+ : undefined;
1015
+ const gate = await this.systemSessionCommandGate({
1016
+ sessionId: cmd.sessionId,
1017
+ delegationToken: cmd.delegationToken,
1018
+ ...(typeof cmd.delegationCommandHash === 'string' ? { delegationCommandHash: cmd.delegationCommandHash } : {}),
1019
+ type: cmd.type,
1020
+ ...(argv ? { argv } : {}),
1021
+ });
1022
+ if (gate && !gate.allow)
1023
+ return { ok: false, code: 'NOT_ALLOWED', policyCode: gate.code, error: gate.reason };
1024
+ }
982
1025
  if (cmd.argv !== undefined && ['handoff-status', 'handoff-list', 'handoff-trace'].includes(cmd.type)) {
983
1026
  try {
984
1027
  const { parseList, parseTrace } = await import('./cli/handoff-command.js');
@@ -1911,37 +1954,6 @@ export class IpcServer {
1911
1954
  return { ok: false, error: e?.message || String(e) };
1912
1955
  }
1913
1956
  }
1914
- case 'memory.feedback': {
1915
- const { sessionId, text } = cmd;
1916
- if (!sessionId)
1917
- return { ok: false, code: 'INVALID_REQUEST', error: 'missing sessionId' };
1918
- if (typeof text !== 'string' || !text.trim()) {
1919
- return { ok: false, code: 'INVALID_REQUEST', error: 'missing feedback text' };
1920
- }
1921
- try {
1922
- const taskCtx = await this.resolveMemoryTaskCtx(sessionId, cmd.debug === true);
1923
- if (!taskCtx)
1924
- return { ok: false, code: 'SESSION_NOT_FOUND', error: `未找到会话 ${sessionId} 的运行时上下文` };
1925
- // 只有整理会话可以记录反馈
1926
- if (taskCtx.sessionType !== 'memory') {
1927
- return { ok: false, code: 'NOT_MEMORY_SESSION', error: '`ec memory feedback` 只能在整理会话内调用' };
1928
- }
1929
- const batchDir = taskCtx.consolidationBatchDir;
1930
- if (!batchDir) {
1931
- return { ok: false, code: 'BATCH_DIR_NOT_FOUND', error: '整理会话缺少 consolidationBatchDir' };
1932
- }
1933
- const { appendOpsRecord } = await import('./core/memory/consolidation-ops-records.js');
1934
- appendOpsRecord(batchDir, {
1935
- ts: new Date().toISOString(),
1936
- op: 'feedback-set',
1937
- text,
1938
- });
1939
- return { ok: true };
1940
- }
1941
- catch (e) {
1942
- return { ok: false, error: e?.message || String(e) };
1943
- }
1944
- }
1945
1957
  case 'config.op': {
1946
1958
  if (!this.configOperationExecutor)
1947
1959
  return { ok: false, code: 'NOT_CONFIGURED', error: 'config.op not configured' };
@@ -2422,8 +2434,7 @@ export class IpcServer {
2422
2434
  const { rebuildIndex } = await import('./core/memory/index-manager.js');
2423
2435
  const mc = this.agentRegistry?.get(taskCtx.selfAid ?? '')?.config?.memory;
2424
2436
  const maxBytes = mc?.indexMaxBytes ?? 5 * 1024;
2425
- const buildResult = await rebuildIndex(dirs.consolidatedDir, path.join(dirs.memoryDir, 'MEMORY.md'), scope, path.join(dirs.memoryDir, 'archive', 'invalid'), undefined, // 验签门控尚未开启,不传 verifyFn
2426
- maxBytes);
2437
+ const buildResult = await rebuildIndex(dirs.consolidatedDir, path.join(dirs.memoryDir, 'MEMORY.md'), scope, maxBytes);
2427
2438
  // 索引重建后顺手检查这一层有没有超期记忆 —— 有则建触发器拉一个独立的遗忘会话。
2428
2439
  await this.maybeTriggerForget(taskCtx, scope, dirs.consolidatedDir, mc, buildResult.verifiedFiles);
2429
2440
  }
package/dist/paths.js CHANGED
@@ -442,3 +442,73 @@ export function getPackageRoot() {
442
442
  // the correct OS-native path, regardless of Git Bash or MSYS2 environment.
443
443
  return path.resolve(import.meta.dirname, '..');
444
444
  }
445
+ // ── FileIntegrityStore 路径 ──
446
+ export function integrityDir() {
447
+ return path.join(resolveRoot(), 'data', 'integrity');
448
+ }
449
+ export function integrityDbPath() {
450
+ return path.join(integrityDir(), 'file-integrity.db');
451
+ }
452
+ export function integritySnapshotDir() {
453
+ return path.join(integrityDir(), 'snapshots');
454
+ }
455
+ /**
456
+ * 判断当前是否为开发仓运行模式。
457
+ * 与 ec start 显示的安装类型保持口径一致:包路径包含 node_modules 则为安装模式,否则为开发仓。
458
+ * 调用方据此决定是否跳过 FileIntegrityStore 的 verify 调用。
459
+ *
460
+ * 唯一覆盖来源是 `ec start --as-mode` 写下的一次性文件(见 asModeOverridePath),
461
+ * 由 daemon 启动时读取并立即删除。**不读环境变量**——环境变量会被 restart 之类的
462
+ * 路径静默继承,导致模式在操作者不知情的情况下改变。
463
+ */
464
+ export function isDevMode() {
465
+ if (!_asModeResolved) {
466
+ _asModeResolved = true;
467
+ _asModeOverride = consumeAsModeOverride();
468
+ }
469
+ if (_asModeOverride)
470
+ return _asModeOverride === 'dev';
471
+ return !getPackageRoot().includes('node_modules');
472
+ }
473
+ /**
474
+ * `ec start --as-mode <mode>` 的一次性覆盖文件。
475
+ *
476
+ * 走文件而不是环境变量:环境变量会被 `ec restart`(全量继承 process.env)
477
+ * 静默带进新 daemon,操作者不重启终端就撤不掉。文件由 CLI 在启动前写入,
478
+ * daemon 首次调用 isDevMode() 时读走并删除,只影响这一次启动。
479
+ */
480
+ export function asModeOverridePath() {
481
+ return path.join(resolveRoot(), 'data', 'daemon', 'as-mode.override');
482
+ }
483
+ /**
484
+ * 本次进程的覆盖值。**必须缓存**:文件读一次就删,若每次都去读,
485
+ * 第二次调用起就会悄悄退回按包路径判断——模式在进程运行中途改变是最坏的情况。
486
+ */
487
+ let _asModeResolved = false;
488
+ let _asModeOverride = null;
489
+ /** 仅供测试:清掉缓存,让下一次 isDevMode() 重新读覆盖文件。 */
490
+ export function _resetAsModeCache() {
491
+ _asModeResolved = false;
492
+ _asModeOverride = null;
493
+ }
494
+ /**
495
+ * 读出并删除覆盖文件(一次性)。返回 null 表示没有覆盖。
496
+ * 只应由 daemon 启动流程(或 isDevMode 的首次求值)调用一次。
497
+ */
498
+ export function consumeAsModeOverride() {
499
+ const file = asModeOverridePath();
500
+ if (!fs.existsSync(file))
501
+ return null;
502
+ let value = null;
503
+ try {
504
+ value = fs.readFileSync(file, 'utf-8').trim();
505
+ }
506
+ catch {
507
+ value = null;
508
+ }
509
+ try {
510
+ fs.rmSync(file, { force: true });
511
+ }
512
+ catch { /* ignore */ }
513
+ return value === 'dev' || value === 'install' ? value : null;
514
+ }
@@ -180,24 +180,36 @@ function loadSpecs(evolcoreHome) {
180
180
  _specCacheTs = now;
181
181
  return _specCache;
182
182
  }
183
- /** 取模型在 ts 时刻的能力参数。找不到时返回默认值。
184
- * 带窗口后缀的 ID 复用基础模型的参数行,但窗口由后缀决定(网关的权威声明)。 */
185
- export function resolveModelSpec(evolcoreHome, model, ts) {
183
+ export function findModelSpec(evolcoreHome, model, ts = Date.now()) {
186
184
  const specs = loadSpecs(evolcoreHome);
187
- const t = ts ?? Date.now();
188
185
  const suffixWindow = parseModelId(model).contextWindow;
189
186
  for (const key of modelLookupKeys(evolcoreHome, model)) {
190
- const candidates = specs.filter(s => s.model === key && s.effective_from <= t);
187
+ const candidates = specs.filter(spec => spec.model === key && spec.effective_from <= ts);
191
188
  if (!candidates.length)
192
189
  continue;
193
- const row = candidates.reduce((a, b) => a.effective_from >= b.effective_from ? a : b);
190
+ const row = candidates.reduce((a, b) => a.effective_from > b.effective_from ? a : b);
194
191
  if (suffixWindow === undefined || suffixWindow === row.context_window)
195
192
  return row;
196
193
  // 后缀声明的窗口覆盖基础行,输入上限按原行的预留比例等比缩放。
197
194
  const ratio = row.context_window > 0 ? row.max_input_tokens / row.context_window : 0.9;
198
195
  return { ...row, model, context_window: suffixWindow, max_input_tokens: Math.floor(suffixWindow * ratio) };
199
196
  }
197
+ return undefined;
198
+ }
199
+ /** 取模型在 ts 时刻的能力参数。找不到时返回 fallback 或默认值。
200
+ * 带窗口后缀的 ID 复用基础模型的参数行,但窗口由后缀决定(网关的权威声明)。 */
201
+ export function resolveModelSpec(evolcoreHome, model, ts, fallback) {
202
+ const spec = findModelSpec(evolcoreHome, model, ts);
203
+ if (spec)
204
+ return spec;
205
+ const suffixWindow = parseModelId(model).contextWindow;
200
206
  // 默认值
201
- const contextWindow = suffixWindow ?? 200000;
202
- return { model, effective_from: 0, context_window: contextWindow, max_input_tokens: Math.floor(contextWindow * 0.9), max_output_tokens: 8192 };
207
+ const contextWindow = suffixWindow ?? fallback?.context_window ?? 200000;
208
+ return {
209
+ model,
210
+ effective_from: 0,
211
+ context_window: contextWindow,
212
+ max_input_tokens: Math.floor(contextWindow * 0.9),
213
+ max_output_tokens: fallback?.max_output_tokens ?? 8192,
214
+ };
203
215
  }
@@ -303,7 +303,12 @@ export class TriggerFeedbackDispatcher {
303
303
  });
304
304
  await binding.adapter.send(envelope, { kind: 'status.error',
305
305
  metadata: { errorType: 'orphaned_target_session', message: text } });
306
- await binding.adapter.send(envelope, { kind: 'result.error', reason: 'orphaned_target_session', text });
306
+ await binding.adapter.send(envelope, {
307
+ kind: 'result.error',
308
+ subtype: 'session_unavailable',
309
+ reason: 'orphaned_target_session',
310
+ text,
311
+ });
307
312
  };
308
313
  const inspect = async () => {
309
314
  if (!this.deps.inspectTargetExecution || disposed || delivery.signal.aborted)
@@ -11,7 +11,7 @@ const TRIGGER_UPDATE_FLAGS = new Set([
11
11
  ]);
12
12
  const TRIGGER_CREATE_FLAGS = new Set([
13
13
  'once', 'delay', 'at', 'cron', 'every', 'event', 'event-filter', 'tz',
14
- 'exec', 'prompt', 'name', 'agent', 'label',
14
+ 'exec', 'prompt', 'name', 'description', 'agent', 'label',
15
15
  'script-path', 'script-runtime', 'script-args', 'script-timeout', 'execution-timeout',
16
16
  'feedback', 'target-channel', 'target-channel-id', 'target-chat-type', 'target-session', 'target-thread-id',
17
17
  'trigger-thread', 'baseagent',
@@ -444,6 +444,9 @@ function commonParsed(flags, opts = {}) {
444
444
  return { ok: false, error: '--script-args 必须是合法的 JSON' };
445
445
  }
446
446
  }
447
+ const description = optionalFlagString(flags, 'description');
448
+ if ('error' in description)
449
+ return { ok: false, error: description.error };
447
450
  const prompt = flags.get('prompt');
448
451
  if (prompt === true)
449
452
  return { ok: false, error: '--prompt 不能为空;复杂内容建议改用 --prompt-file <path>' };
@@ -525,6 +528,8 @@ function commonParsed(flags, opts = {}) {
525
528
  targetThreadId: targetThreadId,
526
529
  agentId: flags.has('agent') ? flags.get('agent') : undefined,
527
530
  name: flags.has('name') ? flags.get('name') : undefined,
531
+ // create 语义:空字符串等同于「未提供」,不写该字段(与 update 的清除语义不同)。
532
+ description: description.value || undefined,
528
533
  prompt: prompt,
529
534
  scriptPath: scriptPath,
530
535
  scriptRuntime: scriptRuntime,
@@ -569,6 +574,7 @@ export function parseTriggerSet(args) {
569
574
  targetThreadId: value.targetThreadId,
570
575
  agentId: value.agentId,
571
576
  name: value.name,
577
+ description: value.description,
572
578
  prompt: value.prompt,
573
579
  scriptPath: value.scriptPath,
574
580
  scriptRuntime: value.scriptRuntime,
@@ -615,6 +621,7 @@ export function parseTriggerSetArgv(args, opts = {}) {
615
621
  targetThreadId: value.targetThreadId,
616
622
  agentId: value.agentId,
617
623
  name: value.name,
624
+ description: value.description,
618
625
  prompt: value.prompt,
619
626
  scriptPath: value.scriptPath,
620
627
  scriptRuntime: value.scriptRuntime,
@@ -121,7 +121,7 @@ export function seedTaskRecoveryTrigger(manager, owner) {
121
121
  fs.rmSync(manager.activePath(definition.id), { force: true });
122
122
  }
123
123
  const SESSION_IDLE_CHECK_PROMPT = `本会话已闲置一段时间。请依据你的职责执行一次例行巡检:
124
- 1. 判断群内是否存在成员有未完成的工作却停滞(如长时间无进展、被点名未回应、承诺后失联)。有,则向该成员催促一次并说明停滞事项,随后即止;无,则不做任何动作。大家都在正常工作或无待办时保持沉默。
124
+ 1. 检查当前会话中未完成却停滞的事项。私聊检查自己承诺或正在推进的任务;群聊还需检查成员长时间无进展、被点名未回应、承诺后失联等情况。有,则在既有授权和职责内推进一次,或向相关成员催促一次并说明停滞事项,随后即止;无,则不做任何动作。不要自行新增任务,正常推进或无待办时保持沉默。
125
125
  2. 巡检优先基于已有上下文判断,确需补充背景再查询历史,并遵守会话自身的发送规范。
126
126
  3. 判定无需任何对外动作时,本轮只输出 [[NOOP]],不发送任何消息、不发布巡检结论。注意:最终对外输出必须是且仅是 [[NOOP]] 这一行——不要在输出它之前先发送任何说明文字或巡检判定,判定过程仅在内部完成。
127
127
  (本指令为通用模板,可按具体职责增删检查项。)`;
@@ -133,7 +133,7 @@ export function seedSessionIdleCheckTrigger(manager, owner) {
133
133
  agentAid: owner.aid,
134
134
  enabled: true,
135
135
  name: '__session-idle-check',
136
- description: 'System trigger: inspect configured idle group sessions.',
136
+ description: 'System trigger: inspect configured idle private and group sessions.',
137
137
  createdAt: now,
138
138
  updatedAt: now,
139
139
  origin: {
@@ -147,7 +147,7 @@ export function seedSessionIdleCheckTrigger(manager, owner) {
147
147
  source: {
148
148
  type: 'event',
149
149
  eventPattern: 'session:idle',
150
- filter: { match: { chatType: 'group' } },
150
+ filter: { match: { chatType: { $in: ['private', 'group'] } } },
151
151
  },
152
152
  execution: {
153
153
  type: 'target_session',
@@ -666,6 +666,13 @@ export async function waitForProcessExit(pid, timeoutMs = 10_000, intervalMs = 2
666
666
  * Returns list of matching PIDs.
667
667
  */
668
668
  export function findProcesses(pattern) {
669
+ return findProcessesWithCommandLine(pattern).map(match => match.pid);
670
+ }
671
+ /**
672
+ * Like findProcesses, but keeps the command line returned by the Windows CIM
673
+ * snapshot so callers do not need a second PowerShell launch per candidate.
674
+ */
675
+ export function findProcessesWithCommandLine(pattern) {
669
676
  try {
670
677
  if (isWindows) {
671
678
  // WMI LIKE is wildcard matching, not JavaScript regex matching. Query a
@@ -696,13 +703,15 @@ export function findProcesses(pattern) {
696
703
  const normalized = commandLine.replace(/\\/g, '/');
697
704
  return matcherCandidates.some(matcher => matcher.test(commandLine) || matcher.test(normalized));
698
705
  })
699
- .map(row => row.ProcessId);
706
+ .map(row => ({ pid: row.ProcessId, commandLine: row.CommandLine || '' }));
700
707
  }
701
708
  return [];
702
709
  }
703
710
  else {
704
711
  const output = execFileSync('pgrep', ['-f', pattern], { encoding: 'utf-8' }).trim();
705
- return output ? output.split('\n').map(Number).filter(pid => pid !== process.pid) : [];
712
+ return output
713
+ ? output.split('\n').map(Number).filter(pid => pid !== process.pid).map(pid => ({ pid }))
714
+ : [];
706
715
  }
707
716
  }
708
717
  catch {
@@ -85,11 +85,25 @@
85
85
  "message": "❌ 当前模型不受支持,请正确配置模型后重试"
86
86
  },
87
87
  {
88
- "id": "api-error-403",
89
- "match": "api error: 403",
90
- "action": "retry",
88
+ "id": "claude-client-upgrade-403-cn",
89
+ "match": "api error: 403 请升级claude code 客户端到最新版",
90
+ "action": "stop",
91
+ "type": "api_error",
92
+ "message": "⚠️ Claude Code 客户端版本过旧,请升级到最新版后重试"
93
+ },
94
+ {
95
+ "id": "insufficient-credit-403-cn",
96
+ "match": "api error: 403 预扣后为-",
97
+ "action": "stop",
91
98
  "type": "api_error",
92
- "message": "⚠️ API 访问受限,请稍后重试"
99
+ "message": "⚠️ 积分余额不足,请充值积分或升级套餐后重试"
100
+ },
101
+ {
102
+ "id": "team-context-limit-403-cn",
103
+ "match": "api error: 403 请求上下文约",
104
+ "action": "stop",
105
+ "type": "context_too_long",
106
+ "message": "⚠️ 请求已超过团队上下文上限,请使用 /compact 压缩上下文后重试,或联系管理员调整上下文配额"
93
107
  },
94
108
  {
95
109
  "id": "api-error-429",