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
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',
@@ -344,9 +344,9 @@ export async function launchMonitorAndConfirm(args, env, isAliveInRegistry, hasO
344
344
  * in a checkout: `npm install -g` swaps the whole package directory, and the
345
345
  * monitor has ~170 lazy `await import()` sites, so a module it has not loaded
346
346
  * yet would come from the new version while the rest is still the old one.
347
- * Staging `dist/` elsewhere keeps its module graph on one consistent version.
347
+ * Staging `dist/` and `bin/` keeps the package-owned code on one version.
348
348
  *
349
- * Only `dist/` and `package.json` are copied (a few MB). `node_modules` is
349
+ * Only `dist/`, `bin/` and `package.json` are copied (a few MB). `node_modules` is
350
350
  * linked, because copying it is hundreds of MB. The link degrades in steps so
351
351
  * that no platform is left without a path forward:
352
352
  *
@@ -379,6 +379,9 @@ export function prepareMonitorStage(packageRoot, stageParent) {
379
379
  try {
380
380
  fs.mkdirSync(stageDir, { recursive: true });
381
381
  fs.cpSync(distSource, path.join(stageDir, 'dist'), { recursive: true });
382
+ const binSource = path.join(packageRoot, 'bin');
383
+ if (fs.existsSync(binSource))
384
+ fs.cpSync(binSource, path.join(stageDir, 'bin'), { recursive: true });
382
385
  const pkgJson = path.join(packageRoot, 'package.json');
383
386
  if (fs.existsSync(pkgJson))
384
387
  fs.copyFileSync(pkgJson, path.join(stageDir, 'package.json'));
@@ -388,7 +391,7 @@ export function prepareMonitorStage(packageRoot, stageParent) {
388
391
  fs.rmSync(stageDir, { recursive: true, force: true });
389
392
  }
390
393
  catch { }
391
- return { stageDir: null, linkMode: 'none', note: `复制 dist/ 失败:${error.message}` };
394
+ return { stageDir: null, linkMode: 'none', note: `复制 monitor 运行文件失败:${error.message}` };
392
395
  }
393
396
  if (!fs.existsSync(modulesSource)) {
394
397
  // Dependencies hoisted elsewhere; nothing to link, node resolves upward.
@@ -666,6 +669,13 @@ export async function waitForProcessExit(pid, timeoutMs = 10_000, intervalMs = 2
666
669
  * Returns list of matching PIDs.
667
670
  */
668
671
  export function findProcesses(pattern) {
672
+ return findProcessesWithCommandLine(pattern).map(match => match.pid);
673
+ }
674
+ /**
675
+ * Like findProcesses, but keeps the command line returned by the Windows CIM
676
+ * snapshot so callers do not need a second PowerShell launch per candidate.
677
+ */
678
+ export function findProcessesWithCommandLine(pattern) {
669
679
  try {
670
680
  if (isWindows) {
671
681
  // WMI LIKE is wildcard matching, not JavaScript regex matching. Query a
@@ -696,13 +706,15 @@ export function findProcesses(pattern) {
696
706
  const normalized = commandLine.replace(/\\/g, '/');
697
707
  return matcherCandidates.some(matcher => matcher.test(commandLine) || matcher.test(normalized));
698
708
  })
699
- .map(row => row.ProcessId);
709
+ .map(row => ({ pid: row.ProcessId, commandLine: row.CommandLine || '' }));
700
710
  }
701
711
  return [];
702
712
  }
703
713
  else {
704
714
  const output = execFileSync('pgrep', ['-f', pattern], { encoding: 'utf-8' }).trim();
705
- return output ? output.split('\n').map(Number).filter(pid => pid !== process.pid) : [];
715
+ return output
716
+ ? output.split('\n').map(Number).filter(pid => pid !== process.pid).map(pid => ({ pid }))
717
+ : [];
706
718
  }
707
719
  }
708
720
  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",
@@ -91,6 +91,117 @@ export var ErrorType;
91
91
  ErrorType["CLAUDE_CODE_STARTUP_FAILED"] = "claude_code_startup_failed";
92
92
  ErrorType["UNKNOWN"] = "unknown";
93
93
  })(ErrorType || (ErrorType = {}));
94
+ export function getErrorRawText(error, seen = new Set()) {
95
+ if (typeof error === 'string') {
96
+ const text = error.trim();
97
+ return text || undefined;
98
+ }
99
+ if (error === null || error === undefined)
100
+ return undefined;
101
+ if (typeof error === 'object') {
102
+ const record = error;
103
+ if (seen.has(error))
104
+ return undefined;
105
+ seen.add(error);
106
+ if ('raw' in record) {
107
+ const raw = record.raw;
108
+ if (typeof raw === 'string' && raw.trim())
109
+ return raw.trim();
110
+ }
111
+ const causeText = getErrorRawText(record.cause, seen);
112
+ if (causeText)
113
+ return causeText;
114
+ for (const key of ['message', 'error', 'reason']) {
115
+ const value = record[key];
116
+ if (typeof value === 'string' && value.trim())
117
+ return value.trim();
118
+ if (value && typeof value === 'object') {
119
+ const nestedText = getErrorRawText(value, seen);
120
+ if (nestedText)
121
+ return nestedText;
122
+ }
123
+ }
124
+ const code = record.code;
125
+ if (typeof code === 'string' && code.trim())
126
+ return code.trim();
127
+ return undefined;
128
+ }
129
+ const text = String(error).trim();
130
+ return text || undefined;
131
+ }
132
+ export function normalizeCoreErrorSubtype(error, reportedSubtype, terminalReason) {
133
+ const reported = reportedSubtype?.trim().toLowerCase();
134
+ if (error instanceof OrphanedTargetSessionError)
135
+ return ErrorType.SESSION_UNAVAILABLE;
136
+ if (error instanceof ExecutionTimeoutError)
137
+ return ErrorType.SDK_TIMEOUT;
138
+ if (terminalReason === 'prompt_too_long' || terminalReason === 'context_compact_failed') {
139
+ return ErrorType.CONTEXT_TOO_LONG;
140
+ }
141
+ if (terminalReason === 'max_output_tokens')
142
+ return ErrorType.MAX_OUTPUT_TOKENS;
143
+ if (terminalReason === 'model_error')
144
+ return ErrorType.MODEL_UNAVAILABLE;
145
+ if (terminalReason === 'permission_denied')
146
+ return 'permission_error';
147
+ if (terminalReason === 'timeout' || terminalReason === 'total_execution_timeout')
148
+ return ErrorType.SDK_TIMEOUT;
149
+ if (reported === 'protocol_incomplete')
150
+ return 'protocol_incomplete';
151
+ if (reported === 'permission_denied' || reported?.startsWith('permission_'))
152
+ return 'permission_error';
153
+ const classified = classifyError(typeof error === 'string' ? new Error(error) : error);
154
+ if (classified !== ErrorType.UNKNOWN)
155
+ return classified;
156
+ if (reported && Object.values(ErrorType).includes(reported))
157
+ return reported;
158
+ switch (reported) {
159
+ case 'error_api':
160
+ case 'api':
161
+ case 'infrastructure':
162
+ return ErrorType.API_ERROR;
163
+ case 'error_model':
164
+ case 'model_error':
165
+ return ErrorType.MODEL_UNAVAILABLE;
166
+ case 'error_tool_use':
167
+ case 'tool_error':
168
+ return ErrorType.STREAM_ERROR;
169
+ }
170
+ if (terminalReason === 'rapid_refill_breaker')
171
+ return ErrorType.API_ERROR;
172
+ if (terminalReason === 'tool_error')
173
+ return ErrorType.STREAM_ERROR;
174
+ if (reported)
175
+ return reported;
176
+ return ErrorType.UNKNOWN;
177
+ }
178
+ export function normalizeCoreErrorReason(reason) {
179
+ const normalized = reason?.trim().toLowerCase();
180
+ switch (normalized) {
181
+ case 'retry.exhausted':
182
+ return 'retry_exhausted';
183
+ case 'sdk_timeout':
184
+ case 'total_execution_timeout':
185
+ return 'timeout';
186
+ case 'aborted_streaming':
187
+ case 'aborted_tools':
188
+ case 'stop':
189
+ return 'cancelled';
190
+ case 'protocol_incomplete':
191
+ case 'max_output_tokens':
192
+ case 'target_session_timeout':
193
+ case 'retry_exhausted':
194
+ case 'timeout':
195
+ case 'cancelled':
196
+ case 'permission_denied':
197
+ case 'input_already_submitted':
198
+ case 'fallback_exhausted':
199
+ case 'orphaned_target_session':
200
+ return normalized;
201
+ default:
202
+ return undefined;
203
+ }
204
+ }
94
205
  /**
95
206
  * 错误来源前缀 — 区分基础设施异常 vs Agent 任务失败
96
207
  *
@@ -516,6 +627,16 @@ export function getErrorMessage(error, terminalReason, includeEmoji = true) {
516
627
  return `${errPrefix}暂无法判定错误原因:${msg}`;
517
628
  return `${errPrefix}处理消息时出错,请稍后重试`;
518
629
  }
630
+ export function getToolResultErrorText(event) {
631
+ const text = event.error || event.reason
632
+ || (event.name === 'Shell'
633
+ ? typeof event.exitCode === 'number' && Number.isInteger(event.exitCode)
634
+ ? 'exitCode: ' + event.exitCode
635
+ : '执行失败'
636
+ : typeof event.result === 'string' ? event.result : JSON.stringify(event.result))
637
+ || '执行失败';
638
+ return text.replace(/<tool_use_error>(.*?)<\/tool_use_error>/gs, '$1');
639
+ }
519
640
  function resolveErrorMessage(error) {
520
641
  if (typeof error === 'string')
521
642
  return error.trim();
@@ -11,8 +11,8 @@
11
11
  import fs from 'fs';
12
12
  import path from 'path';
13
13
  import { spawnSync } from 'child_process';
14
- import { getPackageRoot, resolvePaths } from '../paths.js';
15
- import { isProcessRunning, killProcess, isWindows, findProcesses } from './cross-platform.js';
14
+ import { getPackageRoot, resolvePaths, isDevMode } from '../paths.js';
15
+ import { isProcessRunning, killProcess, isWindows, findProcessesWithCommandLine } from './cross-platform.js';
16
16
  import { getProcessStartTime, startTimeMatches } from './process-introspect.js';
17
17
  import { isConfirmedLeakedTestDaemon, runtimeHomeFromEnv } from './restart-safety.js';
18
18
  import { decodeWindowsOutput } from './windows-output.js';
@@ -55,6 +55,7 @@ export function writeMain(launchedBy) {
55
55
  startedAt,
56
56
  startedAtIso: new Date(startedAt).toISOString(),
57
57
  launchedBy,
58
+ runMode: isDevMode() ? 'dev' : 'install',
58
59
  };
59
60
  const filePath = path.join(dir, mainFileName(process.pid));
60
61
  writeAtomic(filePath, JSON.stringify(record, null, 2));
@@ -376,10 +377,10 @@ export function isEvolCoreMainCommand(cmdline, packageRoot = getPackageRoot()) {
376
377
  *
377
378
  * 不会主动 kill——清理由调用方决定。
378
379
  */
379
- export function findOrphanProcesses() {
380
- // 1. 已登记 PID(自己 HOME 下的 main + 自己进程)
380
+ export function findOrphanProcesses(status = scanInstances()) {
381
+ // 1. 已登记 PID(自己 HOME 下的 main + 自己进程)。调用方刚扫过登记簿时可直接传入,
382
+ // Windows 上每次 scanInstances 都要为活实例跑一遍 CIM 查询。
381
383
  const known = new Set([process.pid]);
382
- const status = scanInstances();
383
384
  for (const m of status.mains) {
384
385
  if (m.alive)
385
386
  known.add(m.record.pid);
@@ -393,14 +394,18 @@ export function findOrphanProcesses() {
393
394
  // regex contains escaped separators and is applied after command lines have
394
395
  // been fetched; embedding it in the CIM pre-filter can silently miss a
395
396
  // backslash-delimited `dist\\index.js` command on some PowerShell builds.
396
- const candidates = findProcesses('index\\.js');
397
+ // `index.js` 会命中所有 Node 服务(各会话拉起的 MCP server 常有十几个),
398
+ // 所以先用快照里的命令行过滤,只对真正的 EvolCore 入口做后续探测。
399
+ const candidates = findProcessesWithCommandLine('index\\.js');
397
400
  const orphans = [];
398
- for (const pid of candidates) {
401
+ for (const { pid, commandLine } of candidates) {
399
402
  if (known.has(pid))
400
403
  continue;
404
+ if (commandLine !== undefined && !isEvolCoreMainCommand(commandLine))
405
+ continue;
401
406
  if (!isProcessRunning(pid))
402
407
  continue;
403
- const cmdline = readCmdline(pid);
408
+ const cmdline = commandLine || readCmdline(pid);
404
409
  // 二次验证:入口必须是当前 EvolCore 包的 dist/index.js。
405
410
  if (!isEvolCoreMainCommand(cmdline))
406
411
  continue;