evolcore 0.0.2 → 0.0.4
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 +93 -778
- 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 +198 -48
- package/dist/agents/codex-app-server-client.js +68 -0
- package/dist/agents/codex-runner.js +203 -8
- 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/aun/outbox.js +8 -0
- package/dist/channels/aun.js +241 -116
- package/dist/channels/daemon.js +18 -4
- package/dist/channels/dingtalk.js +1005 -260
- package/dist/channels/feishu.js +184 -56
- package/dist/channels/qqbot.js +23 -1
- package/dist/channels/wechat.js +303 -155
- package/dist/channels/wecom.js +1132 -164
- package/dist/cli/agent-command.js +2 -1
- package/dist/cli/aun-commands.js +98 -43
- package/dist/cli/bench.js +2 -2
- package/dist/cli/contact.js +71 -0
- package/dist/cli/ctl-command.js +2 -2
- package/dist/cli/daemon-commands.js +77 -214
- package/dist/cli/handoff-command.js +4 -3
- package/dist/cli/help.js +9 -5
- package/dist/cli/index.js +132 -116
- package/dist/cli/init-channel.js +242 -129
- package/dist/cli/init.js +70 -31
- package/dist/cli/model.js +2 -1
- package/dist/cli/net-check.js +2 -2
- package/dist/cli/queue-command.js +30 -6
- package/dist/cli/raw-key-input.js +25 -0
- package/dist/cli/response.js +5 -6
- package/dist/cli/restart-monitor.js +25 -1
- package/dist/cli/stats.js +6 -4
- package/dist/cli/trigger-command.js +146 -25
- package/dist/cli/version.js +6 -1
- package/dist/config/access-policy-domain.js +38 -0
- package/dist/config/access-policy.js +134 -0
- package/dist/config/builtin-role-templates.js +42 -17
- package/dist/config/builtin-roles.js +31 -6
- package/dist/config/config-field-policy.js +17 -5
- package/dist/config/config-manager.js +409 -29
- package/dist/config/config-operation-service.js +35 -38
- package/dist/config/contact-alias.js +68 -0
- package/dist/config/contact-bind-code.js +274 -0
- package/dist/config/contact-book-store.js +622 -0
- package/dist/config/contact-book-v2-startup.js +35 -0
- package/dist/config/contact-book.js +185 -300
- package/dist/config/contact-operation-service.js +116 -0
- package/dist/config/contact-request-service.js +331 -0
- package/dist/config/peer-role-resolver.js +136 -55
- package/dist/config/role-ranks.js +18 -0
- package/dist/config/role-service.js +16 -19
- package/dist/config/role-store.js +16 -5
- package/dist/config/roles.js +10 -1
- package/dist/config/schema-registry.js +49 -24
- package/dist/config-store.js +14 -4
- package/dist/core/auth/agent-delegation.js +105 -11
- package/dist/core/auth/authorization-audit.js +16 -1
- package/dist/core/auth/operation-authorizer.js +10 -0
- package/dist/core/auth/operation-catalog.js +105 -1
- 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 +113 -21
- package/dist/core/command/connect-menu.js +599 -0
- 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 +382 -100
- 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 +128 -29
- package/dist/core/command/slash-gate.js +1 -1
- package/dist/core/command/slash-handler.js +186 -49
- package/dist/core/daemon-file-cache.js +40 -6
- package/dist/core/event-catalog.js +99 -0
- package/dist/core/evolagent-registry.js +4 -40
- package/dist/core/evolagent.js +20 -9
- 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 +20 -14
- package/dist/core/message/inbound-admission.js +124 -0
- package/dist/core/message/message-bridge.js +670 -57
- package/dist/core/message/message-log.js +1 -0
- package/dist/core/message/message-queue.js +195 -10
- package/dist/core/message/message-utils.js +8 -2
- package/dist/core/message/peer-mode.js +7 -8
- package/dist/core/message/response-engine.js +642 -96
- package/dist/core/message/send-receipt.js +24 -0
- package/dist/core/message/stream-debouncer.js +11 -2
- 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 +115 -38
- package/dist/core/protected-paths.js +38 -11
- package/dist/core/session/session-fs-store.js +44 -3
- package/dist/core/session/session-manager.js +86 -8
- 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 +285 -94
- package/dist/ipc.js +114 -8
- 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/stats/billing.js +20 -8
- package/dist/trigger/manager.js +3 -0
- package/dist/trigger/parser.js +126 -11
- package/dist/trigger/patch.js +12 -2
- package/dist/trigger/scheduler.js +444 -40
- package/dist/trigger/validation.js +13 -0
- package/dist/utils/aid-bind.js +43 -29
- package/dist/utils/error-dict.json +7 -0
- package/dist/utils/evolcore-version.js +21 -0
- package/dist/utils/instance-registry.js +14 -7
- package/dist/utils/log-writer.js +46 -0
- package/dist/utils/logger.js +2 -2
- package/dist/utils/media-cache.js +4 -1
- package/dist/utils/model-prices.jsonl +6 -3
- package/dist/utils/restart-safety.js +31 -0
- package/dist/utils/stable-semver.js +21 -0
- package/dist/utils/stats.js +33 -9
- package/dist/utils/system-memory.js +62 -0
- package/kits/docs/INDEX.md +2 -1
- package/kits/docs/channels/aun.md +4 -13
- package/kits/docs/evolcore/INDEX.md +5 -3
- package/kits/docs/evolcore/agent.md +9 -1
- package/kits/docs/evolcore/aid.md +5 -2
- package/kits/docs/evolcore/config.md +47 -2
- package/kits/docs/evolcore/contact.md +61 -0
- package/kits/docs/evolcore/fs.md +9 -0
- package/kits/docs/evolcore/group-rules.md +46 -4
- package/kits/docs/evolcore/group.md +15 -6
- package/kits/docs/evolcore/model.md +4 -1
- package/kits/docs/evolcore/msg.md +12 -6
- package/kits/docs/evolcore/response.md +16 -21
- package/kits/docs/evolcore/rpc.md +2 -0
- package/kits/docs/evolcore/stats.md +15 -2
- package/kits/docs/evolcore/storage.md +1 -0
- package/kits/docs/evolcore/trigger.md +42 -10
- package/kits/docs/path-registry.md +36 -17
- package/kits/eck_manifest.json +12 -0
- package/kits/eck_message_manifest.json +12 -1
- package/kits/migrations/migrate-contact-book-v2.mjs +754 -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 +21 -7
- package/kits/schemas/agent-config.schema.6.json +322 -0
- 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 +374 -0
- package/kits/schemas/contact-book.schema.2.json +43 -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.5.json +47 -0
- package/kits/schemas/relation-config.schema.6.json +46 -0
- package/kits/schemas/relation-config.schema.7.json +59 -0
- package/kits/schemas/role-config.schema.1.json +1 -0
- package/kits/schemas/role-registry.schema.1.json +2 -2
- 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 +47 -0
- package/kits/templates/roles/member.json +5 -0
- package/kits/templates/roles/owner.json +1 -0
- package/kits/templates/roles/visitor.json +5 -0
- package/kits/templates/system-fragments/channel.md +12 -2
- package/kits/templates/system-fragments/commands.md +3 -1
- 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 +7 -5
- package/MIGRATION-0.5.0.md +0 -378
- package/ROLE_ACCESS_CONTROL.md +0 -174
- package/assets/brand/evolcore/README.md +0 -19
- package/assets/brand/evolcore/evolcore-app-icon.png +0 -0
- package/assets/brand/evolcore/evolcore-app-icon.svg +0 -13
- package/assets/brand/evolcore/evolcore-brand-board.png +0 -0
- package/assets/brand/evolcore/evolcore-brand-board.svg +0 -126
- package/assets/brand/evolcore/evolcore-logo-kit.zip +0 -0
- package/assets/brand/evolcore/evolcore-logo-reverse.png +0 -0
- package/assets/brand/evolcore/evolcore-logo-reverse.svg +0 -14
- package/assets/brand/evolcore/evolcore-logo.png +0 -0
- package/assets/brand/evolcore/evolcore-logo.svg +0 -14
- package/assets/brand/evolcore/evolcore-mark.png +0 -0
- package/assets/brand/evolcore/evolcore-mark.svg +0 -10
|
@@ -6,7 +6,7 @@ const H_CLASS_RELATIVE_PATTERNS = [
|
|
|
6
6
|
/^agents\/defaults\.json$/,
|
|
7
7
|
/^agents\/[^/]+\/config\.json$/,
|
|
8
8
|
/^agents\/[^/]+\/contact\.json$/,
|
|
9
|
-
/^agents\/[^/]+\/
|
|
9
|
+
/^agents\/[^/]+\/contact-operations\.jsonl$/,
|
|
10
10
|
/^agents\/[^/]+\/relations\/[^/]+\/config\.json$/,
|
|
11
11
|
/^backups\/config(?:\/|$)/,
|
|
12
12
|
/^\.snapshots(?:\/|$)/,
|
|
@@ -29,17 +29,18 @@ const H_CLASS_RELATIVE_PATTERNS = [
|
|
|
29
29
|
];
|
|
30
30
|
const H_CLASS_REFERENCE_PATTERNS = [
|
|
31
31
|
/(?:^|[^A-Za-z0-9_.-])daemon\.json(?=$|[^A-Za-z0-9_.-])/,
|
|
32
|
-
/(?:^|[^A-Za-z0-9_.-])agents[\\/](?:defaults\.json|[^/\\\s"']+[\\/](?:(?:config|contact)\.json|relations[\\/][^/\\\s"']+[\\/]config\.json))(?=$|[\s"';&|<>)},])/,
|
|
33
|
-
/(?:^|[^A-Za-z0-9_.-])agents[\\/][^/\\\s"']+[\\/]roles(?:[\\/]|$|[\s"';&|<>)},])/,
|
|
32
|
+
/(?:^|[^A-Za-z0-9_.-])agents[\\/](?:defaults\.json|[^/\\\s"']+[\\/](?:(?:config|contact)\.json|contact-operations\.jsonl|relations[\\/][^/\\\s"']+[\\/]config\.json))(?=$|[\s"';&|<>)},])/,
|
|
34
33
|
/(?:^|[^A-Za-z0-9_.-])(?:backups[\\/]config|\.snapshots|CA|(?:AIDs|aids)[\\/][^/\\\s"']+[\\/](?:cert|keys))(?:[\\/]|$|[\s"';&|<>)},])/,
|
|
35
34
|
/(?:^|[/\\\s"'=<>])(?:\.device_id|\.env|\.seed(?:\.[^/\\\s"']*)?|\.migrated-[^/\\\s"']*|\.lock|daemon\.pid|data[\\/](?:causation-aun\.json(?:_|__)?|message-queue\.json(?:_|__|\.tmp-[^/\\\s"']*)?|instance[\\/]control\.token|outbox|handoff))(?:[\\/]|$|[\s"';&|<>)},])/,
|
|
36
35
|
/(?:^|[/\\\s"'=<>])(?:[^/\\\s"']+\.json_|[^/\\\s"']+\.json\.migrated|defaults_\d+\.json)(?=$|[\s"';&|<>)},])/,
|
|
37
36
|
];
|
|
38
37
|
const L_CLASS_RELATIVE_PATTERNS = [
|
|
39
38
|
/^data\/triggers(?:\/|$)/,
|
|
39
|
+
/^agents\/[^/]+\/roles(?:\/|$)/,
|
|
40
40
|
];
|
|
41
41
|
const L_CLASS_REFERENCE_PATTERNS = [
|
|
42
42
|
/(?:^|[/\\\s"'=<>])data[\\/]triggers(?:[\\/]|$|[\s"';&|<>)},])/,
|
|
43
|
+
/(?:^|[^A-Za-z0-9_.-])agents[\\/][^/\\\s"']+[\\/]roles(?:[\\/]|$|[\s"';&|<>)},])/,
|
|
43
44
|
];
|
|
44
45
|
function normalizeForComparison(value) {
|
|
45
46
|
const resolved = path.resolve(value);
|
|
@@ -127,8 +128,7 @@ export function getHClassSandboxPatterns(root = resolveRoot()) {
|
|
|
127
128
|
path.join('agents', 'defaults.json'),
|
|
128
129
|
path.join('agents', '*', 'config.json'),
|
|
129
130
|
path.join('agents', '*', 'contact.json'),
|
|
130
|
-
path.join('agents', '*', '
|
|
131
|
-
path.join('agents', '*', 'roles', '**'),
|
|
131
|
+
path.join('agents', '*', 'contact-operations.jsonl'),
|
|
132
132
|
path.join('agents', '*', 'relations', '*', 'config.json'),
|
|
133
133
|
path.join('backups', 'config'),
|
|
134
134
|
path.join('backups', 'config', '**'),
|
|
@@ -169,6 +169,7 @@ export function getLClassWritePatterns(root = resolveRoot()) {
|
|
|
169
169
|
return [
|
|
170
170
|
path.join(absoluteRoot, 'data', 'triggers'),
|
|
171
171
|
path.join(absoluteRoot, 'data', 'triggers', '**'),
|
|
172
|
+
path.join(absoluteRoot, 'agents', '*', 'roles', '**'),
|
|
172
173
|
];
|
|
173
174
|
}
|
|
174
175
|
const CLAUDE_PROTECTED_TARGET_LIMIT = 8192;
|
|
@@ -213,10 +214,9 @@ export function getExistingHClassMaskTargets(root = resolveRoot()) {
|
|
|
213
214
|
if (!entry.isDirectory())
|
|
214
215
|
continue;
|
|
215
216
|
const agentRoot = path.join(agentsDir, entry.name);
|
|
216
|
-
for (const fileName of ['config.json', 'config.json_', 'contact.json', 'contact.json_']) {
|
|
217
|
+
for (const fileName of ['config.json', 'config.json_', 'contact.json', 'contact.json_', 'contact-operations.jsonl']) {
|
|
217
218
|
addExistingTarget(targets, path.join(agentRoot, fileName));
|
|
218
219
|
}
|
|
219
|
-
addExistingTarget(targets, path.join(agentRoot, 'roles'));
|
|
220
220
|
const relationsDir = path.join(agentRoot, 'relations');
|
|
221
221
|
try {
|
|
222
222
|
for (const relation of fs.readdirSync(relationsDir, { withFileTypes: true })) {
|
|
@@ -247,7 +247,17 @@ export function getExistingHClassMaskTargets(root = resolveRoot()) {
|
|
|
247
247
|
}
|
|
248
248
|
export function getExistingLClassReadOnlyTargets(root = resolveRoot()) {
|
|
249
249
|
const targets = new Map();
|
|
250
|
-
|
|
250
|
+
const absoluteRoot = resolveThroughExistingAncestor(root);
|
|
251
|
+
addExistingTarget(targets, path.join(absoluteRoot, 'data', 'triggers'));
|
|
252
|
+
const agentsDir = path.join(absoluteRoot, 'agents');
|
|
253
|
+
try {
|
|
254
|
+
for (const entry of fs.readdirSync(agentsDir, { withFileTypes: true })) {
|
|
255
|
+
if (!entry.isDirectory())
|
|
256
|
+
continue;
|
|
257
|
+
addExistingTarget(targets, path.join(agentsDir, entry.name, 'roles'));
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
catch { }
|
|
251
261
|
return [...targets.values()];
|
|
252
262
|
}
|
|
253
263
|
export function buildClaudeProtectedFilesystem(root = resolveRoot()) {
|
|
@@ -306,8 +316,9 @@ export function hClassGrantIncludesProtectedPath(value, options = {}) {
|
|
|
306
316
|
return true;
|
|
307
317
|
}
|
|
308
318
|
export function lClassGrantIncludesProtectedPath(value, options = {}) {
|
|
319
|
+
if (!value.trim())
|
|
320
|
+
return false;
|
|
309
321
|
const root = resolveThroughExistingAncestor(options.root ?? resolveRoot());
|
|
310
|
-
const protectedRoot = path.join(root, 'data', 'triggers');
|
|
311
322
|
if (isLClassPath(value, { cwd: options.cwd, root }))
|
|
312
323
|
return true;
|
|
313
324
|
const normalized = value.replace(/\\/g, '/');
|
|
@@ -316,8 +327,24 @@ export function lClassGrantIncludesProtectedPath(value, options = {}) {
|
|
|
316
327
|
? value
|
|
317
328
|
: normalized.slice(0, globIndex).replace(/\/+$/, '') || '.';
|
|
318
329
|
const candidate = resolveProtectedCandidate(literalGrantRoot, options.cwd);
|
|
319
|
-
|
|
320
|
-
|
|
330
|
+
if (isSameOrDescendant(root, candidate))
|
|
331
|
+
return true;
|
|
332
|
+
if (!isSameOrDescendant(candidate, root))
|
|
333
|
+
return false;
|
|
334
|
+
const relative = path.relative(root, candidate);
|
|
335
|
+
const segments = relative.split(path.sep).filter(Boolean);
|
|
336
|
+
if (segments[0] === 'data') {
|
|
337
|
+
if (segments.length === 1)
|
|
338
|
+
return true;
|
|
339
|
+
return segments[1] === 'triggers';
|
|
340
|
+
}
|
|
341
|
+
if (segments[0] === 'agents') {
|
|
342
|
+
// The agents root and each individual Agent root contain a roles/ subtree.
|
|
343
|
+
if (segments.length <= 2)
|
|
344
|
+
return true;
|
|
345
|
+
return segments[2] === 'roles';
|
|
346
|
+
}
|
|
347
|
+
return false;
|
|
321
348
|
}
|
|
322
349
|
export function workspaceContainsHClassPaths(projectPath, root = resolveRoot()) {
|
|
323
350
|
const project = resolveThroughExistingAncestor(projectPath);
|
|
@@ -13,12 +13,16 @@ function decodeSegment(s) {
|
|
|
13
13
|
/**
|
|
14
14
|
* 计算 chat 目录的完整路径。
|
|
15
15
|
* - aun: sessionsDir/aun/<urlEncode(selfAID|'_unknown')>/<urlEncode(channelId)>/
|
|
16
|
+
* - wecom: sessionsDir/wecom/<urlEncode(channelKey)>/<urlEncode(channelId)>/
|
|
16
17
|
* - 其它: sessionsDir/<channelType>/<urlEncode(channelId)>/
|
|
17
18
|
*/
|
|
18
|
-
export function chatDirPath(sessionsDir, channelType, channelId, selfAID) {
|
|
19
|
+
export function chatDirPath(sessionsDir, channelType, channelId, selfAID, channelKey) {
|
|
19
20
|
if (channelType === 'aun') {
|
|
20
21
|
return path.join(sessionsDir, channelType, encodeSegment(selfAID || '_unknown'), encodeSegment(channelId));
|
|
21
22
|
}
|
|
23
|
+
if (channelType === 'wecom' && channelKey?.startsWith('wecom#')) {
|
|
24
|
+
return path.join(sessionsDir, channelType, encodeSegment(channelKey), encodeSegment(channelId));
|
|
25
|
+
}
|
|
22
26
|
return path.join(sessionsDir, channelType, encodeSegment(channelId));
|
|
23
27
|
}
|
|
24
28
|
/** 解码目录段(用于扫描时把目录名还原为原始 channelId/selfAID) */
|
|
@@ -126,6 +130,7 @@ export function readAllJsonlLines(filePath) {
|
|
|
126
130
|
/**
|
|
127
131
|
* 扫描所有 chat 目录。
|
|
128
132
|
* - aun: channelType / selfAID / channelId (3层)
|
|
133
|
+
* - wecom: channelType / channelKey / channelId (3层;兼容旧的2层目录)
|
|
129
134
|
* - 其它: channelType / channelId (2层)
|
|
130
135
|
*/
|
|
131
136
|
export function scanChatDirs(sessionsDir) {
|
|
@@ -176,6 +181,42 @@ export function scanChatDirs(sessionsDir) {
|
|
|
176
181
|
}
|
|
177
182
|
}
|
|
178
183
|
}
|
|
184
|
+
else if (channelType === 'wecom') {
|
|
185
|
+
for (const entry of level2Entries) {
|
|
186
|
+
if (!entry.isDirectory())
|
|
187
|
+
continue;
|
|
188
|
+
const decoded = decodeSegment(entry.name);
|
|
189
|
+
const entryDir = path.join(typeDir, entry.name);
|
|
190
|
+
if (decoded.startsWith('wecom#') && decoded.split('#').length === 3) {
|
|
191
|
+
let chatEntries;
|
|
192
|
+
try {
|
|
193
|
+
chatEntries = fs.readdirSync(entryDir, { withFileTypes: true });
|
|
194
|
+
}
|
|
195
|
+
catch {
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
for (const chatEntry of chatEntries) {
|
|
199
|
+
if (!chatEntry.isDirectory())
|
|
200
|
+
continue;
|
|
201
|
+
results.push({
|
|
202
|
+
channelType,
|
|
203
|
+
selfAID: '',
|
|
204
|
+
channelKey: decoded,
|
|
205
|
+
channelId: decodeSegment(chatEntry.name),
|
|
206
|
+
dirPath: path.join(entryDir, chatEntry.name),
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
else {
|
|
211
|
+
results.push({
|
|
212
|
+
channelType,
|
|
213
|
+
selfAID: '',
|
|
214
|
+
channelId: decoded,
|
|
215
|
+
dirPath: entryDir,
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
179
220
|
else {
|
|
180
221
|
// 2-layer: channelType/channelId
|
|
181
222
|
for (const chatEntry of level2Entries) {
|
|
@@ -205,8 +246,8 @@ export function scanMetaFiles(chatDir) {
|
|
|
205
246
|
throw e;
|
|
206
247
|
}
|
|
207
248
|
}
|
|
208
|
-
export function ensureChatDir(sessionsDir, channelType, channelId, selfAID) {
|
|
209
|
-
const dir = chatDirPath(sessionsDir, channelType, channelId, selfAID);
|
|
249
|
+
export function ensureChatDir(sessionsDir, channelType, channelId, selfAID, channelKey) {
|
|
250
|
+
const dir = chatDirPath(sessionsDir, channelType, channelId, selfAID, channelKey);
|
|
210
251
|
fs.mkdirSync(dir, { recursive: true });
|
|
211
252
|
fs.mkdirSync(path.join(dir, '_threads'), { recursive: true });
|
|
212
253
|
fs.mkdirSync(path.join(dir, '_trash'), { recursive: true });
|
|
@@ -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.migrateWecomChannelDirs();
|
|
26
43
|
}
|
|
27
44
|
setIdentityResolver(resolver) {
|
|
28
45
|
this.identityResolver = resolver;
|
|
@@ -69,20 +86,20 @@ export class SessionManager {
|
|
|
69
86
|
* 需要明确的 channelType + selfAID 才能确定路径。
|
|
70
87
|
*/
|
|
71
88
|
resolveChatDir(channel, channelId, channelType, selfAID) {
|
|
72
|
-
return chatDirPath(this.sessionsDir, channelType, channelId, selfAID);
|
|
89
|
+
return chatDirPath(this.sessionsDir, channelType, channelId, selfAID, channel);
|
|
73
90
|
}
|
|
74
91
|
/**
|
|
75
92
|
* 给定明确的 channelType + selfAID 时直接计算路径(不扫描)。
|
|
76
93
|
* 用于 caller 已经知道完整路由信息的场景(如 message-bridge 透传)。
|
|
77
94
|
*/
|
|
78
95
|
resolveChatDirExact(channel, channelId, channelType, selfAID) {
|
|
79
|
-
return chatDirPath(this.sessionsDir, channelType, channelId, selfAID);
|
|
96
|
+
return chatDirPath(this.sessionsDir, channelType, channelId, selfAID, channel);
|
|
80
97
|
}
|
|
81
98
|
resolveChatDirFromSession(session) {
|
|
82
99
|
if (!session.channelType) {
|
|
83
100
|
throw new Error(`[SessionManager] missing channelType for session ${session.id}`);
|
|
84
101
|
}
|
|
85
|
-
return chatDirPath(this.sessionsDir, session.channelType, session.channelId, session.selfAID);
|
|
102
|
+
return chatDirPath(this.sessionsDir, session.channelType, session.channelId, session.selfAID, session.channel);
|
|
86
103
|
}
|
|
87
104
|
deriveChannelIdentity(channel, channelId) {
|
|
88
105
|
const parsed = tryParseChannelKey(channel);
|
|
@@ -253,7 +270,7 @@ export class SessionManager {
|
|
|
253
270
|
}
|
|
254
271
|
ensureChatDirForSession(session) {
|
|
255
272
|
const channelType = session.channelType || session.channel;
|
|
256
|
-
return ensureChatDir(this.sessionsDir, channelType, session.channelId, session.selfAID);
|
|
273
|
+
return ensureChatDir(this.sessionsDir, channelType, session.channelId, session.selfAID, session.channel);
|
|
257
274
|
}
|
|
258
275
|
metaFilePath(chatDir, sessionId) {
|
|
259
276
|
return path.join(chatDir, `${sessionId}.jsonl`);
|
|
@@ -678,7 +695,7 @@ export class SessionManager {
|
|
|
678
695
|
getOrCreateThreadSession(channel, channelId, threadId, defaultProjectPath, metadata, name, baseagent, selfAID, channelType, peerType, chatType) {
|
|
679
696
|
// 使用精确路径(channelType + selfAID)
|
|
680
697
|
const chatDir = (channelType && selfAID)
|
|
681
|
-
? (() => { const d = chatDirPath(this.sessionsDir, channelType, channelId, selfAID); fs.mkdirSync(d, { recursive: true }); fs.mkdirSync(path.join(d, '_threads'), { recursive: true }); return d; })()
|
|
698
|
+
? (() => { const d = chatDirPath(this.sessionsDir, channelType, channelId, selfAID, channel); fs.mkdirSync(d, { recursive: true }); fs.mkdirSync(path.join(d, '_threads'), { recursive: true }); return d; })()
|
|
682
699
|
: this.ensureResolvedChatDirSafe(channel, channelId, channelType, selfAID);
|
|
683
700
|
const threadIndex = readThreadIndex(chatDir);
|
|
684
701
|
const existingEntry = threadIndex[threadId];
|
|
@@ -1097,18 +1114,20 @@ export class SessionManager {
|
|
|
1097
1114
|
}
|
|
1098
1115
|
}
|
|
1099
1116
|
}
|
|
1100
|
-
async createNewSession(channel, channelId, projectPath, name, baseagent) {
|
|
1117
|
+
async createNewSession(channel, channelId, projectPath, name, baseagent, identityMetadata) {
|
|
1101
1118
|
const inheritedChatType = this.getActiveChatType(channel, channelId);
|
|
1102
1119
|
// Derive selfAID and channelType from existing sessions
|
|
1103
1120
|
const existingDir = this.findExistingChatDir(channel, channelId);
|
|
1104
1121
|
const identity = this.deriveChannelIdentity(channel, channelId);
|
|
1105
1122
|
let channelType = identity.channelType;
|
|
1106
1123
|
let selfAID = identity.selfAID;
|
|
1124
|
+
let activeMetadata;
|
|
1107
1125
|
if (existingDir) {
|
|
1108
1126
|
const active = readJsonFile(path.join(existingDir, 'active.json'));
|
|
1109
1127
|
if (active) {
|
|
1110
1128
|
channelType = active.channelType || channel;
|
|
1111
1129
|
selfAID = active.selfAID || '';
|
|
1130
|
+
activeMetadata = active.metadata;
|
|
1112
1131
|
// 兼容旧字段名
|
|
1113
1132
|
const activeBaseagent = active.baseagent || active.agentType;
|
|
1114
1133
|
if (!baseagent && activeBaseagent)
|
|
@@ -1129,7 +1148,7 @@ export class SessionManager {
|
|
|
1129
1148
|
baseagent,
|
|
1130
1149
|
sessionKey: formatSessionKey(channelType, channelId, DEFAULT_THREAD_ID),
|
|
1131
1150
|
chatType: inheritedChatType,
|
|
1132
|
-
metadata:
|
|
1151
|
+
metadata: newSessionStableMetadata(inheritedChatType, activeMetadata, identityMetadata),
|
|
1133
1152
|
name: name || '默认会话',
|
|
1134
1153
|
createdAt: Date.now(),
|
|
1135
1154
|
updatedAt: Date.now(),
|
|
@@ -1257,7 +1276,7 @@ export class SessionManager {
|
|
|
1257
1276
|
* 为超时后的独立话题创建新会话。保留聊天路由和稳定 metadata,
|
|
1258
1277
|
* 但不继承任何底层 agent session 或临时任务状态。
|
|
1259
1278
|
*/
|
|
1260
|
-
async createRenewedSession(sourceSession) {
|
|
1279
|
+
async createRenewedSession(sourceSession, context) {
|
|
1261
1280
|
const metadata = { ...(sourceSession.metadata || {}) };
|
|
1262
1281
|
delete metadata.agentSessions;
|
|
1263
1282
|
delete metadata.resumeAt;
|
|
@@ -1288,7 +1307,11 @@ export class SessionManager {
|
|
|
1288
1307
|
projectPath: session.projectPath,
|
|
1289
1308
|
name: session.name,
|
|
1290
1309
|
chatType: session.chatType,
|
|
1310
|
+
cause: 'renew',
|
|
1311
|
+
sourceSessionId: sourceSession.id,
|
|
1312
|
+
renewSource: context?.source,
|
|
1291
1313
|
timestamp: Date.now(),
|
|
1314
|
+
causation: context?.causation,
|
|
1292
1315
|
});
|
|
1293
1316
|
return session;
|
|
1294
1317
|
}
|
|
@@ -1584,4 +1607,59 @@ export class SessionManager {
|
|
|
1584
1607
|
}
|
|
1585
1608
|
return null;
|
|
1586
1609
|
}
|
|
1610
|
+
/**
|
|
1611
|
+
* Move legacy `sessions/wecom/<chatId>` directories under their persisted
|
|
1612
|
+
* channelKey. The stored SessionFile.channel is authoritative; ambiguous
|
|
1613
|
+
* directories are deliberately left untouched for manual recovery.
|
|
1614
|
+
*/
|
|
1615
|
+
migrateWecomChannelDirs() {
|
|
1616
|
+
const legacyDirs = scanChatDirs(this.sessionsDir)
|
|
1617
|
+
.filter(entry => entry.channelType === 'wecom' && !entry.channelKey);
|
|
1618
|
+
let migrated = 0;
|
|
1619
|
+
for (const entry of legacyDirs) {
|
|
1620
|
+
const candidates = [];
|
|
1621
|
+
const active = readJsonFile(path.join(entry.dirPath, 'active.json'));
|
|
1622
|
+
if (active)
|
|
1623
|
+
candidates.push(active);
|
|
1624
|
+
for (const metaFile of scanMetaFiles(entry.dirPath)) {
|
|
1625
|
+
const meta = readLastJsonlLine(path.join(entry.dirPath, metaFile));
|
|
1626
|
+
if (meta)
|
|
1627
|
+
candidates.push(meta);
|
|
1628
|
+
}
|
|
1629
|
+
const threadDir = path.join(entry.dirPath, '_threads');
|
|
1630
|
+
for (const metaFile of scanMetaFiles(threadDir)) {
|
|
1631
|
+
const meta = readLastJsonlLine(path.join(threadDir, metaFile));
|
|
1632
|
+
if (meta)
|
|
1633
|
+
candidates.push(meta);
|
|
1634
|
+
}
|
|
1635
|
+
const channelKeys = new Set(candidates
|
|
1636
|
+
.map(item => item.channel)
|
|
1637
|
+
.filter((value) => typeof value === 'string' && value.startsWith('wecom#')));
|
|
1638
|
+
if (channelKeys.size !== 1) {
|
|
1639
|
+
if (channelKeys.size > 1) {
|
|
1640
|
+
logger.error(`[SessionManager] Cannot auto-migrate mixed WeCom session directory: ${entry.dirPath}`);
|
|
1641
|
+
}
|
|
1642
|
+
continue;
|
|
1643
|
+
}
|
|
1644
|
+
const channelKey = [...channelKeys][0];
|
|
1645
|
+
const target = chatDirPath(this.sessionsDir, 'wecom', entry.channelId, undefined, channelKey);
|
|
1646
|
+
if (target === entry.dirPath)
|
|
1647
|
+
continue;
|
|
1648
|
+
if (fs.existsSync(target)) {
|
|
1649
|
+
logger.error(`[SessionManager] Cannot migrate WeCom session directory because target exists: ${target}`);
|
|
1650
|
+
continue;
|
|
1651
|
+
}
|
|
1652
|
+
try {
|
|
1653
|
+
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
1654
|
+
fs.renameSync(entry.dirPath, target);
|
|
1655
|
+
migrated++;
|
|
1656
|
+
}
|
|
1657
|
+
catch (error) {
|
|
1658
|
+
logger.error(`[SessionManager] Failed to migrate WeCom session directory ${entry.dirPath}: ${String(error)}`);
|
|
1659
|
+
}
|
|
1660
|
+
}
|
|
1661
|
+
if (migrated > 0) {
|
|
1662
|
+
logger.info(`[SessionManager] Migrated ${migrated} WeCom session director${migrated === 1 ? 'y' : 'ies'} to channelKey isolation`);
|
|
1663
|
+
}
|
|
1664
|
+
}
|
|
1587
1665
|
}
|
|
@@ -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)
|