evolcore 0.0.32 → 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 (214) hide show
  1. package/CHANGELOG.md +61 -0
  2. package/bin/ec.js +3 -2
  3. package/bin/install-codex-managed-hooks.mjs +135 -37
  4. package/dist/agents/baseagent-availability.js +20 -36
  5. package/dist/agents/claude-runner.js +111 -15
  6. package/dist/agents/codex/app-server-client.js +3 -0
  7. package/dist/agents/codex/diagnostics.js +112 -54
  8. package/dist/agents/codex-runner.js +241 -63
  9. package/dist/agents/ecagent-runner.js +78 -33
  10. package/dist/agents/runner-types.js +5 -6
  11. package/dist/aun/aid/identity.js +1 -1
  12. package/dist/aun/group/operations.js +0 -1
  13. package/dist/aun/msg/history.js +1 -0
  14. package/dist/aun/msg/p2p.js +0 -1
  15. package/dist/aun/outbox.js +50 -1
  16. package/dist/channels/aun.js +44 -8
  17. package/dist/channels/daemon.js +261 -3
  18. package/dist/cli/agent-command.js +58 -24
  19. package/dist/cli/agent.js +1 -1
  20. package/dist/cli/aun-commands.js +368 -31
  21. package/dist/cli/contact.js +8 -2
  22. package/dist/cli/ctl-command.js +3 -1
  23. package/dist/cli/daemon-commands.js +197 -27
  24. package/dist/cli/debug-mode.js +154 -0
  25. package/dist/cli/fs-command.js +2 -1
  26. package/dist/cli/handoff-command.js +3 -0
  27. package/dist/cli/index.js +98 -67
  28. package/dist/cli/init.js +2 -2
  29. package/dist/cli/integrity-command.js +196 -0
  30. package/dist/cli/managed-command-guard.js +5 -0
  31. package/dist/cli/memory-command.js +166 -146
  32. package/dist/cli/queue-command.js +9 -4
  33. package/dist/cli/session-command.js +194 -0
  34. package/dist/cli/skill-command.js +78 -2
  35. package/dist/cli/task-context.js +1 -0
  36. package/dist/cli/thread-command.js +622 -0
  37. package/dist/cli/trigger-command.js +10 -3
  38. package/dist/config/builtin-roles.js +1 -0
  39. package/dist/config/config-field-policy.js +6 -3
  40. package/dist/config/config-manager.js +26 -2
  41. package/dist/config/contact-book-store.js +5 -0
  42. package/dist/config/resolved-config-op.js +3 -0
  43. package/dist/config/role-service.js +13 -0
  44. package/dist/config/runtime-env.js +16 -0
  45. package/dist/config-store.js +45 -0
  46. package/dist/core/audit/log-integrity.js +3 -1
  47. package/dist/core/auth/agent-delegation.js +133 -36
  48. package/dist/core/auth/debug-context.js +139 -0
  49. package/dist/core/auth/delegation-diagnostics.js +414 -0
  50. package/dist/core/auth/operation-authorizer.js +25 -2
  51. package/dist/core/auth/operation-catalog.js +49 -0
  52. package/dist/core/background-session/session-cleanup.js +212 -0
  53. package/dist/core/background-session/types.js +10 -0
  54. package/dist/core/bootstrap-messages.js +1 -1
  55. package/dist/core/bootstrap-service.js +6 -3
  56. package/dist/core/command/agent-control.js +18 -26
  57. package/dist/core/command/command-handler.js +197 -27
  58. package/dist/core/command/daemon-diagnostics.js +1 -0
  59. package/dist/core/command/memory-slash.js +6 -2
  60. package/dist/core/command/menu-catalog.js +51 -1
  61. package/dist/core/command/menu-handler.js +114 -23
  62. package/dist/core/command/menu-protocol.js +25 -1
  63. package/dist/core/command/skill-slash.js +322 -0
  64. package/dist/core/command/slash-gate.js +6 -4
  65. package/dist/core/command/slash-handler.js +195 -13
  66. package/dist/core/command/topic-slash.js +528 -0
  67. package/dist/core/daemon-bootstrap.js +4 -7
  68. package/dist/core/evolagent-registry.js +20 -5
  69. package/dist/core/file-integrity/read.js +61 -0
  70. package/dist/core/file-integrity/store.js +262 -0
  71. package/dist/core/handoff/runtime.js +5 -11
  72. package/dist/core/handoff/store.js +5 -0
  73. package/dist/core/ipc-registration.js +175 -3
  74. package/dist/core/memory/batch-meta.js +132 -0
  75. package/dist/core/memory/consolidation-context.js +83 -29
  76. package/dist/core/memory/consolidation-ops-records.js +49 -16
  77. package/dist/core/memory/consolidation-queue-format.js +21 -0
  78. package/dist/core/memory/consolidation.js +264 -205
  79. package/dist/core/memory/index-manager.js +19 -28
  80. package/dist/core/memory/memory-admin.js +19 -14
  81. package/dist/core/memory/memory-document.js +53 -9
  82. package/dist/core/memory/memory-writer.js +30 -15
  83. package/dist/core/message/aun-ipc-sender.js +161 -6
  84. package/dist/core/message/im-renderer.js +169 -10
  85. package/dist/core/message/message-bridge.js +15 -4
  86. package/dist/core/message/message-log.js +1 -0
  87. package/dist/core/message/peer-mode.js +6 -1
  88. package/dist/core/message/response-engine.js +929 -110
  89. package/dist/core/message/subtask-tool-projection.js +140 -0
  90. package/dist/core/model/catalog-service.js +17 -12
  91. package/dist/core/permission/ec-command-parser.js +217 -9
  92. package/dist/core/permission/protected-paths.js +5 -1
  93. package/dist/core/permission/readonly-shell-query.js +43 -5
  94. package/dist/core/permission/session-path-grants.js +334 -0
  95. package/dist/core/permission/tool-canonical.js +226 -0
  96. package/dist/core/permission/tool-policy.js +30 -62
  97. package/dist/core/role/runtime-policy.js +21 -10
  98. package/dist/core/session/session-fs-store.js +123 -8
  99. package/dist/core/session/session-idle-monitor.js +249 -22
  100. package/dist/core/session/session-manager.js +129 -14
  101. package/dist/core/skill/command-parser.js +13 -0
  102. package/dist/core/skill/index-generator.js +21 -4
  103. package/dist/core/skill/startup.js +41 -0
  104. package/dist/core/skill/switch-resolver.js +55 -29
  105. package/dist/core/system-session/grants.js +40 -0
  106. package/dist/core/system-session/memory.js +222 -0
  107. package/dist/core/system-session/registry.js +74 -0
  108. package/dist/core/system-session/types.js +14 -0
  109. package/dist/core/thread/command-parser.js +125 -0
  110. package/dist/core/thread/index.js +1 -0
  111. package/dist/core/thread/ipc-operation.js +95 -0
  112. package/dist/core/thread/thread-context-storage.js +131 -0
  113. package/dist/core/thread/thread-operation.js +258 -0
  114. package/dist/core/thread/thread-route.js +33 -0
  115. package/dist/core/thread/thread-service.js +770 -0
  116. package/dist/core/thread/types.js +1 -0
  117. package/dist/eck/kit-renderer.js +7 -2
  118. package/dist/eck/manifest-engine.js +24 -4
  119. package/dist/eck/message-renderer.js +35 -0
  120. package/dist/index.js +19 -7
  121. package/dist/ipc.js +140 -50
  122. package/dist/paths.js +93 -0
  123. package/dist/response-system/engines/v1/proactive-flow.js +169 -80
  124. package/dist/stats/billing.js +20 -8
  125. package/dist/trigger/authorization.js +57 -28
  126. package/dist/trigger/feedback.js +13 -2
  127. package/dist/trigger/parser.js +70 -1
  128. package/dist/trigger/system-definitions.js +3 -3
  129. package/dist/trigger/validation.js +14 -0
  130. package/dist/utils/cross-platform.js +11 -2
  131. package/dist/utils/error-dict.json +18 -4
  132. package/dist/utils/error-utils.js +143 -1
  133. package/dist/utils/instance-registry.js +13 -8
  134. package/dist/utils/log-writer.js +12 -0
  135. package/dist/utils/process-introspect.js +11 -0
  136. package/kits/docs/context-assembly.md +1 -1
  137. package/kits/docs/evolcore/INDEX.md +1 -0
  138. package/kits/docs/evolcore/agent-create.md +40 -28
  139. package/kits/docs/evolcore/agent.md +21 -6
  140. package/kits/docs/evolcore/config.md +2 -2
  141. package/kits/docs/evolcore/ctl.md +2 -2
  142. package/kits/docs/evolcore/ec-capabilities.md +5 -1
  143. package/kits/docs/evolcore/fs-architecture.md +4 -5
  144. package/kits/docs/evolcore/fs.md +1 -1
  145. package/kits/docs/evolcore/group-collaboration.md +4 -6
  146. package/kits/docs/evolcore/group.md +11 -12
  147. package/kits/docs/evolcore/memory-architecture.md +4 -5
  148. package/kits/docs/evolcore/memory-consolidation.md +19 -15
  149. package/kits/docs/evolcore/memory-tags.md +3 -1
  150. package/kits/docs/evolcore/memory.md +24 -12
  151. package/kits/docs/evolcore/menu-exec-schema-commands.md +2 -2
  152. package/kits/docs/evolcore/menu-protocol.md +7 -0
  153. package/kits/docs/evolcore/model.md +16 -0
  154. package/kits/docs/evolcore/msg.md +8 -11
  155. package/kits/docs/evolcore/thread-chain.md +267 -0
  156. package/kits/docs/prompt-loading-architecture.md +1 -1
  157. package/kits/eck_manifest.json +55 -40
  158. package/kits/eck_manifest.memory.json +0 -12
  159. package/kits/eck_message_manifest.json +169 -10
  160. package/kits/rules/01-overview.md +11 -0
  161. package/kits/rules/03-identity.md +6 -3
  162. package/kits/rules/05-venue.md +1 -1
  163. package/kits/rules/06-channel.md +1 -1
  164. package/kits/rules/07-memory-and-thread.md +199 -0
  165. package/kits/schemas/_meta.json +7 -1
  166. package/kits/schemas/agent-config.schema.13.json +1 -1
  167. package/kits/schemas/daemon.schema.7.json +140 -0
  168. package/kits/schemas/relation-config.schema.9.json +18 -3
  169. package/kits/schemas/role-config.schema.1.json +6 -6
  170. package/kits/schemas/single-session.schema.1.json +6 -6
  171. package/kits/schemas/single-session.schema.2.json +6 -6
  172. package/kits/schemas/single-session.schema.3.json +6 -6
  173. package/kits/skills/.switches.json +12 -1
  174. package/kits/skills/ec-group/SKILL.md +234 -0
  175. package/kits/skills/ec-skill/SKILL.md +10 -1
  176. package/kits/skills/ec-skill/scripts/cli.mjs +16 -2
  177. package/kits/skills/ec-skill/scripts/skill.mjs +1 -1
  178. package/kits/skills/scrapling-article-fetch/SKILL.md +128 -0
  179. package/kits/skills/scrapling-article-fetch/scripts/check_python_env.py +54 -0
  180. package/kits/skills/scrapling-article-fetch/scripts/render_markdown_reply.py +35 -0
  181. package/kits/skills/scrapling-article-fetch/scripts/run_pipeline.sh +16 -0
  182. package/kits/skills/scrapling-article-fetch/scripts/scrapling_fetch.py +344 -0
  183. package/kits/skills/thread-ops/SKILL.md +492 -0
  184. package/kits/skills/wechat-article-media-download/SKILL.md +65 -0
  185. package/kits/skills/wechat-article-media-download/scripts/download.py +163 -0
  186. package/kits/skills/wechat-article-reader/SKILL.md +97 -0
  187. package/kits/skills/wechat-article-reader/scripts/fetch.mjs +256 -0
  188. package/kits/templates/bootstrap-welcome.md +7 -7
  189. package/kits/templates/memory-consolidation/forget.md +7 -2
  190. package/kits/templates/memory-consolidation/generalize.md +9 -4
  191. package/kits/templates/memory-consolidation/notes.md +11 -11
  192. package/kits/templates/memory-consolidation/profile.md +8 -3
  193. package/kits/templates/memory-consolidation/promote.md +8 -3
  194. package/kits/templates/memory-consolidation/workflow.md +6 -6
  195. package/kits/templates/message-fragments/handoff-request-to-target.md +1 -1
  196. package/kits/templates/message-fragments/item.md +2 -1
  197. package/kits/templates/message-fragments/thread-context.md +6 -0
  198. package/kits/templates/message-fragments/trigger-session.md +2 -0
  199. package/kits/templates/message-fragments/trigger-thread-check.md +2 -0
  200. package/kits/templates/roles/member.json +2 -0
  201. package/kits/templates/roles/visitor.json +2 -0
  202. package/kits/templates/system-fragments/bootstrap.md +67 -72
  203. package/kits/templates/system-fragments/channel.md +3 -6
  204. package/kits/templates/system-fragments/commands.md +1 -0
  205. package/kits/templates/system-fragments/managed-runtime.md +15 -0
  206. package/kits/templates/system-fragments/memory.md +16 -9
  207. package/kits/templates/system-fragments/reply.md +7 -6
  208. package/kits/templates/system-fragments/session.md +5 -14
  209. package/package.json +6 -3
  210. package/dist/core/memory/memory-session-policy.js +0 -41
  211. package/dist/core/memory/memory-signature.js +0 -257
  212. package/kits/docs/evolcore/group-fs.md +0 -17
  213. package/kits/docs/venues/aun-group.md +0 -45
  214. package/kits/docs/venues/group.md +0 -43
@@ -3,6 +3,8 @@ import path from 'path';
3
3
  import { parseDocument } from 'yaml';
4
4
  import { resolveSkills } from './switch-resolver.js';
5
5
  import { logger } from '../../utils/logger.js';
6
+ import { isDevMode } from '../../paths.js';
7
+ import { isShippedPath, readVerifiedText } from '../file-integrity/read.js';
6
8
  /**
7
9
  * 生成索引文件(汇总 → 分组 → 落两文件)。
8
10
  *
@@ -42,6 +44,9 @@ function generateOneIndex(indexDir, filename, skills, label) {
42
44
  const sep = path.sep;
43
45
  const header = `[skills-${label}] 本 Agent 已启用 ${skills.length} 个${label === 'builtin' ? '内置' : '本机'}技能。
44
46
 
47
+ 加载方式: EC 技能用 Read 工具读取上面对应的 SKILL.md 全文后按内容执行;
48
+ 它们不是宿主工具自带的技能或斜杠命令,用 Skill/斜杠命令加载会报 Unknown skill/未知命令。
49
+
45
50
  路径规则:
46
51
  根目录: ${rootDir}
47
52
  技能目录 = 根目录 + "${sep}" + 技能名
@@ -65,11 +70,22 @@ function generateOneIndex(indexDir, filename, skills, label) {
65
70
  *
66
71
  * 直接按目录读,不走 discoverSkillDirectories——后者按父目录扫描再按 name 匹配,
67
72
  * 同名技能(本机覆盖内置)会读到另一份的 description。
73
+ *
74
+ * 内置技能是随包发布的内容:安装模式下按台账校验,带外改动会被恢复成登记版本,
75
+ * 恢复不了就不进索引(返回空描述),与 ECK 文档同一套策略。开发模式下不校验,
76
+ * agent 随手改技能可以立刻生效。
77
+ *
78
+ * 注意:这里保护的是索引里那行描述。模型拿到索引后是用 Read 工具读 SKILL.md 正文的,
79
+ * 那条路径不经过本函数,目前不受台账保护(等单独的 SKILL.md 加载工具接手)。
68
80
  */
69
- function readSkillDescription(skillDir) {
81
+ export function readSkillDescription(skillDir) {
82
+ const file = path.join(skillDir, 'SKILL.md');
83
+ const verify = isShippedPath(file) && !isDevMode();
70
84
  try {
71
- const text = fs.readFileSync(path.join(skillDir, 'SKILL.md'), 'utf-8');
72
- const match = text.match(/^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/);
85
+ const checked = readVerifiedText(file, { verify, tag: 'skill' });
86
+ if (!checked)
87
+ return '';
88
+ const match = checked.text.match(/^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/);
73
89
  if (!match)
74
90
  return '';
75
91
  const doc = parseDocument(match[1]);
@@ -78,7 +94,8 @@ function readSkillDescription(skillDir) {
78
94
  const meta = doc.toJS();
79
95
  return typeof meta?.description === 'string' ? meta.description.trim() : '';
80
96
  }
81
- catch {
97
+ catch (err) {
98
+ logger.warn(`[skill] read description failed path=${file} ${err instanceof Error ? err.message : String(err)}`);
82
99
  return '';
83
100
  }
84
101
  }
@@ -2,6 +2,28 @@ import { generateSkillIndexes } from './index-generator.js';
2
2
  import { globalSkillWatcher } from './global-watcher.js';
3
3
  import { logger } from '../../utils/logger.js';
4
4
  import { invalidateKitCache } from '../../eck/kit-renderer.js';
5
+ import { isSkillWatchEnabled } from '../../config-store.js';
6
+ /**
7
+ * 监控开关(daemon.json `skills.watch`,默认 false),daemon 启动时读一次。
8
+ *
9
+ * 关闭时不创建任何 chokidar watcher:Windows 上 chokidar 每个文件/目录各占一个
10
+ * 目录句柄(本机实测 ~/.evolcore/skills 约 3400 个),子目录因此无法被资源管理器
11
+ * 删除或重命名。关闭后改完技能需执行 `ec skill refresh <aid>`(refreshSkillIndex)。
12
+ * 监控与手动刷新互不冲突,打开开关后两者可同时使用。
13
+ */
14
+ let watchEnabled;
15
+ function skillWatchEnabled() {
16
+ if (watchEnabled === undefined) {
17
+ try {
18
+ watchEnabled = isSkillWatchEnabled();
19
+ }
20
+ catch (e) {
21
+ logger.warn(`[skill] 读取 skills.watch 失败,按关闭处理: ${e instanceof Error ? e.message : String(e)}`);
22
+ watchEnabled = false;
23
+ }
24
+ }
25
+ return watchEnabled;
26
+ }
5
27
  /**
6
28
  * daemon 启动时,启动全局监控器。
7
29
  *
@@ -11,6 +33,10 @@ import { invalidateKitCache } from '../../eck/kit-renderer.js';
11
33
  * - agent 收到消息需要组装系统上下文时,检查并按需生成索引
12
34
  */
13
35
  export function startSkillWatcher() {
36
+ if (!skillWatchEnabled()) {
37
+ logger.info('[skill] 技能目录监控已关闭(daemon.json skills.watch=false),改动技能后请执行 ec skill refresh <aid>');
38
+ return;
39
+ }
14
40
  // 启动全局监控器(监控内置和本机技能目录)
15
41
  globalSkillWatcher.start();
16
42
  // 注册防抖回调:内置和本机技能变化时标记所有 agent 为脏并失效缓存
@@ -36,6 +62,8 @@ export function startSkillWatcher() {
36
62
  * 重复调用会 re-arm debounce timer 造成 2 秒自激循环。
37
63
  */
38
64
  export function watchAgentSkills(aid) {
65
+ if (!skillWatchEnabled())
66
+ return;
39
67
  globalSkillWatcher.watchAgent(aid);
40
68
  globalSkillWatcher.onDebouncedChange(`agent:${aid}`, () => {
41
69
  logger.info(`[skill] Agent ${aid} 技能目录有变化,标记为脏状态并失效缓存`);
@@ -65,6 +93,19 @@ export function ensureSkillIndexUpToDate(agentDir, aid) {
65
93
  }
66
94
  }
67
95
  }
96
+ /**
97
+ * 手动刷新指定 agent 的技能索引(`ec skill refresh <aid>` 的 daemon 侧实现)。
98
+ *
99
+ * 与懒加载入口不同:无条件重建,失败直接抛出(由 IPC 返回给调用方),
100
+ * 成功后清掉该 agent 的脏标记,避免下一轮对话再重建一次。
101
+ */
102
+ export function refreshSkillIndex(agentDir, aid) {
103
+ generateSkillIndexes(agentDir);
104
+ globalSkillWatcher.markAgentChecked(aid);
105
+ globalSkillWatcher.markAgentClean(aid);
106
+ invalidateKitCache();
107
+ logger.info(`[skill] 已手动刷新 agent ${aid} 的技能索引`);
108
+ }
68
109
  /**
69
110
  * 停止全局监控器(daemon 关闭时调用)
70
111
  */
@@ -2,6 +2,58 @@ import fs from 'fs';
2
2
  import path from 'path';
3
3
  import { getPackageRoot } from '../../paths.js';
4
4
  import { resolveRoot } from '../../paths.js';
5
+ /**
6
+ * 覆盖链求值:给定技能名与三级开关,判定最终是否启用、由谁决定。
7
+ *
8
+ * `resolveSkills`(只要启用集合)与 `resolveAllSkillStates`(要全量+归因)
9
+ * 共用这一份实现,避免两条路径对同一条覆盖链给出不同答案。
10
+ */
11
+ function evaluateSkillSwitch(name, sets) {
12
+ const b = getSwitch(sets.builtin, name);
13
+ const hasBuiltinLock = b.locked === true;
14
+ if (hasBuiltinLock) {
15
+ return { enabled: b.enabled === true, decidedBy: 'builtin(locked)', locked: true, overridable: false };
16
+ }
17
+ if (name in sets.agent) {
18
+ return { enabled: getSwitch(sets.agent, name).enabled === true, decidedBy: 'agent', locked: false, overridable: true };
19
+ }
20
+ if (name in sets.local) {
21
+ return { enabled: getSwitch(sets.local, name).enabled === true, decidedBy: 'local', locked: false, overridable: true };
22
+ }
23
+ if (name in sets.builtin) {
24
+ return { enabled: b.enabled === true, decidedBy: 'builtin', locked: false, overridable: true };
25
+ }
26
+ // 三级都没有显式开关 → 缺省禁用(装完必须显式启用)
27
+ return { enabled: false, decidedBy: 'default(缺省禁用)', locked: false, overridable: true };
28
+ }
29
+ /** 读取三级开关文件 */
30
+ function readSkillSwitchSets(paths) {
31
+ return {
32
+ builtin: readSwitchesFile(path.join(paths.builtin, '.switches.json')),
33
+ local: readSwitchesFile(path.join(paths.local, '.switches.json')),
34
+ agent: readSwitchesFile(path.join(paths.agent, '.switches.json')),
35
+ };
36
+ }
37
+ /**
38
+ * 全量解析:返回所有已安装技能(含未启用),带开关归因。
39
+ *
40
+ * 与 `resolveSkills` 的区别是保留禁用技能——斜杠菜单和 `ec skill list`
41
+ * 需要展示"装了但没开"的技能,否则用户看不出为什么某个技能没生效。
42
+ */
43
+ export function resolveAllSkillStates(agentDir, override) {
44
+ const paths = getSkillPaths(agentDir, override);
45
+ const builtinNames = scanSkillNames(paths.builtin);
46
+ const localNames = scanSkillNames(paths.local);
47
+ const sets = readSkillSwitchSets(paths);
48
+ const out = [];
49
+ for (const name of new Set([...builtinNames, ...localNames])) {
50
+ // 本机有 SKILL.md 即算本机技能(本机覆盖内置)
51
+ const source = localNames.has(name) ? 'local' : 'builtin';
52
+ const skillDir = path.join(source === 'local' ? paths.local : paths.builtin, name);
53
+ out.push({ name, source, skillDir, ...evaluateSkillSwitch(name, sets) });
54
+ }
55
+ return out.sort((a, b) => a.name.localeCompare(b.name));
56
+ }
5
57
  function getSkillPaths(agentDir, override) {
6
58
  return {
7
59
  builtin: override?.builtin ?? path.join(getPackageRoot(), 'kits', 'skills'),
@@ -73,41 +125,15 @@ export function resolveSkills(agentDir, override) {
73
125
  const localNames = scanSkillNames(paths.local);
74
126
  const allNames = new Set([...builtinNames, ...localNames]);
75
127
  // ② 读取三级汇总开关文件
76
- const builtinSwitches = readSwitchesFile(path.join(paths.builtin, '.switches.json'));
77
- const localSwitches = readSwitchesFile(path.join(paths.local, '.switches.json'));
78
- const agentSwitches = readSwitchesFile(path.join(paths.agent, '.switches.json'));
128
+ const sets = readSkillSwitchSets(paths);
79
129
  const results = [];
80
130
  for (const name of allNames) {
81
131
  // ③ 判断来源:本机有 SKILL.md → local,否则 builtin
82
132
  const source = localNames.has(name) ? 'local' : 'builtin';
83
133
  const skillRoot = source === 'local' ? paths.local : paths.builtin;
84
134
  const skillDir = path.join(skillRoot, name);
85
- // ④ 覆盖链求值(design §4.1)
86
- const builtinSwitch = getSwitch(builtinSwitches, name);
87
- // 内置 locked 时,直接由内置 enabled 决定
88
- if (builtinSwitch.locked) {
89
- if (builtinSwitch.enabled) {
90
- results.push({ name, source, enabled: true, skillDir });
91
- }
92
- continue;
93
- }
94
- // 否则从 Agent 级往下找第一个存在的开关
95
- const agentSwitch = getSwitch(agentSwitches, name);
96
- if (name in agentSwitches) {
97
- if (agentSwitch.enabled) {
98
- results.push({ name, source, enabled: true, skillDir });
99
- }
100
- continue;
101
- }
102
- const localSwitch = getSwitch(localSwitches, name);
103
- if (name in localSwitches) {
104
- if (localSwitch.enabled) {
105
- results.push({ name, source, enabled: true, skillDir });
106
- }
107
- continue;
108
- }
109
- // 三级都没有显式开关 → 用缺省(enabled:false,装完必须显式启用)
110
- if (builtinSwitch.enabled) {
135
+ // ④ 覆盖链求值(与 resolveAllSkillStates 共用)
136
+ if (evaluateSkillSwitch(name, sets).enabled) {
111
137
  results.push({ name, source, enabled: true, skillDir });
112
138
  }
113
139
  }
@@ -0,0 +1,40 @@
1
+ /**
2
+ * system-session/grants.ts — 写入目标与模块声明的可写文件逐个精确比对
3
+ *
4
+ * 输入只有 canonicalToolCall 给出的写入目标和 grants().writeFiles,不读任何 runner 的工具入参。
5
+ */
6
+ import fs from 'fs';
7
+ import { isSameOrDescendant, resolveProtectedCandidateWithoutFinalSymlink, } from '../permission/protected-paths.js';
8
+ /**
9
+ * 每个写入目标都必须正好是某个声明的可写文件:
10
+ * - 祖先目录按 realpath 解析、最后一段按字面比较(目标若是符号链接,比较的是链接本身,不跟随);
11
+ * - 目标已存在时必须是普通文件且只有一个硬链接,链接、目录、硬链接一律拒绝;
12
+ * - 相对路径按会话 cwd 解析。目标列表为空表示取不到路径,按拒绝处理。
13
+ */
14
+ export function checkWriteTargets(targets, writeFiles, cwd) {
15
+ if (targets.length === 0)
16
+ return { ok: false, reason: 'no-target' };
17
+ const allowed = writeFiles.map(file => resolveProtectedCandidateWithoutFinalSymlink(file));
18
+ for (const target of targets) {
19
+ const resolved = resolveProtectedCandidateWithoutFinalSymlink(target, cwd);
20
+ if (!allowed.some(file => samePath(resolved, file))) {
21
+ return { ok: false, reason: 'outside-grant', path: resolved };
22
+ }
23
+ let stat;
24
+ try {
25
+ stat = fs.lstatSync(resolved);
26
+ }
27
+ catch (error) {
28
+ // 尚未创建是正常情况;其余错误(权限、父路径是文件等)一律按拒绝处理。
29
+ if (error.code === 'ENOENT')
30
+ continue;
31
+ return { ok: false, reason: 'not-regular-file', path: resolved };
32
+ }
33
+ if (!stat.isFile() || stat.nlink > 1)
34
+ return { ok: false, reason: 'not-regular-file', path: resolved };
35
+ }
36
+ return { ok: true };
37
+ }
38
+ function samePath(a, b) {
39
+ return isSameOrDescendant(a, b) && isSameOrDescendant(b, a);
40
+ }
@@ -0,0 +1,222 @@
1
+ /**
2
+ * system-session/memory.ts — 记忆整理会话模块
3
+ *
4
+ * 写入只有两条路:记忆走 `ec memory create|update|archive`;整理反馈写 B/scratch/feedback.txt,
5
+ * 这是本会话唯一的可写文件(见 memoryGrants)。其余路径全部只读:
6
+ *
7
+ * B = 批次目录 R = relations/<peer>/memory A = personal/memory
8
+ * O = memory/owner/<owner> M = 本对话 messages.jsonl(单文件)
9
+ *
10
+ * notes B, R/processing, R/consolidated, R/MEMORY.md, M
11
+ * generalize B, A/consolidated, A/MEMORY.md, R/consolidated, M
12
+ * promote B, O/consolidated, O/MEMORY.md, R/consolidated, M
13
+ * profile B, R/consolidated, R/MEMORY.md, M
14
+ * forget B, X/consolidated, X/MEMORY.md(X=触发层级),X=relation 时加 M
15
+ *
16
+ * 详见 docs/memory/system-session-plugin-design.md §5-§8
17
+ */
18
+ import path from 'path';
19
+ import { resolveRoot } from '../../paths.js';
20
+ import { parseCodexToolCommand } from '../permission/ec-command-parser.js';
21
+ import { canonicalToolCall } from '../permission/tool-canonical.js';
22
+ import { checkReadonly } from '../permission/tool-policy.js';
23
+ import { CONSOLIDATION_MODES } from '../memory/consolidation-context.js';
24
+ import { feedbackFilePath } from '../memory/consolidation-ops-records.js';
25
+ import { checkWriteTargets } from './grants.js';
26
+ export const MEMORY_SESSION_DENY_MESSAGE = '整理会话不允许执行此命令';
27
+ const MEMORY_MANIFEST = 'eck_manifest.memory.json';
28
+ const ALLOWED_MEMORY_SUBCOMMANDS = new Set(['create', 'update', 'archive']);
29
+ const BASH_COMMAND_RULE = 'Bash 只允许单条 `ec memory create|update|archive`,或落在本次整理目录内的只读查询'
30
+ + '(不能拼接其他命令;正文含特殊字符时用单引号包住)。读文件请用 Read / Glob / Grep;'
31
+ + '整理反馈用文件写入工具写到任务书指定的反馈文件。';
32
+ function layerDir(layer, ctx) {
33
+ const root = resolveRoot();
34
+ if (layer === 'agent')
35
+ return path.join(root, 'agents', ctx.selfAid, 'personal', 'memory');
36
+ if (layer === 'relation') {
37
+ return ctx.peerKey ? path.join(root, 'agents', ctx.selfAid, 'relations', ctx.peerKey, 'memory') : undefined;
38
+ }
39
+ return ctx.ownerAid ? path.join(root, 'memory', 'owner', ctx.ownerAid) : undefined;
40
+ }
41
+ /** 本模式的可读根。缺少输入事实(如无 peerKey)的条目直接不给,fail-closed。 */
42
+ export function memoryReadRoots(ctx) {
43
+ const roots = [];
44
+ const add = (...values) => {
45
+ for (const value of values)
46
+ if (value && path.isAbsolute(value))
47
+ roots.push(value);
48
+ };
49
+ const consolidated = (layer) => {
50
+ const dir = layerDir(layer, ctx);
51
+ return dir ? [path.join(dir, 'consolidated'), path.join(dir, 'MEMORY.md')] : [];
52
+ };
53
+ const relation = layerDir('relation', ctx);
54
+ const messages = ctx.chatDir ? path.join(ctx.chatDir, 'messages.jsonl') : undefined;
55
+ // 批次目录已覆盖反馈文件,不必单独列出
56
+ add(ctx.batchDir);
57
+ switch (ctx.subtype) {
58
+ case 'notes':
59
+ add(relation && path.join(relation, 'processing'), ...consolidated('relation'), messages);
60
+ break;
61
+ case 'generalize':
62
+ add(...consolidated('agent'), relation && path.join(relation, 'consolidated'), messages);
63
+ break;
64
+ case 'promote':
65
+ add(...consolidated('owner'), relation && path.join(relation, 'consolidated'), messages);
66
+ break;
67
+ case 'profile':
68
+ add(...consolidated('relation'), messages);
69
+ break;
70
+ case 'forget':
71
+ if (ctx.memoryScope) {
72
+ add(...consolidated(ctx.memoryScope));
73
+ if (ctx.memoryScope === 'relation')
74
+ add(messages);
75
+ }
76
+ break;
77
+ default:
78
+ break;
79
+ }
80
+ return [...new Set(roots)];
81
+ }
82
+ /** 文件权限声明:读按路径矩阵,写只有本批次的反馈文件一个。缺批次目录时不给任何可写文件。 */
83
+ export function memoryGrants(ctx) {
84
+ return {
85
+ readRoots: memoryReadRoots(ctx),
86
+ writeFiles: ctx.batchDir && path.isAbsolute(ctx.batchDir) ? [feedbackFilePath(ctx.batchDir)] : [],
87
+ };
88
+ }
89
+ function deny(code, reason) {
90
+ return { allow: false, code, reason: `${MEMORY_SESSION_DENY_MESSAGE}:${reason}` };
91
+ }
92
+ /**
93
+ * 把 shell 解析失败翻译成可照做的补救动作。
94
+ *
95
+ * `ec memory create … "……"` 因正文含反引号被拒时,泛化文案会把病因指向"拼接了别的命令",
96
+ * 真正的病因是正文的引号选错了,所以按 issue 精确归因。
97
+ */
98
+ function shellIssueRemedy(issue) {
99
+ switch (issue) {
100
+ case 'unsafe-expansion':
101
+ return '自由文本参数(--desc、正文、--keywords)里的反引号或 `$` 出现在双引号内,'
102
+ + '会被 shell 当作命令替换或变量展开,所以无法确认它实际会执行什么。'
103
+ + '请把这些参数改用单引号包住(如 --desc \'……\')。'
104
+ + '注意单引号内部不能再出现单引号本身,正文可以跨多行。';
105
+ case 'invalid-quote':
106
+ return '单引号包裹的正文里出现了单引号本身,导致引号提前闭合、命令无法解析。'
107
+ + '正文里不要写半角撇号(ASCII 0x27):改用 `ʼ`(U+02BC)或 `'`(U+FF07)代替,'
108
+ + '或换一种说法避开这个字符。不要尝试转义——'
109
+ + '单引号内无法转义单引号,转义与拼接一律被拒。';
110
+ case 'structural-newline':
111
+ return '命令里存在未闭合的换行结构。把正文放进单引号内即可直接换行。';
112
+ case 'shell-composition':
113
+ return '命令里含有 shell 拼接、管道或重定向(`;` `&&` `|` `$()` 等)。整理会话只允许单条 ec memory 命令。';
114
+ case 'unsupported-escape':
115
+ return '命令里使用了不受支持的转义写法。请改用单引号包住正文,避免转义。';
116
+ case 'lossy-cmd-shim':
117
+ return '命令经过 Windows cmd 包装后无法无歧义还原。请把正文改成不含换行的单条命令。';
118
+ case 'untrusted-shell':
119
+ return '命令带有不受信任的 shell 包装。请直接写 `ec memory …`,不要经由其他 shell 转发。';
120
+ default:
121
+ return undefined;
122
+ }
123
+ }
124
+ function looksLikeMemoryCommand(command) {
125
+ return /^\s*ec(?:\.cmd|\.exe|\.ps1)?\s+memory\s+\S/.test(command);
126
+ }
127
+ /** 只读路径判定:复用 checkReadonly,可读根只来自 memoryGrants。 */
128
+ function checkReadWithinMatrix(toolName, input, ctx) {
129
+ return checkReadonly(toolName, input, ctx.projectPath, {
130
+ sessionId: ctx.sessionId,
131
+ selfAid: ctx.selfAid,
132
+ managedTempDir: ctx.managedTempDir,
133
+ grantedReadRoots: memoryGrants(ctx).readRoots,
134
+ excludeWorkspace: true,
135
+ allowProtectedMetadata: false,
136
+ });
137
+ }
138
+ /** 写入判定只看 canonicalToolCall 给出的目标,不读工具入参。 */
139
+ function checkWrite(call, ctx) {
140
+ // shell 里的 apply_patch 跑的是 runner 自带的补丁器还是任意可执行文件,取决于 runner;
141
+ // 需要它的 runner 由适配层先转成 FileChange 再交给模块,模块这里一律不认。
142
+ if (call.via === 'shell') {
143
+ return deny('memory_session_shell_write_forbidden', '不能经由 shell 写文件,整理反馈请用文件写入工具写到任务书指定的反馈文件');
144
+ }
145
+ const { writeFiles } = memoryGrants(ctx);
146
+ if (writeFiles.length === 0) {
147
+ return deny('memory_session_write_no_target', '当前会话缺少批次目录,没有可写的反馈文件');
148
+ }
149
+ const result = checkWriteTargets(call.paths, writeFiles, ctx.projectPath);
150
+ if (result.ok)
151
+ return { allow: true };
152
+ const reason = result.reason === 'no-target'
153
+ ? '无法确定写入目标'
154
+ : result.reason === 'not-regular-file'
155
+ ? `写入目标不是普通文件(链接、目录或硬链接):${result.path}`
156
+ : `只能写反馈文件 ${writeFiles[0]},不能写 ${result.path}`;
157
+ return deny('memory_session_write_outside_grant', reason);
158
+ }
159
+ function isAllowedMemoryCommand(argv) {
160
+ return argv[0] === 'ec' && argv[1] === 'memory' && ALLOWED_MEMORY_SUBCOMMANDS.has(argv[2] ?? '');
161
+ }
162
+ function checkShell(input, ctx) {
163
+ const parsed = parseCodexToolCommand(input);
164
+ if (parsed.ok && isAllowedMemoryCommand(parsed.argv))
165
+ return { allow: true, ecCommand: true };
166
+ // Codex 没有 Read 工具,读任务书只能靠可证明只读的 shell 查询
167
+ const readonly = checkReadWithinMatrix('Bash', input, ctx);
168
+ if (readonly.behavior === 'allow')
169
+ return { allow: true };
170
+ if (!parsed.ok) {
171
+ const rawCommand = typeof input.command === 'string' ? input.command : '';
172
+ const remedy = shellIssueRemedy(parsed.issue);
173
+ if (looksLikeMemoryCommand(rawCommand) && remedy) {
174
+ return deny('memory_session_command_forbidden', `这条命令本身就是 \`ec memory\`,但未能解析成单条字面命令。${remedy}`);
175
+ }
176
+ }
177
+ return deny('memory_session_command_forbidden', BASH_COMMAND_RULE);
178
+ }
179
+ function checkTool(rawToolName, input, ctx) {
180
+ const call = canonicalToolCall(rawToolName, input);
181
+ switch (call.access) {
182
+ case 'read': {
183
+ const result = checkReadWithinMatrix(call.name, input, ctx);
184
+ return result.behavior === 'allow'
185
+ ? { allow: true }
186
+ : deny(result.policyCode ?? 'memory_session_read_outside_matrix', '只能读取本次整理的批次目录、对应记忆目录与本对话流水');
187
+ }
188
+ case 'write':
189
+ // 引号内恰好出现 apply_patch 字样的 ec memory 命令可能被保守识别成 shell 写入,先按 ec 命令认定
190
+ if (call.via === 'shell') {
191
+ const parsed = parseCodexToolCommand(input);
192
+ if (parsed.ok && isAllowedMemoryCommand(parsed.argv))
193
+ return { allow: true, ecCommand: true };
194
+ }
195
+ return checkWrite(call, ctx);
196
+ case 'shell':
197
+ return checkShell(input, ctx);
198
+ default:
199
+ return deny('memory_session_tool_forbidden', `整理会话不能使用 ${rawToolName};写记忆用 \`ec memory create|update|archive\`,整理反馈写到任务书指定的反馈文件`);
200
+ }
201
+ }
202
+ function checkCommand(argv) {
203
+ if (argv[0] === 'memory' && ALLOWED_MEMORY_SUBCOMMANDS.has(argv[1] ?? ''))
204
+ return { allow: true };
205
+ return deny('memory_session_command_forbidden', '只允许 `ec memory create|update|archive`');
206
+ }
207
+ export const memorySystemSession = {
208
+ type: 'memory',
209
+ subtypes: CONSOLIDATION_MODES,
210
+ supportedRunners: ['claude', 'codex', 'ecagent'],
211
+ manifest: () => MEMORY_MANIFEST,
212
+ // 不启用 runner 自带的 OS 沙箱:整理会话要读写的路径在 $EVOLCORE_HOME 下,
213
+ // 与会话 cwd 互为兄弟目录,沙箱的「项目目录」模型套不上。边界改由 grants 单独管。
214
+ runnerLaunch: (_runner, ctx) => ({
215
+ sandbox: 'off',
216
+ runnerMode: 'readonly',
217
+ grants: memoryGrants(ctx),
218
+ }),
219
+ grants: memoryGrants,
220
+ checkTool,
221
+ checkCommand: argv => checkCommand(argv),
222
+ };
@@ -0,0 +1,74 @@
1
+ /**
2
+ * system-session/registry.ts — 系统会话模块注册表
3
+ *
4
+ * 只登记仓库内的模块,不做运行时动态加载、不读配置。
5
+ */
6
+ import { SYSTEM_SESSION_TYPES } from './types.js';
7
+ import { memorySystemSession } from './memory.js';
8
+ const MODULES = new Map([
9
+ [memorySystemSession.type, memorySystemSession],
10
+ ]);
11
+ if (SYSTEM_SESSION_TYPES.length !== MODULES.size || SYSTEM_SESSION_TYPES.some(type => !MODULES.has(type))) {
12
+ throw new Error('SYSTEM_SESSION_TYPES 与系统会话注册表不一致');
13
+ }
14
+ export function getSystemSessionModule(type) {
15
+ return MODULES.get(type);
16
+ }
17
+ export function isSystemSessionType(type) {
18
+ return !!type && MODULES.has(type);
19
+ }
20
+ /** 从 session.metadata 读出系统会话标记;存在但不合法时拒绝降级为普通会话。 */
21
+ export function readSystemSessionMarker(metadata) {
22
+ const rawValue = metadata?.systemSession;
23
+ if (rawValue === undefined)
24
+ return undefined;
25
+ if (!rawValue || typeof rawValue !== 'object' || Array.isArray(rawValue)) {
26
+ throw new Error('系统会话标记格式非法');
27
+ }
28
+ const raw = rawValue;
29
+ if (typeof raw.type !== 'string' || typeof raw.subtype !== 'string') {
30
+ throw new Error('系统会话标记缺少 type 或 subtype');
31
+ }
32
+ const module = MODULES.get(raw.type);
33
+ if (!module)
34
+ throw new Error(`未注册的系统会话类型: ${raw.type}`);
35
+ if (!module.subtypes.includes(raw.subtype)) {
36
+ throw new Error(`系统会话 ${raw.type} 不支持 subtype: ${raw.subtype}`);
37
+ }
38
+ return { type: raw.type, subtype: raw.subtype };
39
+ }
40
+ /**
41
+ * 拉起前校验:类型与 subtype 已注册、runner 受支持。
42
+ * 不合法直接抛错,调用方不应降级成普通会话。
43
+ */
44
+ export function assertSystemSessionLaunchable(marker, runner) {
45
+ const module = MODULES.get(marker.type);
46
+ if (!module)
47
+ throw new Error(`未注册的系统会话类型: ${marker.type}`);
48
+ if (!module.subtypes.includes(marker.subtype)) {
49
+ throw new Error(`系统会话 ${marker.type} 不支持 subtype: ${marker.subtype}`);
50
+ }
51
+ if (!module.supportedRunners.includes(runner)) {
52
+ throw new Error(`系统会话 ${marker.type} 不支持在 ${runner} 上运行(支持:${module.supportedRunners.join(', ')})`);
53
+ }
54
+ return module;
55
+ }
56
+ /** 从 session.metadata 构造模块输入事实。 */
57
+ export function buildSystemSessionContext(params) {
58
+ const m = params.metadata ?? {};
59
+ const str = (key) => (typeof m[key] === 'string' && m[key] ? m[key] : undefined);
60
+ const scope = str('memoryScope');
61
+ const batchDir = str('consolidationBatchDir');
62
+ return {
63
+ sessionId: params.sessionId,
64
+ subtype: params.marker.subtype,
65
+ selfAid: params.selfAid,
66
+ projectPath: params.projectPath,
67
+ ...(params.managedTempDir ? { managedTempDir: params.managedTempDir } : {}),
68
+ ...(str('peerKey') ? { peerKey: str('peerKey') } : {}),
69
+ ...(str('consolidationOwnerAid') ? { ownerAid: str('consolidationOwnerAid') } : {}),
70
+ ...(batchDir ? { batchDir } : {}),
71
+ ...(str('consolidationChatDir') ? { chatDir: str('consolidationChatDir') } : {}),
72
+ ...(scope === 'relation' || scope === 'agent' || scope === 'owner' ? { memoryScope: scope } : {}),
73
+ };
74
+ }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * system-session/types.ts — 系统会话模块接口
3
+ *
4
+ * 系统会话(记忆整理等)与角色体系无关:不带角色、不走角色权限判定。
5
+ * 路径、工具、ec 命令、manifest、runner 启动参数全部由对应类型的代码模块决定,
6
+ * 模块的结论就是最终结论,不可配置。
7
+ *
8
+ * 详见 docs/memory/system-session-plugin-design.md
9
+ */
10
+ /**
11
+ * 已注册的系统会话类型。放在无依赖的 types.ts,供 eck / config 层判断而不引入模块实现;
12
+ * registry.ts 启动时校验与注册表一致。
13
+ */
14
+ export const SYSTEM_SESSION_TYPES = ['memory'];