evolcore 0.0.33 → 0.0.35-test.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +59 -0
- package/bin/ec.js +3 -2
- package/bin/install-codex-managed-hooks.mjs +38 -4
- package/dist/agents/baseagent-availability.js +20 -36
- package/dist/agents/claude-runner.js +93 -14
- package/dist/agents/codex/diagnostics.js +79 -22
- package/dist/agents/codex-runner.js +245 -47
- package/dist/agents/ecagent-runner.js +90 -27
- package/dist/agents/runner-types.js +5 -6
- package/dist/aun/aid/agentmd.js +13 -2
- package/dist/aun/aid/client.js +1 -1
- package/dist/aun/aid/identity.js +1 -1
- package/dist/aun/msg/managed-operation.js +16 -0
- package/dist/aun/msg/p2p.js +18 -2
- package/dist/channels/aun.js +123 -7
- package/dist/channels/daemon.js +6 -3
- package/dist/cli/aun-commands.js +39 -14
- package/dist/cli/ctl-command.js +3 -1
- package/dist/cli/daemon-commands.js +130 -23
- package/dist/cli/fs-command.js +2 -1
- package/dist/cli/getmeta-command.js +211 -0
- package/dist/cli/index.js +26 -6
- package/dist/cli/integrity-command.js +196 -0
- package/dist/cli/managed-command-guard.js +3 -0
- package/dist/cli/memory-command.js +200 -156
- package/dist/cli/thread-command.js +17 -7
- package/dist/cli/trigger-command.js +6 -1
- package/dist/config/config-field-policy.js +4 -0
- package/dist/config/config-manager.js +19 -0
- package/dist/config/contact-book-store.js +5 -0
- package/dist/config/resolved-config-op.js +25 -8
- package/dist/config/role-service.js +13 -0
- package/dist/config/runtime-env.js +16 -0
- package/dist/config-store.js +30 -0
- package/dist/core/audit/log-integrity.js +3 -1
- package/dist/core/auth/agent-delegation.js +108 -35
- package/dist/core/auth/delegation-diagnostics.js +414 -0
- package/dist/core/auth/operation-authorizer.js +22 -3
- package/dist/core/auth/operation-catalog.js +16 -0
- package/dist/core/channel-runtime.js +1 -1
- package/dist/core/command/agent-control.js +18 -26
- package/dist/core/command/command-handler.js +145 -5
- package/dist/core/command/daemon-diagnostics.js +1 -0
- package/dist/core/command/menu-catalog.js +3 -0
- package/dist/core/command/menu-handler.js +24 -7
- package/dist/core/command/menu-protocol.js +1 -0
- package/dist/core/command/slash-handler.js +2 -0
- package/dist/core/command/topic-slash.js +4 -7
- package/dist/core/daemon-bootstrap.js +4 -7
- package/dist/core/event-catalog.js +14 -0
- package/dist/core/file-integrity/read.js +61 -0
- package/dist/core/file-integrity/store.js +262 -0
- package/dist/core/ipc-registration.js +47 -2
- package/dist/core/memory/batch-meta.js +133 -0
- package/dist/core/memory/consolidation-context.js +132 -28
- package/dist/core/memory/consolidation-ops-records.js +14 -16
- package/dist/core/memory/consolidation-trigger.js +15 -7
- package/dist/core/memory/consolidation.js +146 -26
- package/dist/core/memory/index-manager.js +19 -28
- package/dist/core/memory/memory-admin.js +19 -14
- package/dist/core/memory/memory-document.js +53 -9
- package/dist/core/memory/memory-writer.js +50 -19
- package/dist/core/message/aun-ipc-sender.js +19 -1
- package/dist/core/message/im-renderer.js +20 -4
- package/dist/core/message/message-bridge.js +5 -4
- package/dist/core/message/message-log.js +1 -0
- package/dist/core/message/peer-mode.js +17 -0
- package/dist/core/message/response-engine.js +598 -50
- package/dist/core/permission/ec-command-parser.js +169 -44
- package/dist/core/permission/protected-paths.js +5 -1
- package/dist/core/permission/readonly-shell-query.js +43 -5
- package/dist/core/permission/session-path-grants.js +334 -0
- package/dist/core/permission/tool-canonical.js +226 -0
- package/dist/core/permission/tool-policy.js +60 -62
- package/dist/core/relation/peer-identity.js +87 -12
- package/dist/core/role/runtime-policy.js +21 -10
- package/dist/core/session/session-fs-store.js +123 -8
- package/dist/core/session/session-manager.js +131 -6
- package/dist/core/session/session-title.js +26 -1
- package/dist/core/skill/index-generator.js +17 -3
- package/dist/core/system-session/grants.js +40 -0
- package/dist/core/system-session/memory.js +289 -0
- package/dist/core/system-session/registry.js +74 -0
- package/dist/core/system-session/types.js +14 -0
- package/dist/core/thread/command-parser.js +6 -1
- package/dist/core/thread/ipc-operation.js +33 -16
- package/dist/core/thread/thread-operation.js +62 -24
- package/dist/core/thread/thread-service.js +258 -51
- package/dist/eck/kit-renderer.js +7 -2
- package/dist/eck/manifest-engine.js +24 -4
- package/dist/index.js +11 -2
- package/dist/ipc.js +51 -40
- package/dist/paths.js +70 -0
- package/dist/stats/billing.js +20 -8
- package/dist/trigger/feedback.js +6 -1
- package/dist/trigger/parser.js +8 -1
- package/dist/trigger/system-definitions.js +3 -3
- package/dist/utils/cross-platform.js +17 -5
- package/dist/utils/error-dict.json +18 -4
- package/dist/utils/error-utils.js +121 -0
- package/dist/utils/instance-registry.js +13 -8
- package/dist/utils/log-writer.js +12 -0
- package/dist/utils/process-introspect.js +11 -0
- package/kits/docs/context-assembly.md +0 -1
- package/kits/docs/evolcore/agent-create.md +5 -4
- package/kits/docs/evolcore/agent.md +2 -2
- package/kits/docs/evolcore/config.md +2 -2
- package/kits/docs/evolcore/ctl.md +2 -2
- package/kits/docs/evolcore/ec-capabilities.md +1 -1
- package/kits/docs/evolcore/fs-architecture.md +9 -10
- package/kits/docs/evolcore/fs.md +2 -2
- package/kits/docs/evolcore/group-collaboration.md +3 -3
- package/kits/docs/evolcore/group.md +14 -12
- package/kits/docs/evolcore/memory-architecture.md +4 -5
- package/kits/docs/evolcore/memory-consolidation.md +19 -16
- package/kits/docs/evolcore/memory-tags.md +3 -1
- package/kits/docs/evolcore/memory.md +24 -12
- package/kits/docs/evolcore/model.md +16 -0
- package/kits/docs/evolcore/msg.md +7 -7
- package/kits/docs/evolcore/stats.md +1 -1
- package/kits/docs/evolcore/thread-chain.md +30 -14
- package/kits/docs/prompt-loading-architecture.md +0 -1
- package/kits/eck_manifest.auxiliary.json +9 -0
- package/kits/eck_manifest.json +20 -20
- package/kits/eck_manifest.memory.json +20 -12
- package/kits/rules/01-overview.md +11 -0
- package/kits/rules/03-identity.md +6 -3
- package/kits/rules/06-channel.md +1 -1
- package/kits/rules/07-memory-and-thread.md +13 -16
- package/kits/schemas/_meta.json +7 -1
- package/kits/schemas/agent-config.schema.13.json +1 -1
- package/kits/schemas/daemon.schema.7.json +140 -0
- package/kits/skills/ec-group/SKILL.md +8 -5
- package/kits/skills/ec-skill/scripts/cli.mjs +1 -1
- package/kits/skills/ec-skill/scripts/skill.mjs +1 -1
- package/kits/skills/thread-ops/SKILL.md +18 -18
- package/kits/templates/memory-consolidation/forget.md +2 -2
- package/kits/templates/memory-consolidation/generalize.md +4 -4
- package/kits/templates/memory-consolidation/notes.md +12 -12
- package/kits/templates/memory-consolidation/profile.md +3 -3
- package/kits/templates/memory-consolidation/promote.md +3 -3
- package/kits/templates/memory-consolidation/workflow.md +42 -7
- package/kits/templates/message-fragments/handoff-request-to-target.md +1 -1
- package/kits/templates/system-fragments/bash-description.md +9 -0
- package/kits/templates/system-fragments/bootstrap.md +4 -3
- package/kits/templates/system-fragments/channel.md +2 -2
- package/kits/templates/system-fragments/memory.md +16 -9
- package/kits/templates/system-fragments/reply.md +6 -6
- package/kits/templates/system-fragments/session.md +1 -1
- package/package.json +4 -3
- package/dist/core/memory/memory-session-policy.js +0 -41
- package/dist/core/memory/memory-signature.js +0 -257
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* index-manager.ts — MEMORY.md 索引管理(v3)
|
|
3
3
|
*
|
|
4
4
|
* 负责:
|
|
5
|
-
* 1. 重建索引:扫描 consolidated
|
|
5
|
+
* 1. 重建索引:扫描 consolidated/,逐份过台账校验,按 tags 筛选,截断到 indexMaxBytes
|
|
6
6
|
* 2. 追加条目:单条写入时的快速路径(写入后仍以 rebuild 为准)
|
|
7
7
|
*
|
|
8
8
|
* 排序与超期判断一律读**文件 mtime**;frontmatter 的 updatedAt 只作可读信息。
|
|
@@ -10,15 +10,16 @@
|
|
|
10
10
|
* 索引行格式:`- 20261005 [slug] — 描述 [tag1,tag2]`
|
|
11
11
|
* slug 即文件名(consolidated/<slug>.md),不再重复写路径。
|
|
12
12
|
*
|
|
13
|
-
*
|
|
14
|
-
* -
|
|
15
|
-
* -
|
|
16
|
-
*
|
|
17
|
-
*
|
|
13
|
+
* 完整性校验走 file-integrity 台账(docs/file-security/):
|
|
14
|
+
* - 没被动过 → 进索引
|
|
15
|
+
* - 被带外修改 → 恢复到最后登记的版本,再进索引
|
|
16
|
+
* - 被改动且无法恢复 → 跳过,不进索引,也不移动文件
|
|
17
|
+
* MEMORY.md 本身每次写入都会登记:它整份注入上下文,被改了同样要能恢复。
|
|
18
18
|
*/
|
|
19
19
|
import fs from 'fs';
|
|
20
20
|
import path from 'path';
|
|
21
21
|
import { getMemoryArray, getMemoryString, parseMemoryDocument } from './memory-document.js';
|
|
22
|
+
import { registerFile, verifyFile } from '../file-integrity/store.js';
|
|
22
23
|
/** 索引容量上限(字节)。实际取值由调用方从 memory.indexMaxBytes 传入。 */
|
|
23
24
|
export const DEFAULT_INDEX_MAX_BYTES = 5 * 1024;
|
|
24
25
|
/** Fast metadata index for consolidation candidate scans. */
|
|
@@ -246,20 +247,19 @@ function writeIndexFile(indexPath, content) {
|
|
|
246
247
|
const tmp = indexPath + '.tmp';
|
|
247
248
|
fs.writeFileSync(tmp, content, 'utf-8');
|
|
248
249
|
fs.renameSync(tmp, indexPath);
|
|
250
|
+
registerFile(indexPath);
|
|
249
251
|
}
|
|
250
252
|
// ── 重建索引 ────────────────────────────────────────────────────────────────
|
|
251
253
|
/**
|
|
252
254
|
* 从 consolidated/ 重建 MEMORY.md。
|
|
253
255
|
*
|
|
254
|
-
*
|
|
255
|
-
*
|
|
256
|
-
* 开启后才需要的,暂未实现。排序读文件 mtime,按 tags 分级,超 indexMaxBytes
|
|
257
|
-
* 从末尾截断(pinned 标签保留)。
|
|
256
|
+
* 每份文件先过台账校验(被带外修改的先恢复),被改动且无法恢复的跳过,不移动文件。
|
|
257
|
+
* 排序读文件 mtime,按 tags 分级,超 indexMaxBytes 从末尾截断(pinned 标签保留)。
|
|
258
258
|
*/
|
|
259
|
-
export async function rebuildIndex(consolidatedDir, indexPath, scope,
|
|
259
|
+
export async function rebuildIndex(consolidatedDir, indexPath, scope, indexMaxBytes = DEFAULT_INDEX_MAX_BYTES) {
|
|
260
260
|
if (!fs.existsSync(consolidatedDir)) {
|
|
261
261
|
writeIndexFile(indexPath, serializeIndex(scope, []));
|
|
262
|
-
return { indexed: 0,
|
|
262
|
+
return { indexed: 0, verifiedFiles: [] };
|
|
263
263
|
}
|
|
264
264
|
const files = fs.readdirSync(consolidatedDir).filter(f => f.endsWith('.md')).sort();
|
|
265
265
|
const entries = [];
|
|
@@ -269,26 +269,17 @@ export async function rebuildIndex(consolidatedDir, indexPath, scope, invalidDir
|
|
|
269
269
|
let content;
|
|
270
270
|
let mtimeMs;
|
|
271
271
|
try {
|
|
272
|
-
|
|
272
|
+
const checked = verifyFile(filePath, { restoreOnFail: true });
|
|
273
|
+
if (!checked.bytes)
|
|
274
|
+
continue; // missing / 无法恢复:不进索引
|
|
275
|
+
content = checked.bytes.toString('utf-8');
|
|
276
|
+
// 恢复会把 mtime 还原成登记时的值,所以在校验之后再取
|
|
273
277
|
mtimeMs = fs.statSync(filePath).mtimeMs;
|
|
274
278
|
}
|
|
275
279
|
catch {
|
|
276
280
|
continue;
|
|
277
281
|
}
|
|
278
|
-
|
|
279
|
-
const verifyResult = await verifyFn(content);
|
|
280
|
-
if (verifyResult.status === 'verified') {
|
|
281
|
-
verifiedFiles.push(file);
|
|
282
|
-
}
|
|
283
|
-
else {
|
|
284
|
-
// 验签失败:跳过,不进索引,不移动文件(门控开启后的移动逻辑暂未实现)
|
|
285
|
-
continue;
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
else {
|
|
289
|
-
// 未开启验签:所有文件视为通过
|
|
290
|
-
verifiedFiles.push(file);
|
|
291
|
-
}
|
|
282
|
+
verifiedFiles.push(file);
|
|
292
283
|
const document = parseMemoryDocument(content);
|
|
293
284
|
const description = getMemoryString(document, 'description') ?? file.replace(/\.md$/, '');
|
|
294
285
|
const tags = getMemoryArray(document, 'tags');
|
|
@@ -304,5 +295,5 @@ export async function rebuildIndex(consolidatedDir, indexPath, scope, invalidDir
|
|
|
304
295
|
sortEntries(entries);
|
|
305
296
|
const kept = truncateToLimit(scope, entries, indexMaxBytes);
|
|
306
297
|
writeIndexFile(indexPath, serializeIndex(scope, kept));
|
|
307
|
-
return { indexed: kept.length,
|
|
298
|
+
return { indexed: kept.length, verifiedFiles };
|
|
308
299
|
}
|
|
@@ -12,7 +12,8 @@ import path from 'path';
|
|
|
12
12
|
import { resolveScopeDirs, archiveMemory, dropMemoryNote, listMemoryNotes } from './memory-writer.js';
|
|
13
13
|
import { rebuildIndex, DEFAULT_INDEX_MAX_BYTES } from './index-manager.js';
|
|
14
14
|
import { refreshMemoryMeta, readMemoryMeta } from './index-manager.js';
|
|
15
|
-
import {
|
|
15
|
+
import { stripLegacySignature } from './memory-document.js';
|
|
16
|
+
import { moveRecord, verifyFile } from '../file-integrity/store.js';
|
|
16
17
|
import { readFmArray, readFmValue, readUpdatedAtMs, reviewWindowDays, DEFAULT_FORGET_AFTER_DAYS } from './staleness.js';
|
|
17
18
|
export const MEMORY_SCOPES = ['relation', 'agent', 'owner'];
|
|
18
19
|
const SLUG_RE = /^[a-zA-Z0-9][a-zA-Z0-9-]{0,63}$/;
|
|
@@ -45,10 +46,6 @@ function fmtDate(ms) {
|
|
|
45
46
|
return '-';
|
|
46
47
|
return new Date(ms).toISOString().slice(0, 16).replace('T', ' ');
|
|
47
48
|
}
|
|
48
|
-
/** 去掉签名块,只留正文(含 frontmatter) */
|
|
49
|
-
function stripSignature(content) {
|
|
50
|
-
return parseSignatureBlock(content).payload.trimEnd();
|
|
51
|
-
}
|
|
52
49
|
// ── 查看 ────────────────────────────────────────────────────────────────────
|
|
53
50
|
/** 概览:各作用域条数、pending 数、索引大小、最近更新时间。 */
|
|
54
51
|
export function memoryOverview(target, scopes) {
|
|
@@ -120,7 +117,6 @@ export function listMemoryFiles(target, scope, archived = false, now = Date.now(
|
|
|
120
117
|
tags,
|
|
121
118
|
mtimeMs,
|
|
122
119
|
updatedAtMs,
|
|
123
|
-
signed: !!parseSignatureBlock(content).fields,
|
|
124
120
|
reviewInDays: Math.floor((updatedAtMs + windowMs - now) / DAY_MS),
|
|
125
121
|
});
|
|
126
122
|
}
|
|
@@ -135,7 +131,7 @@ export function formatMemoryFiles(scope, files, archived = false) {
|
|
|
135
131
|
for (const f of files) {
|
|
136
132
|
const tags = f.tags.length ? ` [${f.tags.join(',')}]` : '';
|
|
137
133
|
const review = archived ? '' : f.reviewInDays < 0 ? ' ⏳待审查' : ` ${f.reviewInDays}天后审查`;
|
|
138
|
-
lines.push(`- ${f.slug}${tags}${
|
|
134
|
+
lines.push(`- ${f.slug}${tags}${review}`);
|
|
139
135
|
if (f.description)
|
|
140
136
|
lines.push(` ${f.description}`);
|
|
141
137
|
lines.push(` 更新于 ${fmtDate(f.updatedAtMs)}`);
|
|
@@ -158,16 +154,21 @@ export async function showMemoryFile(target, scope, slug, archived = false) {
|
|
|
158
154
|
const fp = path.join(dir, `${slug}.md`);
|
|
159
155
|
if (!fs.existsSync(fp))
|
|
160
156
|
throw new Error(`找不到记忆: ${scope}/${archived ? 'archive/' : ''}${slug}`);
|
|
161
|
-
const content = fs.readFileSync(fp, 'utf-8');
|
|
162
157
|
let verify;
|
|
158
|
+
let content;
|
|
163
159
|
try {
|
|
164
|
-
const r =
|
|
165
|
-
verify = r.status === 'verified' ? '✅
|
|
160
|
+
const r = verifyFile(fp, { restoreOnFail: true });
|
|
161
|
+
verify = r.status === 'verified' ? '✅ 校验通过'
|
|
162
|
+
: r.status === 'restored' ? '♻️ 检测到带外修改,已恢复到最后登记版本'
|
|
163
|
+
: '❌ 被改动且无法恢复';
|
|
164
|
+
// tampered 时没有校验过的字节,显示磁盘现状供排查
|
|
165
|
+
content = r.bytes ? r.bytes.toString('utf-8') : fs.readFileSync(fp, 'utf-8');
|
|
166
166
|
}
|
|
167
167
|
catch (e) {
|
|
168
|
-
verify = `⚠️
|
|
168
|
+
verify = `⚠️ 无法校验(${e?.message || String(e)})`;
|
|
169
|
+
content = fs.readFileSync(fp, 'utf-8');
|
|
169
170
|
}
|
|
170
|
-
let body =
|
|
171
|
+
let body = stripLegacySignature(content).trimEnd();
|
|
171
172
|
if (body.length > MAX_SHOW_CHARS)
|
|
172
173
|
body = `${body.slice(0, MAX_SHOW_CHARS)}\n…(已截断)`;
|
|
173
174
|
return `📄 ${scope}/${archived ? 'archive/' : ''}${slug} ${verify}\n${body}`;
|
|
@@ -201,8 +202,7 @@ export function showPendingNote(target, slug) {
|
|
|
201
202
|
/** 重建某作用域的 MEMORY.md 并刷新 _meta.json。 */
|
|
202
203
|
export async function reindexScope(target, scope) {
|
|
203
204
|
const dirs = scopeDirs(target, scope);
|
|
204
|
-
const r = await rebuildIndex(dirs.consolidatedDir, path.join(dirs.memoryDir, 'MEMORY.md'), scope,
|
|
205
|
-
target.memoryConfig?.indexMaxBytes ?? DEFAULT_INDEX_MAX_BYTES);
|
|
205
|
+
const r = await rebuildIndex(dirs.consolidatedDir, path.join(dirs.memoryDir, 'MEMORY.md'), scope, target.memoryConfig?.indexMaxBytes ?? DEFAULT_INDEX_MAX_BYTES);
|
|
206
206
|
refreshMemoryMeta(dirs.memoryDir);
|
|
207
207
|
return { indexed: r.indexed };
|
|
208
208
|
}
|
|
@@ -228,7 +228,12 @@ export async function restoreMemoryFile(target, scope, slug) {
|
|
|
228
228
|
throw new Error(`找不到已归档记忆: ${scope}/${slug}`);
|
|
229
229
|
if (fs.existsSync(dest))
|
|
230
230
|
throw new Error(`正式记忆中已存在同名文件: ${scope}/${slug},未恢复`);
|
|
231
|
+
// 归档期间被改过的话先恢复;恢复不了就拒绝,不把篡改内容放回正式记忆
|
|
232
|
+
const checked = verifyFile(src, { restoreOnFail: true });
|
|
233
|
+
if (!checked.bytes)
|
|
234
|
+
throw new Error(`MEMORY_TAMPERED: 已归档记忆被改动且无法恢复,未恢复: ${scope}/${slug}`);
|
|
231
235
|
fs.mkdirSync(dirs.consolidatedDir, { recursive: true });
|
|
232
236
|
fs.renameSync(src, dest);
|
|
237
|
+
moveRecord(src, dest);
|
|
233
238
|
await reindexScope(target, scope);
|
|
234
239
|
}
|
|
@@ -2,10 +2,58 @@
|
|
|
2
2
|
* Shared representation for memory Markdown documents.
|
|
3
3
|
*
|
|
4
4
|
* Memory files have two mutable blocks: YAML-like frontmatter and Markdown
|
|
5
|
-
* body.
|
|
6
|
-
*
|
|
5
|
+
* body. 新记忆不再签名(完整性改由 file-integrity 台账保证);磁盘上的老记忆
|
|
6
|
+
* 可能还带尾部 AUN 签名块,解析时剥掉,重写时不再写回。
|
|
7
7
|
*/
|
|
8
|
-
|
|
8
|
+
// ── 老签名块兼容剥离 ──────────────────────────────────────────────────────────
|
|
9
|
+
// 规则与原 memory-signature.ts 的 parseSignatureBlock 一致:只有格式完全合法的
|
|
10
|
+
// 签名块才剥;格式不对的标记可能就是正文,原样保留每个字节。
|
|
11
|
+
const LEGACY_SIGNATURE_MARKER = '<!-- AUN-SIGNATURE';
|
|
12
|
+
const LEGACY_SIGNATURE_RE = /^<!-- AUN-SIGNATURE(?:\r\n|\n|\r)(?<body>[\s\S]*?)(?:\r\n|\n|\r)-->\s*$/;
|
|
13
|
+
const LEGACY_REQUIRED_FIELDS = ['cert_fingerprint', 'timestamp', 'signature'];
|
|
14
|
+
const LEGACY_ALLOWED_FIELDS = [...LEGACY_REQUIRED_FIELDS, 'public_key_fingerprint'];
|
|
15
|
+
const SHA256_FP_RE = /^sha256:[0-9a-f]{64}$/i;
|
|
16
|
+
function isStrictBase64(value) {
|
|
17
|
+
if (!value || value.length % 4 !== 0 || !/^[A-Za-z0-9+/]+={0,2}$/.test(value))
|
|
18
|
+
return false;
|
|
19
|
+
return Buffer.from(value, 'base64').toString('base64') === value;
|
|
20
|
+
}
|
|
21
|
+
function isValidLegacySignatureBlock(tail) {
|
|
22
|
+
const match = tail.match(LEGACY_SIGNATURE_RE);
|
|
23
|
+
if (!match)
|
|
24
|
+
return false;
|
|
25
|
+
const fields = {};
|
|
26
|
+
for (const rawLine of (match.groups?.body ?? '').split(/\r\n|\n|\r/)) {
|
|
27
|
+
const line = rawLine.trim();
|
|
28
|
+
if (!line)
|
|
29
|
+
continue;
|
|
30
|
+
const colon = line.indexOf(':');
|
|
31
|
+
if (colon < 0)
|
|
32
|
+
return false;
|
|
33
|
+
const key = line.slice(0, colon).trim().toLowerCase();
|
|
34
|
+
if (Object.prototype.hasOwnProperty.call(fields, key))
|
|
35
|
+
return false;
|
|
36
|
+
fields[key] = line.slice(colon + 1).trim();
|
|
37
|
+
}
|
|
38
|
+
if (LEGACY_REQUIRED_FIELDS.some(k => !fields[k]))
|
|
39
|
+
return false;
|
|
40
|
+
if (Object.keys(fields).some(k => !LEGACY_ALLOWED_FIELDS.includes(k)))
|
|
41
|
+
return false;
|
|
42
|
+
if (!SHA256_FP_RE.test(fields.cert_fingerprint))
|
|
43
|
+
return false;
|
|
44
|
+
if (fields.public_key_fingerprint && !SHA256_FP_RE.test(fields.public_key_fingerprint))
|
|
45
|
+
return false;
|
|
46
|
+
return /^\d+$/.test(fields.timestamp) && isStrictBase64(fields.signature);
|
|
47
|
+
}
|
|
48
|
+
/** 去掉老记忆尾部合法的 AUN 签名块;没有或不合法时原样返回。 */
|
|
49
|
+
export function stripLegacySignature(content) {
|
|
50
|
+
const idx = content.lastIndexOf(LEGACY_SIGNATURE_MARKER);
|
|
51
|
+
if (idx < 0 || content.indexOf(LEGACY_SIGNATURE_MARKER) !== idx)
|
|
52
|
+
return content;
|
|
53
|
+
if (idx > 0 && content[idx - 1] !== '\n' && content[idx - 1] !== '\r')
|
|
54
|
+
return content;
|
|
55
|
+
return isValidLegacySignatureBlock(content.slice(idx)) ? content.slice(0, idx) : content;
|
|
56
|
+
}
|
|
9
57
|
function parseArray(raw) {
|
|
10
58
|
const value = raw.match(/^\[([^\]]*)\]$/)?.[1] ?? raw;
|
|
11
59
|
return value
|
|
@@ -29,13 +77,9 @@ function splitDocument(content) {
|
|
|
29
77
|
hasFrontmatter: true,
|
|
30
78
|
};
|
|
31
79
|
}
|
|
32
|
-
/** Parse frontmatter and body;
|
|
80
|
+
/** Parse frontmatter and body; a legacy AUN signature block is ignored. */
|
|
33
81
|
export function parseMemoryDocument(content) {
|
|
34
|
-
const
|
|
35
|
-
// A malformed marker may be ordinary body text. Only strip a signature when
|
|
36
|
-
// the signature parser accepted the block; otherwise preserve every byte.
|
|
37
|
-
const unsigned = signature.fields ? signature.payload : content;
|
|
38
|
-
const parts = splitDocument(unsigned);
|
|
82
|
+
const parts = splitDocument(stripLegacySignature(content));
|
|
39
83
|
const fields = {};
|
|
40
84
|
const fieldOrder = [];
|
|
41
85
|
const extraFrontmatterLines = [];
|
|
@@ -4,21 +4,22 @@
|
|
|
4
4
|
* 只有整理会话(sessionType=memory)能调用这三个操作,由 ipc.ts 做会话类型与
|
|
5
5
|
* 模式↔作用域的双重校验后委托到这里。本模块只负责文件层面的动作:
|
|
6
6
|
*
|
|
7
|
-
* - createMemory 新建正式记忆:写 consolidated
|
|
8
|
-
* - updateMemory 改标签或续期(--touch
|
|
7
|
+
* - createMemory 新建正式记忆:写 consolidated/、归档 --replaces 的旧文件
|
|
8
|
+
* - updateMemory 改标签或续期(--touch)
|
|
9
9
|
* - archiveMemory 归档(唯一的下线方式,没有删除)
|
|
10
10
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
11
|
+
* 正式记忆的完整性由 file-integrity 台账保证(docs/file-security/):
|
|
12
|
+
* 落盘后 registerFile,rename 后 moveRecord,读改写之前先 verifyFile。
|
|
13
|
+
* pending note 是草稿,不进台账。
|
|
13
14
|
*/
|
|
14
15
|
import fs from 'fs';
|
|
15
16
|
import path from 'path';
|
|
16
17
|
import { resolveRoot } from '../../paths.js';
|
|
17
|
-
import {
|
|
18
|
+
import { moveRecord, registerFile, verifyFile } from '../file-integrity/store.js';
|
|
18
19
|
import { refreshMemoryMeta } from './index-manager.js';
|
|
19
20
|
import { getMemoryArray, getMemoryString, parseMemoryDocument, serializeMemoryDocument, setMemoryField, } from './memory-document.js';
|
|
20
21
|
const SLUG_RE = /^[a-zA-Z0-9][a-zA-Z0-9-]{0,63}$/;
|
|
21
|
-
/**
|
|
22
|
+
/** 一条记忆应是一句话能说清的结论,10KB 足够;超过通常意味着该拆成多条。 */
|
|
22
23
|
const MAX_CONTENT_BYTES = 10 * 1024;
|
|
23
24
|
/** 解析某个作用域的记忆目录。 */
|
|
24
25
|
export function resolveScopeDirs(scope, taskCtx, ownerAid, peerKeyOverride) {
|
|
@@ -49,8 +50,10 @@ export function resolveScopeDirs(scope, taskCtx, ownerAid, peerKeyOverride) {
|
|
|
49
50
|
/**
|
|
50
51
|
* 把用户给的路径解析成 consolidated/ 下的绝对路径。
|
|
51
52
|
* 允许 slug / <slug>.md / 相对或绝对路径(须落在本作用域的 consolidated/ 内)。
|
|
53
|
+
*
|
|
54
|
+
* forget-agent 模式特殊处理:允许路径落在任意 relation 的 consolidated/ 下。
|
|
52
55
|
*/
|
|
53
|
-
export function resolveConsolidatedPath(dirs, input) {
|
|
56
|
+
export function resolveConsolidatedPath(dirs, input, taskCtx) {
|
|
54
57
|
const base = path.resolve(dirs.consolidatedDir);
|
|
55
58
|
const looksLikePath = input.includes('/') || input.includes('\\');
|
|
56
59
|
if (!looksLikePath) {
|
|
@@ -65,6 +68,20 @@ export function resolveConsolidatedPath(dirs, input) {
|
|
|
65
68
|
if (!rel.startsWith('..') && !path.isAbsolute(rel))
|
|
66
69
|
return abs;
|
|
67
70
|
}
|
|
71
|
+
// forget-agent 模式:允许路径落在任意 relation 的 consolidated/ 下,或 agent 自身的 consolidated/
|
|
72
|
+
const isForgetAgent = taskCtx?.memoryMode === 'forget' && taskCtx?.memoryScope === 'agent';
|
|
73
|
+
if (isForgetAgent && taskCtx?.selfAid) {
|
|
74
|
+
const relationsRoot = path.join(resolveRoot(), 'agents', taskCtx.selfAid, 'relations');
|
|
75
|
+
const absInput = path.resolve(input);
|
|
76
|
+
const relToRelations = path.relative(relationsRoot, absInput);
|
|
77
|
+
// 检查是否在某个 relation 的 consolidated/ 下
|
|
78
|
+
if (!relToRelations.startsWith('..') && !path.isAbsolute(relToRelations)) {
|
|
79
|
+
const parts = relToRelations.split(path.sep);
|
|
80
|
+
if (parts.length >= 3 && parts[1] === 'memory' && parts[2] === 'consolidated') {
|
|
81
|
+
return absInput;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
68
85
|
throw new Error(`ROLE_ACCESS_DENIED: 只能操作本作用域 consolidated/ 下的记忆(${base}),当前路径越界: ${input}`);
|
|
69
86
|
}
|
|
70
87
|
function assertSlug(slug) {
|
|
@@ -76,8 +93,18 @@ function assertSlug(slug) {
|
|
|
76
93
|
function readMemory(content) {
|
|
77
94
|
return parseMemoryDocument(content);
|
|
78
95
|
}
|
|
79
|
-
|
|
80
|
-
|
|
96
|
+
/**
|
|
97
|
+
* 读 → 改 → 重写之前先过台账:被带外改过就先恢复,用校验过的内容做修改基础。
|
|
98
|
+
* 直接读盘的话,篡改内容改一下就会被重新登记成新基线。
|
|
99
|
+
*/
|
|
100
|
+
function readVerifiedMemory(filePath) {
|
|
101
|
+
const r = verifyFile(filePath, { restoreOnFail: true });
|
|
102
|
+
if (r.status === 'missing')
|
|
103
|
+
throw new Error(`找不到记忆文件: ${filePath}`);
|
|
104
|
+
if (!r.bytes) {
|
|
105
|
+
throw new Error(`MEMORY_TAMPERED: 记忆文件被改动且无法恢复,已拒绝操作: ${filePath}`);
|
|
106
|
+
}
|
|
107
|
+
return r.bytes.toString('utf-8');
|
|
81
108
|
}
|
|
82
109
|
export async function createMemory(req) {
|
|
83
110
|
assertSlug(req.slug);
|
|
@@ -93,7 +120,7 @@ export async function createMemory(req) {
|
|
|
93
120
|
const dirs = resolveScopeDirs(req.scope, req.taskCtx, req.ownerAid, req.targetPeerKey);
|
|
94
121
|
fs.mkdirSync(dirs.consolidatedDir, { recursive: true });
|
|
95
122
|
const targetPath = path.join(dirs.consolidatedDir, `${req.slug}.md`);
|
|
96
|
-
const replacePaths = (req.replaces ?? []).map(p => resolveConsolidatedPath(dirs, p));
|
|
123
|
+
const replacePaths = (req.replaces ?? []).map(p => resolveConsolidatedPath(dirs, p, req.taskCtx));
|
|
97
124
|
// 撞名闸门:目标已存在且没被 --replaces 覆盖 → 拒绝
|
|
98
125
|
if (fs.existsSync(targetPath) && !replacePaths.includes(targetPath)) {
|
|
99
126
|
throw new Error(`SLUG_EXISTS: 目标已存在: ${targetPath}\n` +
|
|
@@ -134,10 +161,10 @@ export async function createMemory(req) {
|
|
|
134
161
|
if (replacePaths.length > 0)
|
|
135
162
|
setMemoryField(document, 'replaces', replacePaths.map(p => path.relative(dirs.memoryDir, p)));
|
|
136
163
|
document.body = req.content.trimEnd();
|
|
137
|
-
const signed = await writeSignedMemory(document, req.taskCtx.selfAid);
|
|
138
164
|
const tmp = targetPath + '.tmp';
|
|
139
|
-
fs.writeFileSync(tmp,
|
|
165
|
+
fs.writeFileSync(tmp, serializeMemoryDocument(document), 'utf-8');
|
|
140
166
|
fs.renameSync(tmp, targetPath);
|
|
167
|
+
registerFile(targetPath, req.taskCtx.selfAid);
|
|
141
168
|
// 归档被替换的旧文件(在写入成功之后做,失败不影响新文件)
|
|
142
169
|
const archived = [];
|
|
143
170
|
for (const p of replacePaths) {
|
|
@@ -150,6 +177,7 @@ export async function createMemory(req) {
|
|
|
150
177
|
try {
|
|
151
178
|
fs.renameSync(p, dest);
|
|
152
179
|
archived.push(dest);
|
|
180
|
+
moveRecord(p, dest);
|
|
153
181
|
}
|
|
154
182
|
catch { /* 归档失败不影响主流程,由下次整理清理 */ }
|
|
155
183
|
}
|
|
@@ -206,7 +234,8 @@ export async function demoteMemory(req) {
|
|
|
206
234
|
const srcPath = path.join(srcDirs.consolidatedDir, `${req.slug}.md`);
|
|
207
235
|
if (!fs.existsSync(srcPath))
|
|
208
236
|
throw new Error(`找不到记忆文件: ${srcPath}`);
|
|
209
|
-
|
|
237
|
+
// 降级目标由 frontmatter 决定,必须读校验过的内容,否则改一下来源字段就能把记忆搬去别处
|
|
238
|
+
const content = readVerifiedMemory(srcPath);
|
|
210
239
|
const fromRelPath = getMemoryString(readMemory(content), req.scope === 'agent' ? 'generalizedFrom' : 'promotedFrom');
|
|
211
240
|
if (!fromRelPath) {
|
|
212
241
|
throw new Error(`DEMOTE_NO_SOURCE: 记忆文件缺少来源字段(${req.scope === 'agent' ? 'generalizedFrom' : 'promotedFrom'}),` +
|
|
@@ -229,12 +258,14 @@ export async function demoteMemory(req) {
|
|
|
229
258
|
const archiveDest = archivePath(srcDirs.memoryDir, srcPath);
|
|
230
259
|
fs.mkdirSync(path.dirname(archiveDest), { recursive: true });
|
|
231
260
|
fs.renameSync(srcPath, archiveDest);
|
|
261
|
+
moveRecord(srcPath, archiveDest);
|
|
232
262
|
finalDestPath = archiveDest;
|
|
233
263
|
conflictArchived = true;
|
|
234
264
|
}
|
|
235
265
|
else {
|
|
236
266
|
fs.mkdirSync(path.dirname(destPath), { recursive: true });
|
|
237
267
|
fs.renameSync(srcPath, destPath);
|
|
268
|
+
moveRecord(srcPath, destPath);
|
|
238
269
|
}
|
|
239
270
|
// 跨作用域搬运,源与目标两个目录都要刷新元数据
|
|
240
271
|
refreshMemoryMeta(srcDirs.memoryDir);
|
|
@@ -249,21 +280,20 @@ export async function demoteMemory(req) {
|
|
|
249
280
|
}
|
|
250
281
|
export async function updateMemory(req) {
|
|
251
282
|
const dirs = resolveScopeDirs(req.scope, req.taskCtx, req.ownerAid);
|
|
252
|
-
const filePath = resolveConsolidatedPath(dirs, req.target);
|
|
283
|
+
const filePath = resolveConsolidatedPath(dirs, req.target, req.taskCtx);
|
|
253
284
|
if (!fs.existsSync(filePath))
|
|
254
285
|
throw new Error(`找不到记忆文件: ${filePath}`);
|
|
255
|
-
const
|
|
256
|
-
const document = readMemory(raw);
|
|
286
|
+
const document = readMemory(readVerifiedMemory(filePath));
|
|
257
287
|
const prevTags = getMemoryArray(document, 'tags');
|
|
258
288
|
if (req.tags !== undefined)
|
|
259
289
|
setMemoryField(document, 'tags', req.tags.length > 0 ? req.tags : undefined);
|
|
260
290
|
// 刷新 updatedAt(--touch 的续期语义就是刷新这里 + 文件 mtime)
|
|
261
291
|
const now = new Date().toISOString();
|
|
262
292
|
setMemoryField(document, 'updatedAt', now);
|
|
263
|
-
const signed = await writeSignedMemory(document, req.taskCtx.selfAid);
|
|
264
293
|
const tmp = filePath + '.tmp';
|
|
265
|
-
fs.writeFileSync(tmp,
|
|
294
|
+
fs.writeFileSync(tmp, serializeMemoryDocument(document), 'utf-8');
|
|
266
295
|
fs.renameSync(tmp, filePath);
|
|
296
|
+
registerFile(filePath, req.taskCtx.selfAid);
|
|
267
297
|
refreshMemoryMeta(dirs.memoryDir);
|
|
268
298
|
return { ok: true, filePath, prevTags, scopeDirs: dirs };
|
|
269
299
|
}
|
|
@@ -272,12 +302,13 @@ export async function archiveMemory(req) {
|
|
|
272
302
|
const dirs = resolveScopeDirs(req.scope, req.taskCtx, req.ownerAid);
|
|
273
303
|
const archived = [];
|
|
274
304
|
for (const t of req.targets) {
|
|
275
|
-
const p = resolveConsolidatedPath(dirs, t);
|
|
305
|
+
const p = resolveConsolidatedPath(dirs, t, req.taskCtx);
|
|
276
306
|
if (!fs.existsSync(p))
|
|
277
307
|
throw new Error(`找不到记忆文件: ${p}`);
|
|
278
308
|
const dest = archivePath(dirs.memoryDir, p);
|
|
279
309
|
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
280
310
|
fs.renameSync(p, dest);
|
|
311
|
+
moveRecord(p, dest);
|
|
281
312
|
archived.push(dest);
|
|
282
313
|
}
|
|
283
314
|
refreshMemoryMeta(dirs.memoryDir);
|
|
@@ -28,7 +28,7 @@ export function createAunHandoffSender(sessionManager, channelInstances) {
|
|
|
28
28
|
const log = {
|
|
29
29
|
content: classified.content,
|
|
30
30
|
sessionId: targetSession.id,
|
|
31
|
-
threadId: targetSession.threadId ||
|
|
31
|
+
threadId: targetSession.threadId || '',
|
|
32
32
|
msgType: classified.msgType,
|
|
33
33
|
payloadType: classified.payloadType,
|
|
34
34
|
payloadSummary: classified.payloadSummary,
|
|
@@ -267,6 +267,24 @@ export function registerAunIpcSender({ ipcServer, channelInstances, agentDelegat
|
|
|
267
267
|
if (!payload) {
|
|
268
268
|
return { ok: false, error: 'message payload is required', code: 'INVALID_PAYLOAD' };
|
|
269
269
|
}
|
|
270
|
+
// 托管 argv 通道(runManagedSend)先前不带 log 元数据:群聊 sendDaemonGroupMsg
|
|
271
|
+
// 自身会写日志,私聊 sendDaemonMsg 只在 args.log 存在时才写,于是同一份
|
|
272
|
+
// `ec msg send` 在私聊下不落 messages.jsonl,话题历史里查不到已发出的消息。
|
|
273
|
+
// 这里在 payload 定型后按同一套规则补出日志元数据,让两条路径记账一致。
|
|
274
|
+
if (params.argv !== undefined && params.log === undefined) {
|
|
275
|
+
const classified = classifyAunPayloadForLog(payload);
|
|
276
|
+
params = {
|
|
277
|
+
...params,
|
|
278
|
+
log: {
|
|
279
|
+
content: classified.content,
|
|
280
|
+
threadId: params.thread ?? '',
|
|
281
|
+
msgType: classified.msgType,
|
|
282
|
+
payloadType: classified.payloadType,
|
|
283
|
+
payloadSummary: classified.payloadSummary,
|
|
284
|
+
source: 'msg',
|
|
285
|
+
},
|
|
286
|
+
};
|
|
287
|
+
}
|
|
270
288
|
try {
|
|
271
289
|
if (Object.prototype.hasOwnProperty.call(payload, 'mentions')) {
|
|
272
290
|
payload = { ...payload, mentions: normalizeAunMentionEntries(payload.mentions) };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { logger } from '../../utils/logger.js';
|
|
2
2
|
import { summarizeToolInput, summarizeToolInputForDisplay, toolInputForDisplay } from '../../utils/tool-summary.js';
|
|
3
|
-
import { getErrorMessage, isContextTooLongText } from '../../utils/error-utils.js';
|
|
3
|
+
import { getErrorMessage, getToolResultErrorText, isContextTooLongText } from '../../utils/error-utils.js';
|
|
4
4
|
import { tagErrorTextWithEvolcoreVersion } from '../../utils/evolcore-version.js';
|
|
5
5
|
import { DEFAULT_FLUSH_DELAY_MS } from '../../types.js';
|
|
6
6
|
import { classifyAunToolErrorCode, normalizeToolErrorClassification } from '../permission/tool-error-code.js';
|
|
@@ -275,7 +275,13 @@ export class IMRenderer {
|
|
|
275
275
|
name,
|
|
276
276
|
ok,
|
|
277
277
|
...(result !== undefined && { result }),
|
|
278
|
-
...(
|
|
278
|
+
...(!ok
|
|
279
|
+
? { error: getToolResultErrorText({ name, result, error, exitCode: classification?.exitCode }) }
|
|
280
|
+
: error !== undefined ? { error } : {}),
|
|
281
|
+
...(typeof classification?.exitCode === 'number' && Number.isInteger(classification.exitCode)
|
|
282
|
+
? { exit_code: classification.exitCode }
|
|
283
|
+
: {}),
|
|
284
|
+
...(classification?.outputKind ? { output_kind: classification.outputKind } : {}),
|
|
279
285
|
...(!ok && {
|
|
280
286
|
error_code: classifyAunToolErrorCode({
|
|
281
287
|
...(classification ?? {}),
|
|
@@ -904,7 +910,13 @@ export class IMRenderer {
|
|
|
904
910
|
item.arguments = toolInputForDisplay(event.name, event.input);
|
|
905
911
|
return item;
|
|
906
912
|
}
|
|
907
|
-
case 'tool_result':
|
|
913
|
+
case 'tool_result': {
|
|
914
|
+
const executionFields = {
|
|
915
|
+
...(typeof event.exitCode === 'number' && Number.isInteger(event.exitCode)
|
|
916
|
+
? { exit_code: event.exitCode }
|
|
917
|
+
: {}),
|
|
918
|
+
...(event.outputKind ? { output_kind: event.outputKind } : {}),
|
|
919
|
+
};
|
|
908
920
|
if (event.isError) {
|
|
909
921
|
const classification = normalizeToolErrorClassification(event);
|
|
910
922
|
return {
|
|
@@ -912,9 +924,11 @@ export class IMRenderer {
|
|
|
912
924
|
call_id: event.callId || this.synthCallId(),
|
|
913
925
|
name: event.name,
|
|
914
926
|
ok: false,
|
|
927
|
+
...executionFields,
|
|
928
|
+
...(event.result !== undefined ? { result: event.result } : {}),
|
|
915
929
|
error_code: classifyAunToolErrorCode({ ...event, ...classification }),
|
|
916
930
|
...(classification.errorDetailCode ? { detail_code: classification.errorDetailCode } : {}),
|
|
917
|
-
error:
|
|
931
|
+
error: getToolResultErrorText(event),
|
|
918
932
|
...(event.durationMs !== undefined && { duration_ms: event.durationMs }),
|
|
919
933
|
};
|
|
920
934
|
}
|
|
@@ -925,11 +939,13 @@ export class IMRenderer {
|
|
|
925
939
|
call_id: event.callId || this.synthCallId(),
|
|
926
940
|
name: event.name,
|
|
927
941
|
ok: true,
|
|
942
|
+
...executionFields,
|
|
928
943
|
result: event.result,
|
|
929
944
|
text: resultText,
|
|
930
945
|
...(event.durationMs !== undefined && { duration_ms: event.durationMs }),
|
|
931
946
|
};
|
|
932
947
|
}
|
|
948
|
+
}
|
|
933
949
|
case 'compact':
|
|
934
950
|
return {
|
|
935
951
|
kind: 'notice',
|
|
@@ -137,7 +137,8 @@ export class MessageBridge {
|
|
|
137
137
|
logger.debug(`[MessageBridge] Decision context group rules unavailable: ${String(error)}`);
|
|
138
138
|
}
|
|
139
139
|
}
|
|
140
|
-
|
|
140
|
+
// persona.md 是唯一的人格/个人资料文件(personal.md 已废弃并入)
|
|
141
|
+
const personalPath = path.join(agentDir(item.selfAid), 'personal', 'persona.md');
|
|
141
142
|
if (fs.existsSync(personalPath))
|
|
142
143
|
resources.personal = fs.readFileSync(personalPath, 'utf8');
|
|
143
144
|
if (item.projectPath) {
|
|
@@ -1263,7 +1264,7 @@ export class MessageBridge {
|
|
|
1263
1264
|
appendMessageLog(chatDir, buildInboundEntry({
|
|
1264
1265
|
from: msg.peerId || 'unknown',
|
|
1265
1266
|
to: msg.selfAID || 'self',
|
|
1266
|
-
threadId: msg.threadId ??
|
|
1267
|
+
threadId: msg.threadId ?? '',
|
|
1267
1268
|
chatType: msg.chatType || 'private',
|
|
1268
1269
|
groupId: msg.groupId ?? null,
|
|
1269
1270
|
msgId: msg.messageId ?? null,
|
|
@@ -1548,7 +1549,7 @@ export class MessageBridge {
|
|
|
1548
1549
|
from: msg.peerId || 'unknown',
|
|
1549
1550
|
to: msg.selfAID || 'self',
|
|
1550
1551
|
sessionId: session.id,
|
|
1551
|
-
threadId: session.threadId ||
|
|
1552
|
+
threadId: session.threadId || '',
|
|
1552
1553
|
chatType,
|
|
1553
1554
|
groupId: msg.groupId ?? null,
|
|
1554
1555
|
msgId: msg.messageId ?? null,
|
|
@@ -1607,7 +1608,7 @@ export class MessageBridge {
|
|
|
1607
1608
|
const member = item.value;
|
|
1608
1609
|
appendMessageLog(inboundEntry.chatDir, buildInboundEntry({
|
|
1609
1610
|
from: item.sender, to: selfAid || '', sessionId: session.id,
|
|
1610
|
-
threadId: session.threadId ||
|
|
1611
|
+
threadId: session.threadId || '', chatType: 'group', groupId: msg.groupId,
|
|
1611
1612
|
msgId: item.messageId, content: member.messageLogContent ?? item.content,
|
|
1612
1613
|
permMode: item.role, timestamp: item.receivedAt,
|
|
1613
1614
|
receivedAt: member.receivedAt, gatewaySeq: member.gatewaySeq,
|
|
@@ -31,6 +31,23 @@ export function resolveChatMode(params) {
|
|
|
31
31
|
? effectiveValue
|
|
32
32
|
: chatmodeFactoryDefaults()[field];
|
|
33
33
|
}
|
|
34
|
+
/**
|
|
35
|
+
* 出站发送路径(`ec msg send` / daemon 私聊发送)用的最小判定。
|
|
36
|
+
*
|
|
37
|
+
* 这两条路径只有对端身份、拿不到角色,因此不能复用 `resolveChatMode` 的
|
|
38
|
+
* 分层配置。口径与 `resolveChatMode` 的硬约束保持一致:
|
|
39
|
+
* - 非人类对端(isMachine)→ proactive
|
|
40
|
+
* - 人类但角色未知 → proactive(保守侧)
|
|
41
|
+
* - 人类且 owner/admin → interactive
|
|
42
|
+
*
|
|
43
|
+
* 角色未知时不能假设高信任:interactive 表示"输出即回复"的同步交互,
|
|
44
|
+
* 只应给已确认的管理者。
|
|
45
|
+
*/
|
|
46
|
+
export function chatmodeForPeerIdentity(identity, role) {
|
|
47
|
+
if (identity.isMachine)
|
|
48
|
+
return 'proactive';
|
|
49
|
+
return resolveChatMode({ role: role ?? undefined, chatType: 'private', peerType: identity.type ?? 'human' });
|
|
50
|
+
}
|
|
34
51
|
export function resolveChatModeForField(params) {
|
|
35
52
|
return resolveChatMode({
|
|
36
53
|
...params,
|