evolcore 0.0.3 → 0.0.5
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 +83 -0
- package/README.md +45 -10
- package/bin/ec-safe-output.js +89 -24
- package/dist/agents/baseagent.js +46 -0
- package/dist/agents/claude-runner.js +1 -31
- package/dist/agents/codex-app-server-client.js +68 -0
- package/dist/agents/codex-runner.js +157 -5
- package/dist/agents/runner-types.js +2 -2
- package/dist/aun/aid/agentmd.js +79 -0
- package/dist/aun/aid/encryption-seed-policy.js +29 -0
- package/dist/aun/aid/index.js +1 -1
- package/dist/aun/aid/store.js +2 -5
- package/dist/channels/aun.js +220 -112
- package/dist/channels/daemon.js +18 -4
- package/dist/channels/dingtalk.js +52 -137
- package/dist/channels/feishu.js +56 -4
- package/dist/channels/qqbot.js +23 -1
- package/dist/channels/wechat.js +303 -155
- package/dist/channels/wecom.js +383 -7
- package/dist/cli/aun-commands.js +11 -11
- package/dist/cli/daemon-commands.js +46 -15
- package/dist/cli/handoff-command.js +2 -1
- package/dist/cli/init-channel.js +185 -67
- package/dist/cli/init.js +49 -27
- package/dist/cli/trigger-command.js +92 -11
- package/dist/config/access-policy-domain.js +18 -0
- package/dist/config/access-policy.js +110 -0
- package/dist/config/builtin-role-templates.js +27 -14
- package/dist/config/builtin-roles.js +25 -6
- package/dist/config/config-field-policy.js +17 -5
- package/dist/config/config-manager.js +198 -22
- package/dist/config/config-operation-service.js +35 -38
- package/dist/config/contact-bind-code.js +274 -0
- package/dist/config/contact-book-store.js +173 -5
- package/dist/config/contact-book.js +32 -0
- package/dist/config/contact-operation-service.js +9 -3
- package/dist/config/contact-request-service.js +331 -0
- package/dist/config/peer-role-resolver.js +3 -1
- package/dist/config/schema-registry.js +49 -24
- package/dist/config-store.js +14 -2
- package/dist/core/auth/agent-delegation.js +105 -11
- package/dist/core/auth/authorization-audit.js +6 -0
- package/dist/core/auth/operation-authorizer.js +8 -0
- package/dist/core/auth/operation-catalog.js +51 -3
- package/dist/core/auth/trigger-authorization.js +15 -0
- package/dist/core/bootstrap-service.js +2 -9
- package/dist/core/channel-loader.js +6 -2
- package/dist/core/command/command-handler.js +10 -13
- package/dist/core/command/connect-menu.js +229 -26
- package/dist/core/command/evol-menu-version-gate.js +38 -0
- package/dist/core/command/group-menu.js +421 -0
- package/dist/core/command/menu-handler.js +269 -85
- package/dist/core/command/menu-protocol.js +8 -2
- package/dist/core/command/menu-token-store.js +102 -0
- package/dist/core/command/role-menu.js +16 -0
- package/dist/core/command/slash-gate.js +1 -1
- package/dist/core/command/slash-handler.js +158 -31
- package/dist/core/daemon-file-cache.js +28 -0
- package/dist/core/event-catalog.js +29 -0
- package/dist/core/evolagent-registry.js +4 -39
- package/dist/core/handoff/runtime.js +162 -37
- package/dist/core/handoff/store.js +46 -2
- package/dist/core/handoff/types.js +1 -0
- package/dist/core/inference/text-inference.js +16 -74
- package/dist/core/message/file-markers.js +7 -0
- package/dist/core/message/im-renderer.js +18 -14
- package/dist/core/message/inbound-admission.js +134 -0
- package/dist/core/message/message-bridge.js +616 -74
- package/dist/core/message/message-log.js +1 -0
- package/dist/core/message/message-queue.js +185 -10
- package/dist/core/message/peer-mode.js +7 -8
- package/dist/core/message/response-engine.js +381 -79
- package/dist/core/permission/approval-gateway.js +17 -10
- package/dist/core/permission/ec-command-parser.js +101 -46
- package/dist/core/permission/tool-policy.js +69 -24
- package/dist/core/protected-paths.js +36 -11
- package/dist/core/session/session-fs-store.js +19 -4
- package/dist/core/session/session-manager.js +232 -4
- package/dist/core/session/session-mapper.js +2 -0
- package/dist/core/session/session-renew.js +125 -69
- package/dist/core/session/session-turn-coordinator.js +5 -1
- package/dist/eck/kit-renderer.js +12 -1
- package/dist/eck/message-renderer.js +79 -1
- package/dist/index.js +224 -89
- package/dist/ipc.js +81 -4
- package/dist/paths.js +3 -0
- package/dist/response-system/config-resolver.js +29 -0
- package/dist/response-system/coordinator.js +8 -32
- package/dist/response-system/engines/v1/proactive-flow.js +1 -1
- package/dist/response-system/index.js +1 -0
- package/dist/response-system/modes/single-session/index.js +7 -4
- package/dist/trigger/parser.js +55 -15
- package/dist/trigger/patch.js +4 -1
- package/dist/trigger/scheduler.js +441 -39
- package/dist/utils/cross-platform.js +8 -2
- package/dist/utils/error-dict.json +7 -0
- package/dist/utils/evolcore-version.js +21 -0
- package/dist/utils/logger.js +2 -2
- package/dist/utils/process-introspect.js +19 -3
- package/dist/utils/stable-semver.js +21 -0
- package/dist/utils/stats.js +33 -9
- package/kits/docs/channels/aun.md +4 -13
- package/kits/docs/evolcore/INDEX.md +1 -1
- package/kits/docs/evolcore/config.md +47 -2
- package/kits/docs/evolcore/contact.md +8 -3
- package/kits/docs/evolcore/group-rules.md +46 -4
- package/kits/docs/evolcore/group.md +4 -4
- package/kits/docs/evolcore/msg.md +5 -5
- package/kits/docs/evolcore/trigger.md +25 -8
- package/kits/docs/path-registry.md +36 -17
- package/kits/eck_message_manifest.json +12 -1
- package/kits/migrations/migrate-contact-book-v2.mjs +7 -0
- package/kits/rules/01-overview.md +17 -7
- package/kits/rules/02-navigation.md +38 -18
- package/kits/rules/03-identity.md +28 -24
- package/kits/rules/04-relation.md +44 -28
- package/kits/rules/05-venue.md +31 -15
- package/kits/rules/06-channel.md +11 -7
- package/kits/schemas/_meta.json +18 -7
- package/kits/schemas/agent-config.schema.7.json +303 -0
- package/kits/schemas/agent-config.schema.8.json +304 -0
- package/kits/schemas/agent-config.schema.9.json +364 -0
- package/kits/schemas/contact-book.schema.3.json +67 -0
- package/kits/schemas/daemon.schema.1.json +3 -2
- package/kits/schemas/daemon.schema.2.json +101 -0
- package/kits/schemas/daemon.schema.3.json +123 -0
- package/kits/schemas/defaults.schema.2.json +85 -0
- package/kits/schemas/defaults.schema.3.json +73 -0
- package/kits/schemas/relation-config.schema.6.json +46 -0
- package/kits/schemas/relation-config.schema.7.json +59 -0
- package/kits/schemas/single-session.schema.2.json +57 -0
- package/kits/templates/message-fragments/handoff-context-to-target.md +9 -0
- package/kits/templates/message-fragments/handoff-request-to-target.md +14 -8
- package/kits/templates/message-fragments/handoff-response-to-origin.md +5 -6
- package/kits/templates/roles/admin.json +46 -0
- package/kits/templates/roles/member.json +4 -0
- package/kits/templates/roles/visitor.json +4 -0
- package/kits/templates/system-fragments/channel.md +12 -2
- package/kits/templates/system-fragments/identity.md +3 -1
- package/kits/templates/system-fragments/relation.md +1 -1
- package/kits/templates/system-fragments/session.md +6 -2
- package/package.json +4 -2
- package/MIGRATION-0.5.0.md +0 -378
- package/ROLE_ACCESS_CONTROL.md +0 -174
- package/dist/channels/contact-bind-code.js +0 -134
- package/dist/channels/wecom-card.js +0 -101
- package/dist/channels/wecom-onboarding.js +0 -82
- package/dist/channels/wecom-state.js +0 -191
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ensureDir } from '../../utils/atomic-write.js';
|
|
2
2
|
import { logger } from '../../utils/logger.js';
|
|
3
3
|
import { encodePath } from '../../utils/cross-platform.js';
|
|
4
|
-
import { chatDirPath, generateSessionId, formatTimestamp, atomicWriteJson, appendJsonl, readJsonFile, readLastJsonlLine, readAllJsonlLines, scanChatDirs, scanMetaFiles, ensureChatDir, readThreadIndex, writeThreadIndex, } from './session-fs-store.js';
|
|
4
|
+
import { chatDirPath, generateSessionId, formatTimestamp, atomicWriteJson, atomicWriteText, appendJsonl, readJsonFile, readLastJsonlLine, readAllJsonlLines, scanChatDirs, scanMetaFiles, ensureChatDir, readThreadIndex, writeThreadIndex, } from './session-fs-store.js';
|
|
5
5
|
import { sessionToFile, fileToSession } from './session-mapper.js';
|
|
6
6
|
import { formatSessionKey, DEFAULT_THREAD_ID } from './session-key.js';
|
|
7
7
|
import { tryParseChannelKey } from '../channel-loader.js';
|
|
@@ -9,6 +9,22 @@ import { isSystemControlChannel } from '../system-channels.js';
|
|
|
9
9
|
import path from 'path';
|
|
10
10
|
import fs from 'fs';
|
|
11
11
|
import os from 'os';
|
|
12
|
+
function newSessionStableMetadata(chatType, ...sources) {
|
|
13
|
+
const keys = chatType === 'group'
|
|
14
|
+
? ['channelKey', 'groupId', 'groupName', 'dispatchMode']
|
|
15
|
+
: ['channelKey', 'peerId', 'peerName', 'peerType'];
|
|
16
|
+
const metadata = {};
|
|
17
|
+
for (const source of sources) {
|
|
18
|
+
if (!source)
|
|
19
|
+
continue;
|
|
20
|
+
for (const key of keys) {
|
|
21
|
+
const value = source[key];
|
|
22
|
+
if (value !== undefined && value !== null && value !== '')
|
|
23
|
+
metadata[key] = value;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return metadata;
|
|
27
|
+
}
|
|
12
28
|
/** 判定用户是否为指定渠道的 admin */
|
|
13
29
|
/** 判定用户是否为指定渠道的 member */
|
|
14
30
|
export class SessionManager {
|
|
@@ -23,6 +39,7 @@ export class SessionManager {
|
|
|
23
39
|
this.eventBus = eventBus;
|
|
24
40
|
this.identityResolver = identityResolver;
|
|
25
41
|
this.migrateChannelKeyFormat();
|
|
42
|
+
this.migrateUnknownAunDirs();
|
|
26
43
|
this.migrateWecomChannelDirs();
|
|
27
44
|
}
|
|
28
45
|
setIdentityResolver(resolver) {
|
|
@@ -1098,18 +1115,20 @@ export class SessionManager {
|
|
|
1098
1115
|
}
|
|
1099
1116
|
}
|
|
1100
1117
|
}
|
|
1101
|
-
async createNewSession(channel, channelId, projectPath, name, baseagent) {
|
|
1118
|
+
async createNewSession(channel, channelId, projectPath, name, baseagent, identityMetadata) {
|
|
1102
1119
|
const inheritedChatType = this.getActiveChatType(channel, channelId);
|
|
1103
1120
|
// Derive selfAID and channelType from existing sessions
|
|
1104
1121
|
const existingDir = this.findExistingChatDir(channel, channelId);
|
|
1105
1122
|
const identity = this.deriveChannelIdentity(channel, channelId);
|
|
1106
1123
|
let channelType = identity.channelType;
|
|
1107
1124
|
let selfAID = identity.selfAID;
|
|
1125
|
+
let activeMetadata;
|
|
1108
1126
|
if (existingDir) {
|
|
1109
1127
|
const active = readJsonFile(path.join(existingDir, 'active.json'));
|
|
1110
1128
|
if (active) {
|
|
1111
1129
|
channelType = active.channelType || channel;
|
|
1112
1130
|
selfAID = active.selfAID || '';
|
|
1131
|
+
activeMetadata = active.metadata;
|
|
1113
1132
|
// 兼容旧字段名
|
|
1114
1133
|
const activeBaseagent = active.baseagent || active.agentType;
|
|
1115
1134
|
if (!baseagent && activeBaseagent)
|
|
@@ -1130,7 +1149,7 @@ export class SessionManager {
|
|
|
1130
1149
|
baseagent,
|
|
1131
1150
|
sessionKey: formatSessionKey(channelType, channelId, DEFAULT_THREAD_ID),
|
|
1132
1151
|
chatType: inheritedChatType,
|
|
1133
|
-
metadata:
|
|
1152
|
+
metadata: newSessionStableMetadata(inheritedChatType, activeMetadata, identityMetadata),
|
|
1134
1153
|
name: name || '默认会话',
|
|
1135
1154
|
createdAt: Date.now(),
|
|
1136
1155
|
updatedAt: Date.now(),
|
|
@@ -1258,7 +1277,7 @@ export class SessionManager {
|
|
|
1258
1277
|
* 为超时后的独立话题创建新会话。保留聊天路由和稳定 metadata,
|
|
1259
1278
|
* 但不继承任何底层 agent session 或临时任务状态。
|
|
1260
1279
|
*/
|
|
1261
|
-
async createRenewedSession(sourceSession) {
|
|
1280
|
+
async createRenewedSession(sourceSession, context) {
|
|
1262
1281
|
const metadata = { ...(sourceSession.metadata || {}) };
|
|
1263
1282
|
delete metadata.agentSessions;
|
|
1264
1283
|
delete metadata.resumeAt;
|
|
@@ -1289,7 +1308,11 @@ export class SessionManager {
|
|
|
1289
1308
|
projectPath: session.projectPath,
|
|
1290
1309
|
name: session.name,
|
|
1291
1310
|
chatType: session.chatType,
|
|
1311
|
+
cause: 'renew',
|
|
1312
|
+
sourceSessionId: sourceSession.id,
|
|
1313
|
+
renewSource: context?.source,
|
|
1292
1314
|
timestamp: Date.now(),
|
|
1315
|
+
causation: context?.causation,
|
|
1293
1316
|
});
|
|
1294
1317
|
return session;
|
|
1295
1318
|
}
|
|
@@ -1640,4 +1663,209 @@ export class SessionManager {
|
|
|
1640
1663
|
logger.info(`[SessionManager] Migrated ${migrated} WeCom session director${migrated === 1 ? 'y' : 'ies'} to channelKey isolation`);
|
|
1641
1664
|
}
|
|
1642
1665
|
}
|
|
1666
|
+
/**
|
|
1667
|
+
* Recover legacy AUN chats written beneath `_unknown` before selfAID was
|
|
1668
|
+
* persisted. A channel key or persisted selfAID is required to make the
|
|
1669
|
+
* migration unambiguous; all other directories remain untouched.
|
|
1670
|
+
*/
|
|
1671
|
+
migrateUnknownAunDirs() {
|
|
1672
|
+
const legacyDirs = scanChatDirs(this.sessionsDir)
|
|
1673
|
+
.filter(entry => entry.channelType === 'aun' && entry.selfAID === '_unknown');
|
|
1674
|
+
let migrated = 0;
|
|
1675
|
+
for (const entry of legacyDirs) {
|
|
1676
|
+
const selfAIDs = this.findAunSelfAIDCandidates(entry.dirPath);
|
|
1677
|
+
if (selfAIDs.size !== 1) {
|
|
1678
|
+
if (selfAIDs.size > 1) {
|
|
1679
|
+
logger.error(`[SessionManager] Cannot auto-migrate mixed unknown AUN session directory: ${entry.dirPath}`);
|
|
1680
|
+
}
|
|
1681
|
+
continue;
|
|
1682
|
+
}
|
|
1683
|
+
const selfAID = [...selfAIDs][0];
|
|
1684
|
+
const target = chatDirPath(this.sessionsDir, 'aun', entry.channelId, selfAID);
|
|
1685
|
+
try {
|
|
1686
|
+
this.mergeUnknownAunDirectory(entry.dirPath, target, selfAID);
|
|
1687
|
+
migrated++;
|
|
1688
|
+
}
|
|
1689
|
+
catch (error) {
|
|
1690
|
+
logger.error(`[SessionManager] Failed to migrate unknown AUN session directory ${entry.dirPath}: ${String(error)}`);
|
|
1691
|
+
}
|
|
1692
|
+
}
|
|
1693
|
+
if (migrated > 0) {
|
|
1694
|
+
logger.info(`[SessionManager] Migrated ${migrated} unknown AUN session director${migrated === 1 ? 'y' : 'ies'} to selfAID isolation`);
|
|
1695
|
+
}
|
|
1696
|
+
}
|
|
1697
|
+
findAunSelfAIDCandidates(chatDir) {
|
|
1698
|
+
const candidates = new Set();
|
|
1699
|
+
const visitRecord = (record) => {
|
|
1700
|
+
if (!record || typeof record !== 'object')
|
|
1701
|
+
return;
|
|
1702
|
+
const session = record;
|
|
1703
|
+
const direct = typeof session.selfAID === 'string' ? session.selfAID.trim() : '';
|
|
1704
|
+
if (direct && direct !== '_unknown')
|
|
1705
|
+
candidates.add(direct);
|
|
1706
|
+
const channel = typeof session.channel === 'string' ? tryParseChannelKey(session.channel) : null;
|
|
1707
|
+
if (channel?.type === 'aun' && channel.selfAID !== '_unknown')
|
|
1708
|
+
candidates.add(channel.selfAID);
|
|
1709
|
+
};
|
|
1710
|
+
const visitJsonl = (filePath) => {
|
|
1711
|
+
for (const record of readAllJsonlLines(filePath))
|
|
1712
|
+
visitRecord(record);
|
|
1713
|
+
};
|
|
1714
|
+
visitRecord(readJsonFile(path.join(chatDir, 'active.json')));
|
|
1715
|
+
for (const metaFile of scanMetaFiles(chatDir))
|
|
1716
|
+
visitJsonl(path.join(chatDir, metaFile));
|
|
1717
|
+
const threadDir = path.join(chatDir, '_threads');
|
|
1718
|
+
for (const metaFile of scanMetaFiles(threadDir))
|
|
1719
|
+
visitJsonl(path.join(threadDir, metaFile));
|
|
1720
|
+
return candidates;
|
|
1721
|
+
}
|
|
1722
|
+
mergeUnknownAunDirectory(sourceDir, targetDir, selfAID) {
|
|
1723
|
+
if (sourceDir === targetDir)
|
|
1724
|
+
return;
|
|
1725
|
+
fs.mkdirSync(targetDir, { recursive: true });
|
|
1726
|
+
this.mergeUnknownAunDirectoryContents(sourceDir, targetDir, selfAID);
|
|
1727
|
+
fs.rmdirSync(sourceDir);
|
|
1728
|
+
}
|
|
1729
|
+
mergeUnknownAunDirectoryContents(sourceDir, targetDir, selfAID) {
|
|
1730
|
+
for (const entry of fs.readdirSync(sourceDir, { withFileTypes: true })) {
|
|
1731
|
+
const sourcePath = path.join(sourceDir, entry.name);
|
|
1732
|
+
const targetPath = path.join(targetDir, entry.name);
|
|
1733
|
+
if (entry.isDirectory()) {
|
|
1734
|
+
fs.mkdirSync(targetPath, { recursive: true });
|
|
1735
|
+
this.mergeUnknownAunDirectoryContents(sourcePath, targetPath, selfAID);
|
|
1736
|
+
fs.rmdirSync(sourcePath);
|
|
1737
|
+
continue;
|
|
1738
|
+
}
|
|
1739
|
+
if (!entry.isFile())
|
|
1740
|
+
continue;
|
|
1741
|
+
if (entry.name === 'active.json') {
|
|
1742
|
+
this.mergeUnknownAunActiveFile(sourcePath, targetPath, selfAID);
|
|
1743
|
+
}
|
|
1744
|
+
else if (entry.name.endsWith('.jsonl')) {
|
|
1745
|
+
this.mergeUnknownAunJsonlFile(sourcePath, targetPath, selfAID);
|
|
1746
|
+
}
|
|
1747
|
+
else if (entry.name.endsWith('.json')) {
|
|
1748
|
+
this.mergeUnknownAunJsonFile(sourcePath, targetPath);
|
|
1749
|
+
}
|
|
1750
|
+
else if (!fs.existsSync(targetPath)) {
|
|
1751
|
+
fs.renameSync(sourcePath, targetPath);
|
|
1752
|
+
}
|
|
1753
|
+
else if (fs.readFileSync(sourcePath).equals(fs.readFileSync(targetPath))) {
|
|
1754
|
+
fs.unlinkSync(sourcePath);
|
|
1755
|
+
}
|
|
1756
|
+
else {
|
|
1757
|
+
fs.renameSync(sourcePath, this.uniqueLegacyFilePath(targetPath));
|
|
1758
|
+
}
|
|
1759
|
+
}
|
|
1760
|
+
}
|
|
1761
|
+
mergeUnknownAunActiveFile(sourcePath, targetPath, selfAID) {
|
|
1762
|
+
const source = this.normalizeAunSessionRecord(readJsonFile(sourcePath), selfAID);
|
|
1763
|
+
const target = this.normalizeAunSessionRecord(readJsonFile(targetPath), selfAID);
|
|
1764
|
+
const sourceUpdatedAt = typeof source?.updatedAt === 'number' ? source.updatedAt : 0;
|
|
1765
|
+
const targetUpdatedAt = typeof target?.updatedAt === 'number' ? target.updatedAt : 0;
|
|
1766
|
+
const selected = target && targetUpdatedAt >= sourceUpdatedAt ? target : source;
|
|
1767
|
+
if (selected)
|
|
1768
|
+
atomicWriteJson(targetPath, selected);
|
|
1769
|
+
fs.unlinkSync(sourcePath);
|
|
1770
|
+
}
|
|
1771
|
+
mergeUnknownAunJsonlFile(sourcePath, targetPath, selfAID) {
|
|
1772
|
+
const records = [
|
|
1773
|
+
...this.readNormalizedAunJsonl(sourcePath, selfAID),
|
|
1774
|
+
...this.readNormalizedAunJsonl(targetPath, selfAID),
|
|
1775
|
+
];
|
|
1776
|
+
const seen = new Set();
|
|
1777
|
+
const unique = records.filter(record => {
|
|
1778
|
+
if (seen.has(record.key))
|
|
1779
|
+
return false;
|
|
1780
|
+
seen.add(record.key);
|
|
1781
|
+
return true;
|
|
1782
|
+
});
|
|
1783
|
+
unique.sort((left, right) => left.timestamp - right.timestamp || left.order - right.order);
|
|
1784
|
+
atomicWriteText(targetPath, unique.map(record => record.line).join('\n') + (unique.length ? '\n' : ''));
|
|
1785
|
+
fs.unlinkSync(sourcePath);
|
|
1786
|
+
}
|
|
1787
|
+
readNormalizedAunJsonl(filePath, selfAID) {
|
|
1788
|
+
let content;
|
|
1789
|
+
try {
|
|
1790
|
+
content = fs.readFileSync(filePath, 'utf-8');
|
|
1791
|
+
}
|
|
1792
|
+
catch (error) {
|
|
1793
|
+
if (error?.code === 'ENOENT')
|
|
1794
|
+
return [];
|
|
1795
|
+
throw error;
|
|
1796
|
+
}
|
|
1797
|
+
return content.split('\n').flatMap((rawLine, order) => {
|
|
1798
|
+
const line = rawLine.trim();
|
|
1799
|
+
if (!line)
|
|
1800
|
+
return [];
|
|
1801
|
+
try {
|
|
1802
|
+
const record = this.normalizeAunSessionRecord(JSON.parse(line), selfAID);
|
|
1803
|
+
const normalizedLine = JSON.stringify(record);
|
|
1804
|
+
return [{
|
|
1805
|
+
key: this.stableJson(record),
|
|
1806
|
+
line: normalizedLine,
|
|
1807
|
+
timestamp: this.recordTimestamp(record),
|
|
1808
|
+
order,
|
|
1809
|
+
}];
|
|
1810
|
+
}
|
|
1811
|
+
catch {
|
|
1812
|
+
return [{ key: `raw:${line}`, line, timestamp: Number.MAX_SAFE_INTEGER, order }];
|
|
1813
|
+
}
|
|
1814
|
+
});
|
|
1815
|
+
}
|
|
1816
|
+
normalizeAunSessionRecord(record, selfAID) {
|
|
1817
|
+
if (!record || typeof record !== 'object' || Array.isArray(record))
|
|
1818
|
+
return record;
|
|
1819
|
+
const result = { ...record };
|
|
1820
|
+
const isSessionRecord = 'channel' in result || 'channelType' in result || 'sessionKey' in result || 'selfAID' in result;
|
|
1821
|
+
if (!isSessionRecord)
|
|
1822
|
+
return result;
|
|
1823
|
+
if (!result.selfAID || result.selfAID === '_unknown')
|
|
1824
|
+
result.selfAID = selfAID;
|
|
1825
|
+
return result;
|
|
1826
|
+
}
|
|
1827
|
+
stableJson(value) {
|
|
1828
|
+
if (Array.isArray(value))
|
|
1829
|
+
return `[${value.map(item => this.stableJson(item)).join(',')}]`;
|
|
1830
|
+
if (value && typeof value === 'object') {
|
|
1831
|
+
return `{${Object.keys(value).sort().map(key => `${JSON.stringify(key)}:${this.stableJson(value[key])}`).join(',')}}`;
|
|
1832
|
+
}
|
|
1833
|
+
return JSON.stringify(value);
|
|
1834
|
+
}
|
|
1835
|
+
recordTimestamp(record) {
|
|
1836
|
+
if (!record || typeof record !== 'object')
|
|
1837
|
+
return Number.MAX_SAFE_INTEGER;
|
|
1838
|
+
const value = record;
|
|
1839
|
+
for (const key of ['ts', 'at', 'updatedAt', 'createdAt']) {
|
|
1840
|
+
if (typeof value[key] === 'number' && Number.isFinite(value[key]))
|
|
1841
|
+
return value[key];
|
|
1842
|
+
}
|
|
1843
|
+
return Number.MAX_SAFE_INTEGER;
|
|
1844
|
+
}
|
|
1845
|
+
mergeUnknownAunJsonFile(sourcePath, targetPath) {
|
|
1846
|
+
if (!fs.existsSync(targetPath)) {
|
|
1847
|
+
fs.renameSync(sourcePath, targetPath);
|
|
1848
|
+
return;
|
|
1849
|
+
}
|
|
1850
|
+
const source = readJsonFile(sourcePath);
|
|
1851
|
+
const target = readJsonFile(targetPath);
|
|
1852
|
+
if (source && target && !Array.isArray(source) && !Array.isArray(target)) {
|
|
1853
|
+
atomicWriteJson(targetPath, { ...source, ...target });
|
|
1854
|
+
fs.unlinkSync(sourcePath);
|
|
1855
|
+
return;
|
|
1856
|
+
}
|
|
1857
|
+
if (fs.readFileSync(sourcePath).equals(fs.readFileSync(targetPath))) {
|
|
1858
|
+
fs.unlinkSync(sourcePath);
|
|
1859
|
+
return;
|
|
1860
|
+
}
|
|
1861
|
+
fs.renameSync(sourcePath, this.uniqueLegacyFilePath(targetPath));
|
|
1862
|
+
}
|
|
1863
|
+
uniqueLegacyFilePath(targetPath) {
|
|
1864
|
+
let index = 1;
|
|
1865
|
+
let candidate = `${targetPath}.legacy-unknown`;
|
|
1866
|
+
while (fs.existsSync(candidate)) {
|
|
1867
|
+
candidate = `${targetPath}.legacy-unknown-${index++}`;
|
|
1868
|
+
}
|
|
1869
|
+
return candidate;
|
|
1870
|
+
}
|
|
1643
1871
|
}
|
|
@@ -3,6 +3,8 @@ import { formatSessionKey, DEFAULT_THREAD_ID } from './session-key.js';
|
|
|
3
3
|
export function sessionToFile(session) {
|
|
4
4
|
const metadata = {};
|
|
5
5
|
if (session.metadata) {
|
|
6
|
+
if (session.metadata.channelKey)
|
|
7
|
+
metadata.channelKey = session.metadata.channelKey;
|
|
6
8
|
if (session.metadata.peerId)
|
|
7
9
|
metadata.peerId = session.metadata.peerId;
|
|
8
10
|
if (session.metadata.peerName)
|
|
@@ -3,8 +3,11 @@ import { formatPeerKey } from '../relation/peer-identity.js';
|
|
|
3
3
|
import { logger } from '../../utils/logger.js';
|
|
4
4
|
import { isTransientProtocolMessage, messageLogPath } from '../message/message-log.js';
|
|
5
5
|
import { readAllJsonlLines } from './session-fs-store.js';
|
|
6
|
+
import { ResponseModeRegistry } from '../../response-system/registry.js';
|
|
7
|
+
import { registerBuiltinModes } from '../../response-system/modes/index.js';
|
|
8
|
+
import { resolveEffectiveResponseModeConfig } from '../../response-system/config-resolver.js';
|
|
9
|
+
import { normalizeBaseagent } from '../../agents/baseagent.js';
|
|
6
10
|
const DEFAULT_AFTER_HOURS = 24;
|
|
7
|
-
const DEFAULT_EFFORT = 'low';
|
|
8
11
|
const DEFAULT_FALLBACK = 'continue';
|
|
9
12
|
const MAX_CONTEXT_MESSAGES = 40;
|
|
10
13
|
const MAX_CONTEXT_CHARS = 16_000;
|
|
@@ -12,7 +15,7 @@ const MAX_MESSAGE_CHARS = 2_000;
|
|
|
12
15
|
const MODEL_TIMEOUT_MS = 10_000;
|
|
13
16
|
const RECENT_DECISION_TTL_MS = 30_000;
|
|
14
17
|
const NEW_CONFIDENCE_THRESHOLD = 0.85;
|
|
15
|
-
const
|
|
18
|
+
const AUXILIARY_EFFORTS = new Set(['low', 'medium', 'high', 'xhigh', 'max']);
|
|
16
19
|
const EXPLICIT_NEW_RE = /^(?:新话题|换个话题|重新开始|新开会话|新会话)[::,,;;。!!\n]/;
|
|
17
20
|
const EXPLICIT_CONTINUE_RE = /^(?:继续上次|延续上次|继续|接着)[::,,;;。!!\n]/;
|
|
18
21
|
const NON_CONVERSATION_TYPES = new Set([
|
|
@@ -43,41 +46,6 @@ export function selectSessionRenewContext(entries) {
|
|
|
43
46
|
const selected = [entries[0], ...entries.slice(-(MAX_CONTEXT_MESSAGES - 1))];
|
|
44
47
|
return trimContextByChars(selected);
|
|
45
48
|
}
|
|
46
|
-
export function selectLatestHaikuModel(models) {
|
|
47
|
-
let selected;
|
|
48
|
-
for (const rawModel of models) {
|
|
49
|
-
const id = rawModel.trim();
|
|
50
|
-
if (!id)
|
|
51
|
-
continue;
|
|
52
|
-
const leaf = id.toLowerCase().split('/').at(-1) ?? '';
|
|
53
|
-
const tokens = leaf.split('-');
|
|
54
|
-
const haikuIndex = tokens.indexOf('haiku');
|
|
55
|
-
if (haikuIndex < 0)
|
|
56
|
-
continue;
|
|
57
|
-
const versionTokens = haikuIndex <= 1
|
|
58
|
-
? tokens.slice(haikuIndex + 1)
|
|
59
|
-
: tokens.slice(1, haikuIndex);
|
|
60
|
-
const versions = versionTokens
|
|
61
|
-
.filter(token => /^\d{1,2}$/.test(token))
|
|
62
|
-
.map(Number);
|
|
63
|
-
const dates = tokens
|
|
64
|
-
.filter(token => /^\d{8}$/.test(token))
|
|
65
|
-
.map(Number);
|
|
66
|
-
const candidate = {
|
|
67
|
-
id,
|
|
68
|
-
major: versions[0] ?? 0,
|
|
69
|
-
minor: versions[1] ?? 0,
|
|
70
|
-
date: dates.length > 0 ? Math.max(...dates) : 0,
|
|
71
|
-
};
|
|
72
|
-
if (!selected
|
|
73
|
-
|| candidate.major > selected.major
|
|
74
|
-
|| (candidate.major === selected.major && candidate.minor > selected.minor)
|
|
75
|
-
|| (candidate.major === selected.major && candidate.minor === selected.minor && candidate.date > selected.date)) {
|
|
76
|
-
selected = candidate;
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
return selected?.id;
|
|
80
|
-
}
|
|
81
49
|
function trimContextByChars(entries) {
|
|
82
50
|
if (entries.length === 0)
|
|
83
51
|
return [];
|
|
@@ -100,17 +68,27 @@ export class SessionRenewService {
|
|
|
100
68
|
locks = new Map();
|
|
101
69
|
recentDecisions = new Map();
|
|
102
70
|
now;
|
|
103
|
-
|
|
71
|
+
effectiveConfigResolver;
|
|
72
|
+
eventBus;
|
|
73
|
+
responseModeRegistry = new ResponseModeRegistry();
|
|
104
74
|
constructor(sessionManager, inferenceProviderResolver, options = {}) {
|
|
105
75
|
this.sessionManager = sessionManager;
|
|
106
76
|
this.inferenceProviderResolver = inferenceProviderResolver;
|
|
107
77
|
this.now = options.now ?? (() => Date.now());
|
|
108
|
-
this.
|
|
78
|
+
this.effectiveConfigResolver = options.resolveEffectiveConfig;
|
|
79
|
+
this.eventBus = options.eventBus;
|
|
80
|
+
registerBuiltinModes(this.responseModeRegistry);
|
|
109
81
|
}
|
|
110
82
|
async resolve(request) {
|
|
111
|
-
const
|
|
83
|
+
const effectiveConfig = this.resolveEffectiveConfig(request);
|
|
84
|
+
const config = effectiveConfig?.session_renew;
|
|
112
85
|
if (!config || config.enabled !== true)
|
|
113
86
|
return { session: request.session, renewed: false };
|
|
87
|
+
// Thread / processing sessions are already an explicit routing decision.
|
|
88
|
+
// Do not let a cached or active main session replace them before resolveLocked().
|
|
89
|
+
if (request.session.threadId || request.session.processingState) {
|
|
90
|
+
return { session: request.session, renewed: false };
|
|
91
|
+
}
|
|
114
92
|
const routeKey = this.routeKey(request);
|
|
115
93
|
return await this.withLock(routeKey, async () => {
|
|
116
94
|
const cached = this.recentDecisions.get(routeKey);
|
|
@@ -133,12 +111,11 @@ export class SessionRenewService {
|
|
|
133
111
|
active.identity = request.session.identity;
|
|
134
112
|
return { session: active, renewed: true, decision: 'new', source: 'fallback' };
|
|
135
113
|
}
|
|
136
|
-
return await this.resolveLocked(routeKey, request, config);
|
|
114
|
+
return await this.resolveLocked(routeKey, request, config, effectiveConfig);
|
|
137
115
|
});
|
|
138
116
|
}
|
|
139
|
-
async resolveLocked(routeKey, request, config) {
|
|
140
|
-
|
|
141
|
-
return { session: request.session, renewed: false };
|
|
117
|
+
async resolveLocked(routeKey, request, config, effectiveConfig) {
|
|
118
|
+
const evaluationStartedAt = Date.now();
|
|
142
119
|
const afterHours = validPositiveNumber(config.after_hours) ?? DEFAULT_AFTER_HOURS;
|
|
143
120
|
const entries = this.readConversationEntries(request.session);
|
|
144
121
|
const last = entries[entries.length - 1];
|
|
@@ -146,7 +123,7 @@ export class SessionRenewService {
|
|
|
146
123
|
if (request.isNewSession)
|
|
147
124
|
return { session: request.session, renewed: false };
|
|
148
125
|
const idleMs = this.now() - request.session.updatedAt;
|
|
149
|
-
return await this.finishDecision(routeKey, request, 'new', 'missing_history', idleMs);
|
|
126
|
+
return await this.finishDecision(routeKey, request, 'new', 'missing_history', idleMs, evaluationStartedAt);
|
|
150
127
|
}
|
|
151
128
|
const idleMs = this.now() - last.ts;
|
|
152
129
|
if (idleMs <= afterHours * 60 * 60 * 1000)
|
|
@@ -162,13 +139,22 @@ export class SessionRenewService {
|
|
|
162
139
|
source = 'reply';
|
|
163
140
|
}
|
|
164
141
|
else {
|
|
165
|
-
|
|
142
|
+
const auxiliary = this.resolveAuxiliaryConfig(effectiveConfig, request.session.baseagent);
|
|
143
|
+
let judgeMetadata = {
|
|
144
|
+
responseMode: auxiliary.responseMode,
|
|
145
|
+
baseagent: request.session.baseagent,
|
|
146
|
+
model: auxiliary.model,
|
|
147
|
+
effort: auxiliary.effort,
|
|
148
|
+
};
|
|
166
149
|
try {
|
|
167
|
-
|
|
150
|
+
if (!auxiliary.model)
|
|
151
|
+
throw new SessionRenewJudgeError('auxiliary_model_missing');
|
|
152
|
+
if (auxiliary.invalidEffort)
|
|
153
|
+
throw new SessionRenewJudgeError('auxiliary_effort_invalid');
|
|
154
|
+
const provider = this.inferenceProviderResolver.getTextInferenceProvider(request.channelName, request.session.baseagent, request.selfAid);
|
|
168
155
|
if (!provider)
|
|
169
|
-
throw new
|
|
170
|
-
const judged = await this.judgeWithModel(request,
|
|
171
|
-
judgeModel = judged.model;
|
|
156
|
+
throw new SessionRenewJudgeError('provider_unavailable');
|
|
157
|
+
const judged = await this.judgeWithModel(request, entries, idleMs, provider, auxiliary);
|
|
172
158
|
decision = judged.decision === 'new' && judged.confidence < NEW_CONFIDENCE_THRESHOLD
|
|
173
159
|
? 'continue'
|
|
174
160
|
: judged.decision;
|
|
@@ -177,15 +163,19 @@ export class SessionRenewService {
|
|
|
177
163
|
catch (error) {
|
|
178
164
|
decision = config.fallback_action ?? DEFAULT_FALLBACK;
|
|
179
165
|
source = 'fallback';
|
|
166
|
+
judgeMetadata = { ...judgeMetadata, fallbackReason: sessionRenewFallbackReason(error) };
|
|
180
167
|
logger.warn(`[SessionRenew] judge failed, using fallback=${decision}: ${error instanceof Error ? error.message : String(error)}`);
|
|
181
168
|
}
|
|
182
|
-
return await this.finishDecision(routeKey, request, decision, source, idleMs,
|
|
169
|
+
return await this.finishDecision(routeKey, request, decision, source, idleMs, evaluationStartedAt, judgeMetadata);
|
|
183
170
|
}
|
|
184
|
-
return await this.finishDecision(routeKey, request, decision, source, idleMs);
|
|
171
|
+
return await this.finishDecision(routeKey, request, decision, source, idleMs, evaluationStartedAt);
|
|
185
172
|
}
|
|
186
|
-
async finishDecision(routeKey, request, decision, source, idleMs,
|
|
173
|
+
async finishDecision(routeKey, request, decision, source, idleMs, evaluationStartedAt, judge) {
|
|
187
174
|
const selected = decision === 'new'
|
|
188
|
-
? await this.sessionManager.createRenewedSession(request.session
|
|
175
|
+
? await this.sessionManager.createRenewedSession(request.session, {
|
|
176
|
+
source,
|
|
177
|
+
causation: request.causation,
|
|
178
|
+
})
|
|
189
179
|
: request.session;
|
|
190
180
|
selected.identity = request.session.identity;
|
|
191
181
|
this.recentDecisions.set(routeKey, {
|
|
@@ -195,12 +185,39 @@ export class SessionRenewService {
|
|
|
195
185
|
source,
|
|
196
186
|
expiresAt: this.now() + RECENT_DECISION_TTL_MS,
|
|
197
187
|
});
|
|
198
|
-
logger.info(`[SessionRenew] session=${request.session.id} selected=${selected.id} decision=${decision} source=${source}
|
|
188
|
+
logger.info(`[SessionRenew] session=${request.session.id} selected=${selected.id} decision=${decision} source=${source}`
|
|
189
|
+
+ ` idleHours=${(idleMs / 3_600_000).toFixed(2)}`
|
|
190
|
+
+ ` responseMode=${judge?.responseMode ?? 'none'}`
|
|
191
|
+
+ ` baseagent=${judge?.baseagent ?? 'none'}`
|
|
192
|
+
+ ` model=${judge?.model ?? 'none'}`
|
|
193
|
+
+ ` effort=${judge?.effort ?? 'provider-default'}`
|
|
194
|
+
+ ` fallbackReason=${judge?.fallbackReason ?? 'none'}`);
|
|
195
|
+
this.eventBus?.publish({
|
|
196
|
+
type: 'session:renew-evaluated',
|
|
197
|
+
candidateSessionId: request.session.id,
|
|
198
|
+
selectedSessionId: selected.id,
|
|
199
|
+
renewed: decision === 'new',
|
|
200
|
+
decision,
|
|
201
|
+
source,
|
|
202
|
+
channel: request.session.channel,
|
|
203
|
+
channelName: request.channelName,
|
|
204
|
+
channelId: request.channelId,
|
|
205
|
+
selfAid: request.selfAid,
|
|
206
|
+
idleHours: Number((idleMs / 3_600_000).toFixed(2)),
|
|
207
|
+
responseMode: judge?.responseMode,
|
|
208
|
+
baseagent: judge?.baseagent,
|
|
209
|
+
model: judge?.model,
|
|
210
|
+
effort: judge?.effort,
|
|
211
|
+
fallbackReason: judge?.fallbackReason,
|
|
212
|
+
durationMs: Math.max(0, Date.now() - evaluationStartedAt),
|
|
213
|
+
timestamp: this.now(),
|
|
214
|
+
causation: request.causation,
|
|
215
|
+
});
|
|
199
216
|
return { session: selected, renewed: decision === 'new', decision, source };
|
|
200
217
|
}
|
|
201
|
-
|
|
202
|
-
if (this.
|
|
203
|
-
return this.
|
|
218
|
+
resolveEffectiveConfig(request) {
|
|
219
|
+
if (this.effectiveConfigResolver)
|
|
220
|
+
return this.effectiveConfigResolver(request);
|
|
204
221
|
if (!request.selfAid)
|
|
205
222
|
return undefined;
|
|
206
223
|
const peerKeyId = request.chatType === 'group'
|
|
@@ -210,7 +227,30 @@ export class SessionRenewService {
|
|
|
210
227
|
return resolveEffective({
|
|
211
228
|
self: request.selfAid,
|
|
212
229
|
peerKey,
|
|
213
|
-
}, { cache: true })
|
|
230
|
+
}, { cache: true, expand: true });
|
|
231
|
+
}
|
|
232
|
+
resolveAuxiliaryConfig(config, baseagent) {
|
|
233
|
+
const resolved = resolveEffectiveResponseModeConfig(this.responseModeRegistry, config);
|
|
234
|
+
const rawModel = resolved.modeConfig.auxiliaryModel;
|
|
235
|
+
const canonicalBaseagent = normalizeBaseagent(baseagent).canonical;
|
|
236
|
+
const selectedModel = typeof rawModel === 'string'
|
|
237
|
+
? rawModel
|
|
238
|
+
: rawModel && typeof rawModel === 'object' && !Array.isArray(rawModel)
|
|
239
|
+
? rawModel[canonicalBaseagent]
|
|
240
|
+
: undefined;
|
|
241
|
+
const model = typeof selectedModel === 'string' && selectedModel.trim()
|
|
242
|
+
? selectedModel.trim()
|
|
243
|
+
: undefined;
|
|
244
|
+
const rawEffort = resolved.modeConfig.auxiliaryEffort;
|
|
245
|
+
const effort = typeof rawEffort === 'string' && AUXILIARY_EFFORTS.has(rawEffort)
|
|
246
|
+
? rawEffort
|
|
247
|
+
: undefined;
|
|
248
|
+
return {
|
|
249
|
+
responseMode: resolved.mode.id,
|
|
250
|
+
model,
|
|
251
|
+
effort,
|
|
252
|
+
invalidEffort: rawEffort !== undefined && effort === undefined,
|
|
253
|
+
};
|
|
214
254
|
}
|
|
215
255
|
readConversationEntries(session) {
|
|
216
256
|
const entries = readAllJsonlLines(messageLogPath(this.sessionManager.getChatDir(session)));
|
|
@@ -218,7 +258,7 @@ export class SessionRenewService {
|
|
|
218
258
|
.filter(entry => isSessionRenewConversationEntry(entry, session.id))
|
|
219
259
|
.sort((left, right) => left.ts - right.ts);
|
|
220
260
|
}
|
|
221
|
-
async judgeWithModel(request,
|
|
261
|
+
async judgeWithModel(request, entries, idleMs, provider, auxiliary) {
|
|
222
262
|
const context = selectSessionRenewContext(entries).map(entry => ({
|
|
223
263
|
dir: entry.dir,
|
|
224
264
|
content: entry.content.slice(0, MAX_MESSAGE_CHARS),
|
|
@@ -243,20 +283,14 @@ export class SessionRenewService {
|
|
|
243
283
|
let timer;
|
|
244
284
|
try {
|
|
245
285
|
const operation = (async () => {
|
|
246
|
-
if (!provider.listModels)
|
|
247
|
-
throw new Error('Claude text inference provider does not expose a model list');
|
|
248
|
-
const models = await provider.listModels(controller.signal);
|
|
249
|
-
const model = selectLatestHaikuModel(models);
|
|
250
|
-
if (!model)
|
|
251
|
-
throw new Error('Claude model list contains no Haiku model');
|
|
252
286
|
const text = await provider.completeText({
|
|
253
|
-
model,
|
|
254
|
-
effort:
|
|
287
|
+
model: auxiliary.model,
|
|
288
|
+
...(auxiliary.effort ? { effort: auxiliary.effort } : {}),
|
|
255
289
|
system: systemPrompt,
|
|
256
290
|
input: prompt,
|
|
257
291
|
signal: controller.signal,
|
|
258
292
|
});
|
|
259
|
-
return { model, text };
|
|
293
|
+
return { model: auxiliary.model, text };
|
|
260
294
|
})();
|
|
261
295
|
const timeout = new Promise((_resolve, reject) => {
|
|
262
296
|
timer = setTimeout(() => {
|
|
@@ -292,6 +326,28 @@ export class SessionRenewService {
|
|
|
292
326
|
}
|
|
293
327
|
}
|
|
294
328
|
}
|
|
329
|
+
class SessionRenewJudgeError extends Error {
|
|
330
|
+
code;
|
|
331
|
+
constructor(code) {
|
|
332
|
+
super(code);
|
|
333
|
+
this.code = code;
|
|
334
|
+
this.name = 'SessionRenewJudgeError';
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
function sessionRenewFallbackReason(error) {
|
|
338
|
+
if (error instanceof SessionRenewJudgeError)
|
|
339
|
+
return error.code;
|
|
340
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
341
|
+
if (/timed out|abort/i.test(message))
|
|
342
|
+
return 'timeout';
|
|
343
|
+
if (/HTTP \d+/i.test(message))
|
|
344
|
+
return 'http_error';
|
|
345
|
+
if (/no JSON|invalid decision|invalid confidence/i.test(message))
|
|
346
|
+
return 'invalid_response';
|
|
347
|
+
if (/no text|empty/i.test(message))
|
|
348
|
+
return 'empty_response';
|
|
349
|
+
return 'inference_error';
|
|
350
|
+
}
|
|
295
351
|
function validPositiveNumber(value) {
|
|
296
352
|
return typeof value === 'number' && Number.isFinite(value) && value > 0 ? value : undefined;
|
|
297
353
|
}
|
|
@@ -101,7 +101,11 @@ export class SessionTurnCoordinator {
|
|
|
101
101
|
turnId: active.turnId,
|
|
102
102
|
taskId: active.taskId,
|
|
103
103
|
generation: active.generation,
|
|
104
|
-
status: reason === '
|
|
104
|
+
status: reason === 'daemon_restart'
|
|
105
|
+
? 'interrupted'
|
|
106
|
+
: reason === 'stop' || reason === 'explicit_cancel'
|
|
107
|
+
? 'cancelled'
|
|
108
|
+
: 'superseded',
|
|
105
109
|
reason,
|
|
106
110
|
finishedAt: Date.now(),
|
|
107
111
|
},
|
package/dist/eck/kit-renderer.js
CHANGED
|
@@ -3,6 +3,7 @@ import path from 'path';
|
|
|
3
3
|
import { isEckSnapshotsEnabled } from '../config-store.js';
|
|
4
4
|
import { eckDebugDir } from '../paths.js';
|
|
5
5
|
import { logger } from '../utils/logger.js';
|
|
6
|
+
import { clearRoleStoreCache } from '../config/role-store.js';
|
|
6
7
|
import { loadManifest, loadManifestMeta, invalidateManifestCache, evaluateWhen, renderTemplate, renderLoopSection, resolvePathWithDiag, loadSectionFiles, loadChildTemplate, buildPathMappings, shortenPath, } from './manifest-engine.js';
|
|
7
8
|
const DEFAULT_MANIFEST_FILE = 'eck_manifest.json';
|
|
8
9
|
// ── Caches ──
|
|
@@ -19,9 +20,18 @@ export function loadKitManifest() {
|
|
|
19
20
|
const sections = loadManifest(DEFAULT_MANIFEST_FILE);
|
|
20
21
|
logger.info(`[KitRenderer] Loaded manifest: ${sections.length} sections`);
|
|
21
22
|
}
|
|
23
|
+
/**
|
|
24
|
+
* kits 统一失效入口:`kits/` 下的任何东西改了,都从这里一次清干净。
|
|
25
|
+
*
|
|
26
|
+
* 必须连带清 role-store 快照:它的签名含角色模板 mtime,而模板内容走 FileCache
|
|
27
|
+
* 的 on-reload(平时不查盘)。若"模板改盘 → 失效前发生一次鉴权读取",快照就会以
|
|
28
|
+
* **新 mtime + 旧内容** 落盘;此后签名不再变化,光清 FileCache 也换不掉它——
|
|
29
|
+
* 授权数据会永久停在旧版本。
|
|
30
|
+
*/
|
|
22
31
|
export function invalidateKitCache() {
|
|
23
32
|
invalidateManifestCache();
|
|
24
33
|
_sessionPathCache.clear();
|
|
34
|
+
clearRoleStoreCache();
|
|
25
35
|
}
|
|
26
36
|
export function invalidateSessionCache(sessionId) {
|
|
27
37
|
_sessionPathCache.delete(sessionId);
|
|
@@ -203,7 +213,7 @@ const PARAM_DESCRIPTIONS = {
|
|
|
203
213
|
sameEgressIp: '对端与本端共享同一出口 IP',
|
|
204
214
|
groupId: '群组 ID(群聊时)',
|
|
205
215
|
groupRulesPath: '群规则本地物化文件(relations/<peerKey>/rules.md)',
|
|
206
|
-
groupRulesStatus: '
|
|
216
|
+
groupRulesStatus: '群规则状态(ignored/synced/cached/missing/forbidden/invalid_metadata/file_mismatch/too_large/unreadable/error)',
|
|
207
217
|
groupRulesError: '群规则同步错误',
|
|
208
218
|
chatType: '聊天类型(private=私聊 / group=群聊 / null=本地开发)',
|
|
209
219
|
channel: '渠道类型(aun/feishu/wechat/dingtalk/qqbot/wecom)',
|
|
@@ -226,6 +236,7 @@ const PARAM_DESCRIPTIONS = {
|
|
|
226
236
|
osInfo: '操作系统及版本(如 Windows 11 Pro (win32 10.0.26200))',
|
|
227
237
|
threadId: '话题 ID(多话题路由时)',
|
|
228
238
|
chatMode: '会话模式(interactive=同步交互 / proactive=主动推送)',
|
|
239
|
+
isTriggerTask: '当前任务是否来源于 Trigger(true/false)',
|
|
229
240
|
proactivePreTool1stMsgChk: 'proactive 前置工具首消息检查(true/false)',
|
|
230
241
|
proactiveToolUseReminder: 'proactive 工具调用提醒(true/false)',
|
|
231
242
|
proactiveFirstSendRequired: '当前消息是否要求首次非发送工具前先发送表态',
|