evolcore 0.0.28 → 0.0.29
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 +12 -0
- package/bin/ec-safe-output.js +270 -54
- package/dist/agents/baseagent-availability.js +37 -1
- package/dist/agents/baseagent.js +19 -9
- package/dist/agents/claude-runner.js +171 -13
- package/dist/agents/codex/app-server-client.js +6 -2
- package/dist/agents/codex/diagnostics.js +4 -3
- package/dist/agents/codex-runner.js +396 -95
- package/dist/agents/gemini-runner.js +35 -17
- package/dist/aun/group/history.js +130 -90
- package/dist/aun/group/operations.js +1 -3
- package/dist/aun/menu/transport.js +5 -3
- package/dist/aun/msg/managed-operation.js +70 -0
- package/dist/aun/msg/p2p.js +1 -3
- package/dist/aun/msg/upload.js +2 -2
- package/dist/aun/outbox.js +1 -37
- package/dist/channels/aun.js +249 -89
- package/dist/channels/daemon.js +18 -1
- package/dist/channels/feishu.js +1 -2
- package/dist/cli/agent-command.js +23 -3
- package/dist/cli/agent-create.js +68 -2
- package/dist/cli/agent.js +12 -2
- package/dist/cli/aun-commands.js +87 -13
- package/dist/cli/cli-argv.js +26 -0
- package/dist/cli/daemon-commands.js +40 -10
- package/dist/cli/data-command.js +4 -4
- package/dist/cli/handoff-command.js +2 -0
- package/dist/cli/index.js +18 -1
- package/dist/cli/managed-command-guard.js +10 -0
- package/dist/cli/memory-command.js +441 -0
- package/dist/cli/restart-monitor.js +62 -6
- package/dist/cli/skill-command.js +41 -0
- package/dist/cli/task-context.js +2 -23
- package/dist/cli/trigger-command.js +9 -1
- package/dist/cli/watch-msg-diag.js +209 -0
- package/dist/config/config-field-policy.js +52 -3
- package/dist/config/config-manager.js +17 -4
- package/dist/config/contact-book-store.js +2 -2
- package/dist/config/mention-mode.js +7 -2
- package/dist/config/merge.js +28 -9
- package/dist/config/resolved-config-op.js +1 -1
- package/dist/config/runtime-env.js +137 -0
- package/dist/config/schema-registry.js +11 -1
- package/dist/config-store.js +10 -10
- package/dist/core/audit/event-key.js +3 -1
- package/dist/core/audit/log-integrity.js +4 -0
- package/dist/core/auth/agent-delegation.js +2 -1
- package/dist/core/auth/auth-gateway.js +13 -6
- package/dist/core/auth/authorization-audit.js +54 -19
- package/dist/core/auth/operation-authorizer.js +17 -0
- package/dist/core/auth/operation-catalog.js +9 -0
- package/dist/core/causation/context.js +5 -1
- package/dist/core/command/agent-control.js +1 -2
- package/dist/core/command/command-handler.js +51 -23
- package/dist/core/command/memory-slash.js +270 -0
- package/dist/core/command/menu-catalog.js +1 -0
- package/dist/core/command/menu-handler.js +315 -60
- package/dist/core/command/menu-protocol.js +42 -4
- package/dist/core/command/slash-gate.js +10 -10
- package/dist/core/command/slash-handler.js +206 -64
- package/dist/core/data-migration.js +282 -505
- package/dist/core/event-catalog.js +26 -0
- package/dist/core/evolagent-registry.js +6 -0
- package/dist/core/evolagent.js +0 -10
- package/dist/core/handoff/store.js +1 -20
- package/dist/core/inference/text-inference.js +57 -1
- package/dist/core/memory/consolidation-context.js +505 -0
- package/dist/core/memory/consolidation-ops-records.js +124 -0
- package/dist/core/memory/consolidation-trigger.js +394 -0
- package/dist/core/memory/index-manager.js +308 -0
- package/dist/core/memory/launch-consolidation.js +395 -0
- package/dist/core/memory/memory-admin.js +234 -0
- package/dist/core/memory/memory-document.js +100 -0
- package/dist/core/memory/memory-session-policy.js +41 -0
- package/dist/core/memory/memory-signature.js +257 -0
- package/dist/core/memory/memory-writer.js +432 -0
- package/dist/core/memory/staleness.js +117 -0
- package/dist/core/message/auto-mention-gate.js +454 -0
- package/dist/core/message/im-renderer.js +69 -16
- package/dist/core/message/message-bridge.js +287 -21
- package/dist/core/message/message-log.js +1 -0
- package/dist/core/message/message-queue.js +114 -27
- package/dist/core/message/pending-hints.js +329 -161
- package/dist/core/message/response-engine.js +567 -80
- package/dist/core/permission/ec-command-parser.js +1029 -80
- package/dist/core/permission/protected-paths.js +376 -126
- package/dist/core/permission/sandbox-runtime.js +40 -12
- package/dist/core/permission/tool-error-code.js +96 -13
- package/dist/core/permission/tool-policy.js +276 -23
- package/dist/core/role/runtime-policy.js +3 -7
- package/dist/core/session/{runner-task-history.js → runner-event-summary.js} +1 -1
- package/dist/core/session/session-fs-store.js +0 -76
- package/dist/core/session/session-idle-monitor.js +355 -0
- package/dist/core/session/session-manager.js +478 -17
- package/dist/core/session/session-mapper.js +33 -16
- package/dist/core/session/session-renew.js +217 -155
- package/dist/core/session/{session-turns.js → session-transcript.js} +35 -6
- package/dist/core/skill/global-watcher.js +282 -0
- package/dist/core/skill/index-generator.js +84 -0
- package/dist/core/skill/startup.js +73 -0
- package/dist/core/skill/switch-resolver.js +115 -0
- package/dist/core/task/task-activity-store.js +219 -0
- package/dist/eck/kit-renderer.js +30 -3
- package/dist/eck/manifest-engine.js +26 -4
- package/dist/index.js +708 -18
- package/dist/ipc.js +564 -2
- package/dist/paths.js +30 -9
- package/dist/response-system/engines/v1/proactive-flow.js +116 -15
- package/dist/response-system/modes/single-session/index.js +3 -0
- package/dist/trigger/feedback.js +1 -0
- package/dist/trigger/manager.js +0 -10
- package/dist/trigger/scheduler.js +11 -0
- package/dist/trigger/validation.js +22 -0
- package/dist/utils/avatar-upload.js +59 -18
- package/dist/utils/error-dict.json +6 -3
- package/dist/utils/error-utils.js +27 -0
- package/dist/utils/logger.js +32 -10
- package/dist/utils/media-cache.js +84 -0
- package/dist/utils/msg-diag.js +97 -0
- package/dist/utils/welcome.js +37 -8
- package/kits/docs/AUTHORING.md +314 -0
- package/kits/docs/GUIDE.md +2 -0
- package/kits/docs/INDEX.md +2 -0
- package/kits/docs/aun/CHEATSHEET.md +4 -0
- package/kits/docs/aun/SDK.md +60 -0
- package/kits/docs/context-assembly.md +10 -6
- package/kits/docs/evolcore/INDEX.md +17 -3
- package/kits/docs/evolcore/agent-create.md +24 -0
- package/kits/docs/evolcore/agent.md +5 -2
- package/kits/docs/evolcore/config.md +1 -1
- package/kits/docs/evolcore/ec-capabilities.md +364 -0
- package/kits/docs/evolcore/group.md +34 -18
- package/kits/docs/evolcore/memory-architecture.md +78 -0
- package/kits/docs/evolcore/memory-consolidation-batches.md +133 -0
- package/kits/docs/evolcore/memory-consolidation.md +263 -0
- package/kits/docs/evolcore/memory-tags.md +63 -0
- package/kits/docs/evolcore/memory.md +110 -0
- package/kits/docs/evolcore/menu-client.md +27 -11
- package/kits/{schemas → docs/evolcore}/menu-exec-schema-commands.md +12 -6
- package/kits/docs/evolcore/menu-protocol.md +213 -0
- package/kits/docs/evolcore/msg.md +2 -0
- package/kits/docs/evolcore/trigger.md +7 -5
- package/kits/docs/path-registry.md +6 -8
- package/kits/docs/prompt-loading-architecture.md +5 -3
- package/kits/docs/venues/group.md +5 -4
- package/kits/eck_manifest.json +294 -88
- package/kits/eck_manifest.memory.json +257 -0
- package/kits/rules/03-identity.md +8 -8
- package/kits/rules/05-venue.md +4 -2
- package/kits/rules/06-channel.md +1 -1
- package/kits/schemas/_meta.json +276 -47
- package/kits/schemas/agent-config.schema.12.json +38 -2
- package/kits/schemas/agent-config.schema.13.json +591 -0
- package/kits/schemas/daemon.schema.6.json +9 -9
- package/kits/schemas/defaults.schema.5.json +29 -0
- package/kits/schemas/relation-config.schema.8.json +14 -1
- package/kits/schemas/relation-config.schema.9.json +129 -0
- package/kits/schemas/role-config.schema.1.json +3 -3
- package/kits/skills/.switches.json +3 -0
- package/kits/skills/ec-skill/SKILL.md +217 -0
- package/kits/skills/ec-skill/scripts/cli.mjs +128 -0
- package/kits/skills/ec-skill/scripts/deps.mjs +107 -0
- package/kits/skills/ec-skill/scripts/frontmatter.mjs +104 -0
- package/kits/skills/ec-skill/scripts/skill.mjs +629 -0
- package/kits/skills/ec-skill/scripts/template/SKILL.md.tmpl +30 -0
- package/kits/templates/decision/auto-mention.md +25 -0
- package/kits/templates/decision/session-renew.md +22 -0
- package/kits/templates/memory-consolidation/forget.md +104 -0
- package/kits/templates/memory-consolidation/generalize.md +76 -0
- package/kits/templates/memory-consolidation/notes.md +133 -0
- package/kits/templates/memory-consolidation/profile.md +58 -0
- package/kits/templates/memory-consolidation/promote.md +72 -0
- package/kits/templates/memory-consolidation/workflow.md +75 -0
- package/kits/templates/system-fragments/bootstrap.md +14 -5
- package/kits/templates/system-fragments/commands.md +1 -0
- package/kits/templates/system-fragments/group-history.md +6 -0
- package/kits/templates/system-fragments/identity.md +0 -3
- package/kits/templates/system-fragments/memory.md +79 -0
- package/kits/templates/system-fragments/reply.md +1 -0
- package/kits/templates/system-fragments/venue.md +1 -1
- package/package.json +4 -6
- package/dist/trigger/legacy-session-history.js +0 -19
- package/kits/templates/system-fragments/group-unread-history.md +0 -7
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// 【重要】最先加载环境变量,确保后续模块初始化时可用
|
|
2
2
|
import dotenv from 'dotenv';
|
|
3
3
|
import path from 'path';
|
|
4
|
-
import { ensureDataDirs, resolvePaths, getPackageRoot, agentMdPath, eckDebugDir, daemonControlDir, daemonQueuePath, daemonMessageDedupPath } from './paths.js';
|
|
4
|
+
import { ensureDataDirs, resolvePaths, agentDir, getPackageRoot, agentMdPath, eckDebugDir, daemonControlDir, daemonQueuePath, daemonMessageDedupPath } from './paths.js';
|
|
5
5
|
// 立即加载 .env 文件(在其他模块导入之前)
|
|
6
6
|
try {
|
|
7
7
|
dotenv.config({ path: path.join(resolvePaths().root, '.env') });
|
|
@@ -13,7 +13,7 @@ import { ClaudeSessionFileAdapter } from './core/session/adapters/claude-session
|
|
|
13
13
|
import { CodexSessionFileAdapter } from './core/session/adapters/codex-session-file-adapter.js';
|
|
14
14
|
import { GeminiSessionFileAdapter } from './core/session/adapters/gemini-session-file-adapter.js';
|
|
15
15
|
import { EcagentSessionFileAdapter } from './core/session/adapters/ecagent-session-file-adapter.js';
|
|
16
|
-
import { autoMigrateIfNeeded, loadDefaults, loadAllAgents, migrateIdentitiesIfNeeded, loadDaemonConfig, initializeEckSnapshotsConfig
|
|
16
|
+
import { autoMigrateIfNeeded, loadDefaults, loadAllAgents, migrateIdentitiesIfNeeded, loadDaemonConfig, initializeEckSnapshotsConfig } from './config-store.js';
|
|
17
17
|
import { migrateRelationSharedDirectories } from './core/relation/shared-data.js';
|
|
18
18
|
import { DEFAULT_ECWEB_PORT, ecwebService } from './config/daemon-services.js';
|
|
19
19
|
import { ConfigTarget, initConfigManager, onConfigWrite, read as readConfig, shouldFailFastForMissingOwners } from './config/config-manager.js';
|
|
@@ -28,6 +28,7 @@ import { ensureStartupConfigSnapshot, isConfigSnapshotsEnabled, retentionCleanup
|
|
|
28
28
|
import { appendBootLog, selfDiagnose } from './config/boot-log.js';
|
|
29
29
|
import { CONFIG_SCHEMA_VERSION } from './types.js';
|
|
30
30
|
import { SessionManager } from './core/session/session-manager.js';
|
|
31
|
+
import { SessionIdleMonitor } from './core/session/session-idle-monitor.js';
|
|
31
32
|
import { ClaudeAgentPlugin } from './agents/claude-runner.js';
|
|
32
33
|
import { CodexAgentPlugin } from './agents/codex-runner.js';
|
|
33
34
|
import { GeminiAgentPlugin } from './agents/gemini-runner.js';
|
|
@@ -44,9 +45,10 @@ import { WecomChannelPlugin, registerPendingWecomContactBind } from './channels/
|
|
|
44
45
|
import { buildEnvelope, isDeliveryTargetForChannel, replyContextFromSession } from './core/message/message-utils.js';
|
|
45
46
|
import { ResponseEngine } from './core/message/response-engine.js';
|
|
46
47
|
import { MessageQueue } from './core/message/message-queue.js';
|
|
48
|
+
import { pendingInterruptHints } from './core/message/pending-hints.js';
|
|
47
49
|
import { MessageBridge } from './core/message/message-bridge.js';
|
|
48
50
|
import { evaluateOutboundContactAdmission } from './core/message/message-admission.js';
|
|
49
|
-
import { enforceMenuResponseSize, evaluateMenuClientCompatibility, evolMenuResponseTransportMetadata, MenuRequestDeduper, hasValidMenuId, menuFailure, menuPayloadFingerprint, normalizeMenuResponseTiming, parseMenuControl, validateMenuRequest, withMenuProcessingTime, logMenuRequestCompleted, logMenuRequestReceived } from './core/command/menu-protocol.js';
|
|
51
|
+
import { enforceMenuResponseSize, evaluateMenuClientCompatibility, evolMenuResponseTransportMetadata, MenuRequestDeduper, hasValidMenuId, menuFailure, menuPayloadFingerprint, normalizeMenuResponseTiming, parseMenuControl, validateMenuRequest, withMenuProcessingTime, logMenuRequestCompleted, logMenuRequestReceived, MENU_RESPONSE_MAX_BYTES, MENU_RESPONSE_SAFETY_MARGIN_BYTES } from './core/command/menu-protocol.js';
|
|
50
52
|
import { computeBuildId, readBuildIdentity, readEvolcoreRuntimeFlavor, readEvolcoreStartedAtIso, readInstalledEvolcoreVersion, tagErrorTextWithEvolcoreVersion, } from './utils/evolcore-version.js';
|
|
51
53
|
import { recoverAllRoleMutationsSync } from './core/command/role-menu.js';
|
|
52
54
|
import { HandoffRuntime } from './core/handoff/runtime.js';
|
|
@@ -58,6 +60,17 @@ import { executeLocalInitializationCommand, AgentInitializationService, hasSuppl
|
|
|
58
60
|
import { loadAgent } from './config-store.js';
|
|
59
61
|
import { AgentApplicationService } from './core/agent-application-service.js';
|
|
60
62
|
import { EventBus } from './core/event-bus.js';
|
|
63
|
+
import { seedConsolidationCheckTrigger, removeConsolidationCheckTrigger } from './core/memory/consolidation-trigger.js';
|
|
64
|
+
import { migrateLegacyConsolidationBatches } from './core/memory/consolidation-context.js';
|
|
65
|
+
/** 由 checkIntervalHours 生成 cron 表达式(每小时检查的分钟位错开整点)。 */
|
|
66
|
+
function consolidationCheckCron(intervalHours) {
|
|
67
|
+
const hours = intervalHours && intervalHours > 0 ? Math.floor(intervalHours) : 1;
|
|
68
|
+
if (hours === 1)
|
|
69
|
+
return '17 * * * *';
|
|
70
|
+
if (hours >= 24)
|
|
71
|
+
return '17 3 * * *';
|
|
72
|
+
return `17 */${hours} * * *`;
|
|
73
|
+
}
|
|
61
74
|
import { eventPatternIncludesInternal, getEventCatalog } from './core/event-catalog.js';
|
|
62
75
|
import { StatsCollector } from './utils/stats.js';
|
|
63
76
|
import { AidStatsCollector } from './utils/stats.js';
|
|
@@ -68,8 +81,8 @@ import { AgentDelegationRegistry, authorizeDelegatedAunMsgSend, hasTrustedFullAc
|
|
|
68
81
|
import { authorizeOperation, buildAuthSubject, canApprovePersistentFullAccessTrigger, isCrossAgentTriggerOperationAllowed, } from './core/auth/auth-gateway.js';
|
|
69
82
|
import { authorizationDenialData, formatAuthorizationDenial } from './core/auth/authorization-denial.js';
|
|
70
83
|
import { auditFullAccessEvent } from './core/auth/authorization-audit.js';
|
|
71
|
-
import { parsePeerKey } from './core/relation/peer-identity.js';
|
|
72
|
-
import { isHClassPath, isSameOrDescendant } from './core/permission/protected-paths.js';
|
|
84
|
+
import { formatPeerKey, parsePeerKey } from './core/relation/peer-identity.js';
|
|
85
|
+
import { isHClassPath, isLClassPath, isSameOrDescendant } from './core/permission/protected-paths.js';
|
|
73
86
|
import { ChannelLoader, tryParseChannelKey } from './core/channel-loader.js';
|
|
74
87
|
import { AgentLoader } from './core/baseagent-loader.js';
|
|
75
88
|
import { EvolAgentRegistry } from './core/evolagent-registry.js';
|
|
@@ -101,6 +114,7 @@ import { validateModelSelectionForRole } from './core/model/model-permission.js'
|
|
|
101
114
|
import { constrainRuntimePermissionMode, validateRuntimeStringFieldOverride, } from './core/role/runtime-policy.js';
|
|
102
115
|
import { atomicWriteJson } from './core/session/session-fs-store.js';
|
|
103
116
|
import { DAEMON_RUNTIME_EPOCH_ENV, ensureProcessManagedTempDir } from './cli/task-context.js';
|
|
117
|
+
import { isLinkedInstall } from './utils/npm-ops.js';
|
|
104
118
|
import { appendMessageLog, buildOutboundEntry, classifyAunPayloadForLog } from './core/message/message-log.js';
|
|
105
119
|
import { normalizeAunMentionEntries } from './aun/msg/mention-schema.js';
|
|
106
120
|
import { sanitizeMenuLogValue } from './core/command/menu-protocol.js';
|
|
@@ -318,6 +332,44 @@ function originFromActorSession(session, authenticatedPeerId, authenticatedChann
|
|
|
318
332
|
delivery: { chatType: 'private' },
|
|
319
333
|
};
|
|
320
334
|
}
|
|
335
|
+
/** 整理会话未调用 `ec memory feedback` 时的默认反馈模板。 */
|
|
336
|
+
function renderDefaultConsolidationFeedback(input) {
|
|
337
|
+
const triggerLabel = {
|
|
338
|
+
manual: '手动触发',
|
|
339
|
+
compress: '上下文压缩',
|
|
340
|
+
'session-new': '新会话',
|
|
341
|
+
'periodic-trigger': '定时检查',
|
|
342
|
+
};
|
|
343
|
+
const mode = input.mode ?? 'notes';
|
|
344
|
+
const cause = triggerLabel[input.triggeredBy] ?? input.triggeredBy;
|
|
345
|
+
if (!input.success) {
|
|
346
|
+
return [
|
|
347
|
+
'## 记忆整理失败',
|
|
348
|
+
'',
|
|
349
|
+
`**模式**:${mode}`,
|
|
350
|
+
`**触发**:${cause}`,
|
|
351
|
+
'',
|
|
352
|
+
'本次整理未完成,已回退未归档的改动。详情见整理批次:',
|
|
353
|
+
`\`${input.batchDir}\``,
|
|
354
|
+
].join('\n');
|
|
355
|
+
}
|
|
356
|
+
const { created, updated, archived, errors } = input.summary;
|
|
357
|
+
const lines = [
|
|
358
|
+
'## 记忆整理完成',
|
|
359
|
+
'',
|
|
360
|
+
`**模式**:${mode} `,
|
|
361
|
+
`**触发**:${cause}`,
|
|
362
|
+
'',
|
|
363
|
+
'### 处理结果',
|
|
364
|
+
`- 新建记忆:${created} 条`,
|
|
365
|
+
`- 更新记忆:${updated} 条`,
|
|
366
|
+
`- 归档记忆:${archived} 条`,
|
|
367
|
+
];
|
|
368
|
+
if (errors > 0)
|
|
369
|
+
lines.push(`- 错误:${errors} 条`);
|
|
370
|
+
lines.push('', `整理批次:\`${input.batchDir}\``);
|
|
371
|
+
return lines.join('\n');
|
|
372
|
+
}
|
|
321
373
|
function seedUpgradeCheckTrigger(manager, owner) {
|
|
322
374
|
const now = Date.now();
|
|
323
375
|
const scriptName = 'upgrade-check.cjs';
|
|
@@ -442,6 +494,52 @@ function seedTaskRecoveryTrigger(manager, owner) {
|
|
|
442
494
|
atomicWriteJson(manager.definitionPath(definition.id), definition);
|
|
443
495
|
fs.rmSync(manager.activePath(definition.id), { force: true });
|
|
444
496
|
}
|
|
497
|
+
const SESSION_IDLE_CHECK_PROMPT = `本会话已闲置一段时间。请依据你的职责执行一次例行巡检:
|
|
498
|
+
1. 判断群内是否存在成员有未完成的工作却停滞(如长时间无进展、被点名未回应、承诺后失联)。有,则向该成员催促一次并说明停滞事项,随后即止;无,则不做任何动作。大家都在正常工作或无待办时保持沉默。
|
|
499
|
+
2. 巡检优先基于已有上下文判断,确需补充背景再查询历史,并遵守会话自身的发送规范。
|
|
500
|
+
3. 判定无需任何对外动作时,本轮只输出 [[NOOP]],不发送任何消息、不发布巡检结论。注意:最终对外输出必须是且仅是 [[NOOP]] 这一行——不要在输出它之前先发送任何说明文字或巡检判定,判定过程仅在内部完成。
|
|
501
|
+
(本指令为通用模板,可按具体职责增删检查项。)`;
|
|
502
|
+
function seedSessionIdleCheckTrigger(manager, owner) {
|
|
503
|
+
const now = Date.now();
|
|
504
|
+
const definition = normalizeTriggerDefinition({
|
|
505
|
+
$schema_version: 3.1,
|
|
506
|
+
id: '__session-idle-check',
|
|
507
|
+
agentAid: owner.aid,
|
|
508
|
+
enabled: true,
|
|
509
|
+
name: '__session-idle-check',
|
|
510
|
+
description: 'System trigger: inspect configured idle group sessions.',
|
|
511
|
+
createdAt: now,
|
|
512
|
+
updatedAt: now,
|
|
513
|
+
origin: {
|
|
514
|
+
channelKey: 'daemon',
|
|
515
|
+
channelType: 'daemon',
|
|
516
|
+
channelId: owner.originPeerId || owner.aid,
|
|
517
|
+
session: 'main',
|
|
518
|
+
peerId: owner.originPeerId || owner.aid,
|
|
519
|
+
sessionKey: `daemon#${owner.originPeerId || owner.aid}#__session-idle-check__`,
|
|
520
|
+
},
|
|
521
|
+
source: {
|
|
522
|
+
type: 'event',
|
|
523
|
+
eventPattern: 'session:idle',
|
|
524
|
+
filter: { match: { chatType: 'group' } },
|
|
525
|
+
},
|
|
526
|
+
execution: {
|
|
527
|
+
type: 'target_session',
|
|
528
|
+
prompt: SESSION_IDLE_CHECK_PROMPT,
|
|
529
|
+
onError: 'retry',
|
|
530
|
+
noopSentinel: '[[NOOP]]',
|
|
531
|
+
},
|
|
532
|
+
feedback: { strategy: 'event_session' },
|
|
533
|
+
reliability: {
|
|
534
|
+
concurrency: 'forbid',
|
|
535
|
+
missedPolicy: 'run_once',
|
|
536
|
+
retry: { maxAttempts: 2, backoffMs: 30_000 },
|
|
537
|
+
},
|
|
538
|
+
});
|
|
539
|
+
fs.mkdirSync(manager.triggerDir(definition.id), { recursive: true });
|
|
540
|
+
atomicWriteJson(manager.definitionPath(definition.id), definition);
|
|
541
|
+
fs.rmSync(manager.activePath(definition.id), { force: true });
|
|
542
|
+
}
|
|
445
543
|
function removeLegacyAgentUpgradeCheck(manager) {
|
|
446
544
|
let existing;
|
|
447
545
|
try {
|
|
@@ -874,7 +972,16 @@ async function main() {
|
|
|
874
972
|
if (relationSharedMigration.copied > 0) {
|
|
875
973
|
logger.info(`[migrate] Synced ${relationSharedMigration.copied} relation shared file(s) across ${relationSharedMigration.relations} relation(s)`);
|
|
876
974
|
}
|
|
975
|
+
migrateLegacyConsolidationBatches();
|
|
877
976
|
// daemon.json 的旧 ecweb/serviceProxy 结构已在启动前归一化为 services[]。
|
|
977
|
+
// ── Skill 监控器启动(采用懒加载机制)──
|
|
978
|
+
try {
|
|
979
|
+
const { startSkillWatcher } = await import('./core/skill/startup.js');
|
|
980
|
+
startSkillWatcher();
|
|
981
|
+
}
|
|
982
|
+
catch (e) {
|
|
983
|
+
logger.warn(`[skill] 监控器启动失败: ${e instanceof Error ? e.message : String(e)}`);
|
|
984
|
+
}
|
|
878
985
|
// ── 配置体系初始化(schema 字段不相交硬约束校验)──
|
|
879
986
|
try {
|
|
880
987
|
initConfigManager();
|
|
@@ -1139,20 +1246,185 @@ async function main() {
|
|
|
1139
1246
|
logger.info(`[PermissionGateway] Cancelled ${cancelled} stale management approval(s) after ${target} config write: agent=${selector.self}`);
|
|
1140
1247
|
}
|
|
1141
1248
|
});
|
|
1249
|
+
// 整理会话结束(成功 / 失败 / 中断):回撤、processing/ 归位、定稿 rebuild、forget 后置检查、反馈
|
|
1250
|
+
// releaseSlot / feedbackSender 由后面的块赋值(避免暂时性死区)。
|
|
1251
|
+
let releaseConsolidationSlotRef = () => { };
|
|
1252
|
+
let sendConsolidationFeedbackRef = async () => { };
|
|
1253
|
+
const finishConsolidationSession = async (sessionId, success) => {
|
|
1254
|
+
let releaseAid;
|
|
1255
|
+
try {
|
|
1256
|
+
if (!sessionId)
|
|
1257
|
+
return;
|
|
1258
|
+
const session = await sessionManager.getSessionById(sessionId);
|
|
1259
|
+
if (!session)
|
|
1260
|
+
return;
|
|
1261
|
+
const meta = (session.metadata ?? {});
|
|
1262
|
+
const batchDir = (meta.consolidationBatchDir ?? meta.consolidationLogBase);
|
|
1263
|
+
if (!batchDir)
|
|
1264
|
+
return; // 不是整理会话
|
|
1265
|
+
releaseAid = session.selfAID;
|
|
1266
|
+
const { onConsolidationComplete, rollbackConsolidation } = await import('./core/memory/launch-consolidation.js');
|
|
1267
|
+
const { appendOpsRecord, summarizeOpsRecords, collectAffectedScopes } = await import('./core/memory/consolidation-ops-records.js');
|
|
1268
|
+
// ① 失败时先回撤写操作,再做 processing/ 归位
|
|
1269
|
+
if (!success) {
|
|
1270
|
+
await rollbackConsolidation(batchDir, session.selfAID);
|
|
1271
|
+
}
|
|
1272
|
+
// ② processing/ 归位(成功→archive/notes,失败→pending)+ 写 checkpoint
|
|
1273
|
+
const completeResult = await onConsolidationComplete({
|
|
1274
|
+
success,
|
|
1275
|
+
batchDir,
|
|
1276
|
+
chatDir: meta.consolidationChatDir,
|
|
1277
|
+
memoryDir: meta.consolidationMemoryDir,
|
|
1278
|
+
});
|
|
1279
|
+
// ③ 收尾定稿 rebuild:对 ops.jsonl 里出现的所有 scope 各 rebuild 一次
|
|
1280
|
+
// 无论成败都做,确保索引和磁盘文件一致(失败回撤后索引也跟着还原)
|
|
1281
|
+
try {
|
|
1282
|
+
const affectedScopes = collectAffectedScopes(batchDir); // Map<scope, memoryDir>
|
|
1283
|
+
if (affectedScopes.size > 0) {
|
|
1284
|
+
const { rebuildIndex } = await import('./core/memory/index-manager.js');
|
|
1285
|
+
const selfAid = session.selfAID;
|
|
1286
|
+
const mc = selfAid ? agentRegistry?.get(selfAid)?.config?.memory : undefined;
|
|
1287
|
+
const maxBytes = mc?.indexMaxBytes ?? 5 * 1024;
|
|
1288
|
+
for (const [scope, memoryDir] of affectedScopes) {
|
|
1289
|
+
const consolidatedDir = path.join(memoryDir, 'consolidated');
|
|
1290
|
+
const indexPath = path.join(memoryDir, 'MEMORY.md');
|
|
1291
|
+
const invalidDir = path.join(memoryDir, 'archive', 'invalid');
|
|
1292
|
+
try {
|
|
1293
|
+
await rebuildIndex(consolidatedDir, indexPath, scope, invalidDir, undefined, maxBytes);
|
|
1294
|
+
}
|
|
1295
|
+
catch { /* 单个 scope rebuild 失败不影响其他 scope */ }
|
|
1296
|
+
}
|
|
1297
|
+
}
|
|
1298
|
+
}
|
|
1299
|
+
catch { /* rebuild 失败下次整理时补上 */ }
|
|
1300
|
+
// ④ 写 session-end 日志(落笔,记录最终状态)
|
|
1301
|
+
appendOpsRecord(batchDir, {
|
|
1302
|
+
ts: new Date().toISOString(),
|
|
1303
|
+
op: 'session-end',
|
|
1304
|
+
success,
|
|
1305
|
+
movedNotes: completeResult.moved,
|
|
1306
|
+
checkpointWritten: completeResult.checkpointWritten,
|
|
1307
|
+
});
|
|
1308
|
+
// ⑤ forget 后置检查:成功且有产出且不是 forget 模式本身
|
|
1309
|
+
if (success) {
|
|
1310
|
+
const memoryMode = meta.memoryMode;
|
|
1311
|
+
if (memoryMode && memoryMode !== 'forget') {
|
|
1312
|
+
const opsSummary = summarizeOpsRecords(batchDir);
|
|
1313
|
+
const hasOutput = completeResult.moved > 0 || opsSummary.created > 0 || opsSummary.updated > 0;
|
|
1314
|
+
if (hasOutput) {
|
|
1315
|
+
try {
|
|
1316
|
+
const { collectAffectedScopes: getScopes } = await import('./core/memory/consolidation-ops-records.js');
|
|
1317
|
+
const { collectCandidates, isDueForReview, deferCandidate } = await import('./core/memory/staleness.js');
|
|
1318
|
+
const selfAid = session.selfAID;
|
|
1319
|
+
if (selfAid) {
|
|
1320
|
+
const mc = agentRegistry?.get(selfAid)?.config?.memory;
|
|
1321
|
+
const thresholds = { forgetAfterDays: mc?.forgetAfterDays ?? 3 };
|
|
1322
|
+
const now = Date.now();
|
|
1323
|
+
const affectedScopes = getScopes(batchDir);
|
|
1324
|
+
for (const [scope, memoryDir] of affectedScopes) {
|
|
1325
|
+
const consolidatedDir = path.join(memoryDir, 'consolidated');
|
|
1326
|
+
let due = 0;
|
|
1327
|
+
try {
|
|
1328
|
+
for (const info of collectCandidates(consolidatedDir, thresholds, now)) {
|
|
1329
|
+
if (isDueForReview(info, thresholds, now))
|
|
1330
|
+
due++;
|
|
1331
|
+
else {
|
|
1332
|
+
try {
|
|
1333
|
+
deferCandidate(info.filePath, now);
|
|
1334
|
+
}
|
|
1335
|
+
catch { /* ignore */ }
|
|
1336
|
+
}
|
|
1337
|
+
}
|
|
1338
|
+
}
|
|
1339
|
+
catch {
|
|
1340
|
+
continue;
|
|
1341
|
+
}
|
|
1342
|
+
if (due > 0) {
|
|
1343
|
+
const ownerAid = mc ? agentRegistry?.get(selfAid)?.config?.owners?.[0] : undefined;
|
|
1344
|
+
// 构造最小 taskCtx,定时触发不需要 sessionId/peerKey
|
|
1345
|
+
const taskCtx = { selfAid, peerKey: meta.peerKey };
|
|
1346
|
+
void enqueueConsolidation({
|
|
1347
|
+
taskCtx,
|
|
1348
|
+
mode: 'forget',
|
|
1349
|
+
triggeredBy: 'periodic-trigger',
|
|
1350
|
+
ownerAid,
|
|
1351
|
+
forgetAfterDays: mc?.forgetAfterDays,
|
|
1352
|
+
scopeOverride: scope,
|
|
1353
|
+
}).catch(() => { });
|
|
1354
|
+
}
|
|
1355
|
+
}
|
|
1356
|
+
}
|
|
1357
|
+
}
|
|
1358
|
+
catch { /* forget 后置检查失败不影响主流程 */ }
|
|
1359
|
+
}
|
|
1360
|
+
}
|
|
1361
|
+
}
|
|
1362
|
+
// ⑥ 会话历史导出 → <batchDir>/history.jsonl
|
|
1363
|
+
const agentSessionId = session.agentSessionId;
|
|
1364
|
+
const projectPath = session.projectPath;
|
|
1365
|
+
const baseagent = session.baseagent ?? 'claude';
|
|
1366
|
+
if (agentSessionId && projectPath) {
|
|
1367
|
+
const fs = await import('fs');
|
|
1368
|
+
const historyPath = path.join(batchDir, 'history.jsonl');
|
|
1369
|
+
const runner = agentMap.get(`${session.selfAID}::${baseagent}`);
|
|
1370
|
+
if (runner?.getSessionMessages) {
|
|
1371
|
+
try {
|
|
1372
|
+
const messages = await runner.getSessionMessages(agentSessionId, projectPath);
|
|
1373
|
+
fs.default.writeFileSync(historyPath, messages.map((m) => JSON.stringify(m)).join('\n') + '\n', 'utf-8');
|
|
1374
|
+
}
|
|
1375
|
+
catch { /* 导出失败不影响主流程 */ }
|
|
1376
|
+
}
|
|
1377
|
+
else {
|
|
1378
|
+
fs.default.writeFileSync(historyPath, JSON.stringify({ note: `runner ${baseagent} does not support getSessionMessages` }) + '\n', 'utf-8');
|
|
1379
|
+
}
|
|
1380
|
+
}
|
|
1381
|
+
// ⑦ 反馈:非定时触发时,把整理结果投递回发起时的会话和话题
|
|
1382
|
+
const feedbackOrigin = meta.feedbackOrigin;
|
|
1383
|
+
const triggeredBy = meta.consolidationTriggeredBy;
|
|
1384
|
+
if (feedbackOrigin && triggeredBy && triggeredBy !== 'periodic-trigger' && session.selfAID) {
|
|
1385
|
+
try {
|
|
1386
|
+
const { readFeedbackText, summarizeOpsRecords } = await import('./core/memory/consolidation-ops-records.js');
|
|
1387
|
+
const customText = readFeedbackText(batchDir);
|
|
1388
|
+
const text = customText ?? renderDefaultConsolidationFeedback({
|
|
1389
|
+
mode: meta.memoryMode,
|
|
1390
|
+
triggeredBy,
|
|
1391
|
+
success,
|
|
1392
|
+
summary: summarizeOpsRecords(batchDir),
|
|
1393
|
+
batchDir,
|
|
1394
|
+
});
|
|
1395
|
+
await sendConsolidationFeedbackRef(feedbackOrigin, text, session.selfAID);
|
|
1396
|
+
}
|
|
1397
|
+
catch (e) {
|
|
1398
|
+
logger.warn(`[Memory] 整理反馈投递失败(session=${sessionId}): ${e?.message}`);
|
|
1399
|
+
}
|
|
1400
|
+
}
|
|
1401
|
+
}
|
|
1402
|
+
catch (e) {
|
|
1403
|
+
logger.warn(`[Memory] 整理收尾失败(session=${sessionId}): ${e?.message}`);
|
|
1404
|
+
}
|
|
1405
|
+
finally {
|
|
1406
|
+
// 释放该 agent 的并发位并推进队列(幂等:不在活跃集合里也无害)
|
|
1407
|
+
if (releaseAid)
|
|
1408
|
+
releaseConsolidationSlotRef(releaseAid);
|
|
1409
|
+
}
|
|
1410
|
+
};
|
|
1142
1411
|
eventBus.subscribe('task:interrupted', (event) => {
|
|
1143
1412
|
if ('sessionId' in event && event.sessionId) {
|
|
1144
1413
|
permissionGateway.cancelAll(event.sessionId, event.reason || 'interrupted');
|
|
1145
1414
|
}
|
|
1415
|
+
void finishConsolidationSession(event.sessionId, false);
|
|
1146
1416
|
});
|
|
1147
1417
|
eventBus.subscribe('task:error', (event) => {
|
|
1148
1418
|
if ('sessionId' in event && event.sessionId) {
|
|
1149
1419
|
permissionGateway.cancelAll(event.sessionId, 'task_error', false);
|
|
1150
1420
|
}
|
|
1421
|
+
void finishConsolidationSession(event.sessionId, false);
|
|
1151
1422
|
});
|
|
1152
1423
|
eventBus.subscribe('task:completed', (event) => {
|
|
1153
1424
|
if ('sessionId' in event && event.sessionId) {
|
|
1154
1425
|
permissionGateway.cancelAll(event.sessionId, 'task_completed', false);
|
|
1155
1426
|
}
|
|
1427
|
+
void finishConsolidationSession(event.sessionId, true);
|
|
1156
1428
|
});
|
|
1157
1429
|
// 交互路由器
|
|
1158
1430
|
const interactionRouter = new InteractionRouter();
|
|
@@ -1270,6 +1542,16 @@ async function main() {
|
|
|
1270
1542
|
}, primaryRunnerKey);
|
|
1271
1543
|
responseEngine.setAgentDelegationRegistry(agentDelegationRegistry);
|
|
1272
1544
|
const processor = responseEngine;
|
|
1545
|
+
responseEngine.setObserverHintConsumed((hint, target, meta) => {
|
|
1546
|
+
const observerChannel = channelInstances.find(candidate => candidate.adapter.channelKey === target.channelKey
|
|
1547
|
+
&& candidate.channelType === 'aun')?.channel;
|
|
1548
|
+
observerChannel?.notifyObserverHintConsumed(hint, target, meta);
|
|
1549
|
+
});
|
|
1550
|
+
responseEngine.setObserverHintFailed((hint, target, failure) => {
|
|
1551
|
+
const observerChannel = channelInstances.find(candidate => candidate.adapter.channelKey === target.channelKey
|
|
1552
|
+
&& candidate.channelType === 'aun')?.channel;
|
|
1553
|
+
observerChannel?.notifyObserverHintFailed(hint, target, failure);
|
|
1554
|
+
});
|
|
1273
1555
|
// 回填 processor 和 messageQueue 的引用
|
|
1274
1556
|
cmdHandler.setProcessor(processor);
|
|
1275
1557
|
// Inject EvolAgentRegistry (methods added by T6/T7)
|
|
@@ -1295,13 +1577,13 @@ async function main() {
|
|
|
1295
1577
|
dedupPath: daemonMessageDedupPath(),
|
|
1296
1578
|
});
|
|
1297
1579
|
// 设置中断回调(精确中断正在处理的 agent)
|
|
1298
|
-
messageQueue.setInterruptCallback(async (sessionKey, baseagent, evolagentName, reason = 'new_message', timeout) => {
|
|
1580
|
+
messageQueue.setInterruptCallback(async (sessionKey, baseagent, evolagentName, reason = 'new_message', timeout, strict) => {
|
|
1299
1581
|
void baseagent;
|
|
1300
1582
|
void evolagentName;
|
|
1301
1583
|
if (timeout)
|
|
1302
1584
|
await responseEngine.timeoutSession(sessionKey, timeout);
|
|
1303
1585
|
else
|
|
1304
|
-
await responseEngine.interruptSession(sessionKey, reason);
|
|
1586
|
+
await responseEngine.interruptSession(sessionKey, reason, strict);
|
|
1305
1587
|
});
|
|
1306
1588
|
messageQueue.setEventBus(eventBus);
|
|
1307
1589
|
// Restore persisted input before Trigger schedulers start. Trigger-owned work
|
|
@@ -1369,6 +1651,37 @@ async function main() {
|
|
|
1369
1651
|
cmdHandler.setHandoffRuntime(handoffRuntime);
|
|
1370
1652
|
// Trigger runtime: daemon-level script + feedback scheduler.
|
|
1371
1653
|
const triggerSchedulers = new Map();
|
|
1654
|
+
const sessionIdleMonitor = new SessionIdleMonitor({
|
|
1655
|
+
eventBus,
|
|
1656
|
+
statePath: path.join(paths.dataDir, 'session-idle-state.json'),
|
|
1657
|
+
getSessionById: sessionId => sessionManager.getSessionById(sessionId),
|
|
1658
|
+
isSessionBusy: sessionId => messageQueue.isProcessing(sessionId) || messageQueue.getQueueLength(sessionId) > 0,
|
|
1659
|
+
getSessionIdleTriggerIds: () => [...triggerSchedulers.values()].flatMap(scheduler => scheduler.list({ all: true })
|
|
1660
|
+
.filter(definition => definition.source.type === 'event' && definition.source.eventPattern === 'session:idle')
|
|
1661
|
+
.map(definition => definition.id)),
|
|
1662
|
+
getSessionIdleSeconds: (session) => {
|
|
1663
|
+
if (!session.selfAID || !session.channelId)
|
|
1664
|
+
return undefined;
|
|
1665
|
+
try {
|
|
1666
|
+
const relation = readConfig(ConfigTarget.Relation, {
|
|
1667
|
+
self: session.selfAID,
|
|
1668
|
+
peerKey: formatPeerKey(session.channelType || session.channel, session.channelId),
|
|
1669
|
+
}, { cache: true, expand: true });
|
|
1670
|
+
return relation?.sessionIdleSec;
|
|
1671
|
+
}
|
|
1672
|
+
catch (error) {
|
|
1673
|
+
logger.warn(`[SessionIdleMonitor] Failed to read relation config for ${session.id}: ${error instanceof Error ? error.message : String(error)}`);
|
|
1674
|
+
return undefined;
|
|
1675
|
+
}
|
|
1676
|
+
},
|
|
1677
|
+
getSessionEncrypt: sessionId => sessionManager.getSessionEncrypt(sessionId),
|
|
1678
|
+
});
|
|
1679
|
+
sessionIdleMonitor.start();
|
|
1680
|
+
onConfigWrite(({ target, selector }) => {
|
|
1681
|
+
if (target === ConfigTarget.Relation && selector.self)
|
|
1682
|
+
sessionIdleMonitor.refresh();
|
|
1683
|
+
});
|
|
1684
|
+
onShutdown(() => sessionIdleMonitor.stop());
|
|
1372
1685
|
const targetSessionLocks = new TargetSessionLockRegistry();
|
|
1373
1686
|
const triggerAudit = new TriggerAuditLogger();
|
|
1374
1687
|
const triggerScriptExecutor = new TriggerScriptExecutor();
|
|
@@ -1376,6 +1689,81 @@ async function main() {
|
|
|
1376
1689
|
watchdogMs: daemonConversationWatchdogMs(globalSettings),
|
|
1377
1690
|
totalExecutionMs: taskExecutionTtlMs,
|
|
1378
1691
|
});
|
|
1692
|
+
// ── 记忆整理队列:同 agent 串行 + 全局并发上限 ─────────────────────────
|
|
1693
|
+
//
|
|
1694
|
+
// 整理会话不再经 trigger 系统创建,改为 launchConsolidation 直接创建会话。
|
|
1695
|
+
// 并发控制由这里接管(原来靠 trigger 的 reliability.concurrency='forbid'):
|
|
1696
|
+
// - 同一 agent 同时最多 1 个整理会话
|
|
1697
|
+
// - 全局同时最多 MAX_CONCURRENT_CONSOLIDATION(5) 个 agent 在整理
|
|
1698
|
+
// - 超限的请求排队,前面的结束后自动启动下一个
|
|
1699
|
+
const MAX_CONCURRENT_CONSOLIDATION = 5;
|
|
1700
|
+
const activeConsolidationAgents = new Set(); // 正在整理的 agentAid
|
|
1701
|
+
const consolidationQueues = new Map(); // agentAid → 待办
|
|
1702
|
+
const startConsolidation = async (params) => {
|
|
1703
|
+
const aid = params.taskCtx.selfAid;
|
|
1704
|
+
if (!aid)
|
|
1705
|
+
return null;
|
|
1706
|
+
activeConsolidationAgents.add(aid);
|
|
1707
|
+
try {
|
|
1708
|
+
const { launchConsolidation } = await import('./core/memory/launch-consolidation.js');
|
|
1709
|
+
return await launchConsolidation(daemonChannel, params);
|
|
1710
|
+
}
|
|
1711
|
+
catch (e) {
|
|
1712
|
+
logger.warn(`[Memory] 拉起整理会话失败(aid=${aid},mode=${params.mode}): ${e?.message}`);
|
|
1713
|
+
// 启动失败不进收尾事件,直接释放并发位并推进队列
|
|
1714
|
+
activeConsolidationAgents.delete(aid);
|
|
1715
|
+
void drainConsolidationQueue();
|
|
1716
|
+
return null;
|
|
1717
|
+
}
|
|
1718
|
+
};
|
|
1719
|
+
/** 有空位就从队列里取等待最久的请求启动。 */
|
|
1720
|
+
const drainConsolidationQueue = async () => {
|
|
1721
|
+
while (activeConsolidationAgents.size < MAX_CONCURRENT_CONSOLIDATION) {
|
|
1722
|
+
// 找第一个队列非空、且该 agent 当前不活跃的请求
|
|
1723
|
+
let picked;
|
|
1724
|
+
for (const [aid, queue] of consolidationQueues) {
|
|
1725
|
+
if (queue.length === 0)
|
|
1726
|
+
continue;
|
|
1727
|
+
if (activeConsolidationAgents.has(aid))
|
|
1728
|
+
continue;
|
|
1729
|
+
picked = { aid, params: queue.shift() };
|
|
1730
|
+
break;
|
|
1731
|
+
}
|
|
1732
|
+
if (!picked)
|
|
1733
|
+
break;
|
|
1734
|
+
if (consolidationQueues.get(picked.aid)?.length === 0)
|
|
1735
|
+
consolidationQueues.delete(picked.aid);
|
|
1736
|
+
await startConsolidation(picked.params);
|
|
1737
|
+
}
|
|
1738
|
+
};
|
|
1739
|
+
/**
|
|
1740
|
+
* 整理请求统一入口。四种触发方式(手动 / compact / renew·new / 定时)都走这里。
|
|
1741
|
+
* 该 agent 不活跃且全局有空位 → 立即启动并返回结果;否则入队并返回 null。
|
|
1742
|
+
*/
|
|
1743
|
+
const enqueueConsolidation = async (params) => {
|
|
1744
|
+
const aid = params.taskCtx.selfAid;
|
|
1745
|
+
if (!aid)
|
|
1746
|
+
return null;
|
|
1747
|
+
const agentBusy = activeConsolidationAgents.has(aid);
|
|
1748
|
+
const queue = consolidationQueues.get(aid) ?? [];
|
|
1749
|
+
const globalFull = activeConsolidationAgents.size >= MAX_CONCURRENT_CONSOLIDATION;
|
|
1750
|
+
if (!agentBusy && !globalFull && queue.length === 0) {
|
|
1751
|
+
return await startConsolidation(params);
|
|
1752
|
+
}
|
|
1753
|
+
queue.push(params);
|
|
1754
|
+
consolidationQueues.set(aid, queue);
|
|
1755
|
+
logger.info(`[Memory] 整理请求入队(aid=${aid},mode=${params.mode},` +
|
|
1756
|
+
`队列长度=${queue.length},全局活跃=${activeConsolidationAgents.size})`);
|
|
1757
|
+
return null;
|
|
1758
|
+
};
|
|
1759
|
+
/** 整理会话结束(成功/失败/中断)时调用:释放并发位,推进队列。 */
|
|
1760
|
+
const releaseConsolidationSlot = (aid) => {
|
|
1761
|
+
activeConsolidationAgents.delete(aid);
|
|
1762
|
+
void drainConsolidationQueue();
|
|
1763
|
+
};
|
|
1764
|
+
// 接到前面 finishConsolidationSession 的 holder(那个函数定义在本块之前)
|
|
1765
|
+
releaseConsolidationSlotRef = releaseConsolidationSlot;
|
|
1766
|
+
cmdHandler.setMemoryConsolidationLauncher(enqueueConsolidation);
|
|
1379
1767
|
const triggerControlToken = crypto.randomBytes(32).toString('base64url');
|
|
1380
1768
|
fs.writeFileSync(resolvePaths().controlToken, triggerControlToken, { mode: 0o600 });
|
|
1381
1769
|
try {
|
|
@@ -1456,6 +1844,43 @@ async function main() {
|
|
|
1456
1844
|
baseagent: agent.baseagent,
|
|
1457
1845
|
};
|
|
1458
1846
|
};
|
|
1847
|
+
// 整理会话反馈投递:用发起时抓取的渠道三元组直接调 adapter.send()。
|
|
1848
|
+
// 不查 sessionId——renew/`/new` 换了会话 ID 也不影响送达。
|
|
1849
|
+
const sendConsolidationFeedback = async (origin, text, selfAid) => {
|
|
1850
|
+
const { buildEnvelope } = await import('./core/message/message-utils.js');
|
|
1851
|
+
const channelKey = origin.channelKey;
|
|
1852
|
+
const channelId = origin.channelId;
|
|
1853
|
+
if (!channelKey || !channelId) {
|
|
1854
|
+
logger.warn(`[Memory] 反馈目标缺少 channelKey/channelId,跳过投递`);
|
|
1855
|
+
return;
|
|
1856
|
+
}
|
|
1857
|
+
const binding = getTriggerChannel(selfAid, channelKey);
|
|
1858
|
+
if (!binding) {
|
|
1859
|
+
logger.warn(`[Memory] 找不到渠道绑定(aid=${selfAid},channelKey=${channelKey}),跳过反馈`);
|
|
1860
|
+
return;
|
|
1861
|
+
}
|
|
1862
|
+
const threadId = origin.threadId;
|
|
1863
|
+
const chatType = origin.delivery?.chatType;
|
|
1864
|
+
const replyContext = {};
|
|
1865
|
+
if (threadId)
|
|
1866
|
+
replyContext.threadId = threadId;
|
|
1867
|
+
if (chatType)
|
|
1868
|
+
replyContext.delivery = { chatType };
|
|
1869
|
+
await binding.adapter.send(buildEnvelope({
|
|
1870
|
+
taskId: `memory-feedback-${Date.now()}`,
|
|
1871
|
+
channel: binding.adapter.channelKey,
|
|
1872
|
+
channelId,
|
|
1873
|
+
agentName: binding.agentName,
|
|
1874
|
+
replyContext: Object.keys(replyContext).length > 0 ? replyContext : undefined,
|
|
1875
|
+
}), {
|
|
1876
|
+
kind: 'result.text',
|
|
1877
|
+
text,
|
|
1878
|
+
isFinal: true,
|
|
1879
|
+
format: 'markdown',
|
|
1880
|
+
});
|
|
1881
|
+
logger.info(`[Memory] 整理反馈已投递(aid=${selfAid},channelId=${channelId})`);
|
|
1882
|
+
};
|
|
1883
|
+
sendConsolidationFeedbackRef = sendConsolidationFeedback;
|
|
1459
1884
|
const validateTriggerFeedbackChannels = (definition) => {
|
|
1460
1885
|
if (definition.feedback.strategy === 'silent' || definition.feedback.strategy === 'event_session')
|
|
1461
1886
|
return;
|
|
@@ -1496,8 +1921,6 @@ async function main() {
|
|
|
1496
1921
|
const hasDaemonTriggerAuthority = (subject) => (subject.isDaemonOwner || subject.processRole === 'daemon-service' || subject.dataScope === 'daemon');
|
|
1497
1922
|
const validateTriggerDefinitionForActor = (definition, actor) => {
|
|
1498
1923
|
if (definition.execution.permissionMode === 'fullaccess') {
|
|
1499
|
-
if (!isFullAccessEnabled())
|
|
1500
|
-
throw new Error('fullaccess is disabled by daemon.json');
|
|
1501
1924
|
if (!actor.daemonOwner && !actor.fullAccess) {
|
|
1502
1925
|
throw new Error('persistent fullaccess Trigger requires DaemonOwner or fullaccess-run');
|
|
1503
1926
|
}
|
|
@@ -1556,9 +1979,6 @@ async function main() {
|
|
|
1556
1979
|
}
|
|
1557
1980
|
};
|
|
1558
1981
|
const authorizeTriggerExecution = (definition) => {
|
|
1559
|
-
if (definition.execution.permissionMode === 'fullaccess' && !isFullAccessEnabled()) {
|
|
1560
|
-
return { allowed: false, reason: 'fullaccess is disabled by daemon.json' };
|
|
1561
|
-
}
|
|
1562
1982
|
if (definition.execution.permissionMode === 'fullaccess') {
|
|
1563
1983
|
if (!definition.authorizedBy) {
|
|
1564
1984
|
return { allowed: false, reason: 'fullaccess Trigger approval provenance is missing' };
|
|
@@ -1575,7 +1995,7 @@ async function main() {
|
|
|
1575
1995
|
// task. Its origin remains useful for audit and feedback routing, but a
|
|
1576
1996
|
// relation lookup on the origin actor would make a cross-Agent Trigger
|
|
1577
1997
|
// persist successfully and then fail on every fire after creation.
|
|
1578
|
-
// Approval provenance
|
|
1998
|
+
// Approval provenance remains mandatory; only
|
|
1579
1999
|
// the ordinary creator relation gate is bypassed for this mode.
|
|
1580
2000
|
if (definition.execution.permissionMode === 'fullaccess') {
|
|
1581
2001
|
return { allowed: true };
|
|
@@ -1763,10 +2183,22 @@ async function main() {
|
|
|
1763
2183
|
if (opts.seedUpgradeCheck) {
|
|
1764
2184
|
seedUpgradeCheckTrigger(manager, owner);
|
|
1765
2185
|
seedTaskRecoveryTrigger(manager, owner);
|
|
2186
|
+
seedSessionIdleCheckTrigger(manager, owner);
|
|
1766
2187
|
}
|
|
1767
2188
|
else {
|
|
1768
2189
|
removeLegacyAgentUpgradeCheck(manager);
|
|
1769
2190
|
}
|
|
2191
|
+
// 记忆整理定时检查(每个 agent 一个)。记忆关闭时不注册。
|
|
2192
|
+
const memConfig = agentRegistry.get(owner.aid)?.config?.memory;
|
|
2193
|
+
if (memConfig?.enableRelation !== false) {
|
|
2194
|
+
seedConsolidationCheckTrigger(manager, {
|
|
2195
|
+
aid: owner.aid,
|
|
2196
|
+
cronExpression: consolidationCheckCron(memConfig?.checkIntervalHours),
|
|
2197
|
+
});
|
|
2198
|
+
}
|
|
2199
|
+
else {
|
|
2200
|
+
removeConsolidationCheckTrigger(manager);
|
|
2201
|
+
}
|
|
1770
2202
|
const state = new TriggerRunStateStore(manager);
|
|
1771
2203
|
const agentTriggerAudit = triggerAudit.withHistory(manager.history);
|
|
1772
2204
|
const dispatcher = new TriggerFeedbackDispatcher({
|
|
@@ -2048,6 +2480,12 @@ async function main() {
|
|
|
2048
2480
|
};
|
|
2049
2481
|
});
|
|
2050
2482
|
}
|
|
2483
|
+
if (inst.channelType === 'aun') {
|
|
2484
|
+
inst.channel.setObserverHintRuntime({
|
|
2485
|
+
hasSession: target => responseEngine.hasObserverTargetSession(target),
|
|
2486
|
+
schedule: target => responseEngine.scheduleObserverHint(target),
|
|
2487
|
+
});
|
|
2488
|
+
}
|
|
2051
2489
|
// 5. 撤回消息 → 中断执行中任务
|
|
2052
2490
|
inst.channel.onRecall?.((messageId, selfAID) => {
|
|
2053
2491
|
msgBridge.cancel(messageId, selfAID);
|
|
@@ -2068,6 +2506,19 @@ async function main() {
|
|
|
2068
2506
|
agent.status = 'running';
|
|
2069
2507
|
}
|
|
2070
2508
|
}
|
|
2509
|
+
// 为所有已置为 running 的 agent 注册技能目录监控(懒加载机制)
|
|
2510
|
+
// 必须在 agent.status = 'running' 之后执行,否则全部还是 'stopped'
|
|
2511
|
+
try {
|
|
2512
|
+
const { watchAgentSkills } = await import('./core/skill/startup.js');
|
|
2513
|
+
for (const info of agentRegistry.list()) {
|
|
2514
|
+
if (info.status === 'running') {
|
|
2515
|
+
watchAgentSkills(info.aid);
|
|
2516
|
+
}
|
|
2517
|
+
}
|
|
2518
|
+
}
|
|
2519
|
+
catch (e) {
|
|
2520
|
+
logger.warn(`[skill] agent 监控注册失败: ${e instanceof Error ? e.message : String(e)}`);
|
|
2521
|
+
}
|
|
2071
2522
|
if (agentRegistry.list().some((info) => info.status === 'running')) {
|
|
2072
2523
|
agentRuntimeState = 'running';
|
|
2073
2524
|
agentRuntimeError = undefined;
|
|
@@ -2278,10 +2729,23 @@ async function main() {
|
|
|
2278
2729
|
})
|
|
2279
2730
|
.join(', ');
|
|
2280
2731
|
};
|
|
2732
|
+
// ChannelLoader deliberately does not await onConnected. Keep the initial
|
|
2733
|
+
// recovery promises so restored ordinary messages cannot start ahead of
|
|
2734
|
+
// pending priority hints for the same session.
|
|
2735
|
+
const startupObserverRecoveries = [];
|
|
2736
|
+
let restoringStartupQueue = true;
|
|
2737
|
+
const queuePendingObserverHints = (aid, channelKey) => pendingInterruptHints(paths.sessionsDir, aid)
|
|
2738
|
+
.filter(pending => pending.channelKey === channelKey)
|
|
2739
|
+
.map(pending => responseEngine.scheduleObserverHint({ ...pending, selfAid: aid })
|
|
2740
|
+
.then(result => {
|
|
2741
|
+
if (result === 'SESSION_NOT_FOUND')
|
|
2742
|
+
logger.warn(`[ObserverHint] recovery target missing hint=${pending.hintId}`);
|
|
2743
|
+
})
|
|
2744
|
+
.catch(error => logger.warn(`[ObserverHint] recovery failed hint=${pending.hintId}: ${error}`)));
|
|
2281
2745
|
const markChannelConnected = async (inst) => {
|
|
2282
2746
|
const name = inst.adapter.channelName;
|
|
2283
2747
|
const key = inst.adapter.channelKey;
|
|
2284
|
-
if (connectedChannels.has(
|
|
2748
|
+
if (connectedChannels.has(key))
|
|
2285
2749
|
return;
|
|
2286
2750
|
connectedChannels.add(name);
|
|
2287
2751
|
connectedChannels.add(key);
|
|
@@ -2294,6 +2758,11 @@ async function main() {
|
|
|
2294
2758
|
});
|
|
2295
2759
|
// Run async operations in parallel
|
|
2296
2760
|
const agent = agentRegistry.resolveByChannel(inst.adapter.channelKey) ?? agentRegistry.resolveByChannel(name);
|
|
2761
|
+
if (inst.channelType === 'aun' && agent) {
|
|
2762
|
+
const recoveries = queuePendingObserverHints(agent.aid, key);
|
|
2763
|
+
if (restoringStartupQueue)
|
|
2764
|
+
startupObserverRecoveries.push(...recoveries);
|
|
2765
|
+
}
|
|
2297
2766
|
await Promise.all([
|
|
2298
2767
|
bootstrapService.tryStartBootstrap({
|
|
2299
2768
|
adapter: inst.adapter,
|
|
@@ -2441,6 +2910,7 @@ async function main() {
|
|
|
2441
2910
|
logger.warn(`[startup] trigger scheduler start failed for ${agent.aid}: ${e}`);
|
|
2442
2911
|
})),
|
|
2443
2912
|
]);
|
|
2913
|
+
sessionIdleMonitor.activate();
|
|
2444
2914
|
// ── 控制 AID(daemon 进程身份):pureIdentity 接入 AUN,独立于 evolagent ──
|
|
2445
2915
|
// 证书缺失检测/生成在 CLI 侧(ec start)完成。daemon 是后台进程无终端,
|
|
2446
2916
|
// 这里只做兜底:证书缺失时 warn 并继续(AUNChannel 内部后台重连),绝不阻塞。
|
|
@@ -2500,11 +2970,11 @@ async function main() {
|
|
|
2500
2970
|
parsed = null;
|
|
2501
2971
|
}
|
|
2502
2972
|
const menuResponseContext = () => ({
|
|
2503
|
-
...controlReplyContext(opts, evolMenuResponseTransportMetadata(opts.protectedHeaders?.sdk_version)),
|
|
2973
|
+
...controlReplyContext(opts, evolMenuResponseTransportMetadata(opts.protectedHeaders?.sdk_version, opts.protectedHeaders?.menu_volatile_v1)),
|
|
2504
2974
|
...(opts.peerDeviceId ? { toDeviceId: opts.peerDeviceId } : {}),
|
|
2505
2975
|
});
|
|
2506
2976
|
const sendControlMenuResponse = async (response) => {
|
|
2507
|
-
const timedResponse = withMenuProcessingTime(enforceMenuResponseSize(response,
|
|
2977
|
+
const timedResponse = withMenuProcessingTime(enforceMenuResponseSize(response, MENU_RESPONSE_MAX_BYTES - MENU_RESPONSE_SAFETY_MARGIN_BYTES), menuTiming);
|
|
2508
2978
|
const request = activeMenuRequest ?? {
|
|
2509
2979
|
type: 'menu.unknown',
|
|
2510
2980
|
id: response.id,
|
|
@@ -2715,6 +3185,19 @@ async function main() {
|
|
|
2715
3185
|
}
|
|
2716
3186
|
const recoverHandoffsAndStartRestored = async () => {
|
|
2717
3187
|
try {
|
|
3188
|
+
// Recovery must not require the gateway to connect: pending interrupt
|
|
3189
|
+
// hints are local durable work, while ordinary restored input may run
|
|
3190
|
+
// even when its channel is offline. Scheduling twice is deduplicated by
|
|
3191
|
+
// the ResponseEngine's per-session hint key.
|
|
3192
|
+
for (const inst of channelInstances) {
|
|
3193
|
+
if (inst.channelType !== 'aun')
|
|
3194
|
+
continue;
|
|
3195
|
+
const agent = agentRegistry.resolveByChannel(inst.adapter.channelKey);
|
|
3196
|
+
if (agent && recoveryAids.includes(agent.aid)) {
|
|
3197
|
+
startupObserverRecoveries.push(...queuePendingObserverHints(agent.aid, inst.adapter.channelKey));
|
|
3198
|
+
}
|
|
3199
|
+
}
|
|
3200
|
+
await Promise.all(startupObserverRecoveries);
|
|
2718
3201
|
const outcomes = await Promise.allSettled(recoveryAids.map(aid => handoffRuntime.recover([aid])));
|
|
2719
3202
|
outcomes.forEach((outcome, index) => {
|
|
2720
3203
|
if (outcome.status === 'rejected') {
|
|
@@ -2723,6 +3206,8 @@ async function main() {
|
|
|
2723
3206
|
});
|
|
2724
3207
|
}
|
|
2725
3208
|
finally {
|
|
3209
|
+
restoringStartupQueue = false;
|
|
3210
|
+
startupObserverRecoveries.length = 0;
|
|
2726
3211
|
messageQueue.startRestored();
|
|
2727
3212
|
}
|
|
2728
3213
|
};
|
|
@@ -2976,6 +3461,22 @@ async function main() {
|
|
|
2976
3461
|
aidStatsCollector.recordOutbound(params.aid, params.toPeer, Buffer.byteLength(params.text || '', 'utf-8'), params.text, false, params.encrypt, params.chatmode, 'send');
|
|
2977
3462
|
});
|
|
2978
3463
|
ipcServer.setTaskRuntimeContextProvider(({ sessionId }) => responseEngine.getTaskRuntimeContext(sessionId));
|
|
3464
|
+
// `ec memory ... --debug <sessionId>`:仅开发仓安装从会话文件重建上下文,正式安装不注册
|
|
3465
|
+
if (isLinkedInstall()) {
|
|
3466
|
+
ipcServer.setDebugTaskRuntimeContextProvider(async ({ sessionId }) => {
|
|
3467
|
+
const session = await sessionManager.getSessionById(sessionId);
|
|
3468
|
+
if (!session)
|
|
3469
|
+
return null;
|
|
3470
|
+
const { buildDebugTaskContext } = await import('./core/memory/launch-consolidation.js');
|
|
3471
|
+
const { ensureSessionRuntimeDir } = await import('./cli/task-context.js');
|
|
3472
|
+
return buildDebugTaskContext({
|
|
3473
|
+
session,
|
|
3474
|
+
getTrigger: (agentAid, triggerId) => new TriggerDefinitionManager(agentAid).get(triggerId),
|
|
3475
|
+
sessionRuntimeDir: ensureSessionRuntimeDir(session.id),
|
|
3476
|
+
});
|
|
3477
|
+
});
|
|
3478
|
+
logger.info('[Memory] 开发仓安装:已启用 ec memory --debug <sessionId>');
|
|
3479
|
+
}
|
|
2979
3480
|
ipcServer.setTaskRuntimeContextAuthorizer(({ sessionId, delegationToken, delegationCommandHash }) => {
|
|
2980
3481
|
// Codex persistent threads carry a command-scoped authorization. Looking
|
|
2981
3482
|
// up fresh routing context must not consume the subsequent CLI operation.
|
|
@@ -3440,6 +3941,119 @@ async function main() {
|
|
|
3440
3941
|
},
|
|
3441
3942
|
});
|
|
3442
3943
|
ipcServer.setAgentReloadExecutor((aid, options) => reloadCoordinator.reload(aid, options));
|
|
3944
|
+
// ── 记忆整理定时检查(v4):候选检查在 daemon 侧,定时由 cron script trigger 驱动 ──
|
|
3945
|
+
//
|
|
3946
|
+
// 脚本(consolidation-trigger.ts 生成)调 `memory.check-consolidation`,
|
|
3947
|
+
// 本 executor 跑候选检查并把命中的候选经 enqueueConsolidation 入队。
|
|
3948
|
+
ipcServer.setConsolidationCheckExecutor(async ({ selfAid }) => {
|
|
3949
|
+
const { checkConsolidationCandidates, shouldUseMetaIndex } = await import('./core/memory/consolidation-trigger.js');
|
|
3950
|
+
const agentConfig = agentRegistry.get(selfAid)?.config;
|
|
3951
|
+
if (agentConfig?.memory?.enableRelation === false)
|
|
3952
|
+
return { launched: [] };
|
|
3953
|
+
const ownerAid = agentConfig?.owners?.[0];
|
|
3954
|
+
// 规模决定扫描路径:agent 数 × 本 agent 关系数超过阈值才走 _meta.json 快路径。
|
|
3955
|
+
const agentCount = agentRegistry.runnableAgents().length;
|
|
3956
|
+
const relationsDir = path.join(agentDir(selfAid), 'relations');
|
|
3957
|
+
let relationCount = 0;
|
|
3958
|
+
try {
|
|
3959
|
+
relationCount = fs.existsSync(relationsDir)
|
|
3960
|
+
? fs.readdirSync(relationsDir, { withFileTypes: true }).filter(d => d.isDirectory()).length
|
|
3961
|
+
: 0;
|
|
3962
|
+
}
|
|
3963
|
+
catch { /* ignore */ }
|
|
3964
|
+
const useMetaIndex = shouldUseMetaIndex(agentCount, relationCount);
|
|
3965
|
+
const candidates = checkConsolidationCandidates({
|
|
3966
|
+
selfAid,
|
|
3967
|
+
ownerAid,
|
|
3968
|
+
config: agentConfig?.memory,
|
|
3969
|
+
useMetaIndex,
|
|
3970
|
+
});
|
|
3971
|
+
const launched = [];
|
|
3972
|
+
for (const candidate of candidates) {
|
|
3973
|
+
// 定时没有会话上下文,用 agent 级最小上下文(selfAid + peerKey)
|
|
3974
|
+
const taskCtx = {
|
|
3975
|
+
selfAid,
|
|
3976
|
+
peerKey: candidate.peerKey,
|
|
3977
|
+
baseagent: undefined,
|
|
3978
|
+
sessionId: undefined,
|
|
3979
|
+
};
|
|
3980
|
+
try {
|
|
3981
|
+
await enqueueConsolidation({
|
|
3982
|
+
taskCtx,
|
|
3983
|
+
mode: candidate.mode,
|
|
3984
|
+
triggeredBy: 'periodic-trigger',
|
|
3985
|
+
ownerAid,
|
|
3986
|
+
forgetAfterDays: agentConfig?.memory?.forgetAfterDays,
|
|
3987
|
+
...(candidate.scope ? { scopeOverride: candidate.scope } : {}),
|
|
3988
|
+
});
|
|
3989
|
+
launched.push({ mode: candidate.mode, ...(candidate.peerKey ? { peerKey: candidate.peerKey } : {}) });
|
|
3990
|
+
}
|
|
3991
|
+
catch (e) {
|
|
3992
|
+
logger.warn(`[Memory] 定时检查入队失败(aid=${selfAid},mode=${candidate.mode}): ${e?.message}`);
|
|
3993
|
+
}
|
|
3994
|
+
}
|
|
3995
|
+
if (launched.length > 0) {
|
|
3996
|
+
logger.info(`[Memory] 定时检查拉起 ${launched.length} 个整理会话(aid=${selfAid}): ` +
|
|
3997
|
+
launched.map(l => `${l.mode}${l.peerKey ? `(${l.peerKey})` : ''}`).join(', '));
|
|
3998
|
+
}
|
|
3999
|
+
return { launched };
|
|
4000
|
+
});
|
|
4001
|
+
// ── T17:compact / session-new 节点自动触发 relation 整理 ────────────────
|
|
4002
|
+
// 走统一的 enqueueConsolidation(notes 模式),与手动 consolidate 同一条队列。
|
|
4003
|
+
const triggerRelationConsolidation = async (taskCtx, triggeredBy) => {
|
|
4004
|
+
try {
|
|
4005
|
+
if (!taskCtx?.selfAid || !taskCtx?.peerKey)
|
|
4006
|
+
return;
|
|
4007
|
+
if (taskCtx.sessionType === 'memory')
|
|
4008
|
+
return;
|
|
4009
|
+
const mc = agentRegistry?.get(taskCtx.selfAid)?.config?.memory;
|
|
4010
|
+
if (mc?.enableRelation === false)
|
|
4011
|
+
return;
|
|
4012
|
+
const ownerAid = agentRegistry?.get(taskCtx.selfAid)?.config?.owners?.[0];
|
|
4013
|
+
await enqueueConsolidation({
|
|
4014
|
+
taskCtx,
|
|
4015
|
+
mode: 'notes',
|
|
4016
|
+
triggeredBy,
|
|
4017
|
+
ownerAid,
|
|
4018
|
+
});
|
|
4019
|
+
}
|
|
4020
|
+
catch (e) {
|
|
4021
|
+
logger.warn(`[MemoryT17] relation 整理触发失败(${triggeredBy}): ${e?.message}`);
|
|
4022
|
+
}
|
|
4023
|
+
};
|
|
4024
|
+
eventBus.subscribe('runner:compact-start', (event) => {
|
|
4025
|
+
const { sessionId } = event;
|
|
4026
|
+
if (!sessionId)
|
|
4027
|
+
return;
|
|
4028
|
+
const taskCtx = responseEngine.getTaskRuntimeContext(sessionId);
|
|
4029
|
+
void triggerRelationConsolidation(taskCtx, 'compress');
|
|
4030
|
+
});
|
|
4031
|
+
eventBus.subscribe('session:created', (event) => {
|
|
4032
|
+
const ev = event;
|
|
4033
|
+
if (!ev.sourceSessionId)
|
|
4034
|
+
return;
|
|
4035
|
+
// 优先用事件快照构造 taskCtx(不依赖内存 map,renew/`/new` 后旧会话任务可能已结束);
|
|
4036
|
+
// 快照缺失时回退到内存 map(compact 等路径仍在活动任务内)。
|
|
4037
|
+
let snapshot;
|
|
4038
|
+
if (ev.sourceChannel && ev.sourceChannelId && ev.sourceSelfAid) {
|
|
4039
|
+
// peerKey:群聊用群 ID(channelId),私聊用对端 AID(peerId)。
|
|
4040
|
+
// 与 response-engine 的构造保持一致(见 formatPeerKey 用法)。
|
|
4041
|
+
const peerKeyId = ev.sourceChatType === 'group' ? ev.sourceChannelId : (ev.sourcePeerId ?? ev.sourceChannelId);
|
|
4042
|
+
const peerKey = peerKeyId ? `${ev.sourceChannel}#${encodeURIComponent(peerKeyId)}` : undefined;
|
|
4043
|
+
snapshot = {
|
|
4044
|
+
sessionId: ev.sourceSessionId,
|
|
4045
|
+
selfAid: ev.sourceSelfAid,
|
|
4046
|
+
peerKey,
|
|
4047
|
+
channel: ev.sourceChannel,
|
|
4048
|
+
channelId: ev.sourceChannelId,
|
|
4049
|
+
peerId: ev.sourcePeerId ?? ev.sourceChannelId,
|
|
4050
|
+
chatType: ev.sourceChatType,
|
|
4051
|
+
threadId: ev.sourceThreadId,
|
|
4052
|
+
};
|
|
4053
|
+
}
|
|
4054
|
+
const taskCtx = snapshot ?? responseEngine.getTaskRuntimeContext(ev.sourceSessionId);
|
|
4055
|
+
void triggerRelationConsolidation(taskCtx, 'session-new');
|
|
4056
|
+
});
|
|
3443
4057
|
// Make reload hooks accessible to IPC handler & ctl handler (both run in this process)
|
|
3444
4058
|
globalThis.__evolcore_reloadHooks = reloadHooks;
|
|
3445
4059
|
// Hot-load handler: dynamically add a new agent at runtime
|
|
@@ -4035,6 +4649,22 @@ async function main() {
|
|
|
4035
4649
|
if (!argv.includes('--owner') && !argv.includes('--owners'))
|
|
4036
4650
|
return fail('INITIALIZE_OWNER_MISMATCH', 'Explicit Owner required');
|
|
4037
4651
|
const plan = resolveInitializationPlan(argv.slice(1));
|
|
4652
|
+
if (plan.avatarFile?.startsWith('attachment:')) {
|
|
4653
|
+
const { readRelationAttachment } = await import('./utils/media-cache.js');
|
|
4654
|
+
plan.avatarFile = readRelationAttachment(taskAid, delegation.grant.peerKey, plan.avatarFile.slice('attachment:'.length)).attachment.filePath;
|
|
4655
|
+
}
|
|
4656
|
+
else if (plan.avatarFile) {
|
|
4657
|
+
const session = await sessionManager.getSessionById(sessionId);
|
|
4658
|
+
const candidate = path.resolve(plan.avatarFile);
|
|
4659
|
+
const roots = [session?.projectPath, delegation.grant.sessionRuntimeDir]
|
|
4660
|
+
.filter((root) => !!root && path.isAbsolute(root))
|
|
4661
|
+
.map(root => fs.realpathSync(root));
|
|
4662
|
+
const real = fs.realpathSync(candidate);
|
|
4663
|
+
if (!roots.some(root => isSameOrDescendant(real, root)) || isHClassPath(real) || isLClassPath(real)) {
|
|
4664
|
+
return fail('AVATAR_INVALID', '--avatar-file must be a current project/TMPDIR file or authorized relation attachment');
|
|
4665
|
+
}
|
|
4666
|
+
plan.avatarFile = real;
|
|
4667
|
+
}
|
|
4038
4668
|
if (!plan.owners.includes(actor))
|
|
4039
4669
|
return fail('INITIALIZE_OWNER_MISMATCH', 'The authenticated requesting Owner must be retained');
|
|
4040
4670
|
return { ok: true, result: agentInitializationService.start(plan, actor, parsed.dryRun) };
|
|
@@ -4072,6 +4702,8 @@ async function main() {
|
|
|
4072
4702
|
description: value('--description'),
|
|
4073
4703
|
type: value('--type'),
|
|
4074
4704
|
tags,
|
|
4705
|
+
avatarFile: value('--avatar-file'),
|
|
4706
|
+
avatarUrl: value('--avatar-url'),
|
|
4075
4707
|
force: argv.includes('--force'),
|
|
4076
4708
|
});
|
|
4077
4709
|
const validationError = validateAgentCreatePlan(plan);
|
|
@@ -4131,7 +4763,33 @@ async function main() {
|
|
|
4131
4763
|
type: plan.type,
|
|
4132
4764
|
tags: plan.tags,
|
|
4133
4765
|
force: argv.includes('--force'),
|
|
4766
|
+
avatarFile: value('--avatar-file'),
|
|
4767
|
+
avatarUrl: value('--avatar-url'),
|
|
4134
4768
|
};
|
|
4769
|
+
if (createOptions.avatarFile) {
|
|
4770
|
+
try {
|
|
4771
|
+
if (createOptions.avatarFile.startsWith('attachment:')) {
|
|
4772
|
+
const { readRelationAttachment } = await import('./utils/media-cache.js');
|
|
4773
|
+
const source = readRelationAttachment(taskAid, delegation.grant.peerKey, createOptions.avatarFile.slice('attachment:'.length));
|
|
4774
|
+
createOptions.avatarFile = source.attachment.filePath;
|
|
4775
|
+
}
|
|
4776
|
+
else {
|
|
4777
|
+
const session = await sessionManager.getSessionById(sessionId);
|
|
4778
|
+
const candidate = path.resolve(createOptions.avatarFile);
|
|
4779
|
+
const roots = [session?.projectPath, delegation.grant.sessionRuntimeDir]
|
|
4780
|
+
.filter((root) => !!root && path.isAbsolute(root))
|
|
4781
|
+
.map(root => fs.realpathSync(root));
|
|
4782
|
+
const real = fs.realpathSync(candidate);
|
|
4783
|
+
if (!roots.some(root => isSameOrDescendant(real, root)) || isHClassPath(real) || isLClassPath(real)) {
|
|
4784
|
+
return fail('AVATAR_INVALID', '--avatar-file must be a current project/TMPDIR file or authorized relation attachment');
|
|
4785
|
+
}
|
|
4786
|
+
createOptions.avatarFile = real;
|
|
4787
|
+
}
|
|
4788
|
+
}
|
|
4789
|
+
catch (error) {
|
|
4790
|
+
return fail('AVATAR_INVALID', error instanceof Error ? error.message : String(error));
|
|
4791
|
+
}
|
|
4792
|
+
}
|
|
4135
4793
|
try {
|
|
4136
4794
|
const result = agentApplicationService
|
|
4137
4795
|
? await agentApplicationService.create(createOptions)
|
|
@@ -4247,6 +4905,36 @@ async function main() {
|
|
|
4247
4905
|
return { ok: false, code: 'INVALID_LOCAL_FILE', error: error instanceof Error ? error.message : String(error) };
|
|
4248
4906
|
}
|
|
4249
4907
|
});
|
|
4908
|
+
ipcServer.setConsolidationLaunchExecutor(async (params) => {
|
|
4909
|
+
const selfAid = params.taskCtx.selfAid;
|
|
4910
|
+
if (!selfAid)
|
|
4911
|
+
throw new Error('无法确定当前 agent AID(selfAid 为空)');
|
|
4912
|
+
// 手动触发也走统一队列:不忙时立即启动并拿回结果,忙时入队。
|
|
4913
|
+
const launched = await enqueueConsolidation(params);
|
|
4914
|
+
if (!launched) {
|
|
4915
|
+
// 已入队:返回一个占位结果,告知调用方稍后执行
|
|
4916
|
+
return { triggerId: '', contextFile: '', contextFilePath: '', logFile: '', batchDir: '', queued: true };
|
|
4917
|
+
}
|
|
4918
|
+
// 写 session-start 记录
|
|
4919
|
+
{
|
|
4920
|
+
const { appendOpsRecord } = await import('./core/memory/consolidation-ops-records.js');
|
|
4921
|
+
appendOpsRecord(launched.batchDir, {
|
|
4922
|
+
ts: new Date().toISOString(),
|
|
4923
|
+
op: 'session-start',
|
|
4924
|
+
sessionId: launched.triggerId,
|
|
4925
|
+
mode: params.mode,
|
|
4926
|
+
triggeredBy: params.triggeredBy,
|
|
4927
|
+
});
|
|
4928
|
+
}
|
|
4929
|
+
// 手动触发:等 runner 分配 agentSessionId 后再返回(超时返回已拿到的部分)。
|
|
4930
|
+
// 会话刚创建时只有 executionSessionId,agentSessionId 要等 runner 初始化后才有。
|
|
4931
|
+
const { waitForConsolidationSession } = await import('./core/memory/launch-consolidation.js');
|
|
4932
|
+
const ids = await waitForConsolidationSession({
|
|
4933
|
+
executionSessionId: launched.executionSessionId,
|
|
4934
|
+
readAgentSessionId: async (id) => (await sessionManager.getSessionById(id))?.agentSessionId,
|
|
4935
|
+
});
|
|
4936
|
+
return { ...launched, ...ids };
|
|
4937
|
+
});
|
|
4250
4938
|
ipcServer.setTriggerExecutor(async (cmd) => {
|
|
4251
4939
|
const schedulerFor = (agentAid) => {
|
|
4252
4940
|
const scheduler = triggerSchedulers.get(agentAid);
|
|
@@ -4516,8 +5204,6 @@ async function main() {
|
|
|
4516
5204
|
throw new Error(`trigger not found: ${cmd.triggerId}`);
|
|
4517
5205
|
if (cmd.enabled && existing.execution.permissionMode === 'fullaccess') {
|
|
4518
5206
|
rejectUnapprovedFullAccessConfiguration(existing, actor);
|
|
4519
|
-
if (!isFullAccessEnabled())
|
|
4520
|
-
throw new Error('fullaccess is disabled by daemon.json');
|
|
4521
5207
|
if (!actor.daemonOwner && !actor.fullAccess) {
|
|
4522
5208
|
throw new Error('enabling a fullaccess Trigger requires a DaemonOwner or fullaccess-run');
|
|
4523
5209
|
}
|
|
@@ -4652,6 +5338,10 @@ async function main() {
|
|
|
4652
5338
|
// 断开插件系统的渠道
|
|
4653
5339
|
removeGroupInviteConfigListener();
|
|
4654
5340
|
await shutdownStep('stop group invitation review', () => groupInviteReviewService.dispose());
|
|
5341
|
+
await shutdownStep('stop global skill watcher', async () => {
|
|
5342
|
+
const { stopGlobalSkillWatcher } = await import('./core/skill/global-watcher.js');
|
|
5343
|
+
await stopGlobalSkillWatcher();
|
|
5344
|
+
});
|
|
4655
5345
|
await shutdownStep('disconnect channels', () => channelLoader.disconnectAll(channelInstances));
|
|
4656
5346
|
for (const inst of channelInstances) {
|
|
4657
5347
|
const type = inst.channelType || inst.adapter.channelName;
|