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.
- package/CHANGELOG.md +61 -0
- package/bin/ec.js +3 -2
- package/bin/install-codex-managed-hooks.mjs +135 -37
- package/dist/agents/baseagent-availability.js +20 -36
- package/dist/agents/claude-runner.js +111 -15
- package/dist/agents/codex/app-server-client.js +3 -0
- package/dist/agents/codex/diagnostics.js +112 -54
- package/dist/agents/codex-runner.js +241 -63
- package/dist/agents/ecagent-runner.js +78 -33
- package/dist/agents/runner-types.js +5 -6
- package/dist/aun/aid/identity.js +1 -1
- package/dist/aun/group/operations.js +0 -1
- package/dist/aun/msg/history.js +1 -0
- package/dist/aun/msg/p2p.js +0 -1
- package/dist/aun/outbox.js +50 -1
- package/dist/channels/aun.js +44 -8
- package/dist/channels/daemon.js +261 -3
- package/dist/cli/agent-command.js +58 -24
- package/dist/cli/agent.js +1 -1
- package/dist/cli/aun-commands.js +368 -31
- package/dist/cli/contact.js +8 -2
- package/dist/cli/ctl-command.js +3 -1
- package/dist/cli/daemon-commands.js +197 -27
- package/dist/cli/debug-mode.js +154 -0
- package/dist/cli/fs-command.js +2 -1
- package/dist/cli/handoff-command.js +3 -0
- package/dist/cli/index.js +98 -67
- package/dist/cli/init.js +2 -2
- package/dist/cli/integrity-command.js +196 -0
- package/dist/cli/managed-command-guard.js +5 -0
- package/dist/cli/memory-command.js +166 -146
- package/dist/cli/queue-command.js +9 -4
- package/dist/cli/session-command.js +194 -0
- package/dist/cli/skill-command.js +78 -2
- package/dist/cli/task-context.js +1 -0
- package/dist/cli/thread-command.js +622 -0
- package/dist/cli/trigger-command.js +10 -3
- package/dist/config/builtin-roles.js +1 -0
- package/dist/config/config-field-policy.js +6 -3
- package/dist/config/config-manager.js +26 -2
- package/dist/config/contact-book-store.js +5 -0
- package/dist/config/resolved-config-op.js +3 -0
- package/dist/config/role-service.js +13 -0
- package/dist/config/runtime-env.js +16 -0
- package/dist/config-store.js +45 -0
- package/dist/core/audit/log-integrity.js +3 -1
- package/dist/core/auth/agent-delegation.js +133 -36
- package/dist/core/auth/debug-context.js +139 -0
- package/dist/core/auth/delegation-diagnostics.js +414 -0
- package/dist/core/auth/operation-authorizer.js +25 -2
- package/dist/core/auth/operation-catalog.js +49 -0
- package/dist/core/background-session/session-cleanup.js +212 -0
- package/dist/core/background-session/types.js +10 -0
- package/dist/core/bootstrap-messages.js +1 -1
- package/dist/core/bootstrap-service.js +6 -3
- package/dist/core/command/agent-control.js +18 -26
- package/dist/core/command/command-handler.js +197 -27
- package/dist/core/command/daemon-diagnostics.js +1 -0
- package/dist/core/command/memory-slash.js +6 -2
- package/dist/core/command/menu-catalog.js +51 -1
- package/dist/core/command/menu-handler.js +114 -23
- package/dist/core/command/menu-protocol.js +25 -1
- package/dist/core/command/skill-slash.js +322 -0
- package/dist/core/command/slash-gate.js +6 -4
- package/dist/core/command/slash-handler.js +195 -13
- package/dist/core/command/topic-slash.js +528 -0
- package/dist/core/daemon-bootstrap.js +4 -7
- package/dist/core/evolagent-registry.js +20 -5
- package/dist/core/file-integrity/read.js +61 -0
- package/dist/core/file-integrity/store.js +262 -0
- package/dist/core/handoff/runtime.js +5 -11
- package/dist/core/handoff/store.js +5 -0
- package/dist/core/ipc-registration.js +175 -3
- package/dist/core/memory/batch-meta.js +132 -0
- package/dist/core/memory/consolidation-context.js +83 -29
- package/dist/core/memory/consolidation-ops-records.js +49 -16
- package/dist/core/memory/consolidation-queue-format.js +21 -0
- package/dist/core/memory/consolidation.js +264 -205
- 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 +30 -15
- package/dist/core/message/aun-ipc-sender.js +161 -6
- package/dist/core/message/im-renderer.js +169 -10
- package/dist/core/message/message-bridge.js +15 -4
- package/dist/core/message/message-log.js +1 -0
- package/dist/core/message/peer-mode.js +6 -1
- package/dist/core/message/response-engine.js +929 -110
- package/dist/core/message/subtask-tool-projection.js +140 -0
- package/dist/core/model/catalog-service.js +17 -12
- package/dist/core/permission/ec-command-parser.js +217 -9
- 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 +30 -62
- package/dist/core/role/runtime-policy.js +21 -10
- package/dist/core/session/session-fs-store.js +123 -8
- package/dist/core/session/session-idle-monitor.js +249 -22
- package/dist/core/session/session-manager.js +129 -14
- package/dist/core/skill/command-parser.js +13 -0
- package/dist/core/skill/index-generator.js +21 -4
- package/dist/core/skill/startup.js +41 -0
- package/dist/core/skill/switch-resolver.js +55 -29
- package/dist/core/system-session/grants.js +40 -0
- package/dist/core/system-session/memory.js +222 -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 +125 -0
- package/dist/core/thread/index.js +1 -0
- package/dist/core/thread/ipc-operation.js +95 -0
- package/dist/core/thread/thread-context-storage.js +131 -0
- package/dist/core/thread/thread-operation.js +258 -0
- package/dist/core/thread/thread-route.js +33 -0
- package/dist/core/thread/thread-service.js +770 -0
- package/dist/core/thread/types.js +1 -0
- package/dist/eck/kit-renderer.js +7 -2
- package/dist/eck/manifest-engine.js +24 -4
- package/dist/eck/message-renderer.js +35 -0
- package/dist/index.js +19 -7
- package/dist/ipc.js +140 -50
- package/dist/paths.js +93 -0
- package/dist/response-system/engines/v1/proactive-flow.js +169 -80
- package/dist/stats/billing.js +20 -8
- package/dist/trigger/authorization.js +57 -28
- package/dist/trigger/feedback.js +13 -2
- package/dist/trigger/parser.js +70 -1
- package/dist/trigger/system-definitions.js +3 -3
- package/dist/trigger/validation.js +14 -0
- package/dist/utils/cross-platform.js +11 -2
- package/dist/utils/error-dict.json +18 -4
- package/dist/utils/error-utils.js +143 -1
- 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 +1 -1
- package/kits/docs/evolcore/INDEX.md +1 -0
- package/kits/docs/evolcore/agent-create.md +40 -28
- package/kits/docs/evolcore/agent.md +21 -6
- package/kits/docs/evolcore/config.md +2 -2
- package/kits/docs/evolcore/ctl.md +2 -2
- package/kits/docs/evolcore/ec-capabilities.md +5 -1
- package/kits/docs/evolcore/fs-architecture.md +4 -5
- package/kits/docs/evolcore/fs.md +1 -1
- package/kits/docs/evolcore/group-collaboration.md +4 -6
- package/kits/docs/evolcore/group.md +11 -12
- package/kits/docs/evolcore/memory-architecture.md +4 -5
- package/kits/docs/evolcore/memory-consolidation.md +19 -15
- package/kits/docs/evolcore/memory-tags.md +3 -1
- package/kits/docs/evolcore/memory.md +24 -12
- package/kits/docs/evolcore/menu-exec-schema-commands.md +2 -2
- package/kits/docs/evolcore/menu-protocol.md +7 -0
- package/kits/docs/evolcore/model.md +16 -0
- package/kits/docs/evolcore/msg.md +8 -11
- package/kits/docs/evolcore/thread-chain.md +267 -0
- package/kits/docs/prompt-loading-architecture.md +1 -1
- package/kits/eck_manifest.json +55 -40
- package/kits/eck_manifest.memory.json +0 -12
- package/kits/eck_message_manifest.json +169 -10
- package/kits/rules/01-overview.md +11 -0
- package/kits/rules/03-identity.md +6 -3
- package/kits/rules/05-venue.md +1 -1
- package/kits/rules/06-channel.md +1 -1
- package/kits/rules/07-memory-and-thread.md +199 -0
- 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/schemas/relation-config.schema.9.json +18 -3
- package/kits/schemas/role-config.schema.1.json +6 -6
- package/kits/schemas/single-session.schema.1.json +6 -6
- package/kits/schemas/single-session.schema.2.json +6 -6
- package/kits/schemas/single-session.schema.3.json +6 -6
- package/kits/skills/.switches.json +12 -1
- package/kits/skills/ec-group/SKILL.md +234 -0
- package/kits/skills/ec-skill/SKILL.md +10 -1
- package/kits/skills/ec-skill/scripts/cli.mjs +16 -2
- package/kits/skills/ec-skill/scripts/skill.mjs +1 -1
- package/kits/skills/scrapling-article-fetch/SKILL.md +128 -0
- package/kits/skills/scrapling-article-fetch/scripts/check_python_env.py +54 -0
- package/kits/skills/scrapling-article-fetch/scripts/render_markdown_reply.py +35 -0
- package/kits/skills/scrapling-article-fetch/scripts/run_pipeline.sh +16 -0
- package/kits/skills/scrapling-article-fetch/scripts/scrapling_fetch.py +344 -0
- package/kits/skills/thread-ops/SKILL.md +492 -0
- package/kits/skills/wechat-article-media-download/SKILL.md +65 -0
- package/kits/skills/wechat-article-media-download/scripts/download.py +163 -0
- package/kits/skills/wechat-article-reader/SKILL.md +97 -0
- package/kits/skills/wechat-article-reader/scripts/fetch.mjs +256 -0
- package/kits/templates/bootstrap-welcome.md +7 -7
- package/kits/templates/memory-consolidation/forget.md +7 -2
- package/kits/templates/memory-consolidation/generalize.md +9 -4
- package/kits/templates/memory-consolidation/notes.md +11 -11
- package/kits/templates/memory-consolidation/profile.md +8 -3
- package/kits/templates/memory-consolidation/promote.md +8 -3
- package/kits/templates/memory-consolidation/workflow.md +6 -6
- package/kits/templates/message-fragments/handoff-request-to-target.md +1 -1
- package/kits/templates/message-fragments/item.md +2 -1
- package/kits/templates/message-fragments/thread-context.md +6 -0
- package/kits/templates/message-fragments/trigger-session.md +2 -0
- package/kits/templates/message-fragments/trigger-thread-check.md +2 -0
- package/kits/templates/roles/member.json +2 -0
- package/kits/templates/roles/visitor.json +2 -0
- package/kits/templates/system-fragments/bootstrap.md +67 -72
- package/kits/templates/system-fragments/channel.md +3 -6
- package/kits/templates/system-fragments/commands.md +1 -0
- package/kits/templates/system-fragments/managed-runtime.md +15 -0
- package/kits/templates/system-fragments/memory.md +16 -9
- package/kits/templates/system-fragments/reply.md +7 -6
- package/kits/templates/system-fragments/session.md +5 -14
- package/package.json +6 -3
- package/dist/core/memory/memory-session-policy.js +0 -41
- package/dist/core/memory/memory-signature.js +0 -257
- package/kits/docs/evolcore/group-fs.md +0 -17
- package/kits/docs/venues/aun-group.md +0 -45
- package/kits/docs/venues/group.md +0 -43
|
@@ -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) {
|
|
@@ -76,8 +77,18 @@ function assertSlug(slug) {
|
|
|
76
77
|
function readMemory(content) {
|
|
77
78
|
return parseMemoryDocument(content);
|
|
78
79
|
}
|
|
79
|
-
|
|
80
|
-
|
|
80
|
+
/**
|
|
81
|
+
* 读 → 改 → 重写之前先过台账:被带外改过就先恢复,用校验过的内容做修改基础。
|
|
82
|
+
* 直接读盘的话,篡改内容改一下就会被重新登记成新基线。
|
|
83
|
+
*/
|
|
84
|
+
function readVerifiedMemory(filePath) {
|
|
85
|
+
const r = verifyFile(filePath, { restoreOnFail: true });
|
|
86
|
+
if (r.status === 'missing')
|
|
87
|
+
throw new Error(`找不到记忆文件: ${filePath}`);
|
|
88
|
+
if (!r.bytes) {
|
|
89
|
+
throw new Error(`MEMORY_TAMPERED: 记忆文件被改动且无法恢复,已拒绝操作: ${filePath}`);
|
|
90
|
+
}
|
|
91
|
+
return r.bytes.toString('utf-8');
|
|
81
92
|
}
|
|
82
93
|
export async function createMemory(req) {
|
|
83
94
|
assertSlug(req.slug);
|
|
@@ -134,10 +145,10 @@ export async function createMemory(req) {
|
|
|
134
145
|
if (replacePaths.length > 0)
|
|
135
146
|
setMemoryField(document, 'replaces', replacePaths.map(p => path.relative(dirs.memoryDir, p)));
|
|
136
147
|
document.body = req.content.trimEnd();
|
|
137
|
-
const signed = await writeSignedMemory(document, req.taskCtx.selfAid);
|
|
138
148
|
const tmp = targetPath + '.tmp';
|
|
139
|
-
fs.writeFileSync(tmp,
|
|
149
|
+
fs.writeFileSync(tmp, serializeMemoryDocument(document), 'utf-8');
|
|
140
150
|
fs.renameSync(tmp, targetPath);
|
|
151
|
+
registerFile(targetPath, req.taskCtx.selfAid);
|
|
141
152
|
// 归档被替换的旧文件(在写入成功之后做,失败不影响新文件)
|
|
142
153
|
const archived = [];
|
|
143
154
|
for (const p of replacePaths) {
|
|
@@ -150,6 +161,7 @@ export async function createMemory(req) {
|
|
|
150
161
|
try {
|
|
151
162
|
fs.renameSync(p, dest);
|
|
152
163
|
archived.push(dest);
|
|
164
|
+
moveRecord(p, dest);
|
|
153
165
|
}
|
|
154
166
|
catch { /* 归档失败不影响主流程,由下次整理清理 */ }
|
|
155
167
|
}
|
|
@@ -206,7 +218,8 @@ export async function demoteMemory(req) {
|
|
|
206
218
|
const srcPath = path.join(srcDirs.consolidatedDir, `${req.slug}.md`);
|
|
207
219
|
if (!fs.existsSync(srcPath))
|
|
208
220
|
throw new Error(`找不到记忆文件: ${srcPath}`);
|
|
209
|
-
|
|
221
|
+
// 降级目标由 frontmatter 决定,必须读校验过的内容,否则改一下来源字段就能把记忆搬去别处
|
|
222
|
+
const content = readVerifiedMemory(srcPath);
|
|
210
223
|
const fromRelPath = getMemoryString(readMemory(content), req.scope === 'agent' ? 'generalizedFrom' : 'promotedFrom');
|
|
211
224
|
if (!fromRelPath) {
|
|
212
225
|
throw new Error(`DEMOTE_NO_SOURCE: 记忆文件缺少来源字段(${req.scope === 'agent' ? 'generalizedFrom' : 'promotedFrom'}),` +
|
|
@@ -229,12 +242,14 @@ export async function demoteMemory(req) {
|
|
|
229
242
|
const archiveDest = archivePath(srcDirs.memoryDir, srcPath);
|
|
230
243
|
fs.mkdirSync(path.dirname(archiveDest), { recursive: true });
|
|
231
244
|
fs.renameSync(srcPath, archiveDest);
|
|
245
|
+
moveRecord(srcPath, archiveDest);
|
|
232
246
|
finalDestPath = archiveDest;
|
|
233
247
|
conflictArchived = true;
|
|
234
248
|
}
|
|
235
249
|
else {
|
|
236
250
|
fs.mkdirSync(path.dirname(destPath), { recursive: true });
|
|
237
251
|
fs.renameSync(srcPath, destPath);
|
|
252
|
+
moveRecord(srcPath, destPath);
|
|
238
253
|
}
|
|
239
254
|
// 跨作用域搬运,源与目标两个目录都要刷新元数据
|
|
240
255
|
refreshMemoryMeta(srcDirs.memoryDir);
|
|
@@ -252,18 +267,17 @@ export async function updateMemory(req) {
|
|
|
252
267
|
const filePath = resolveConsolidatedPath(dirs, req.target);
|
|
253
268
|
if (!fs.existsSync(filePath))
|
|
254
269
|
throw new Error(`找不到记忆文件: ${filePath}`);
|
|
255
|
-
const
|
|
256
|
-
const document = readMemory(raw);
|
|
270
|
+
const document = readMemory(readVerifiedMemory(filePath));
|
|
257
271
|
const prevTags = getMemoryArray(document, 'tags');
|
|
258
272
|
if (req.tags !== undefined)
|
|
259
273
|
setMemoryField(document, 'tags', req.tags.length > 0 ? req.tags : undefined);
|
|
260
274
|
// 刷新 updatedAt(--touch 的续期语义就是刷新这里 + 文件 mtime)
|
|
261
275
|
const now = new Date().toISOString();
|
|
262
276
|
setMemoryField(document, 'updatedAt', now);
|
|
263
|
-
const signed = await writeSignedMemory(document, req.taskCtx.selfAid);
|
|
264
277
|
const tmp = filePath + '.tmp';
|
|
265
|
-
fs.writeFileSync(tmp,
|
|
278
|
+
fs.writeFileSync(tmp, serializeMemoryDocument(document), 'utf-8');
|
|
266
279
|
fs.renameSync(tmp, filePath);
|
|
280
|
+
registerFile(filePath, req.taskCtx.selfAid);
|
|
267
281
|
refreshMemoryMeta(dirs.memoryDir);
|
|
268
282
|
return { ok: true, filePath, prevTags, scopeDirs: dirs };
|
|
269
283
|
}
|
|
@@ -278,6 +292,7 @@ export async function archiveMemory(req) {
|
|
|
278
292
|
const dest = archivePath(dirs.memoryDir, p);
|
|
279
293
|
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
280
294
|
fs.renameSync(p, dest);
|
|
295
|
+
moveRecord(p, dest);
|
|
281
296
|
archived.push(dest);
|
|
282
297
|
}
|
|
283
298
|
refreshMemoryMeta(dirs.memoryDir);
|
|
@@ -1,14 +1,17 @@
|
|
|
1
|
+
import crypto from 'crypto';
|
|
1
2
|
import { authorizeDelegatedAunMsgSend, } from '../auth/agent-delegation.js';
|
|
2
|
-
import { tryParseChannelKey } from '../channel-loader.js';
|
|
3
|
+
import { formatChannelKey, tryParseChannelKey } from '../channel-loader.js';
|
|
3
4
|
import { AUN_HANDOFF_MARKER_FIELD } from '../../channels/aun.js';
|
|
4
5
|
import { classifyAunPayloadForLog } from './message-log.js';
|
|
5
6
|
import { isValidAunDeliveryForChannel } from './outbound.js';
|
|
6
7
|
import { isHClassPath } from '../permission/protected-paths.js';
|
|
7
8
|
import { evaluateOutboundContactAdmission } from './message-admission.js';
|
|
8
9
|
import { normalizeAunMentionEntries, withAunAttention } from '../../aun/msg/mention-schema.js';
|
|
9
|
-
import { resolveManagedSendArgv } from '../../cli/aun-commands.js';
|
|
10
|
+
import { buildThreadCheckTrigger, resolveManagedSendArgv } from '../../cli/aun-commands.js';
|
|
10
11
|
import { readTriggerLocalFile, resolveTriggerLocalPath } from '../../trigger/local-files.js';
|
|
11
12
|
import { sameGroupAid } from '../../aun/group/identity.js';
|
|
13
|
+
import { resolveThreadRouteTarget } from '../thread/thread-route.js';
|
|
14
|
+
import { ThreadService } from '../thread/thread-service.js';
|
|
12
15
|
export function createAunHandoffSender(sessionManager, channelInstances) {
|
|
13
16
|
return async (handoff) => {
|
|
14
17
|
const targetSession = await sessionManager.getSessionById(handoff.target_session_id);
|
|
@@ -58,7 +61,92 @@ export function createAunHandoffSender(sessionManager, channelInstances) {
|
|
|
58
61
|
return { ok: result.ok, message_id: result.message_id, error: result.error };
|
|
59
62
|
};
|
|
60
63
|
}
|
|
61
|
-
|
|
64
|
+
async function applyManagedCrossThreadSendEffects(input) {
|
|
65
|
+
const originSessionId = input.runtime?.sessionId;
|
|
66
|
+
if (!originSessionId)
|
|
67
|
+
return undefined;
|
|
68
|
+
const originThreadId = input.runtime?.threadId || 'main';
|
|
69
|
+
const originPeer = input.runtime?.channelId || input.targetChannelId;
|
|
70
|
+
const originChatType = input.runtime?.chatType === 'group' ? 'group' : 'private';
|
|
71
|
+
const originChannelKey = input.runtime?.channelKey;
|
|
72
|
+
const originChannelId = input.runtime?.channelId;
|
|
73
|
+
const originPeerId = input.runtime?.peerId || (originChatType === 'private' ? originChannelId : undefined);
|
|
74
|
+
const origin = originChannelKey && originChannelId && originPeerId
|
|
75
|
+
? {
|
|
76
|
+
channelKey: originChannelKey,
|
|
77
|
+
channelType: input.runtime?.channel || 'aun',
|
|
78
|
+
channelId: originChannelId,
|
|
79
|
+
session: input.runtime?.threadId ? 'thread' : 'main',
|
|
80
|
+
...(input.runtime?.threadId ? { threadId: input.runtime.threadId } : {}),
|
|
81
|
+
peerId: originPeerId,
|
|
82
|
+
delivery: originChatType === 'group'
|
|
83
|
+
? { chatType: 'group', groupId: originChannelId }
|
|
84
|
+
: { chatType: 'private' },
|
|
85
|
+
}
|
|
86
|
+
: undefined;
|
|
87
|
+
const triggerSeed = `${Date.now()}_${crypto.randomBytes(4).toString('hex')}`;
|
|
88
|
+
const triggerId = `trig_${triggerSeed}`;
|
|
89
|
+
const triggerName = `check-${input.targetThreadId}-from-${originThreadId}-${triggerSeed.split('_')[1]}`;
|
|
90
|
+
const prompt = [
|
|
91
|
+
`【Trigger 复查】话题 ${input.targetThreadId}(对端 ${input.targetChannelId})`,
|
|
92
|
+
`这是周期复查:每 ${input.checkEveryLabel} 唤醒一次,直到你显式结束它。`,
|
|
93
|
+
'',
|
|
94
|
+
input.triggerPromptExtra
|
|
95
|
+
|| `请检查该话题的最新进度,根据情况:继续等待、调整周期(ec trigger update ${triggerId} --every <时长>)或结束复查(ec trigger cancel ${triggerId})。`,
|
|
96
|
+
'',
|
|
97
|
+
'参考命令:',
|
|
98
|
+
` ec thread history ${input.targetChannelId}/${input.targetThreadId} --limit 10`,
|
|
99
|
+
` ec thread history ${originPeer}/${originThreadId} --after ${Date.now()}`,
|
|
100
|
+
` ec trigger cancel ${triggerId}`,
|
|
101
|
+
` ec trigger update ${triggerId} --every <时长>`,
|
|
102
|
+
`Trigger id:${triggerId}`,
|
|
103
|
+
`Trigger 名称:${triggerName}`,
|
|
104
|
+
].join('\n');
|
|
105
|
+
let createdTriggerId;
|
|
106
|
+
try {
|
|
107
|
+
if (!origin)
|
|
108
|
+
throw new Error('managed task runtime context is missing a trusted Trigger origin');
|
|
109
|
+
const scheduler = input.triggerSchedulers.get(input.selfAid);
|
|
110
|
+
if (!scheduler)
|
|
111
|
+
throw new Error(`trigger scheduler not found for agent: ${input.selfAid}`);
|
|
112
|
+
const definition = buildThreadCheckTrigger({
|
|
113
|
+
agentAid: input.selfAid,
|
|
114
|
+
id: triggerId,
|
|
115
|
+
name: triggerName,
|
|
116
|
+
prompt,
|
|
117
|
+
everyMs: input.durationMs,
|
|
118
|
+
checkEvery: input.checkEveryLabel,
|
|
119
|
+
channelKey: formatChannelKey({ type: 'aun', selfAID: input.selfAid, name: 'main' }),
|
|
120
|
+
targetChannelId: input.targetChannelId,
|
|
121
|
+
targetThreadId: input.targetThreadId,
|
|
122
|
+
originThreadId,
|
|
123
|
+
originPeer,
|
|
124
|
+
origin,
|
|
125
|
+
feedbackChatType: input.feedbackChatType,
|
|
126
|
+
});
|
|
127
|
+
createdTriggerId = scheduler.create(definition, [], { enable: true }).id;
|
|
128
|
+
}
|
|
129
|
+
catch (error) {
|
|
130
|
+
console.warn(`⚠ Trigger 创建失败: ${error instanceof Error ? error.message : String(error)}(inbox 仍会写入)`);
|
|
131
|
+
}
|
|
132
|
+
try {
|
|
133
|
+
ThreadService.appendInbox(input.selfAid, {
|
|
134
|
+
messageId: input.messageId,
|
|
135
|
+
content: input.contentSummary,
|
|
136
|
+
timestamp: input.timestamp,
|
|
137
|
+
originThreadId,
|
|
138
|
+
originPeer,
|
|
139
|
+
originSessionId,
|
|
140
|
+
targetSessionId: input.targetSessionId,
|
|
141
|
+
...(createdTriggerId ? { triggerId: createdTriggerId } : {}),
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
catch (error) {
|
|
145
|
+
console.warn(`⚠ 话题 inbox 写入失败: ${error instanceof Error ? error.message : String(error)}`);
|
|
146
|
+
}
|
|
147
|
+
return createdTriggerId;
|
|
148
|
+
}
|
|
149
|
+
export function registerAunIpcSender({ ipcServer, channelInstances, agentDelegationRegistry, responseEngine, handoffRuntime, triggerSchedulers, }) {
|
|
62
150
|
const sender = async (params) => {
|
|
63
151
|
let textFile;
|
|
64
152
|
try {
|
|
@@ -76,6 +164,12 @@ export function registerAunIpcSender({ ipcServer, channelInstances, agentDelegat
|
|
|
76
164
|
}
|
|
77
165
|
const runtime = params.originSessionId ? responseEngine.getTaskRuntimeContext(params.originSessionId) : undefined;
|
|
78
166
|
const runtimeCausation = runtime?.causation;
|
|
167
|
+
const threadRoute = params.thread
|
|
168
|
+
? resolveThreadRouteTarget(params.aid, params.thread, params.scope, params.to)
|
|
169
|
+
: undefined;
|
|
170
|
+
if (params.thread && !threadRoute) {
|
|
171
|
+
return { ok: false, code: 'TOPIC_NOT_FOUND', error: `话题不存在或不属于目标对端: ${params.thread}` };
|
|
172
|
+
}
|
|
79
173
|
if (params.argv !== undefined && !params.delegationCommandHash) {
|
|
80
174
|
return { ok: false, code: 'INVALID_DELEGATION', error: 'Managed AUN sends require an approved command hash' };
|
|
81
175
|
}
|
|
@@ -88,6 +182,7 @@ export function registerAunIpcSender({ ipcServer, channelInstances, agentDelegat
|
|
|
88
182
|
to: params.to,
|
|
89
183
|
scope: params.scope,
|
|
90
184
|
action: params.file ? 'file' : 'send',
|
|
185
|
+
threadRouted: !!threadRoute,
|
|
91
186
|
});
|
|
92
187
|
if (!delegation.ok) {
|
|
93
188
|
return { ok: false, error: delegation.reason, code: delegation.code };
|
|
@@ -219,13 +314,33 @@ export function registerAunIpcSender({ ipcServer, channelInstances, agentDelegat
|
|
|
219
314
|
});
|
|
220
315
|
targetSessionId = created.targetSession.id;
|
|
221
316
|
if (created.crossSession && created.handoff) {
|
|
222
|
-
|
|
317
|
+
const response = {
|
|
223
318
|
ok: true,
|
|
224
319
|
status: created.handoff.state === 'target_sent' ? 'delivered' : 'queued',
|
|
225
320
|
message_id: created.handoff.target_message_id ?? undefined,
|
|
226
321
|
handoff_id: created.handoff.handoff_id,
|
|
227
322
|
target_session_id: created.targetSession.id,
|
|
228
323
|
};
|
|
324
|
+
if (params.thread && params.checkEveryMs !== undefined && response.message_id && threadRoute) {
|
|
325
|
+
const triggerId = await applyManagedCrossThreadSendEffects({
|
|
326
|
+
triggerSchedulers,
|
|
327
|
+
runtime,
|
|
328
|
+
selfAid: params.aid,
|
|
329
|
+
targetThreadId: params.thread,
|
|
330
|
+
targetSessionId: threadRoute.sessionId,
|
|
331
|
+
targetChannelId: params.to,
|
|
332
|
+
feedbackChatType: params.scope === 'group' ? 'group' : 'private',
|
|
333
|
+
messageId: response.message_id,
|
|
334
|
+
contentSummary: params.log?.content ?? classifyAunPayloadForLog(payload).content,
|
|
335
|
+
timestamp: response.timestamp ?? Date.now(),
|
|
336
|
+
durationMs: params.checkEveryMs,
|
|
337
|
+
checkEveryLabel: params.checkEvery ?? '5m',
|
|
338
|
+
triggerPromptExtra: params.triggerPromptExtra,
|
|
339
|
+
});
|
|
340
|
+
if (triggerId)
|
|
341
|
+
response.trigger_id = triggerId;
|
|
342
|
+
}
|
|
343
|
+
return response;
|
|
229
344
|
}
|
|
230
345
|
if (params.scope === 'msg' && !payload.ref_message_id) {
|
|
231
346
|
payload.ref_message_id = params.originMessageId;
|
|
@@ -247,24 +362,64 @@ export function registerAunIpcSender({ ipcServer, channelInstances, agentDelegat
|
|
|
247
362
|
if (typeof ch.sendDaemonGroupMsg !== 'function') {
|
|
248
363
|
return { ok: false, error: 'AUN channel does not support daemon group sends', code: 'AUN_GROUP_UNSUPPORTED' };
|
|
249
364
|
}
|
|
250
|
-
|
|
365
|
+
const sent = await ch.sendDaemonGroupMsg({
|
|
251
366
|
groupId: params.to,
|
|
252
367
|
payload,
|
|
253
368
|
mentions: params.mentions,
|
|
254
369
|
encrypt,
|
|
255
370
|
log: params.log ? { ...params.log, sessionId: targetSessionId ?? params.log.sessionId } : undefined,
|
|
256
371
|
});
|
|
372
|
+
if (params.thread && params.checkEveryMs !== undefined && sent.ok && sent.message_id && threadRoute) {
|
|
373
|
+
const triggerId = await applyManagedCrossThreadSendEffects({
|
|
374
|
+
triggerSchedulers,
|
|
375
|
+
runtime,
|
|
376
|
+
selfAid: params.aid,
|
|
377
|
+
targetThreadId: params.thread,
|
|
378
|
+
targetSessionId: threadRoute.sessionId,
|
|
379
|
+
targetChannelId: params.to,
|
|
380
|
+
feedbackChatType: 'group',
|
|
381
|
+
messageId: sent.message_id,
|
|
382
|
+
contentSummary: params.log?.content ?? classifyAunPayloadForLog(payload).content,
|
|
383
|
+
timestamp: sent.timestamp ?? Date.now(),
|
|
384
|
+
durationMs: params.checkEveryMs,
|
|
385
|
+
checkEveryLabel: params.checkEvery ?? '5m',
|
|
386
|
+
triggerPromptExtra: params.triggerPromptExtra,
|
|
387
|
+
});
|
|
388
|
+
if (triggerId)
|
|
389
|
+
sent.trigger_id = triggerId;
|
|
390
|
+
}
|
|
391
|
+
return sent;
|
|
257
392
|
}
|
|
258
393
|
if (typeof ch.sendDaemonMsg !== 'function') {
|
|
259
394
|
return { ok: false, error: 'AUN channel does not support daemon private sends', code: 'AUN_MSG_UNSUPPORTED' };
|
|
260
395
|
}
|
|
261
|
-
|
|
396
|
+
const sent = await ch.sendDaemonMsg({
|
|
262
397
|
to: params.to,
|
|
263
398
|
payload,
|
|
264
399
|
encrypt,
|
|
265
400
|
causation: runtimeCausation,
|
|
266
401
|
log: params.log ? { ...params.log, sessionId: targetSessionId ?? params.log.sessionId } : undefined,
|
|
267
402
|
});
|
|
403
|
+
if (params.thread && params.checkEveryMs !== undefined && sent.ok && sent.message_id && threadRoute) {
|
|
404
|
+
const triggerId = await applyManagedCrossThreadSendEffects({
|
|
405
|
+
triggerSchedulers,
|
|
406
|
+
runtime,
|
|
407
|
+
selfAid: params.aid,
|
|
408
|
+
targetThreadId: params.thread,
|
|
409
|
+
targetSessionId: threadRoute.sessionId,
|
|
410
|
+
targetChannelId: params.to,
|
|
411
|
+
feedbackChatType: 'private',
|
|
412
|
+
messageId: sent.message_id,
|
|
413
|
+
contentSummary: params.log?.content ?? classifyAunPayloadForLog(payload).content,
|
|
414
|
+
timestamp: sent.timestamp ?? Date.now(),
|
|
415
|
+
durationMs: params.checkEveryMs,
|
|
416
|
+
checkEveryLabel: params.checkEvery ?? '5m',
|
|
417
|
+
triggerPromptExtra: params.triggerPromptExtra,
|
|
418
|
+
});
|
|
419
|
+
if (triggerId)
|
|
420
|
+
sent.trigger_id = triggerId;
|
|
421
|
+
}
|
|
422
|
+
return sent;
|
|
268
423
|
};
|
|
269
424
|
ipcServer.setAunMsgSender(sender);
|
|
270
425
|
}
|