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
@@ -0,0 +1,262 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import crypto from 'crypto';
4
+ import { createRequire } from 'module';
5
+ import { integrityDbPath, integritySnapshotDir, resolveRoot } from '../../paths.js';
6
+ import { logger } from '../../utils/logger.js';
7
+ // 项目是 ESM,裸 require 不存在;与 codex-session-file-adapter 同款懒加载
8
+ const requireFromHere = createRequire(import.meta.url);
9
+ // ── SQLite 懒加载 ─────────────────────────────────────────────────────────────
10
+ let _db = null;
11
+ const CREATE_TABLE_SQL = `
12
+ CREATE TABLE IF NOT EXISTS file_integrity (
13
+ file_path TEXT PRIMARY KEY,
14
+ sha256 TEXT NOT NULL,
15
+ registered_at INTEGER NOT NULL,
16
+ last_verified_at INTEGER,
17
+ registrar_aid TEXT
18
+ )
19
+ `;
20
+ function getDb() {
21
+ if (_db)
22
+ return _db;
23
+ // node:sqlite (DatabaseSync) 在 Node 22.5+ 可用,懒加载兼容低版本
24
+ let DatabaseSync;
25
+ try {
26
+ ({ DatabaseSync } = requireFromHere('node:sqlite'));
27
+ }
28
+ catch {
29
+ throw new Error('[file-integrity] node:sqlite 不可用,需要 Node.js 22.5+');
30
+ }
31
+ fs.mkdirSync(path.dirname(integrityDbPath()), { recursive: true });
32
+ _db = new DatabaseSync(integrityDbPath());
33
+ // CLI 进程和 daemon 可能同时打开同一个库:先设等待,再切 WAL
34
+ _db.exec('PRAGMA busy_timeout = 5000');
35
+ _db.exec('PRAGMA journal_mode = WAL');
36
+ _db.exec(CREATE_TABLE_SQL);
37
+ return _db;
38
+ }
39
+ // ── 快照路径规范化 ────────────────────────────────────────────────────────────
40
+ /**
41
+ * 台账主键:绝对路径 + 正斜杠;Windows 文件系统大小写不敏感,再统一转小写,
42
+ * 避免 `Evol` / `evol` 这类同一文件两种写法登记成两条。
43
+ */
44
+ function recordKey(filePath) {
45
+ const normalized = path.resolve(filePath).replace(/\\/g, '/');
46
+ return process.platform === 'win32' ? normalized.toLowerCase() : normalized;
47
+ }
48
+ function snapshotPath(key) {
49
+ const rootKey = recordKey(resolveRoot());
50
+ if (key.startsWith(rootKey + '/')) {
51
+ return path.join(integritySnapshotDir(), key.slice(rootKey.length + 1));
52
+ }
53
+ // 路径在 home 外(如包内 kits/),用 key 的 sha256 命名,防路径穿越和盘符冒号
54
+ const hash = crypto.createHash('sha256').update(key).digest('hex');
55
+ return path.join(integritySnapshotDir(), hash);
56
+ }
57
+ // ── SHA-256 计算 ──────────────────────────────────────────────────────────────
58
+ function computeSha256(bytes) {
59
+ return crypto.createHash('sha256').update(bytes).digest('hex');
60
+ }
61
+ /** 快照存在且内容与台账一致,才算可恢复 */
62
+ function snapshotIntact(key, expected) {
63
+ const snap = snapshotPath(key);
64
+ return fs.existsSync(snap) && computeSha256(fs.readFileSync(snap)) === expected;
65
+ }
66
+ // ── 原子写文件(tmp → rename) ────────────────────────────────────────────────
67
+ function atomicWrite(destPath, bytes) {
68
+ // 带 pid:恢复写盘不在事务里,daemon 与 CLI 同时恢复同一文件时不能共用临时名
69
+ const tmpPath = `${destPath}.integrity-tmp-${process.pid}`;
70
+ fs.mkdirSync(path.dirname(destPath), { recursive: true });
71
+ fs.writeFileSync(tmpPath, bytes);
72
+ fs.renameSync(tmpPath, destPath);
73
+ }
74
+ // ── 日志(复用 daemon logger,固定前缀便于过滤) ─────────────────────────────
75
+ function short(hash) {
76
+ return hash.slice(0, 12);
77
+ }
78
+ // ── 核心 API ──────────────────────────────────────────────────────────────────
79
+ /**
80
+ * 登记文件到台账并备份快照。
81
+ * 在文件写入完成后由写入方调用,建立或更新可信基线。
82
+ */
83
+ export function registerFile(filePath, aid) {
84
+ const key = recordKey(filePath);
85
+ const bytes = fs.readFileSync(filePath);
86
+ registerBytes(key, bytes, aid, fs.statSync(filePath).mtime);
87
+ logger.info(`[file-integrity] registered path=${key} sha256=${short(computeSha256(bytes))}${aid ? ` aid=${aid}` : ''}`);
88
+ }
89
+ /**
90
+ * node:sqlite 没有 transaction() 包装,手写 BEGIN/COMMIT:
91
+ * 快照写失败则 ROLLBACK,台账不会指向一份不存在的快照。
92
+ */
93
+ function registerBytes(key, bytes, aid, mtime) {
94
+ const sha256 = computeSha256(bytes);
95
+ const now = Math.floor(Date.now() / 1000);
96
+ const db = getDb();
97
+ db.exec('BEGIN');
98
+ try {
99
+ db.prepare(`INSERT INTO file_integrity (file_path, sha256, registered_at, registrar_aid)
100
+ VALUES (?, ?, ?, ?)
101
+ ON CONFLICT(file_path) DO UPDATE SET
102
+ sha256 = excluded.sha256,
103
+ registered_at = excluded.registered_at,
104
+ registrar_aid = excluded.registrar_aid`).run(key, sha256, now, aid ?? null);
105
+ const snap = snapshotPath(key);
106
+ atomicWrite(snap, bytes);
107
+ // 快照带上原文件 mtime,恢复时还原回去;记忆的排序和超期判断都读 mtime
108
+ if (mtime)
109
+ fs.utimesSync(snap, mtime, mtime);
110
+ db.exec('COMMIT');
111
+ }
112
+ catch (err) {
113
+ try {
114
+ db.exec('ROLLBACK');
115
+ }
116
+ catch { /* ignore */ }
117
+ throw err;
118
+ }
119
+ }
120
+ /**
121
+ * 校验文件完整性。
122
+ *
123
+ * - 文件不存在 → missing
124
+ * - 台账无记录 → 用本次读到的字节自动登记,返回 verified
125
+ * - sha256 匹配 → verified,更新 last_verified_at
126
+ * - 不匹配且 restoreOnFail → 快照 sha256 与台账一致则原子恢复,返回 restored;否则 tampered
127
+ * - 不匹配且未要求恢复 → tampered
128
+ *
129
+ * 返回的 bytes 就是本次校验过的内容,调用方直接使用,不要再读一次文件(TOCTOU)。
130
+ */
131
+ export function verifyFile(filePath, opts) {
132
+ const key = recordKey(filePath);
133
+ if (!fs.existsSync(filePath))
134
+ return { status: 'missing' };
135
+ const bytes = fs.readFileSync(filePath);
136
+ const actual = computeSha256(bytes);
137
+ const db = getDb();
138
+ const row = db.prepare('SELECT sha256 FROM file_integrity WHERE file_path = ?').get(key);
139
+ if (!row) {
140
+ // 用已读字节登记,避免二次读文件时内容已变
141
+ registerBytes(key, bytes, undefined, fs.statSync(filePath).mtime);
142
+ logger.info(`[file-integrity] auto-registered path=${key} sha256=${short(actual)}`);
143
+ return { status: 'verified', bytes };
144
+ }
145
+ if (actual === row.sha256) {
146
+ db.prepare('UPDATE file_integrity SET last_verified_at = ? WHERE file_path = ?')
147
+ .run(Math.floor(Date.now() / 1000), key);
148
+ return { status: 'verified', bytes };
149
+ }
150
+ const snap = snapshotPath(key);
151
+ const snapExists = fs.existsSync(snap);
152
+ if (opts?.restoreOnFail && snapExists) {
153
+ const snapBytes = fs.readFileSync(snap);
154
+ if (computeSha256(snapBytes) === row.sha256) {
155
+ atomicWrite(filePath, snapBytes);
156
+ // 还原成登记时的 mtime,不让恢复动作把文件"续期"成刚写入
157
+ try {
158
+ const { mtime } = fs.statSync(snap);
159
+ fs.utimesSync(filePath, mtime, mtime);
160
+ }
161
+ catch { /* best-effort */ }
162
+ logger.warn(`[file-integrity] restored path=${key} expected=${short(row.sha256)} actual=${short(actual)}`);
163
+ return { status: 'restored', bytes: snapBytes };
164
+ }
165
+ logger.error(`[file-integrity] snapshot-corrupted path=${key}`);
166
+ return { status: 'tampered', restorable: false, expected: row.sha256, actual };
167
+ }
168
+ const restorable = snapshotIntact(key, row.sha256);
169
+ logger.warn(`[file-integrity] tampered path=${key} expected=${short(row.sha256)} actual=${short(actual)} restorable=${restorable}`);
170
+ return { status: 'tampered', restorable, expected: row.sha256, actual };
171
+ }
172
+ /** 只读查询台账记录,不读文件,供诊断和 CLI 使用。 */
173
+ export function queryRecord(filePath) {
174
+ const row = getDb().prepare('SELECT file_path, sha256, registered_at, last_verified_at, registrar_aid FROM file_integrity WHERE file_path = ?').get(recordKey(filePath));
175
+ return row ?? null;
176
+ }
177
+ /**
178
+ * 文件经正规渠道 rename 后调用:台账主键和快照一起搬到新路径,sha256 不变。
179
+ * 搬移前后的篡改都会在新路径上被校验出来。rename 后不要对新路径重新 registerFile,
180
+ * 那会把搬移前的篡改登记成新基线。
181
+ */
182
+ export function moveRecord(fromPath, toPath) {
183
+ const fromKey = recordKey(fromPath);
184
+ const toKey = recordKey(toPath);
185
+ if (fromKey === toKey)
186
+ return;
187
+ const db = getDb();
188
+ const row = db.prepare('SELECT 1 FROM file_integrity WHERE file_path = ?').get(fromKey);
189
+ if (!row) {
190
+ // 源文件从未登记过,没有可搬的基线;新路径下次 verify 时自动登记
191
+ logger.warn(`[file-integrity] move-without-record from=${fromKey} to=${toKey}`);
192
+ return;
193
+ }
194
+ const fromSnap = snapshotPath(fromKey);
195
+ const toSnap = snapshotPath(toKey);
196
+ db.exec('BEGIN');
197
+ try {
198
+ db.prepare('DELETE FROM file_integrity WHERE file_path = ?').run(toKey);
199
+ db.prepare('UPDATE file_integrity SET file_path = ? WHERE file_path = ?').run(toKey, fromKey);
200
+ if (fs.existsSync(fromSnap)) {
201
+ fs.mkdirSync(path.dirname(toSnap), { recursive: true });
202
+ fs.renameSync(fromSnap, toSnap);
203
+ }
204
+ else {
205
+ fs.rmSync(toSnap, { force: true });
206
+ }
207
+ db.exec('COMMIT');
208
+ }
209
+ catch (err) {
210
+ try {
211
+ db.exec('ROLLBACK');
212
+ }
213
+ catch { /* ignore */ }
214
+ throw err;
215
+ }
216
+ logger.info(`[file-integrity] moved from=${fromKey} to=${toKey}`);
217
+ }
218
+ /** 文件经正规渠道删除后调用:删掉台账记录和快照。 */
219
+ export function unregisterFile(filePath) {
220
+ const key = recordKey(filePath);
221
+ const result = getDb().prepare('DELETE FROM file_integrity WHERE file_path = ?').run(key);
222
+ fs.rmSync(snapshotPath(key), { force: true });
223
+ if (Number(result?.changes ?? 0) > 0)
224
+ logger.info(`[file-integrity] unregistered path=${key}`);
225
+ }
226
+ /**
227
+ * 登记一段**尚未落盘**的内容。写盘之前调用。
228
+ *
229
+ * 为什么不在写完之后登记:写方和读方可能不在同一个进程(daemon 与 CLI)。
230
+ * 若先改盘再登记,中间那个窗口里另一个进程读到的就是"新内容 + 旧台账",
231
+ * 会被判成篡改并回滚掉刚写进去的合法内容。
232
+ *
233
+ * 配置类文件不靠 mtime 判断超期,所以这里用登记时刻作快照 mtime。
234
+ */
235
+ export function registerContent(filePath, content, aid) {
236
+ const key = recordKey(filePath);
237
+ const bytes = Buffer.isBuffer(content) ? content : Buffer.from(content, 'utf-8');
238
+ registerBytes(key, bytes, aid);
239
+ }
240
+ /** 只读检查,不改台账、不恢复文件,也不更新 last_verified_at。供诊断和 CLI 使用。 */
241
+ export function checkFile(filePath) {
242
+ const key = recordKey(filePath);
243
+ if (!fs.existsSync(filePath))
244
+ return { status: 'missing' };
245
+ const actual = computeSha256(fs.readFileSync(filePath));
246
+ const row = getDb().prepare('SELECT sha256 FROM file_integrity WHERE file_path = ?').get(key);
247
+ if (!row)
248
+ return { status: 'unregistered', actual };
249
+ if (row.sha256 === actual)
250
+ return { status: 'verified', expected: row.sha256, actual };
251
+ return { status: 'tampered', expected: row.sha256, actual, restorable: snapshotIntact(key, row.sha256) };
252
+ }
253
+ /** 仅供测试:关闭并重置 SQLite 连接缓存 */
254
+ export function _resetDb() {
255
+ if (_db) {
256
+ try {
257
+ _db.close();
258
+ }
259
+ catch { /* ignore */ }
260
+ _db = null;
261
+ }
262
+ }
@@ -33,13 +33,6 @@ export class HandoffDeliveryFailedError extends Error {
33
33
  this.name = 'HandoffDeliveryFailedError';
34
34
  }
35
35
  }
36
- export class HandoffReturnPolicyRequiredError extends Error {
37
- code = 'HANDOFF_RETURN_POLICY_REQUIRED';
38
- constructor() {
39
- super('跨会话 ec msg/group send 必须显式指定 --return none|required');
40
- this.name = 'HandoffReturnPolicyRequiredError';
41
- }
42
- }
43
36
  export class TriggerHandoffRequiredForbiddenError extends Error {
44
37
  code = 'TRIGGER_HANDOFF_REQUIRED_FORBIDDEN';
45
38
  constructor() {
@@ -99,9 +92,10 @@ export class HandoffRuntime {
99
92
  throw new Error('handoff context requires a different target session');
100
93
  return { crossSession: false, targetSession: target };
101
94
  }
102
- if (!input.explicitReturnPolicy)
103
- throw new HandoffReturnPolicyRequiredError();
104
- if (input.explicitReturnPolicy === 'required' && input.causation?.trigger?.path.length) {
95
+ // --return 参数已废弃:跨会话 send 不再要求显式指定返回策略,默认 'none'。
96
+ // 内部调用方(agent-create 等)仍可显式传 'required' 走回流路径。
97
+ const effectiveReturnPolicy = input.explicitReturnPolicy ?? 'none';
98
+ if (effectiveReturnPolicy === 'required' && input.causation?.trigger?.path.length) {
105
99
  throw new TriggerHandoffRequiredForbiddenError();
106
100
  }
107
101
  const handoffCausation = deriveCausation(normalizeCausation(input.causation) ?? createRootCausation());
@@ -127,7 +121,7 @@ export class HandoffRuntime {
127
121
  delivery: targetChatType === 'group'
128
122
  ? { chatType: 'group', groupId: input.to }
129
123
  : { chatType: 'private' },
130
- returnPolicy: input.explicitReturnPolicy ?? 'required',
124
+ returnPolicy: effectiveReturnPolicy,
131
125
  originAuthorization: input.originAuthorization,
132
126
  causation: handoffCausation,
133
127
  now: this.now?.(),
@@ -1,3 +1,8 @@
1
+ /**
2
+ * @deprecated Handoff 机制已被 Thread Context 取代,见
3
+ * `docs/thread-chain/handoff-deprecation.md`。保留仅为兼容在途 handoff,
4
+ * 将在清理阶段删除。
5
+ */
1
6
  import crypto from 'crypto';
2
7
  import fs from 'fs';
3
8
  import path from 'path';
@@ -1,4 +1,5 @@
1
1
  import { TriggerDefinitionManager } from '../trigger/manager.js';
2
+ import { ensureSessionRuntimeDir } from '../cli/task-context.js';
2
3
  import { logger } from '../utils/logger.js';
3
4
  import crypto from 'crypto';
4
5
  import fs from 'fs';
@@ -6,6 +7,10 @@ import path from 'path';
6
7
  import { executeTaskResume } from './task/task-resume-operation.js';
7
8
  import { isHClassPath, isLClassPath, isSameOrDescendant } from './permission/protected-paths.js';
8
9
  import { resolveStorageCommandArgv } from '../aun/storage/index.js';
10
+ import { agentDir } from '../paths.js';
11
+ import { parsePeerKey } from './relation/peer-identity.js';
12
+ import { resolvePeerRoleDetail } from '../config/peer-role-resolver.js';
13
+ import { buildDebugTaskContext } from './auth/debug-context.js';
9
14
  import { computeBuildId } from '../utils/evolcore-version.js';
10
15
  import { loadAgent } from '../config-store.js';
11
16
  import { executeLocalInitializationCommand, hasSuppliedInitialization } from '../cli/agent-create.js';
@@ -17,7 +22,9 @@ import { registerHandoffIpc } from './handoff/ipc-registration.js';
17
22
  import { registerAunIpcSender } from './message/aun-ipc-sender.js';
18
23
  import { registerConsolidationCheckExecutor, registerConsolidationLaunchExecutor, } from './memory/consolidation.js';
19
24
  import { createTriggerBaseagentResolver, registerTriggerIpcExecutor } from '../trigger/ipc-service.js';
25
+ import { createThreadOperationExecutor } from './thread/ipc-operation.js';
20
26
  import { isLinkedInstall } from '../utils/npm-ops.js';
27
+ import { buildSystemSessionContext, getSystemSessionModule } from './system-session/registry.js';
21
28
  export function registerDaemonIpc({ ipcServer, agentRegistry, agentDelegationRegistry, sessionManager, statsCollector, aidStatsCollector, responseEngine, messageQueue, agentMap, commandHandler, channelInstances, eventBus, handoffRuntime, reloadCoordinator, agentApplicationService, agentInitializationService, enqueueConsolidation, completeAgentBootstrap, contactBinds, bindService, diagService, getProcessOwners, getControlAidState, daemonAid, triggerControlToken, triggerSchedulers, daemonTriggerOwner, primaryAgent, triggerAuthorization, }) {
22
29
  const authorizeManagedAgentOperation = createDelegationAuthorizer({
23
30
  agentDelegationRegistry,
@@ -52,6 +59,137 @@ export function registerDaemonIpc({ ipcServer, agentRegistry, agentDelegationReg
52
59
  agentMap,
53
60
  isLinkedInstall: isLinkedInstall(),
54
61
  });
62
+ if (process.env.EVOLCORE_DEBUG_MODE === '1') {
63
+ ipcServer.setDebugIssueTokenExecutor(async ({ sessionId }) => {
64
+ try {
65
+ const activeContext = responseEngine.getTaskRuntimeContext(sessionId);
66
+ let runtimeContext = activeContext;
67
+ if (!runtimeContext) {
68
+ const session = await sessionManager.getSessionById(sessionId);
69
+ if (!session)
70
+ return { ok: false, error: `session not found: ${sessionId}` };
71
+ runtimeContext = buildDebugTaskContext({
72
+ session,
73
+ getTrigger: (agentAid, triggerId) => new TriggerDefinitionManager(agentAid).get(triggerId),
74
+ sessionRuntimeDir: ensureSessionRuntimeDir(session.id),
75
+ });
76
+ }
77
+ if (!runtimeContext.selfAid)
78
+ return { ok: false, error: 'debug session has no selfAid' };
79
+ const channelType = runtimeContext.channel || 'aun';
80
+ const channel = runtimeContext.channelKey || channelType;
81
+ const peerKeyId = (() => {
82
+ try {
83
+ return parsePeerKey(runtimeContext.peerKey || '').channelId;
84
+ }
85
+ catch {
86
+ return runtimeContext.peerId || runtimeContext.channelId || '';
87
+ }
88
+ })();
89
+ const chatType = runtimeContext.chatType === 'group' ? 'group' : 'private';
90
+ const conversationId = chatType === 'group'
91
+ ? (runtimeContext.channelId || peerKeyId)
92
+ : peerKeyId;
93
+ const peerRole = resolvePeerRoleDetail({
94
+ selfAid: runtimeContext.selfAid,
95
+ channelKey: channel,
96
+ channelType,
97
+ chatType,
98
+ actorId: peerKeyId,
99
+ conversationId,
100
+ });
101
+ const token = agentDelegationRegistry.issueDebug({
102
+ sessionId,
103
+ selfAid: runtimeContext.selfAid,
104
+ channel,
105
+ channelType,
106
+ chatType,
107
+ peerKey: runtimeContext.peerKey || '',
108
+ actorId: peerKeyId || runtimeContext.selfAid,
109
+ issuedRole: peerRole.effectiveRole || 'none',
110
+ });
111
+ return { ok: true, token, runtimeContext };
112
+ }
113
+ catch (error) {
114
+ return { ok: false, error: error instanceof Error ? error.message : String(error) };
115
+ }
116
+ });
117
+ }
118
+ ipcServer.setThreadOperationExecutor(createThreadOperationExecutor({
119
+ sessionManager,
120
+ agentDelegationRegistry,
121
+ getProcessOwners,
122
+ sendSystemMsg: async (params) => {
123
+ if (ipcServer.hasAunMsgSender()) {
124
+ const sent = await ipcServer.sendAunMsg({
125
+ aid: params.from,
126
+ to: params.to,
127
+ scope: params.peerKind === 'group' ? 'group' : 'msg',
128
+ payload: { type: 'topic-created', topicName: params.topicName, threadId: params.threadId },
129
+ thread: params.threadId,
130
+ });
131
+ if (!sent?.ok)
132
+ throw new Error(sent?.error || 'Topic notification failed');
133
+ }
134
+ },
135
+ onDeleted: (aid, threadId) => {
136
+ const scheduler = triggerSchedulers.get(aid);
137
+ if (scheduler) {
138
+ const related = scheduler.list({ all: true }).filter((trigger) => trigger.origin?.threadId === threadId
139
+ || trigger.labels?.originThreadId === threadId
140
+ || trigger.labels?.targetThreadId === threadId);
141
+ for (const trigger of related) {
142
+ try {
143
+ scheduler.delete(trigger.id);
144
+ }
145
+ catch { }
146
+ }
147
+ }
148
+ },
149
+ }));
150
+ ipcServer.setSkillRefreshExecutor(async (aid, sessionId, delegationToken, delegationCommandHash) => {
151
+ const fail = (code, error) => ({ ok: false, code, error });
152
+ if (!agentRegistry.get(aid))
153
+ return fail('AGENT_NOT_FOUND', `Agent "${aid}" not found`);
154
+ if (sessionId) {
155
+ const authorization = await authorizeManagedAgentOperation({
156
+ sessionId,
157
+ delegationToken,
158
+ delegationCommandHash,
159
+ targetAid: aid,
160
+ }, 'skill.refresh');
161
+ if (!authorization.ok)
162
+ return fail(authorization.code, authorization.error);
163
+ }
164
+ try {
165
+ const { refreshSkillIndex } = await import('./skill/startup.js');
166
+ refreshSkillIndex(agentDir(aid), aid);
167
+ const skillsDir = path.join(agentDir(aid), 'skills');
168
+ return {
169
+ ok: true,
170
+ result: {
171
+ aid,
172
+ builtinIndexPath: path.join(skillsDir, '.index-builtin.md'),
173
+ localIndexPath: path.join(skillsDir, '.index-local.md'),
174
+ },
175
+ };
176
+ }
177
+ catch (error) {
178
+ return fail('INTERNAL', error?.message || String(error));
179
+ }
180
+ });
181
+ commandHandler.setSkillIndexRefresher(async (aid) => {
182
+ if (!agentRegistry.get(aid))
183
+ return { ok: false, error: `Agent "${aid}" not found` };
184
+ try {
185
+ const { refreshSkillIndex } = await import('./skill/startup.js');
186
+ refreshSkillIndex(agentDir(aid), aid);
187
+ return { ok: true };
188
+ }
189
+ catch (error) {
190
+ return { ok: false, error: error instanceof Error ? error.message : String(error) };
191
+ }
192
+ });
55
193
  registerHandoffIpc({
56
194
  ipcServer,
57
195
  agentDelegationRegistry,
@@ -61,7 +199,7 @@ export function registerDaemonIpc({ ipcServer, agentRegistry, agentDelegationReg
61
199
  handoffRuntime,
62
200
  getProcessOwners,
63
201
  });
64
- registerAunIpcSender({ ipcServer, channelInstances, agentDelegationRegistry, responseEngine, handoffRuntime });
202
+ registerAunIpcSender({ ipcServer, channelInstances, agentDelegationRegistry, responseEngine, handoffRuntime, triggerSchedulers });
65
203
  ipcServer.setAgentReloadExecutor((aid, options) => reloadCoordinator.reload(aid, options));
66
204
  registerConsolidationCheckExecutor({ ipcServer, agentRegistry, enqueueConsolidation });
67
205
  ipcServer.setInitializationExecutor(async (command) => executeLocalInitializationCommand(agentInitializationService, command));
@@ -212,7 +350,7 @@ export function registerRuntimeIpcProviders({ ipcServer, channelInstances, messa
212
350
  const session = await sessionManager.getSessionById(sessionId);
213
351
  if (!session)
214
352
  return null;
215
- const { buildDebugTaskContext } = await import('./memory/consolidation.js');
353
+ const { buildDebugTaskContext } = await import('./auth/debug-context.js');
216
354
  const { ensureSessionRuntimeDir } = await import('../cli/task-context.js');
217
355
  return buildDebugTaskContext({
218
356
  session,
@@ -222,6 +360,31 @@ export function registerRuntimeIpcProviders({ ipcServer, channelInstances, messa
222
360
  });
223
361
  logger.info('[Memory] 开发仓安装:已启用 ec memory --debug <sessionId>');
224
362
  }
363
+ ipcServer.setSystemSessionCommandGate(async ({ sessionId, delegationToken, delegationCommandHash, type, argv }) => {
364
+ // 只读查询,不消耗已审批命令
365
+ const validation = agentDelegationRegistry.validateForContext(delegationToken, sessionId, delegationCommandHash);
366
+ const marker = validation.ok ? validation.grant.systemSession : undefined;
367
+ if (!validation.ok || !marker)
368
+ return undefined;
369
+ const module = getSystemSessionModule(marker.type);
370
+ if (!module)
371
+ return { allow: false, code: 'system_session_unknown', reason: `未注册的系统会话类型: ${marker.type}` };
372
+ // 无 argv 的请求只放行任务上下文查询(ec 命令取自身上下文用)
373
+ if (!argv) {
374
+ return type === 'task-runtime-context'
375
+ ? { allow: true }
376
+ : { allow: false, code: 'system_session_command_forbidden', reason: `系统会话不允许调用 ${type}` };
377
+ }
378
+ const session = await sessionManager.getSessionById(sessionId);
379
+ const ctx = buildSystemSessionContext({
380
+ marker,
381
+ sessionId,
382
+ selfAid: validation.grant.selfAid,
383
+ projectPath: session?.projectPath ?? '',
384
+ metadata: session?.metadata,
385
+ });
386
+ return module.checkCommand(argv, ctx);
387
+ });
225
388
  ipcServer.setTaskRuntimeContextAuthorizer(({ sessionId, delegationToken, delegationCommandHash, consumeApproval }) => {
226
389
  if (consumeApproval && !agentDelegationRegistry.validate)
227
390
  return { ok: false, code: 'DELEGATION_REQUIRED', error: 'Command authorization is unavailable' };
@@ -230,8 +393,17 @@ export function registerRuntimeIpcProviders({ ipcServer, channelInstances, messa
230
393
  : agentDelegationRegistry.validateForContext(delegationToken, sessionId, delegationCommandHash);
231
394
  return authorization.ok
232
395
  ? { ok: true }
233
- : { ok: false, code: authorization.code, error: authorization.reason };
396
+ : {
397
+ ok: false,
398
+ code: authorization.code,
399
+ error: authorization.reason,
400
+ diagnosticCause: authorization.diagnosticCause,
401
+ ...(authorization.diagnosticEventId ? { diagnosticEventId: authorization.diagnosticEventId } : {}),
402
+ };
234
403
  });
404
+ if (agentDelegationRegistry.recordDiagnosticFailure) {
405
+ ipcServer.setDelegationFailureRecorder(agentDelegationRegistry.recordDiagnosticFailure.bind(agentDelegationRegistry));
406
+ }
235
407
  ipcServer.setCodexPreToolUseExecutor(async (params) => {
236
408
  for (const runner of new Set(agentMap.values())) {
237
409
  if (typeof runner?.evaluatePreToolUse !== 'function')
@@ -0,0 +1,132 @@
1
+ /**
2
+ * batch-meta.ts — 整理批次元数据文件(batch.md)
3
+ *
4
+ * 格式:YAML frontmatter + 正文(feedback 内容)
5
+ *
6
+ * ---
7
+ * executionSessionId: memory_20261015T000000_abc
8
+ * selfAid: dddd.agentid.pub
9
+ * mode: notes
10
+ * ...
11
+ * ---
12
+ *
13
+ * ## 整理反馈
14
+ *
15
+ * <feedback 正文>
16
+ *
17
+ * 在 batchDir 创建时写入初始元数据(status=running),会话结束时更新状态、填入 feedback。
18
+ */
19
+ import fs from 'fs';
20
+ import path from 'path';
21
+ const BATCH_MD = 'batch.md';
22
+ const FEEDBACK_HEADING = '## 整理反馈';
23
+ // ── frontmatter 序列化 / 解析 ──────────────────────────────────────────────
24
+ function serializeFrontmatter(meta) {
25
+ const lines = ['---'];
26
+ for (const [k, v] of Object.entries(meta)) {
27
+ if (v === undefined)
28
+ continue;
29
+ lines.push(`${k}: ${String(v)}`);
30
+ }
31
+ lines.push('---');
32
+ return lines.join('\n');
33
+ }
34
+ function parseFrontmatter(content) {
35
+ if (!content.startsWith('---'))
36
+ return { meta: {}, body: content };
37
+ const end = content.indexOf('\n---', 3);
38
+ if (end === -1)
39
+ return { meta: {}, body: content };
40
+ const block = content.slice(4, end);
41
+ const body = content.slice(end + 4).replace(/^\n/, '');
42
+ const meta = {};
43
+ for (const line of block.split('\n')) {
44
+ const sep = line.indexOf(':');
45
+ if (sep === -1)
46
+ continue;
47
+ const key = line.slice(0, sep).trim();
48
+ const val = line.slice(sep + 1).trim();
49
+ if (val === 'true')
50
+ meta[key] = true;
51
+ else if (val === 'false')
52
+ meta[key] = false;
53
+ else if (val === '')
54
+ meta[key] = undefined;
55
+ else
56
+ meta[key] = val;
57
+ }
58
+ return { meta: meta, body };
59
+ }
60
+ function batchMdPath(batchDir) {
61
+ return path.join(batchDir, BATCH_MD);
62
+ }
63
+ // ── 公开 API ───────────────────────────────────────────────────────────────
64
+ /**
65
+ * 创建 batchDir 后立即写入初始 batch.md(status=running,无 feedback 正文)。
66
+ * 文件已存在时跳过(重入保护)。
67
+ */
68
+ export function writeBatchMeta(batchDir, meta) {
69
+ try {
70
+ fs.mkdirSync(batchDir, { recursive: true });
71
+ const filePath = batchMdPath(batchDir);
72
+ if (fs.existsSync(filePath))
73
+ return;
74
+ const content = serializeFrontmatter(meta) + '\n';
75
+ fs.writeFileSync(filePath, content, 'utf-8');
76
+ }
77
+ catch { /* 失败静默,不影响主流程 */ }
78
+ }
79
+ /**
80
+ * 会话结束时更新 batch.md:
81
+ * - 合并 frontmatter 字段(status / finishedAt / success / agentSessionId)
82
+ * - 如果 feedbackText 非空,写入或替换「## 整理反馈」段落
83
+ */
84
+ export function updateBatchMeta(batchDir, updates) {
85
+ try {
86
+ const filePath = batchMdPath(batchDir);
87
+ if (!fs.existsSync(filePath))
88
+ return;
89
+ const raw = fs.readFileSync(filePath, 'utf-8');
90
+ const { meta, body } = parseFrontmatter(raw);
91
+ // 合并字段
92
+ const updated = {
93
+ ...meta,
94
+ status: updates.status,
95
+ finishedAt: updates.finishedAt,
96
+ success: updates.success,
97
+ ...(updates.agentSessionId ? { agentSessionId: updates.agentSessionId } : {}),
98
+ };
99
+ // 更新 feedback 正文
100
+ let newBody = body;
101
+ if (updates.feedbackText) {
102
+ const feedbackBlock = `${FEEDBACK_HEADING}\n\n${updates.feedbackText}\n`;
103
+ const headingIdx = newBody.indexOf(FEEDBACK_HEADING);
104
+ if (headingIdx !== -1) {
105
+ // 替换已有段落
106
+ newBody = newBody.slice(0, headingIdx) + feedbackBlock;
107
+ }
108
+ else {
109
+ newBody = (newBody ? newBody.trimEnd() + '\n\n' : '') + feedbackBlock;
110
+ }
111
+ }
112
+ const result = serializeFrontmatter(updated) + '\n' + (newBody ? '\n' + newBody : '');
113
+ fs.writeFileSync(filePath, result, 'utf-8');
114
+ }
115
+ catch { /* 失败静默 */ }
116
+ }
117
+ /**
118
+ * 读取 batch.md 的 frontmatter(解析失败返回 undefined)。
119
+ */
120
+ export function readBatchMeta(batchDir) {
121
+ try {
122
+ const filePath = batchMdPath(batchDir);
123
+ if (!fs.existsSync(filePath))
124
+ return undefined;
125
+ const raw = fs.readFileSync(filePath, 'utf-8');
126
+ const { meta } = parseFrontmatter(raw);
127
+ return Object.keys(meta).length > 0 ? meta : undefined;
128
+ }
129
+ catch {
130
+ return undefined;
131
+ }
132
+ }