evolcore 0.0.11 → 0.0.12

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 (102) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/bin/codex-managed-hook.mjs +80 -0
  3. package/dist/agents/claude-runner.js +35 -2
  4. package/dist/agents/codex-app-server-client.js +120 -14
  5. package/dist/agents/codex-runner.js +273 -35
  6. package/dist/agents/ecagent-runner.js +36 -4
  7. package/dist/agents/gemini-runner.js +1 -0
  8. package/dist/aun/aid/client.js +11 -21
  9. package/dist/aun/aid/managed-operation.js +107 -0
  10. package/dist/aun/msg/group.js +81 -12
  11. package/dist/aun/msg/managed-operation.js +804 -0
  12. package/dist/aun/msg/mention-schema.js +20 -0
  13. package/dist/aun/msg/p2p.js +7 -0
  14. package/dist/channels/aun.js +288 -108
  15. package/dist/channels/daemon.js +13 -0
  16. package/dist/cli/agent-command.js +56 -8
  17. package/dist/cli/agent.js +170 -6
  18. package/dist/cli/aun-commands.js +270 -69
  19. package/dist/cli/bench.js +12 -3
  20. package/dist/cli/daemon-commands.js +31 -31
  21. package/dist/cli/fs-command.js +60 -0
  22. package/dist/cli/handoff-command.js +3 -0
  23. package/dist/cli/index.js +30 -2
  24. package/dist/cli/managed-command-guard.js +41 -0
  25. package/dist/cli/model.js +11 -4
  26. package/dist/cli/queue-command.js +11 -1
  27. package/dist/cli/response.js +71 -0
  28. package/dist/cli/restart-monitor.js +4 -18
  29. package/dist/cli/trigger-command.js +4 -0
  30. package/dist/config/builtin-roles.js +4 -0
  31. package/dist/config/contact-book-store.js +11 -3
  32. package/dist/config/contact-request-service.js +161 -15
  33. package/dist/config/mention-mode.js +8 -24
  34. package/dist/core/auth/agent-delegation.js +0 -1
  35. package/dist/core/auth/operation-authorizer.js +30 -7
  36. package/dist/core/auth/operation-catalog.js +187 -25
  37. package/dist/core/bootstrap-messages.js +18 -0
  38. package/dist/core/bootstrap-service.js +63 -4
  39. package/dist/core/capability/providers/claude-capability-provider.js +9 -31
  40. package/dist/core/capability/providers/codex-capability-provider.js +4 -15
  41. package/dist/core/capability/skill-discovery.js +55 -0
  42. package/dist/core/channel-loader.js +1 -1
  43. package/dist/core/command/cli-intent-parser.js +9 -0
  44. package/dist/core/command/command-handler.js +467 -6
  45. package/dist/core/command/connect-menu.js +15 -0
  46. package/dist/core/command/group-menu.js +4 -4
  47. package/dist/core/command/menu-catalog.js +366 -0
  48. package/dist/core/command/menu-handler.js +147 -35
  49. package/dist/core/command/menu-protocol.js +71 -0
  50. package/dist/core/command/slash-gate.js +5 -2
  51. package/dist/core/command/slash-handler.js +41 -21
  52. package/dist/core/daemon-file-cache.js +2 -1
  53. package/dist/core/data-migration.js +9 -2
  54. package/dist/core/evolagent.js +4 -2
  55. package/dist/core/handoff/dispatcher.js +5 -2
  56. package/dist/core/handoff/runtime.js +145 -21
  57. package/dist/core/handoff/store.js +24 -0
  58. package/dist/core/handoff/types.js +1 -0
  59. package/dist/core/message/logical-queue-bridge.js +1 -0
  60. package/dist/core/message/mention-schema.js +126 -0
  61. package/dist/core/message/message-bridge.js +236 -78
  62. package/dist/core/message/message-log.js +1 -0
  63. package/dist/core/message/message-queue.js +1 -0
  64. package/dist/core/message/response-engine.js +54 -36
  65. package/dist/core/permission/ec-command-parser.js +156 -10
  66. package/dist/core/permission/sandbox-runtime.js +22 -1
  67. package/dist/core/permission/tool-policy.js +73 -30
  68. package/dist/core/protected-paths.js +7 -2
  69. package/dist/core/session/session-manager.js +21 -2
  70. package/dist/eck/manifest-engine.js +39 -13
  71. package/dist/index.js +384 -98
  72. package/dist/ipc.js +218 -9
  73. package/dist/response-system/coordinator.js +6 -4
  74. package/dist/response-system/engines/v1/proactive-flow.js +26 -8
  75. package/dist/response-system/modes/single-session/index.js +14 -1
  76. package/dist/response-system/selector.js +6 -5
  77. package/dist/trigger/anomaly-store.js +4 -0
  78. package/dist/trigger/script-executor.js +1 -0
  79. package/dist/utils/codex-app-server-registry.js +90 -0
  80. package/dist/utils/codex-cli.js +5 -1
  81. package/dist/utils/cross-platform.js +15 -0
  82. package/dist/utils/npm-ops.js +5 -12
  83. package/dist/utils/tool-summary.js +11 -0
  84. package/kits/docs/channels/aun.md +1 -1
  85. package/kits/docs/context-assembly.md +2 -2
  86. package/kits/docs/evolcore/agent.md +11 -6
  87. package/kits/docs/evolcore/aid.md +14 -0
  88. package/kits/docs/prompt-loading-architecture.md +1 -2
  89. package/kits/docs/venues/group.md +1 -1
  90. package/kits/eck_manifest.json +0 -12
  91. package/kits/rules/04-relation.md +1 -1
  92. package/kits/rules/05-venue.md +2 -2
  93. package/kits/schemas/single-session.schema.1.json +3 -3
  94. package/kits/schemas/single-session.schema.2.json +3 -3
  95. package/kits/templates/roles/admin.json +22 -0
  96. package/kits/templates/roles/member.json +27 -0
  97. package/kits/templates/roles/visitor.json +33 -3
  98. package/kits/templates/system-fragments/bootstrap.md +1 -1
  99. package/kits/templates/system-fragments/channel.md +1 -1
  100. package/kits/templates/system-fragments/session.md +1 -1
  101. package/package.json +2 -2
  102. package/dist/core/command/evol-menu-version-gate.js +0 -39
@@ -4,7 +4,7 @@ import { randomUUID } from 'crypto';
4
4
  import { logger } from '../../utils/logger.js';
5
5
  import { resolveRoot } from '../../paths.js';
6
6
  import { containsHClassReference, containsLClassReference, getExistingHClassMaskTargets, hClassGrantIncludesProtectedPath, isHClassPath, isSameOrDescendant, isLClassPath, lClassGrantIncludesProtectedPath, resolveProtectedCandidate, } from '../protected-paths.js';
7
- import { classifyEvolcoreShellCommand, parseBoundedOutputShellCommand, parseLiteralShellArgv, } from './ec-command-parser.js';
7
+ import { classifyEvolcoreShellCommand, parseCodexToolCommandArgv, parseBoundedOutputShellCommand, parseLiteralShellArgv, unwrapCodexShellCommandArgv, } from './ec-command-parser.js';
8
8
  import { analyzeReadonlyShellQuery, } from './readonly-shell-query.js';
9
9
  // 绝对禁止命令(所有角色、所有权限模式下都禁止,不可授权)
10
10
  // 这些是系统级破坏操作,任何情况下都不应该允许
@@ -1071,30 +1071,57 @@ function prepareBoundedOutputInput(input, command, kind, context) {
1071
1071
  export function evaluateToolPreflight(toolName, input, context) {
1072
1072
  if (toolName === 'Bash') {
1073
1073
  const command = typeof input.command === 'string' ? input.command : '';
1074
- if (context.sessionId && /^\s*(?:command\s+-v|which|type)\s+ec\s*$/i.test(command)) {
1074
+ const explicitCommandArgv = Array.isArray(input.commandArgv)
1075
+ && input.commandArgv.every(value => typeof value === 'string')
1076
+ ? input.commandArgv
1077
+ : undefined;
1078
+ const outerArgv = explicitCommandArgv ?? parseLiteralShellArgv(command) ?? undefined;
1079
+ const wrappedCommand = outerArgv ? unwrapCodexShellCommandArgv(outerArgv) : undefined;
1080
+ const policyCommand = wrappedCommand ?? command;
1081
+ if (context.sessionId && /^\s*(?:command\s+-v|which|type)\s+ec\s*$/i.test(policyCommand)) {
1075
1082
  return {
1076
1083
  behavior: 'deny',
1077
1084
  input,
1078
1085
  message: '🔒 EvolCore 托管会话不需要探测 `ec` 是否存在;请不要调用 `command -v`/`which`/`type`,也不要拼接其它命令。通信必须走当前 session 的 delegated sender。',
1086
+ policyCode: 'managed_ec_discovery_forbidden',
1079
1087
  };
1080
1088
  }
1081
- const argv = context.sessionId ? parseLiteralShellArgv(command) : null;
1082
- const isOwnAgentMdPublish = !!argv
1083
- && argv.length === 5
1084
- && argv[0] === 'ec'
1085
- && argv[1] === 'aid'
1086
- && argv[2] === 'agentmd'
1087
- && argv[3] === 'put'
1089
+ const argv = context.sessionId ? parseCodexToolCommandArgv(input) : null;
1090
+ const managedAidArgv = argv?.at(-2) === '--format' && argv.at(-1) === 'json'
1091
+ ? argv.slice(0, -2)
1092
+ : argv;
1093
+ const ownAid = !!managedAidArgv
1088
1094
  && !!context.selfAid
1089
- && argv[4].replace(/^@/, '') === context.selfAid.replace(/^@/, '');
1090
- if (context.sessionId && /^\s*ec\s+aid(?:\s|$)/i.test(command) && !isOwnAgentMdPublish) {
1095
+ && managedAidArgv.at(-1)?.replace(/^@/, '') === context.selfAid.replace(/^@/, '');
1096
+ const isSelfManagedAidRead = !!managedAidArgv
1097
+ && managedAidArgv[0] === 'ec'
1098
+ && managedAidArgv[1] === 'aid'
1099
+ && ownAid
1100
+ && ((managedAidArgv.length === 4 && ['show', 'lookup'].includes(managedAidArgv[2]))
1101
+ || (managedAidArgv.length === 5
1102
+ && managedAidArgv[2] === 'agentmd'
1103
+ && managedAidArgv[3] === 'get'));
1104
+ const isSelfAgentMdPublish = !!managedAidArgv
1105
+ && managedAidArgv.length === 5
1106
+ && managedAidArgv[0] === 'ec'
1107
+ && managedAidArgv[1] === 'aid'
1108
+ && managedAidArgv[2] === 'agentmd'
1109
+ && managedAidArgv[3] === 'put'
1110
+ && ownAid;
1111
+ const canPublishSelfAgentMd = isSelfAgentMdPublish
1112
+ && ['request', 'bypass'].includes(context.permissionMode ?? '');
1113
+ const isAllowedManagedAidCommand = isSelfManagedAidRead || canPublishSelfAgentMd;
1114
+ if (context.sessionId && /^\s*ec\s+aid(?:\s|$)/i.test(policyCommand) && !isAllowedManagedAidCommand) {
1091
1115
  return {
1092
1116
  behavior: 'deny',
1093
1117
  input,
1094
- message: '🔒 EvolCore 托管会话仅允许通过 `ec aid agentmd put <当前 self AID>` 发布自身名片;禁止通过 `ec aid` 自行发现、切换或操作其他身份。',
1118
+ message: isSelfAgentMdPublish && (context.permissionMode === 'readonly' || context.permissionMode === 'auto')
1119
+ ? `🔒 ${context.permissionMode} 模式禁止发布 agent.md;请切换到 request 或 bypass 后再执行 \`ec aid agentmd put <当前 self AID>\`。`
1120
+ : '🔒 EvolCore 托管会话仅允许针对当前 self AID 精确调用 `ec aid show`、`ec aid lookup` 或 `ec aid agentmd get`;`ec aid agentmd put` 还要求 request 或 bypass。可选后缀仅限 `--format json`。禁止枚举、切换或操作其他身份。',
1121
+ policyCode: 'managed_ec_aid_scope_forbidden',
1095
1122
  };
1096
1123
  }
1097
- const ecCommand = classifyEvolcoreShellCommand(command);
1124
+ const ecCommand = classifyEvolcoreShellCommand(policyCommand);
1098
1125
  if (ecCommand.kind === 'literal') {
1099
1126
  return { behavior: 'allow', input, reason: 'ec-command' };
1100
1127
  }
@@ -1102,25 +1129,40 @@ export function evaluateToolPreflight(toolName, input, context) {
1102
1129
  return prepareBoundedOutputInput(input, ecCommand.command, 'ec', context);
1103
1130
  }
1104
1131
  if (ecCommand.kind === 'composite') {
1105
- if (ecCommand.issue === 'unsafe-expansion') {
1132
+ // Older app-server approval callbacks may provide only the rendered
1133
+ // `/bin/bash -lc ...` command string. A composite inside that carrier
1134
+ // is not eligible for a delegation ticket, but owner bypass still
1135
+ // handles it as an ordinary shell request. Structured `commandArgv`
1136
+ // callbacks remain fail-closed below because their command boundary is
1137
+ // authoritative.
1138
+ const legacyRenderedCarrier = !explicitCommandArgv && wrappedCommand !== undefined;
1139
+ const allowLegacyBypassComposition = legacyRenderedCarrier
1140
+ && context.permissionMode === 'bypass'
1141
+ && ecCommand.issue === 'shell-composition';
1142
+ if (!allowLegacyBypassComposition) {
1143
+ if (ecCommand.issue === 'unsafe-expansion') {
1144
+ return {
1145
+ behavior: 'deny',
1146
+ input,
1147
+ message: '🔒 EC 双引号正文包含未转义的 Shell 展开;请将反引号写成 \\`,将 $()、${}、$变量中的 $ 写成 \\$。Shell 传给 ec 时会还原为原文字面量',
1148
+ policyCode: 'ec_shell_unsafe_expansion',
1149
+ };
1150
+ }
1151
+ if (ecCommand.issue === 'invalid-quote') {
1152
+ return {
1153
+ behavior: 'deny',
1154
+ input,
1155
+ message: '🔒 EC 命令引号未闭合或存在错误嵌套;正文中的双引号请写成 \\"',
1156
+ policyCode: 'ec_shell_invalid_quote',
1157
+ };
1158
+ }
1106
1159
  return {
1107
1160
  behavior: 'deny',
1108
1161
  input,
1109
- message: '🔒 EC 双引号正文包含未转义的 Shell 展开;请将反引号写成 \\`,将 $()、${}、$变量中的 $ 写成 \\$。Shell 传给 ec 时会还原为原文字面量',
1162
+ message: '🔒 EC 命令调用被拒绝:一次 Bash/exec_command 只能执行一条完整的 `ec` 命令。请直接调用 `ec ...`,不要先用 `command -v`/`ec aid` 探测,也不要使用 `&&`、`||`、`;`、`|`、重定向、变量展开、子 shell、`sh -c` 或 `bash -lc` 拼接其它命令。',
1163
+ policyCode: 'ec_shell_composite_command',
1110
1164
  };
1111
1165
  }
1112
- if (ecCommand.issue === 'invalid-quote') {
1113
- return {
1114
- behavior: 'deny',
1115
- input,
1116
- message: '🔒 EC 命令引号未闭合或存在错误嵌套;正文中的双引号请写成 \\"',
1117
- };
1118
- }
1119
- return {
1120
- behavior: 'deny',
1121
- input,
1122
- message: '🔒 EC 命令调用被拒绝:一次 Bash/exec_command 只能执行一条完整的 `ec` 命令。请直接调用 `ec ...`,不要先用 `command -v`/`ec aid` 探测,也不要使用 `&&`、`||`、`;`、`|`、重定向、变量展开、子 shell、`sh -c` 或 `bash -lc` 拼接其它命令。',
1123
- };
1124
1166
  }
1125
1167
  const boundedOutput = parseBoundedOutputShellCommand(command);
1126
1168
  const hostProcessCommand = boundedOutput
@@ -1137,7 +1179,7 @@ export function evaluateToolPreflight(toolName, input, context) {
1137
1179
  }
1138
1180
  const blacklist = checkBlacklist(toolName, input);
1139
1181
  if (blacklist.behavior === 'deny') {
1140
- return { behavior: 'deny', input, message: blacklist.message };
1182
+ return { behavior: 'deny', input, message: blacklist.message, policyCode: 'tool_blacklist' };
1141
1183
  }
1142
1184
  const checkedInput = blacklist.updatedInput;
1143
1185
  const hClass = checkHClassWrite(toolName, checkedInput, {
@@ -1150,7 +1192,7 @@ export function evaluateToolPreflight(toolName, input, context) {
1150
1192
  root: context.root,
1151
1193
  });
1152
1194
  if (hClass.behavior === 'deny') {
1153
- return { behavior: 'deny', input: checkedInput, message: hClass.message };
1195
+ return { behavior: 'deny', input: checkedInput, message: hClass.message, policyCode: 'h_class_protection' };
1154
1196
  }
1155
1197
  const lClass = checkLClassWrite(toolName, checkedInput, {
1156
1198
  sessionId: context.sessionId,
@@ -1163,7 +1205,7 @@ export function evaluateToolPreflight(toolName, input, context) {
1163
1205
  root: context.root,
1164
1206
  });
1165
1207
  if (lClass.behavior === 'deny') {
1166
- return { behavior: 'deny', input: checkedInput, message: lClass.message };
1208
+ return { behavior: 'deny', input: checkedInput, message: lClass.message, policyCode: 'l_class_protection' };
1167
1209
  }
1168
1210
  const dangerous = checkDangerousCommand(toolName, checkedInput);
1169
1211
  if (dangerous.isDangerous
@@ -1174,6 +1216,7 @@ export function evaluateToolPreflight(toolName, input, context) {
1174
1216
  behavior: 'deny',
1175
1217
  input: checkedInput,
1176
1218
  message: '🔒 Git 破坏性操作仅允许 owner/admin 调用;当前角色无权执行',
1219
+ policyCode: 'role_git_destructive_forbidden',
1177
1220
  };
1178
1221
  }
1179
1222
  return { behavior: 'continue', input: checkedInput };
@@ -11,7 +11,7 @@ const H_CLASS_RELATIVE_PATTERNS = [
11
11
  /^backups\/config(?:\/|$)/,
12
12
  /^\.snapshots(?:\/|$)/,
13
13
  /^CA(?:\/|$)/,
14
- /^(?:AIDs|aids)\/[^/]+\/(?:cert|keys)(?:\/|$)/,
14
+ /^(?:AIDs|aids)\/[^/]+\/(?:cert|keys|private)(?:\/|$)/,
15
15
  /^\.device_id$/,
16
16
  /^\.env$/,
17
17
  /^\.seed(?:\.|$)/,
@@ -33,7 +33,7 @@ const H_CLASS_RELATIVE_PATTERNS = [
33
33
  const H_CLASS_REFERENCE_PATTERNS = [
34
34
  /(?:^|[^A-Za-z0-9_.-])daemon\.json(?=$|[^A-Za-z0-9_.-])/,
35
35
  /(?:^|[^A-Za-z0-9_.-])agents[\\/](?:defaults\.json|[^/\\\s"']+[\\/](?:(?:config|contact)\.json|contact-operations\.jsonl|relations[\\/][^/\\\s"']+[\\/]config\.json))(?=$|[\s"';&|<>)},])/,
36
- /(?:^|[^A-Za-z0-9_.-])(?:backups[\\/]config|\.snapshots|CA|(?:AIDs|aids)[\\/][^/\\\s"']+[\\/](?:cert|keys))(?:[\\/]|$|[\s"';&|<>)},])/,
36
+ /(?:^|[^A-Za-z0-9_.-])(?:backups[\\/]config|\.snapshots|CA|(?:AIDs|aids)[\\/][^/\\\s"']+[\\/](?:cert|keys|private))(?:[\\/]|$|[\s"';&|<>)},])/,
37
37
  /(?:^|[/\\\s"'=<>])(?:\.device_id|\.env|\.seed(?:\.[^/\\\s"']*)?|\.migrated-[^/\\\s"']*|\.lock|daemon\.pid|data[\\/](?:causation-aun\.json(?:_|__)?|sessions|contact-book-audit\.jsonl|feishu-seen-[^/\\\s"']+\.jsonl|[a-z0-9_-]+-greeted-[^/\\\s"']+\.jsonl|message-queue\.json(?:_|__|\.tmp-[^/\\\s"']*)?|daemon[\\/](?:message-queue\.json(?:_|__|\.tmp-[^/\\\s"']*)?|control|migrations)|channel-state|instance[\\/]control\.token|outbox|handoff)|agents[\\/][^/\\\s"']+[\\/](?:sessions|data[\\/](?:channels|handoff|outbox\.jsonl|contact-audit\.jsonl)))(?:[\\/]|$|[\s"';&|<>)},])/,
38
38
  /(?:^|[/\\\s"'=<>])data[\\/]triggers(?:[\\/]|$|[\s"';&|<>)},])/,
39
39
  /(?:^|[/\\\s"'=<>])(?:[^/\\\s"']+\.json_|[^/\\\s"']+\.json\.migrated|defaults_\d+\.json)(?=$|[\s"';&|<>)},])/,
@@ -144,10 +144,14 @@ export function getHClassSandboxPatterns(root = resolveRoot()) {
144
144
  path.join('AIDs', '*', 'cert', '**'),
145
145
  path.join('AIDs', '*', 'keys'),
146
146
  path.join('AIDs', '*', 'keys', '**'),
147
+ path.join('AIDs', '*', 'private'),
148
+ path.join('AIDs', '*', 'private', '**'),
147
149
  path.join('aids', '*', 'cert'),
148
150
  path.join('aids', '*', 'cert', '**'),
149
151
  path.join('aids', '*', 'keys'),
150
152
  path.join('aids', '*', 'keys', '**'),
153
+ path.join('aids', '*', 'private'),
154
+ path.join('aids', '*', 'private', '**'),
151
155
  '.device_id',
152
156
  '.env',
153
157
  '.seed',
@@ -272,6 +276,7 @@ export function getExistingHClassMaskTargets(root = resolveRoot()) {
272
276
  continue;
273
277
  addExistingTarget(targets, path.join(aidsRoot, aid.name, 'cert'));
274
278
  addExistingTarget(targets, path.join(aidsRoot, aid.name, 'keys'));
279
+ addExistingTarget(targets, path.join(aidsRoot, aid.name, 'private'));
275
280
  }
276
281
  }
277
282
  catch { }
@@ -11,7 +11,7 @@ import fs from 'fs';
11
11
  import os from 'os';
12
12
  function newSessionStableMetadata(chatType, ...sources) {
13
13
  const keys = chatType === 'group'
14
- ? ['channelKey', 'groupId', 'groupName', 'dispatchMode']
14
+ ? ['channelKey', 'groupId', 'groupName', 'mentionMode']
15
15
  : ['channelKey', 'peerId', 'peerName', 'peerType'];
16
16
  const metadata = {};
17
17
  for (const source of sources) {
@@ -1135,6 +1135,10 @@ export class SessionManager {
1135
1135
  if (!baseagent) {
1136
1136
  throw new Error(`[SessionManager] createNewSession: baseagent is empty for channel=${channel} channelId=${channelId}`);
1137
1137
  }
1138
+ const metadata = newSessionStableMetadata(inheritedChatType, activeMetadata, identityMetadata);
1139
+ if (identityMetadata?.sessionBoundaryOperationId) {
1140
+ metadata.sessionBoundaryOperationId = identityMetadata.sessionBoundaryOperationId;
1141
+ }
1138
1142
  const session = {
1139
1143
  id: generateSessionId(),
1140
1144
  channel,
@@ -1146,7 +1150,7 @@ export class SessionManager {
1146
1150
  baseagent,
1147
1151
  sessionKey: formatSessionKey(channelType, channelId, DEFAULT_THREAD_ID),
1148
1152
  chatType: inheritedChatType,
1149
- metadata: newSessionStableMetadata(inheritedChatType, activeMetadata, identityMetadata),
1153
+ metadata,
1150
1154
  name: name || '默认会话',
1151
1155
  createdAt: Date.now(),
1152
1156
  updatedAt: Date.now(),
@@ -1163,6 +1167,21 @@ export class SessionManager {
1163
1167
  });
1164
1168
  return session;
1165
1169
  }
1170
+ /**
1171
+ * Create and activate one fresh main session for a durable system operation.
1172
+ * Replaying the same operation returns the already-created session.
1173
+ */
1174
+ async ensureNewMainSessionForOperation(opts) {
1175
+ const active = this.getActiveSessionSync(opts.channel, opts.channelId);
1176
+ if (active?.metadata?.sessionBoundaryOperationId === opts.operationId) {
1177
+ return { session: active, created: false };
1178
+ }
1179
+ const session = await this.createNewSession(opts.channel, opts.channelId, opts.projectPath, undefined, opts.baseagent, {
1180
+ ...(opts.identityMetadata ?? {}),
1181
+ sessionBoundaryOperationId: opts.operationId,
1182
+ });
1183
+ return { session, created: true };
1184
+ }
1166
1185
  async createForkedSession(sourceSession, forkedAgentSessionId, name) {
1167
1186
  const channelType = sourceSession.channelType || sourceSession.channel;
1168
1187
  const threadId = sourceSession.threadId || '';
@@ -55,6 +55,10 @@ function loadAndMergeManifest(filename) {
55
55
  for (const s of base.sections)
56
56
  merged.set(s.id, { ...s });
57
57
  for (const s of override.sections) {
58
+ if (s.id === 'relation-group-profile' && merged.has('peer-profile')) {
59
+ logger.warn('[ManifestEngine] Ignoring deprecated patch section "relation-group-profile"; use "peer-profile" instead');
60
+ continue;
61
+ }
58
62
  const existing = merged.get(s.id);
59
63
  merged.set(s.id, existing ? { ...existing, ...s } : s);
60
64
  }
@@ -320,15 +324,35 @@ function resolvePath(rawPath, vars) {
320
324
  const r = resolvePathWithDiag(rawPath, vars);
321
325
  return r.status === 'ok' ? r.resolved : null;
322
326
  }
327
+ function isWithinDirectory(filePath, directory) {
328
+ const relative = path.relative(path.resolve(directory), path.resolve(filePath));
329
+ return relative === '' || (!path.isAbsolute(relative) && relative !== '..' && !relative.startsWith(`..${path.sep}`));
330
+ }
331
+ /**
332
+ * 随包发布的 kits 内容只在 reload/重启后刷新;personal/relations 及用户自定义
333
+ * manifest 路径属于运行时数据,必须用 mtime 检查,否则同一 daemon 内会长期陈旧。
334
+ */
335
+ function sectionCacheOptions(resolvedPath, vars) {
336
+ if (isWithinDirectory(resolvedPath, kitsDir())) {
337
+ return { policy: 'on-reload', group: 'kits' };
338
+ }
339
+ const personalDir = typeof vars.PERSONAL_DIR === 'string' ? vars.PERSONAL_DIR : null;
340
+ const selfAid = typeof vars.selfAid === 'string' ? vars.selfAid : null;
341
+ if (personalDir && selfAid && isWithinDirectory(resolvedPath, personalDir)) {
342
+ return { policy: 'mtime', group: `agent-files:${selfAid}` };
343
+ }
344
+ return { policy: 'mtime', group: 'eck-runtime' };
345
+ }
323
346
  /** 加载 loop 的 child 子模板文件内容(路径解析 + fileCache)。找不到返回 null。 */
324
347
  export function loadChildTemplate(childFile, vars) {
325
348
  const resolved = resolvePath(childFile, vars);
326
349
  if (!resolved)
327
350
  return null;
328
- return fileCache.getText(resolved, { policy: 'on-reload', group: 'kits' });
351
+ return fileCache.getText(resolved, sectionCacheOptions(resolved, vars));
329
352
  }
330
353
  /**
331
- * 返回 [filePath, rawContent][];按 sessionId 缓存已读文件内容。
354
+ * 返回 [filePath, rawContent][]。kits 文件跨 session 缓存到 reload;运行时文件
355
+ * (personal/relations/用户自定义路径)每次按 mtime 检查。
332
356
  * 若传入 overflowOut 且目录段触发单目录限额(maxFiles/maxBytes),
333
357
  * 会把溢出信息写入 overflowOut.value(不传则不启用限额收集,行为同旧版但仍应用默认上限)。
334
358
  */
@@ -343,7 +367,7 @@ export function loadSectionFiles(section, vars, sessionCache, overflowOut) {
343
367
  return [];
344
368
  const maxFiles = section.maxFiles ?? DIR_MAX_FILES;
345
369
  const maxBytes = section.maxBytes ?? DIR_MAX_BYTES;
346
- const { files, overflow } = readDirectoryFiles(resolved, section.pattern, maxFiles, maxBytes);
370
+ const { files, overflow } = readDirectoryFiles(resolved, section.pattern, maxFiles, maxBytes, vars);
347
371
  if (overflow && overflowOut)
348
372
  overflowOut.value = overflow;
349
373
  return files.map(([name, content]) => [path.join(resolved, name), content]);
@@ -351,31 +375,33 @@ export function loadSectionFiles(section, vars, sessionCache, overflowOut) {
351
375
  return [];
352
376
  }
353
377
  function loadFileSection(filePath, vars, sessionCache) {
354
- void sessionCache; // 内容跨 session 共享,改走全局 fileCache(on-reload)
378
+ void sessionCache; // 内容跨 session 共享,统一走全局 fileCache
355
379
  const resolved = resolvePath(filePath, vars);
356
380
  if (!resolved)
357
381
  return null;
358
- // 内容跨 session 共享:用全局 fileCache(on-reload,reload/重启时失效),
359
- // 不再按 session 重复缓存同一文件内容。
360
- const content = fileCache.getText(resolved, { policy: 'on-reload', group: 'kits' });
382
+ const content = fileCache.getText(resolved, sectionCacheOptions(resolved, vars));
361
383
  return content === null ? null : [resolved, content];
362
384
  }
363
385
  /**
364
386
  * 读取目录下匹配文件,受单目录限额(maxFiles/maxBytes)约束。
365
387
  * 返回已加载文件 + 溢出信息(超限时)。按 fileCache 缓存目录列表与文件内容。
366
388
  */
367
- function readDirectoryFiles(dirPath, pattern, maxFiles, maxBytes) {
389
+ function readDirectoryFiles(dirPath, pattern, maxFiles, maxBytes, vars) {
368
390
  const glob = pattern || '*.md';
369
- // 目录列表 + 各文件内容均走 fileCache(on-reload)。目录列表以 "<dir>|<glob>"
370
- // 为键缓存文件名数组;各文件内容走 fileCache.getText 共享。
371
- const names = fileCache.get(`${dirPath} ${glob}`, () => {
391
+ const cacheOptions = sectionCacheOptions(dirPath, vars);
392
+ const readNames = () => {
372
393
  try {
373
394
  return fs.readdirSync(dirPath).filter(f => matchGlob(f, glob)).sort();
374
395
  }
375
396
  catch {
376
397
  return [];
377
398
  }
378
- }, { policy: 'on-reload', group: 'kits' });
399
+ };
400
+ // synthetic cache key 无法用文件 mtime 门控。kits 目录结构只在 reload 后刷新;
401
+ // 运行时目录直接重扫,确保新增/删除文件在同一 session 内可见。
402
+ const names = cacheOptions.policy === 'on-reload'
403
+ ? fileCache.get(`${dirPath} ${glob}`, readNames, cacheOptions)
404
+ : readNames();
379
405
  const out = [];
380
406
  let usedBytes = 0;
381
407
  let overflow;
@@ -387,7 +413,7 @@ function readDirectoryFiles(dirPath, pattern, maxFiles, maxBytes) {
387
413
  break;
388
414
  }
389
415
  const fp = path.join(dirPath, f);
390
- const content = fileCache.getText(fp, { policy: 'on-reload', group: 'kits' });
416
+ const content = fileCache.getText(fp, sectionCacheOptions(fp, vars));
391
417
  if (content === null)
392
418
  continue;
393
419
  // 字节限额(至少加载一个文件,避免单个超大文件导致空目录)