evolcore 0.0.39 → 0.0.40
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 +49 -0
- package/README.md +31 -20
- package/bin/ec-safe-output.js +14 -2
- package/dist/agents/baseagent-availability.js +1 -0
- package/dist/agents/claude-runner.js +32 -46
- package/dist/agents/codex-runner.js +51 -27
- package/dist/agents/ecagent-runner.js +18 -7
- package/dist/agents/gemini-runner.js +1 -2
- package/dist/agents/runner-types.js +28 -6
- package/dist/aun/aid/client.js +1 -1
- package/dist/aun/aid/control-aid.js +23 -98
- package/dist/aun/aid/ownership-operation.js +1 -3
- package/dist/aun/group/invite-service.js +6 -0
- package/dist/aun/msg/p2p.js +8 -12
- package/dist/channels/aun.js +66 -48
- package/dist/channels/dingtalk.js +8 -2
- package/dist/channels/feishu.js +6 -1
- package/dist/channels/wecom.js +7 -3
- package/dist/cli/agent-create.js +23 -4
- package/dist/cli/aid-bind-command.js +4 -1
- package/dist/cli/daemon-commands.js +242 -17
- package/dist/cli/index.js +25 -13
- package/dist/cli/memory-command.js +33 -14
- package/dist/cli/restart-monitor.js +35 -13
- package/dist/cli/task-context.js +1 -0
- package/dist/config/config-field-policy.js +1 -0
- package/dist/contracts/ecweb-service-protocol.js +44 -0
- package/dist/core/agent-runtime.js +6 -0
- package/dist/core/auth/agent-delegation.js +7 -4
- package/dist/core/auth/auth-gateway.js +3 -1
- package/dist/core/auth/authorization-audit.js +5 -1
- package/dist/core/auth/debug-context.js +5 -1
- package/dist/core/auth/operation-catalog.js +16 -0
- package/dist/core/command/command-handler.js +7 -1
- package/dist/core/command/menu-catalog.js +48 -12
- package/dist/core/command/menu-handler.js +72 -4
- package/dist/core/command/menu-protocol.js +20 -1
- package/dist/core/command/slash-gate.js +17 -3
- package/dist/core/command/slash-handler.js +90 -7
- package/dist/core/event-catalog.js +2 -0
- package/dist/core/interaction-router.js +2 -1
- package/dist/core/ipc-registration.js +26 -7
- package/dist/core/memory/batch-meta.js +2 -0
- package/dist/core/memory/consolidation-context.js +42 -51
- package/dist/core/memory/consolidation-ops-records.js +39 -7
- package/dist/core/memory/consolidation-trigger.js +71 -64
- package/dist/core/memory/consolidation.js +226 -20
- package/dist/core/memory/memory-writer.js +160 -34
- package/dist/core/memory/relation-idle-monitor.js +287 -0
- package/dist/core/memory/relation-sweep.js +206 -0
- package/dist/core/message/aun-ipc-sender.js +5 -0
- package/dist/core/message/message-bridge.js +64 -55
- package/dist/core/message/message-log.js +17 -0
- package/dist/core/message/message-queue.js +6 -0
- package/dist/core/message/outbound.js +70 -0
- package/dist/core/message/peer-mode.js +7 -2
- package/dist/core/message/response-engine.js +341 -73
- package/dist/core/ownership/owner-resolver.js +384 -178
- package/dist/core/permission/ec-command-parser.js +19 -3
- package/dist/core/permission/tool-policy.js +7 -7
- package/dist/core/role/runtime-policy.js +64 -0
- package/dist/core/session/session-manager.js +25 -5
- package/dist/core/system-session/memory.js +3 -18
- package/dist/ecweb/basic-settings-service.js +709 -0
- package/dist/ecweb/providers-service.js +172 -0
- package/dist/index.js +92 -4
- package/dist/ipc.js +78 -11
- package/dist/stats/db.js +33 -0
- package/dist/stats/index.js +2 -2
- package/dist/stats/query.js +246 -0
- package/dist/stats/writer.js +41 -1
- package/dist/trigger/application-service.js +12 -0
- package/dist/trigger/authorization.js +2 -2
- package/dist/trigger/history.js +36 -0
- package/dist/trigger/scheduler.js +3 -0
- package/dist/utils/ecweb-supervisor.js +31 -10
- package/dist/utils/error-utils.js +8 -4
- package/dist/utils/evolcore-version.js +3 -2
- package/dist/utils/host-info.js +52 -0
- package/ecagent/dist/harness/agent-harness.js +4 -1
- package/kits/docs/evolcore/INDEX.md +1 -1
- package/kits/docs/evolcore/agent.md +1 -1
- package/kits/docs/evolcore/aid.md +29 -12
- package/kits/docs/evolcore/config.md +9 -7
- package/kits/docs/evolcore/memory-architecture.md +3 -4
- package/kits/docs/evolcore/memory-consolidation.md +28 -7
- package/kits/docs/evolcore/memory-tags.md +1 -1
- package/kits/docs/evolcore/memory.md +1 -1
- package/kits/docs/evolcore/menu-client.md +1 -1
- package/kits/docs/evolcore/menu-protocol.md +6 -0
- package/kits/docs/evolcore/msg.md +1 -1
- package/kits/eck_manifest.json +3 -3
- package/kits/rules/04-relation.md +3 -2
- package/kits/schemas/agent-config.schema.13.json +5 -1
- package/kits/templates/memory-consolidation/generalize.md +32 -4
- package/kits/templates/memory-consolidation/notes.md +2 -2
- package/kits/templates/memory-consolidation/promote.md +10 -2
- package/kits/templates/system-fragments/baseagent.md +5 -0
- package/kits/templates/system-fragments/memory.md +12 -3
- package/kits/templates/system-fragments/session.md +1 -1
- package/kits/{rules/07-memory-and-thread.md → templates/system-fragments/thread.md} +17 -80
- package/package.json +3 -3
|
@@ -10,8 +10,9 @@ import crypto from 'crypto';
|
|
|
10
10
|
import path from 'path';
|
|
11
11
|
import fs from 'fs';
|
|
12
12
|
import { ensureFreshPackageVersionInfo, refreshManagedPackageVersions } from '../../utils/package-version-info.js';
|
|
13
|
+
import { loadDaemonConfig } from '../../config-store.js';
|
|
13
14
|
import { remoteAccessService } from '../auth/remote-access-store.js';
|
|
14
|
-
import { read as cfgRead, resolveEffective, routeFieldPath, write as cfgWrite, } from '../../config/config-manager.js';
|
|
15
|
+
import { read as cfgRead, resolveEffective, routeFieldPath, write as cfgWrite, ConfigTarget, } from '../../config/config-manager.js';
|
|
15
16
|
import { execAgentAction } from './agent-control.js';
|
|
16
17
|
import { handleMemorySlash, memoryHelpEntries } from './memory-slash.js';
|
|
17
18
|
import { handleSkillSlash, skillHelpEntries } from './skill-slash.js';
|
|
@@ -21,11 +22,12 @@ import { getDaemonOwners, ownerResolver, refreshOwnershipForAuthorization, OWNER
|
|
|
21
22
|
import { authorizationNextStep, formatAuthorizationDenial } from '../auth/authorization-denial.js';
|
|
22
23
|
import { resolvePermissionMode, writeScope } from '../model/config-scope.js';
|
|
23
24
|
import { formatPeerKey } from '../relation/peer-identity.js';
|
|
25
|
+
import { auditFullAccessEvent } from '../auth/authorization-audit.js';
|
|
24
26
|
import { isMainSession, isTopicSession, isTopicThreadId, sessionTopicThreadId } from '../thread/topic-id.js';
|
|
25
27
|
import { modelMatches, getCatalog, assertCatalogSelectable, resolveCatalogModel, CONFIGURABLE_EFFORTS, configurableEfforts } from '../model/model-catalog.js';
|
|
26
28
|
import { formatModelCheck, runModelCheck } from '../model/model-diagnostics.js';
|
|
27
29
|
import { constrainResolvedModelForRole, filterModelsForRole, validateModelSelectionForRole } from '../model/model-permission.js';
|
|
28
|
-
import { resolveRuntimeStringField } from '../role/runtime-policy.js';
|
|
30
|
+
import { isAgentFullAccessOwner, resolveRuntimeStringField, sessionFullAccessStatus, } from '../role/runtime-policy.js';
|
|
29
31
|
import { displaySessionTitle } from '../session/session-title.js';
|
|
30
32
|
import { chatmodeFieldForPeer, isPrivateChatmodeRoleLocked, resolveChatModeForField } from '../message/peer-mode.js';
|
|
31
33
|
import { normalizePermissionMode as normalizePermissionModeContract, PUBLIC_PERMISSION_MODES } from '../permission/mode.js';
|
|
@@ -33,7 +35,7 @@ import { isManagementRole } from '../../config/builtin-roles.js';
|
|
|
33
35
|
import { isSystemControlChannel } from '../system-channels.js';
|
|
34
36
|
import { launchMonitorAndConfirm, resolveMonitorLaunch, cleanupMonitorStage, isProcessRunning } from '../../utils/cross-platform.js';
|
|
35
37
|
import { scanInstances } from '../../utils/instance-registry.js';
|
|
36
|
-
import { guardIdleCommand, guardGroupSlashCommand, guardKnownCommand, guardRoleCommand, guardThreadCommand, isRecognizedSlashCommand, normalizeSlashContent, probeSessionActivity, } from './slash-gate.js';
|
|
38
|
+
import { guardIdleCommand, guardGroupSlashCommand, guardKnownCommand, guardRoleCommand, guardThreadCommand, isRecognizedSlashCommand, normalizeSlashContent, parseFullAccessCommand, probeSessionActivity, } from './slash-gate.js';
|
|
37
39
|
import { MAX_AUN_ATTACHMENT_SIZE } from '../../aun/msg/upload.js';
|
|
38
40
|
import { DEFAULT_SESSION_LOOP_SECONDS, MAX_SESSION_LOOP_SECONDS, MIN_SESSION_LOOP_SECONDS, refreshSessionLoopTarget, resolveCurrentSessionLoopTarget, sessionLoopPayload, validateIdleSeconds, writeSessionLoopConfig, } from '../session/session-idle-monitor.js';
|
|
39
41
|
const allEfforts = CONFIGURABLE_EFFORTS;
|
|
@@ -400,6 +402,7 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
|
|
|
400
402
|
const narrowedChatType = chatType === 'group' ? 'group' : chatType === 'private' ? 'private' : undefined;
|
|
401
403
|
await refreshOwnershipForAuthorization(selfAID ?? this.agentRegistry?.resolveByChannel(channel)?.aid ?? this.resolveSelfAID(channel), {
|
|
402
404
|
maxAgeMs: OWNERSHIP_CACHE_MAX_AGE_MS,
|
|
405
|
+
allowTransient: true,
|
|
403
406
|
});
|
|
404
407
|
const identityConversationId = narrowedChatType === 'group' ? channelId : userId;
|
|
405
408
|
const resolvedIdentity = overrideIdentity ?? this.sessionManager.resolveIdentity(channel, userId, narrowedChatType, identityConversationId);
|
|
@@ -523,6 +526,10 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
|
|
|
523
526
|
|| normalizedContent === '/reload'
|
|
524
527
|
|| normalizedContent.startsWith('/reload ')
|
|
525
528
|
|| normalizedContent === '/pair'
|
|
529
|
+
|| normalizedContent === '/fa'
|
|
530
|
+
|| normalizedContent === '/fullaccess'
|
|
531
|
+
|| normalizedContent.startsWith('/fa ')
|
|
532
|
+
|| normalizedContent.startsWith('/fullaccess ')
|
|
526
533
|
|| /^\/diag(?:\s|$)/.test(normalizedContent);
|
|
527
534
|
const roleGuard = guardRoleCommand(normalizedContent, activeChatType, isAdmin || (isDaemonOwner && isProcessLevelSlash));
|
|
528
535
|
if (roleGuard)
|
|
@@ -622,6 +629,71 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
|
|
|
622
629
|
return { kind: 'command.error', text: `❌ ${error instanceof Error ? error.message : String(error)}` };
|
|
623
630
|
}
|
|
624
631
|
}
|
|
632
|
+
const fullAccessControl = parseFullAccessCommand(normalizedContent);
|
|
633
|
+
if (fullAccessControl.matched) {
|
|
634
|
+
if (activeChatType === 'group' || source === 'card-trigger') {
|
|
635
|
+
return { kind: 'command.error', text: '❌ /fa 仅支持私聊中的当前 EC 会话' };
|
|
636
|
+
}
|
|
637
|
+
const commandSession = await getExistingSessionForCommand();
|
|
638
|
+
const agentAid = authSubject.selfAid ?? selfAID;
|
|
639
|
+
const principalId = authSubject.principalId;
|
|
640
|
+
if (!commandSession || !agentAid) {
|
|
641
|
+
return { kind: 'command.error', text: '❌ 当前没有可绑定的 EC 会话' };
|
|
642
|
+
}
|
|
643
|
+
if (this.messageQueue.isProcessing(commandSession.id) || this.messageQueue.getQueueLength(commandSession.id) > 0) {
|
|
644
|
+
return { kind: 'command.error', text: '❌ 当前会话有任务执行中,结束后再修改 fullaccess' };
|
|
645
|
+
}
|
|
646
|
+
if (!principalId || !authSubject.isDaemonOwner || !isAgentFullAccessOwner(agentAid, principalId)) {
|
|
647
|
+
return { kind: 'command.error', text: '❌ /fa 需要同一身份同时具备 Agent Owner 和 daemon Owner 权限' };
|
|
648
|
+
}
|
|
649
|
+
if (fullAccessControl.action === 'status') {
|
|
650
|
+
const status = sessionFullAccessStatus(commandSession, principalId);
|
|
651
|
+
const scope = isTopicSession(commandSession) ? 'session' : 'agent';
|
|
652
|
+
return {
|
|
653
|
+
kind: 'command.result',
|
|
654
|
+
text: '当前 fullaccess(' + scope + ',session=' + commandSession.id + '):'
|
|
655
|
+
+ (status.effective ? '开启' : '关闭'),
|
|
656
|
+
};
|
|
657
|
+
}
|
|
658
|
+
const enabled = fullAccessControl.action === 'on';
|
|
659
|
+
try {
|
|
660
|
+
if (isTopicSession(commandSession)) {
|
|
661
|
+
const latest = await this.sessionManager.getThreadSession(channel, channelId, threadId);
|
|
662
|
+
if (!latest || latest.id !== commandSession.id || !isTopicSession(latest)) {
|
|
663
|
+
return { kind: 'command.error', text: '❌ 当前话题已发生变化,请重新执行 /fa' };
|
|
664
|
+
}
|
|
665
|
+
this.sessionManager.updateSessionFullAccess(latest.id, { enabled, authorizedBy: principalId });
|
|
666
|
+
}
|
|
667
|
+
else {
|
|
668
|
+
const latest = await this.sessionManager.getActiveSession(channel, channelId);
|
|
669
|
+
if (!latest || latest.id !== commandSession.id || !isMainSession(latest)) {
|
|
670
|
+
return { kind: 'command.error', text: '❌ 当前主会话已发生变化,请重新执行 /fa' };
|
|
671
|
+
}
|
|
672
|
+
const config = cfgRead(ConfigTarget.Agent, { self: agentAid }, { cache: false });
|
|
673
|
+
if (!config)
|
|
674
|
+
return { kind: 'command.error', text: '❌ 找不到当前 Agent 配置' };
|
|
675
|
+
config.fullaccess = { enabled };
|
|
676
|
+
cfgWrite(ConfigTarget.Agent, config, { self: agentAid });
|
|
677
|
+
}
|
|
678
|
+
auditFullAccessEvent({
|
|
679
|
+
event: enabled ? 'fullaccess.session-policy.configured' : 'fullaccess.session-policy.deescalated',
|
|
680
|
+
source: 'session-policy',
|
|
681
|
+
actorId: principalId,
|
|
682
|
+
processRole: 'daemon-owner',
|
|
683
|
+
agentAid,
|
|
684
|
+
sessionId: commandSession.id,
|
|
685
|
+
authorizedBy: principalId,
|
|
686
|
+
reason: isTopicSession(commandSession) ? 'topic session fullaccess updated' : 'Agent main-session fullaccess updated',
|
|
687
|
+
});
|
|
688
|
+
return {
|
|
689
|
+
kind: 'command.result',
|
|
690
|
+
text: '✅ 当前 EC 会话 fullaccess 已' + (enabled ? '开启' : '关闭'),
|
|
691
|
+
};
|
|
692
|
+
}
|
|
693
|
+
catch (error) {
|
|
694
|
+
return { kind: 'command.error', text: '❌ ' + (error instanceof Error ? error.message : String(error)) };
|
|
695
|
+
}
|
|
696
|
+
}
|
|
625
697
|
const interruptPausedSessionBeforeReplacement = async (commandSession) => {
|
|
626
698
|
if (commandSession && (this.processor?.isPauseRequested?.(commandSession.id) ?? false)) {
|
|
627
699
|
this.eventBus.publish({
|
|
@@ -629,6 +701,7 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
|
|
|
629
701
|
sessionId: commandSession.id,
|
|
630
702
|
reason: 'stop',
|
|
631
703
|
agentName: this.agentRegistry?.resolveByChannel(channel)?.name ?? '<unknown>',
|
|
704
|
+
agentAid: commandSession.selfAID,
|
|
632
705
|
});
|
|
633
706
|
await this.processor.interruptSession(commandSession.id, 'stop');
|
|
634
707
|
this.processor?.clearPauseSession?.(commandSession.id);
|
|
@@ -775,7 +848,7 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
|
|
|
775
848
|
const appendProcessCommands = (lines) => {
|
|
776
849
|
if (!isDaemonOwner)
|
|
777
850
|
return;
|
|
778
|
-
lines.push('', '🛠️ 进程级运维:', ' /restart - 重启服务', ' /upgrade - 检查版本更新', ' /reload [aid] - 热重载 Agent 配置', ' /fa
|
|
851
|
+
lines.push('', '🛠️ 进程级运维:', ' /restart - 重启服务', ' /upgrade - 检查版本更新', ' /reload [aid] - 热重载 Agent 配置', ' /fa [on|off] - 查看或切换当前 EC 会话 fullaccess', ' /pair - 获取远程控制配对码', ' /diag <问题描述> - 邀请或追加远程诊断;/diag stop 撤销授权');
|
|
779
852
|
};
|
|
780
853
|
if (!isAdmin && activeChatType === 'group') {
|
|
781
854
|
const lines = [
|
|
@@ -907,7 +980,7 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
|
|
|
907
980
|
...(isDaemonOwner ? [
|
|
908
981
|
' /restart - 重启服务',
|
|
909
982
|
' /reload [aid] - 热重载 Agent 配置',
|
|
910
|
-
' /fa
|
|
983
|
+
' /fa [on|off] - 查看或切换当前 EC 会话 fullaccess',
|
|
911
984
|
' /pair - 获取远程控制配对码',
|
|
912
985
|
] : []),
|
|
913
986
|
...(!isDaemonOwner && isAdmin ? [
|
|
@@ -1067,7 +1140,7 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
|
|
|
1067
1140
|
cmds.push({ command: '/observable', args: '[true|false]', description: '查看或切换观察者模式', category: '运维', roles: ['owner'] });
|
|
1068
1141
|
}
|
|
1069
1142
|
if (isDaemonOwner) {
|
|
1070
|
-
cmds.push({ command: '/fa', aliases: ['/fullaccess'], args: '
|
|
1143
|
+
cmds.push({ command: '/fa', aliases: ['/fullaccess'], args: '[on|off]', description: '查看或切换当前 EC 会话 fullaccess', category: '权限管理', roles: ['daemon-owner'] });
|
|
1071
1144
|
cmds.push({ command: '/restart', description: '重启服务', category: '运维', roles: ['daemon-owner'] });
|
|
1072
1145
|
cmds.push({ command: '/upgrade', description: '检查版本更新', category: '运维', roles: ['daemon-owner'] });
|
|
1073
1146
|
cmds.push({ command: '/reload', args: '[aid]', description: '热重载 Agent 配置', category: '运维', roles: ['daemon-owner'] });
|
|
@@ -2475,6 +2548,7 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
|
|
|
2475
2548
|
sessionId: sessionKey,
|
|
2476
2549
|
reason: 'stop',
|
|
2477
2550
|
agentName: this.agentRegistry?.resolveByChannel(channel)?.name ?? '<unknown>',
|
|
2551
|
+
agentAid: stopSession.selfAID,
|
|
2478
2552
|
});
|
|
2479
2553
|
await this.processor.interruptSession(sessionKey, 'stop');
|
|
2480
2554
|
return { kind: 'command.result', text: '✓ 已发送中断信号,任务将尽快停止' };
|
|
@@ -3214,7 +3288,16 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
|
|
|
3214
3288
|
logger.warn(`[System] Refusing restart without a valid explicit delivery route: channel=${channel} channelId=${channelId}`);
|
|
3215
3289
|
return false;
|
|
3216
3290
|
}
|
|
3291
|
+
let autoUpgrade = true;
|
|
3217
3292
|
if (!suppressRealRestart) {
|
|
3293
|
+
try {
|
|
3294
|
+
autoUpgrade = loadDaemonConfig().upgrade?.autoOnStart !== false;
|
|
3295
|
+
}
|
|
3296
|
+
catch {
|
|
3297
|
+
autoUpgrade = false;
|
|
3298
|
+
}
|
|
3299
|
+
}
|
|
3300
|
+
if (!suppressRealRestart && autoUpgrade) {
|
|
3218
3301
|
await ensureFreshPackageVersionInfo(undefined, {
|
|
3219
3302
|
source: 'slash-restart',
|
|
3220
3303
|
log: message => logger.info(`[System] ${message}`),
|
|
@@ -3251,7 +3334,7 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
|
|
|
3251
3334
|
cleanupMonitorStage(monitorEntry.stageDir);
|
|
3252
3335
|
return { code: 'RESTART_PENDING_CONFIRMATION', reason: '已有重启请求正在处理', requestId: restartInfo.requestId };
|
|
3253
3336
|
}
|
|
3254
|
-
const confirmation = await launchMonitorAndConfirm([...monitorEntry.args, 'restart-monitor'], { EVOLCORE_HOME: resolvePaths().root, ...monitorEntry.env, [RESTART_REQUEST_ID_ENV]: restartInfo.requestId }, pid => scanInstances().restartMonitors.some(m => m.alive && m.record.pid === pid
|
|
3337
|
+
const confirmation = await launchMonitorAndConfirm([...monitorEntry.args, 'restart-monitor', autoUpgrade ? '--apply-package-upgrades' : '--skip-package-upgrades'], { EVOLCORE_HOME: resolvePaths().root, ...monitorEntry.env, [RESTART_REQUEST_ID_ENV]: restartInfo.requestId }, pid => scanInstances().restartMonitors.some(m => m.alive && m.record.pid === pid
|
|
3255
3338
|
&& m.record.requestId === restartInfo.requestId && m.record.phase === 'accepted'), pid => scanInstances().restartMonitors.some(m => m.alive && m.record.pid !== pid
|
|
3256
3339
|
&& m.record.requestId === restartInfo.requestId && m.record.phase === 'accepted'), {
|
|
3257
3340
|
requestId: restartInfo.requestId,
|
|
@@ -318,6 +318,7 @@ const CATALOG = [
|
|
|
318
318
|
{ path: 'durationMs', type: 'number', optional: true },
|
|
319
319
|
{ path: 'terminalReason', type: 'string', optional: true },
|
|
320
320
|
{ path: 'agentName', type: 'string', optional: true },
|
|
321
|
+
{ path: 'agentAid', type: 'string', optional: true },
|
|
321
322
|
{ path: 'numTurns', type: 'number', optional: true },
|
|
322
323
|
{ path: 'encrypted', type: 'boolean', optional: true },
|
|
323
324
|
{ path: 'timestamp', type: 'number', optional: true },
|
|
@@ -354,6 +355,7 @@ const CATALOG = [
|
|
|
354
355
|
{ path: 'generation', type: 'number', optional: true },
|
|
355
356
|
{ path: 'reason', type: 'string', optional: true },
|
|
356
357
|
{ path: 'agentName', type: 'string', optional: true },
|
|
358
|
+
{ path: 'agentAid', type: 'string', optional: true },
|
|
357
359
|
{ path: 'encrypted', type: 'boolean', optional: true },
|
|
358
360
|
],
|
|
359
361
|
},
|
|
@@ -290,7 +290,8 @@ export function renderActionAsText(req) {
|
|
|
290
290
|
lines.push('', '回复:');
|
|
291
291
|
for (const btn of action.buttons) {
|
|
292
292
|
const arg = fb.buttonArgMap?.[btn.key] ?? btn.key;
|
|
293
|
-
|
|
293
|
+
const label = action.recommendedOption === btn.key ? `💡 ${btn.label}` : btn.label;
|
|
294
|
+
lines.push(` /${fb.command} ${arg} ← ${label}`);
|
|
294
295
|
}
|
|
295
296
|
if (fb.acceptFreeText && fb.freeTextHint) {
|
|
296
297
|
lines.push(` ${fb.freeTextHint}`);
|
|
@@ -11,7 +11,6 @@ import { agentDir } from '../paths.js';
|
|
|
11
11
|
import { parsePeerKey } from './relation/peer-identity.js';
|
|
12
12
|
import { resolvePeerRoleDetail } from '../config/peer-role-resolver.js';
|
|
13
13
|
import { buildDebugTaskContext } from './auth/debug-context.js';
|
|
14
|
-
import { computeBuildId } from '../utils/evolcore-version.js';
|
|
15
14
|
import { loadAgent } from '../config-store.js';
|
|
16
15
|
import { executeLocalInitializationCommand, hasSuppliedInitialization } from '../cli/agent-create.js';
|
|
17
16
|
import { remoteAccessService } from './auth/remote-access-store.js';
|
|
@@ -25,7 +24,7 @@ import { createTriggerBaseagentResolver, registerTriggerIpcExecutor } from '../t
|
|
|
25
24
|
import { createThreadOperationExecutor } from './thread/ipc-operation.js';
|
|
26
25
|
import { isLinkedInstall } from '../utils/npm-ops.js';
|
|
27
26
|
import { buildSystemSessionContext, getSystemSessionModule } from './system-session/registry.js';
|
|
28
|
-
export function registerDaemonIpc({ ipcServer, agentRegistry, agentDelegationRegistry, sessionManager, statsCollector, aidStatsCollector, responseEngine, messageQueue, agentMap, commandHandler, channelInstances, eventBus, handoffRuntime, reloadCoordinator, agentApplicationService, agentInitializationService, enqueueConsolidation, completeAgentBootstrap, contactBinds, bindService, diagService, getProcessOwners, getControlAidState, daemonAid, triggerControlToken, triggerSchedulers, daemonTriggerOwner, primaryAgent, triggerAuthorization, }) {
|
|
27
|
+
export function registerDaemonIpc({ ipcServer, agentRegistry, agentDelegationRegistry, sessionManager, statsCollector, aidStatsCollector, responseEngine, messageQueue, agentMap, commandHandler, channelInstances, eventBus, handoffRuntime, reloadCoordinator, agentApplicationService, agentInitializationService, enqueueConsolidation, requestRelationSweep, completeAgentBootstrap, contactBinds, bindService, diagService, getProcessOwners, getControlAidState, daemonAid, triggerControlToken, triggerSchedulers, daemonTriggerOwner, primaryAgent, triggerAuthorization, }) {
|
|
29
28
|
const authorizeManagedAgentOperation = createDelegationAuthorizer({
|
|
30
29
|
agentDelegationRegistry,
|
|
31
30
|
sessionManager,
|
|
@@ -229,7 +228,7 @@ export function registerDaemonIpc({ ipcServer, agentRegistry, agentDelegationReg
|
|
|
229
228
|
});
|
|
230
229
|
registerAunIpcSender({ ipcServer, channelInstances, agentDelegationRegistry, responseEngine, handoffRuntime, triggerSchedulers, sessionManager });
|
|
231
230
|
ipcServer.setAgentReloadExecutor((aid, options) => reloadCoordinator.reload(aid, options));
|
|
232
|
-
registerConsolidationCheckExecutor({ ipcServer, agentRegistry, enqueueConsolidation });
|
|
231
|
+
registerConsolidationCheckExecutor({ ipcServer, agentRegistry, enqueueConsolidation, requestRelationSweep });
|
|
233
232
|
ipcServer.setInitializationExecutor(async (command) => executeLocalInitializationCommand(agentInitializationService, command));
|
|
234
233
|
ipcServer.setAgentApplicationService(agentApplicationService);
|
|
235
234
|
registerQueueIpcProviders({ ipcServer, statsCollector, agentRegistry, messageQueue });
|
|
@@ -288,10 +287,12 @@ export function createIpcStatusProvider(dependencies) {
|
|
|
288
287
|
return {
|
|
289
288
|
pid: process.pid,
|
|
290
289
|
uptime: snapshot.uptimeMs,
|
|
290
|
+
...(dependencies.runMode ? { runMode: dependencies.runMode } : {}),
|
|
291
|
+
...(dependencies.host ? { host: dependencies.host } : {}),
|
|
291
292
|
build: {
|
|
292
293
|
runtimeMode: dependencies.runtimeIsDist ? 'dist' : 'source',
|
|
293
294
|
runtimeId: dependencies.runtimeBuildIdentity.id,
|
|
294
|
-
diskDistId:
|
|
295
|
+
diskDistId: dependencies.diskDistId ?? null,
|
|
295
296
|
entry: dependencies.runtimeBuildIdentity.entry,
|
|
296
297
|
version: dependencies.runtimeBuildIdentity.version,
|
|
297
298
|
},
|
|
@@ -302,9 +303,26 @@ export function createIpcStatusProvider(dependencies) {
|
|
|
302
303
|
runningAgents: agents.filter(agent => agent.status === 'running').length,
|
|
303
304
|
...(runtimeError ? { error: runtimeError } : {}),
|
|
304
305
|
},
|
|
306
|
+
...(dependencies.baseagents ? {
|
|
307
|
+
baseagents: {
|
|
308
|
+
source: 'runtime-cache',
|
|
309
|
+
items: dependencies.baseagents.baseagents.map(baseagent => ({
|
|
310
|
+
...baseagent,
|
|
311
|
+
state: baseagent.loaded === true
|
|
312
|
+
? 'ready'
|
|
313
|
+
: baseagent.detected === true
|
|
314
|
+
? 'available'
|
|
315
|
+
: baseagent.detected === false
|
|
316
|
+
? 'unavailable'
|
|
317
|
+
: 'unknown',
|
|
318
|
+
})),
|
|
319
|
+
},
|
|
320
|
+
} : {}),
|
|
305
321
|
channels,
|
|
306
322
|
channelsByType,
|
|
307
323
|
queue: { pending: dependencies.messageQueue.getGlobalQueueLength(), processing: dependencies.messageQueue.getGlobalProcessingCount() },
|
|
324
|
+
...(dependencies.getTriggerSummary ? { triggers: dependencies.getTriggerSummary() } : {}),
|
|
325
|
+
...(dependencies.getStatusSnapshot ? dependencies.getStatusSnapshot() : {}),
|
|
308
326
|
stats: {
|
|
309
327
|
received: snapshot.lastHour.received,
|
|
310
328
|
sent: snapshot.lastHour.sent,
|
|
@@ -455,6 +473,7 @@ export function registerQueueIpcProviders({ ipcServer, statsCollector, agentRegi
|
|
|
455
473
|
const snapshots = statsCollector.getSnapshots(agents.map(agent => agent.aid));
|
|
456
474
|
return agents.map((agent) => {
|
|
457
475
|
const snapshot = snapshots.get(agent.aid);
|
|
476
|
+
const queueAgentName = agentRegistry.get(agent.aid)?.name ?? agent.aid;
|
|
458
477
|
return {
|
|
459
478
|
aid: agent.aid,
|
|
460
479
|
received: snapshot.lastHour.received,
|
|
@@ -463,9 +482,9 @@ export function registerQueueIpcProviders({ ipcServer, statsCollector, agentRegi
|
|
|
463
482
|
errors: snapshot.lastHour.errors,
|
|
464
483
|
interrupts: snapshot.lastHour.interrupts,
|
|
465
484
|
avgResponseMs: snapshot.lastHour.avgResponseMs,
|
|
466
|
-
processing: messageQueue.getProcessingCountByAgent(
|
|
467
|
-
queued: messageQueue.getQueueLengthByAgent(
|
|
468
|
-
muted: messageQueue.isAgentMuted(
|
|
485
|
+
processing: messageQueue.getProcessingCountByAgent(queueAgentName),
|
|
486
|
+
queued: messageQueue.getQueueLengthByAgent(queueAgentName),
|
|
487
|
+
muted: messageQueue.isAgentMuted(queueAgentName),
|
|
469
488
|
};
|
|
470
489
|
});
|
|
471
490
|
});
|
|
@@ -95,6 +95,8 @@ export function updateBatchMeta(batchDir, updates) {
|
|
|
95
95
|
finishedAt: updates.finishedAt,
|
|
96
96
|
success: updates.success,
|
|
97
97
|
...(updates.agentSessionId ? { agentSessionId: updates.agentSessionId } : {}),
|
|
98
|
+
...(updates.requestedModel ? { requestedModel: updates.requestedModel } : {}),
|
|
99
|
+
...(updates.actualModel ? { actualModel: updates.actualModel } : {}),
|
|
98
100
|
...(updates.failureReason ? { failureReason: updates.failureReason } : {}),
|
|
99
101
|
};
|
|
100
102
|
// 更新 feedback 正文
|
|
@@ -38,6 +38,10 @@ export const MODE_SCOPE = {
|
|
|
38
38
|
promote: 'owner',
|
|
39
39
|
profile: 'relation',
|
|
40
40
|
};
|
|
41
|
+
/** 系统自动发起、不向对端发反馈的触发来源。 */
|
|
42
|
+
export function isSilentConsolidationTrigger(triggeredBy) {
|
|
43
|
+
return triggeredBy === 'periodic-trigger' || triggeredBy === 'relation-idle';
|
|
44
|
+
}
|
|
41
45
|
// 超期判据集中在 staleness.ts;这里做转发,保证 trigger 与 context 用同一套规则
|
|
42
46
|
export { DEFAULT_FORGET_AFTER_DAYS, LONG_TERM_REVIEW_DAYS } from './staleness.js';
|
|
43
47
|
// ── Checkpoint 读写 ──────────────────────────────────────────────────────────
|
|
@@ -136,6 +140,7 @@ function buildScopeContext(scope, input, selfAid,
|
|
|
136
140
|
/** 候选来源作用域,默认与本作用域一致 */
|
|
137
141
|
sourceScope) {
|
|
138
142
|
const memoryDir = memoryDirOf(scope, selfAid, input.taskCtx.peerKey, input.ownerAid);
|
|
143
|
+
// 整理一律按关系进行:generalize / promote 的候选只来自本次会话所属关系(taskCtx.peerKey)。
|
|
139
144
|
const candidateSourceDir = sourceScope === undefined || sourceScope === scope
|
|
140
145
|
? path.join(memoryDir, 'consolidated')
|
|
141
146
|
: path.join(memoryDirOf(sourceScope, selfAid, input.taskCtx.peerKey, input.ownerAid), 'consolidated');
|
|
@@ -212,14 +217,24 @@ function scanCandidates(consolidatedDir, pick) {
|
|
|
212
217
|
out.sort((a, b) => a.relPath.localeCompare(b.relPath));
|
|
213
218
|
return out;
|
|
214
219
|
}
|
|
220
|
+
/**
|
|
221
|
+
* 给关系级候选补上来源关系前缀:`consolidated/x.md` → `<peerKey>/consolidated/x.md`。
|
|
222
|
+
* generalize / promote 的产出层(agent / owner)与候选层(relation)不同,不带前缀的
|
|
223
|
+
* 路径在产出层会解析成同名文件。
|
|
224
|
+
*/
|
|
225
|
+
function withPeerPrefix(candidates, peerKey) {
|
|
226
|
+
if (!peerKey)
|
|
227
|
+
return candidates;
|
|
228
|
+
return candidates.map(c => ({ ...c, relPath: `${peerKey}/${c.relPath}` }));
|
|
229
|
+
}
|
|
215
230
|
/** 按模式填充候选清单。 */
|
|
216
|
-
function fillCandidates(ctx, mode, forgetAfterDays) {
|
|
231
|
+
function fillCandidates(ctx, mode, forgetAfterDays, sourcePeerKey) {
|
|
217
232
|
const source = ctx.candidateSourceDir ?? ctx.consolidatedDir;
|
|
218
233
|
if (mode === 'generalize') {
|
|
219
|
-
ctx.candidates = scanCandidates(source, ({ tags }) => tags.includes('to-generalize'));
|
|
234
|
+
ctx.candidates = withPeerPrefix(scanCandidates(source, ({ tags }) => tags.includes('to-generalize')), sourcePeerKey);
|
|
220
235
|
}
|
|
221
236
|
else if (mode === 'promote') {
|
|
222
|
-
ctx.candidates = scanCandidates(source, ({ tags }) => tags.includes('to-promote'));
|
|
237
|
+
ctx.candidates = withPeerPrefix(scanCandidates(source, ({ tags }) => tags.includes('to-promote')), sourcePeerKey);
|
|
223
238
|
}
|
|
224
239
|
else if (mode === 'profile') {
|
|
225
240
|
ctx.candidates = scanCandidates(source, () => true);
|
|
@@ -227,50 +242,15 @@ function fillCandidates(ctx, mode, forgetAfterDays) {
|
|
|
227
242
|
else if (mode === 'forget') {
|
|
228
243
|
const now = Date.now();
|
|
229
244
|
const thresholds = { forgetAfterDays };
|
|
230
|
-
//
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
const consolidatedDir = path.join(relationsRoot, peerKey, 'memory', 'consolidated');
|
|
240
|
-
if (fs.existsSync(consolidatedDir)) {
|
|
241
|
-
const candidates = collectCandidates(consolidatedDir, thresholds, now)
|
|
242
|
-
.filter(info => isDueForReview(info, thresholds, now))
|
|
243
|
-
.map(info => ({
|
|
244
|
-
relPath: `${peerKey}/consolidated/${info.name}`,
|
|
245
|
-
description: readFmValue(info.content, 'description') ?? info.name.replace(/\.md$/, ''),
|
|
246
|
-
tags: info.tags,
|
|
247
|
-
}));
|
|
248
|
-
ctx.candidates.push(...candidates);
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
// agent 自身的 consolidated(原 ⑤b 拉起 forget/agent 的来源)
|
|
253
|
-
if (fs.existsSync(ctx.consolidatedDir)) {
|
|
254
|
-
const own = collectCandidates(ctx.consolidatedDir, thresholds, now)
|
|
255
|
-
.filter(info => isDueForReview(info, thresholds, now))
|
|
256
|
-
.map(info => ({
|
|
257
|
-
relPath: `consolidated/${info.name}`,
|
|
258
|
-
description: readFmValue(info.content, 'description') ?? info.name.replace(/\.md$/, ''),
|
|
259
|
-
tags: info.tags,
|
|
260
|
-
}));
|
|
261
|
-
ctx.candidates.push(...own);
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
else {
|
|
265
|
-
// relation / owner 级 forget:只扫描当前层级的 consolidated
|
|
266
|
-
ctx.candidates = collectCandidates(source, thresholds, now)
|
|
267
|
-
.filter(info => isDueForReview(info, thresholds, now))
|
|
268
|
-
.map(info => ({
|
|
269
|
-
relPath: `consolidated/${info.name}`,
|
|
270
|
-
description: readFmValue(info.content, 'description') ?? info.name.replace(/\.md$/, ''),
|
|
271
|
-
tags: info.tags,
|
|
272
|
-
}));
|
|
273
|
-
}
|
|
245
|
+
// 一次会话只审一层:relation / agent / owner 各自只扫本层 consolidated/。
|
|
246
|
+
// 关系层由关系整理链逐关系审,agent 层只审 personal/memory 自身。
|
|
247
|
+
ctx.candidates = collectCandidates(source, thresholds, now)
|
|
248
|
+
.filter(info => isDueForReview(info, thresholds, now))
|
|
249
|
+
.map(info => ({
|
|
250
|
+
relPath: `consolidated/${info.name}`,
|
|
251
|
+
description: readFmValue(info.content, 'description') ?? info.name.replace(/\.md$/, ''),
|
|
252
|
+
tags: info.tags,
|
|
253
|
+
}));
|
|
274
254
|
}
|
|
275
255
|
}
|
|
276
256
|
// ── 渲染 ────────────────────────────────────────────────────────────────────
|
|
@@ -285,7 +265,10 @@ function renderScopeSection(ctx, mode, threadFiles) {
|
|
|
285
265
|
lines.push(`- ownerAid: ${ctx.ownerAid}`);
|
|
286
266
|
if (ctx.baseagent)
|
|
287
267
|
lines.push(`- baseagent: ${ctx.baseagent}`);
|
|
288
|
-
|
|
268
|
+
// processingDir 只在 notes 模式下是有效输入(待整理的 note 在那里)。
|
|
269
|
+
// 其他模式不读它,列出来会诱导模型去 Glob 一个 hook 不放行的目录。
|
|
270
|
+
if (mode === 'notes')
|
|
271
|
+
lines.push(`- processingDir: ${ctx.processingDir}`);
|
|
289
272
|
lines.push(`- consolidatedDir: ${ctx.consolidatedDir}`);
|
|
290
273
|
lines.push(`- indexPath: ${ctx.indexPath}`);
|
|
291
274
|
lines.push('');
|
|
@@ -499,7 +482,7 @@ export function serializeConsolidationContext(input) {
|
|
|
499
482
|
const now = new Date();
|
|
500
483
|
const forgetAfterDays = input.forgetAfterDays ?? DEFAULT_FORGET_AFTER_DAYS;
|
|
501
484
|
for (const ctx of scopes) {
|
|
502
|
-
fillCandidates(ctx, input.mode, forgetAfterDays);
|
|
485
|
+
fillCandidates(ctx, input.mode, forgetAfterDays, input.taskCtx.peerKey);
|
|
503
486
|
}
|
|
504
487
|
// ── 批次目录(长期批次)────────────────────────────────────────────────────
|
|
505
488
|
const batchRoot = path.join(resolveRoot(), 'memory', 'consolidation-batches');
|
|
@@ -564,16 +547,24 @@ export function serializeConsolidationContext(input) {
|
|
|
564
547
|
'',
|
|
565
548
|
...scopes.flatMap(ctx => [renderScopeSection(ctx, input.mode, threadFiles), '', '---', '']),
|
|
566
549
|
];
|
|
567
|
-
// 列出可读根(工具 path/file_path
|
|
550
|
+
// 列出可读根(工具 path/file_path 参数必须落在这些路径内)。
|
|
551
|
+
// 这份清单必须与 system-session/memory.ts 的 memoryReadRoots 一致——两边各算一份
|
|
552
|
+
// 就会出现"任务书让模型去读、hook 却拒绝"的假阴性。
|
|
568
553
|
lines.push('', '**可读根(工具 path/file_path 参数必须落在这些路径内)**:');
|
|
569
554
|
lines.push('- 批次目录:`' + batchDir + '`');
|
|
570
555
|
for (const ctx of scopes) {
|
|
571
|
-
|
|
556
|
+
// 与 renderScopeSection 同判据:processingDir 只在 notes 模式可读
|
|
557
|
+
if (input.mode === 'notes' && ctx.processingDir)
|
|
572
558
|
lines.push('- processingDir: `' + ctx.processingDir + '`');
|
|
573
559
|
if (ctx.consolidatedDir)
|
|
574
560
|
lines.push('- consolidatedDir: `' + ctx.consolidatedDir + '`');
|
|
575
561
|
if (ctx.indexPath)
|
|
576
562
|
lines.push('- indexPath: `' + ctx.indexPath + '`');
|
|
563
|
+
// generalize / promote 的候选在本关系的 consolidated/(hook 只放行该目录,不含关系级 MEMORY.md)
|
|
564
|
+
if ((input.mode === 'generalize' || input.mode === 'promote') && input.taskCtx.peerKey) {
|
|
565
|
+
const relationMemoryDir = path.join(resolveRoot(), 'agents', ctx.selfAid, 'relations', input.taskCtx.peerKey, 'memory');
|
|
566
|
+
lines.push('- relationConsolidatedDir: `' + path.join(relationMemoryDir, 'consolidated') + '`');
|
|
567
|
+
}
|
|
577
568
|
}
|
|
578
569
|
if (historyJsonlPath)
|
|
579
570
|
lines.push('- 消息流水: `' + historyJsonlPath + '`');
|
|
@@ -76,15 +76,19 @@ export function readFeedbackText(batchDir) {
|
|
|
76
76
|
return undefined;
|
|
77
77
|
}
|
|
78
78
|
/**
|
|
79
|
-
* 收集 ops
|
|
80
|
-
* 供 finishConsolidationSession
|
|
81
|
-
*
|
|
79
|
+
* 收集 ops 记录里所有被写过的 memory 根目录及其作用域。
|
|
80
|
+
* 供 finishConsolidationSession 决定要对哪几个目录重建索引。
|
|
81
|
+
*
|
|
82
|
+
* 按 memoryDir 去重,不按 scope:同一 scope 可能涉及多个目录(群聊按发言人分流到
|
|
83
|
+
* 多条关系;archive 记的是源文件所属层)。按 scope 去重会只重建第一个目录的索引。
|
|
84
|
+
* 作用域优先从路径推断(archive 流水的 scope 是会话写入层,不一定是 memoryDir 所属层),
|
|
85
|
+
* 推不出来时用记录里的 scope。
|
|
82
86
|
*/
|
|
83
87
|
export function collectAffectedScopes(batchDir) {
|
|
84
88
|
const result = new Map();
|
|
85
89
|
const filePath = path.join(batchDir, 'ops.jsonl');
|
|
86
90
|
if (!fs.existsSync(filePath))
|
|
87
|
-
return
|
|
91
|
+
return [];
|
|
88
92
|
try {
|
|
89
93
|
for (const line of fs.readFileSync(filePath, 'utf-8').split('\n').filter(Boolean)) {
|
|
90
94
|
const entry = JSON.parse(line);
|
|
@@ -92,13 +96,41 @@ export function collectAffectedScopes(batchDir) {
|
|
|
92
96
|
continue;
|
|
93
97
|
const e = entry;
|
|
94
98
|
const s = e.scope;
|
|
95
|
-
if ((s === 'relation' || s === 'agent' || s === 'owner') && e.memoryDir && !result.has(
|
|
96
|
-
result.set(
|
|
99
|
+
if ((s === 'relation' || s === 'agent' || s === 'owner') && e.memoryDir && !result.has(e.memoryDir)) {
|
|
100
|
+
result.set(e.memoryDir, scopeOfMemoryDir(e.memoryDir) ?? s);
|
|
101
|
+
}
|
|
102
|
+
// create --replaces 跨层归档(如泛化把关系级源文件归档):源文件所在层也被改了,
|
|
103
|
+
// 它的索引同样要重建。源路径是绝对路径,形如 <memoryDir>/consolidated/<file>。
|
|
104
|
+
for (const af of e.archivedFiles ?? []) {
|
|
105
|
+
if (!af.source || !path.isAbsolute(af.source))
|
|
106
|
+
continue;
|
|
107
|
+
const consolidatedDir = path.dirname(af.source);
|
|
108
|
+
if (path.basename(consolidatedDir) !== 'consolidated')
|
|
109
|
+
continue;
|
|
110
|
+
const srcMemoryDir = path.dirname(consolidatedDir);
|
|
111
|
+
const srcScope = scopeOfMemoryDir(srcMemoryDir);
|
|
112
|
+
if (srcScope && !result.has(srcMemoryDir))
|
|
113
|
+
result.set(srcMemoryDir, srcScope);
|
|
97
114
|
}
|
|
98
115
|
}
|
|
99
116
|
}
|
|
100
117
|
catch { /* ignore */ }
|
|
101
|
-
return result;
|
|
118
|
+
return [...result].map(([memoryDir, scope]) => [scope, memoryDir]);
|
|
119
|
+
}
|
|
120
|
+
/** 从 memory 根目录路径推断作用域;不是标准布局时返回 undefined。 */
|
|
121
|
+
function scopeOfMemoryDir(memoryDir) {
|
|
122
|
+
const parts = path.resolve(memoryDir).split(path.sep);
|
|
123
|
+
const last = parts.length - 1;
|
|
124
|
+
// .../agents/<selfAid>/relations/<peerKey>/memory
|
|
125
|
+
if (parts[last] === 'memory' && parts[last - 2] === 'relations')
|
|
126
|
+
return 'relation';
|
|
127
|
+
// .../agents/<selfAid>/personal/memory
|
|
128
|
+
if (parts[last] === 'memory' && parts[last - 1] === 'personal')
|
|
129
|
+
return 'agent';
|
|
130
|
+
// .../memory/owner/<ownerAid>
|
|
131
|
+
if (parts[last - 1] === 'owner' && parts[last - 2] === 'memory')
|
|
132
|
+
return 'owner';
|
|
133
|
+
return undefined;
|
|
102
134
|
}
|
|
103
135
|
/**
|
|
104
136
|
* 读取 ops 记录里所有成功的写操作,按写入顺序倒序返回(最后写的先回撤)。
|