evolcore 0.0.10 → 0.0.12
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 +44 -0
- package/README.md +3 -3
- package/bin/codex-managed-hook.mjs +80 -0
- package/dist/agents/baseagent.js +4 -0
- package/dist/agents/claude-runner.js +158 -44
- package/dist/agents/codex-app-server-client.js +153 -23
- package/dist/agents/codex-runner.js +331 -43
- package/dist/agents/ecagent-runner.js +53 -6
- package/dist/agents/gemini-runner.js +1 -0
- package/dist/agents/request-identity.js +55 -0
- package/dist/aun/aid/client.js +11 -21
- package/dist/aun/aid/managed-operation.js +107 -0
- package/dist/aun/msg/group.js +81 -12
- package/dist/aun/msg/managed-operation.js +804 -0
- package/dist/aun/msg/mention-schema.js +20 -0
- package/dist/aun/msg/p2p.js +7 -0
- package/dist/aun/outbox.js +28 -31
- package/dist/channels/aun.js +418 -235
- package/dist/channels/daemon.js +13 -0
- package/dist/cli/agent-command.js +72 -17
- package/dist/cli/agent.js +249 -22
- package/dist/cli/aun-commands.js +270 -69
- package/dist/cli/bench.js +12 -3
- package/dist/cli/daemon-commands.js +52 -33
- package/dist/cli/fs-command.js +60 -0
- package/dist/cli/handoff-command.js +3 -0
- package/dist/cli/index.js +106 -63
- package/dist/cli/init-cancel.js +208 -0
- package/dist/cli/init-channel.js +343 -195
- package/dist/cli/init.js +21 -9
- package/dist/cli/managed-command-guard.js +41 -0
- package/dist/cli/model.js +11 -4
- package/dist/cli/queue-command.js +11 -1
- package/dist/cli/response.js +71 -0
- package/dist/cli/restart-monitor.js +4 -18
- package/dist/cli/trigger-command.js +4 -0
- package/dist/config/builtin-roles.js +5 -0
- package/dist/config/contact-book-store.js +11 -3
- package/dist/config/contact-request-service.js +161 -15
- package/dist/config/gateway-config.js +26 -10
- package/dist/config/mention-mode.js +8 -24
- package/dist/core/agent-reload-coordinator.js +53 -0
- package/dist/core/auth/agent-delegation.js +0 -1
- package/dist/core/auth/operation-authorizer.js +62 -154
- package/dist/core/auth/operation-catalog.js +264 -22
- package/dist/core/bootstrap-messages.js +68 -0
- package/dist/core/bootstrap-service.js +144 -10
- package/dist/core/capability/providers/claude-capability-provider.js +9 -31
- package/dist/core/capability/providers/codex-capability-provider.js +4 -15
- package/dist/core/capability/skill-discovery.js +55 -0
- package/dist/core/channel-loader.js +24 -7
- package/dist/core/command/agent-control.js +14 -11
- package/dist/core/command/cli-intent-parser.js +9 -0
- package/dist/core/command/command-handler.js +467 -6
- package/dist/core/command/connect-menu.js +15 -0
- package/dist/core/command/group-menu.js +4 -4
- package/dist/core/command/menu-catalog.js +366 -0
- package/dist/core/command/menu-handler.js +213 -110
- package/dist/core/command/menu-protocol.js +71 -0
- package/dist/core/command/slash-gate.js +5 -2
- package/dist/core/command/slash-handler.js +45 -25
- package/dist/core/daemon-file-cache.js +2 -1
- package/dist/core/data-migration.js +9 -2
- package/dist/core/evolagent-registry.js +125 -35
- package/dist/core/evolagent.js +12 -5
- package/dist/core/handoff/dispatcher.js +5 -2
- package/dist/core/handoff/runtime.js +145 -21
- package/dist/core/handoff/store.js +24 -0
- package/dist/core/handoff/types.js +1 -0
- package/dist/core/inference/text-inference.js +38 -4
- package/dist/core/message/logical-queue-bridge.js +1 -0
- package/dist/core/message/mention-schema.js +126 -0
- package/dist/core/message/message-bridge.js +237 -79
- package/dist/core/message/message-log.js +23 -0
- package/dist/core/message/message-queue.js +20 -4
- package/dist/core/message/response-engine.js +54 -36
- package/dist/core/model/model-catalog.js +143 -24
- package/dist/core/model/model-diagnostics.js +28 -10
- package/dist/core/permission/ec-command-parser.js +156 -10
- package/dist/core/permission/index.js +1 -0
- package/dist/core/permission/readonly-shell-query.js +532 -0
- package/dist/core/permission/sandbox-runtime.js +22 -1
- package/dist/core/permission/shell-environment.js +46 -0
- package/dist/core/permission/tool-policy.js +292 -111
- package/dist/core/protected-paths.js +17 -9
- package/dist/core/runner-reload-transaction.js +57 -0
- package/dist/core/session/session-manager.js +21 -2
- package/dist/eck/manifest-engine.js +39 -13
- package/dist/index.js +644 -180
- package/dist/ipc.js +246 -19
- package/dist/response-system/coordinator.js +6 -4
- package/dist/response-system/engines/v1/proactive-flow.js +26 -8
- package/dist/response-system/modes/single-session/index.js +14 -1
- package/dist/response-system/selector.js +6 -5
- package/dist/trigger/anomaly-store.js +4 -0
- package/dist/trigger/script-executor.js +1 -0
- package/dist/utils/aid-bind.js +3 -8
- package/dist/utils/codex-app-server-registry.js +90 -0
- package/dist/utils/codex-cli.js +5 -1
- package/dist/utils/cross-platform.js +15 -0
- package/dist/utils/log-writer.js +6 -10
- package/dist/utils/logger.js +5 -5
- package/dist/utils/npm-ops.js +5 -12
- package/dist/utils/tool-summary.js +11 -0
- package/kits/docs/channels/aun.md +1 -1
- package/kits/docs/context-assembly.md +2 -2
- package/kits/docs/evolcore/agent.md +11 -6
- package/kits/docs/evolcore/aid.md +14 -0
- package/kits/docs/evolcore/msg.md +13 -0
- package/kits/docs/prompt-loading-architecture.md +1 -2
- package/kits/docs/venues/group.md +1 -1
- package/kits/eck_manifest.json +0 -12
- package/kits/rules/01-overview.md +9 -0
- package/kits/rules/04-relation.md +1 -1
- package/kits/rules/05-venue.md +2 -2
- package/kits/schemas/agent-config.schema.3.json +1 -1
- package/kits/schemas/agent-config.schema.4.json +1 -1
- package/kits/schemas/relation-config.schema.2.json +1 -1
- package/kits/schemas/role-config.schema.1.json +1 -1
- package/kits/schemas/single-session.schema.1.json +3 -3
- package/kits/schemas/single-session.schema.2.json +3 -3
- package/kits/templates/roles/admin.json +27 -0
- package/kits/templates/roles/member.json +44 -0
- package/kits/templates/roles/visitor.json +40 -2
- package/kits/templates/system-fragments/bootstrap.md +12 -6
- package/kits/templates/system-fragments/channel.md +6 -0
- package/kits/templates/system-fragments/session.md +2 -0
- package/package.json +3 -2
- package/skills/eclink/SKILL.md +15 -3
- package/skills/eclink/agents/openai.yaml +3 -3
- package/dist/core/command/evol-menu-version-gate.js +0 -39
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import { createHash } from 'crypto';
|
|
2
|
+
import { loadDaemonConfig } from '../../config-store.js';
|
|
3
|
+
import { readInstalledEvolcoreVersion } from '../../utils/evolcore-version.js';
|
|
4
|
+
import { compareStableSemver, parseStableSemver } from '../../utils/stable-semver.js';
|
|
2
5
|
export const MENU_REQUEST_TYPES = new Set([
|
|
3
6
|
'menu.token.request',
|
|
4
7
|
'menu.list',
|
|
@@ -7,6 +10,72 @@ export const MENU_REQUEST_TYPES = new Set([
|
|
|
7
10
|
'menu.update',
|
|
8
11
|
'menu.action',
|
|
9
12
|
]);
|
|
13
|
+
export const MENU_NAME_COMMANDS = Object.freeze({
|
|
14
|
+
pwd: '/pwd',
|
|
15
|
+
session: '/session',
|
|
16
|
+
topic: '/topic',
|
|
17
|
+
baseagent: '/baseagent',
|
|
18
|
+
model: '/model',
|
|
19
|
+
effort: '/effort',
|
|
20
|
+
chatmode: '/chatmode',
|
|
21
|
+
mentionmode: '/mentionmode',
|
|
22
|
+
group: '/group',
|
|
23
|
+
permission: '/perm',
|
|
24
|
+
activity: '/activity',
|
|
25
|
+
dispatch: '/dispatch',
|
|
26
|
+
observable: '/observable',
|
|
27
|
+
system: '/system',
|
|
28
|
+
cli: '/cli',
|
|
29
|
+
agent: '/agent',
|
|
30
|
+
trigger: '/trigger',
|
|
31
|
+
file: '/file',
|
|
32
|
+
gateway: '/gateway',
|
|
33
|
+
config: '/config',
|
|
34
|
+
capability: '/capability',
|
|
35
|
+
role: '/role',
|
|
36
|
+
connect: '/connect',
|
|
37
|
+
});
|
|
38
|
+
export function menuCommandForName(name) {
|
|
39
|
+
if (typeof name !== 'string')
|
|
40
|
+
return undefined;
|
|
41
|
+
return MENU_NAME_COMMANDS[name];
|
|
42
|
+
}
|
|
43
|
+
export function evaluateEvolMenuVersionGate(input) {
|
|
44
|
+
const minimum = loadDaemonConfig().aun?.minEvolVersion;
|
|
45
|
+
if (!minimum)
|
|
46
|
+
return null;
|
|
47
|
+
const minimumVersion = parseStableSemver(minimum);
|
|
48
|
+
if (!minimumVersion) {
|
|
49
|
+
throw new Error('daemon.json.aun.minEvolVersion must use stable X.Y.Z format');
|
|
50
|
+
}
|
|
51
|
+
const received = input.protectedHeaders?.evol_version;
|
|
52
|
+
const receivedVersion = input.encrypted ? parseStableSemver(received) : null;
|
|
53
|
+
if (!receivedVersion || compareStableSemver(receivedVersion, minimumVersion) < 0) {
|
|
54
|
+
return {
|
|
55
|
+
code: 'UPGRADE_REQUIRED',
|
|
56
|
+
message: `Evol ${minimum} or later is required`,
|
|
57
|
+
data: {
|
|
58
|
+
minimum,
|
|
59
|
+
received: receivedVersion ? received : null,
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
export function isEvolMenuVersionGateEnabled() {
|
|
66
|
+
return loadDaemonConfig().aun?.minEvolVersion !== undefined;
|
|
67
|
+
}
|
|
68
|
+
/** Token may be issued while disabled, but only this flag makes it an access requirement. */
|
|
69
|
+
export function isAunMenuTokenRequired() {
|
|
70
|
+
return loadDaemonConfig().aun?.menuTokenRequired === true;
|
|
71
|
+
}
|
|
72
|
+
export function evolMenuResponseTransportMetadata() {
|
|
73
|
+
return {
|
|
74
|
+
// Clear inherited request state so AUNChannel applies daemon aun.defaultEncrypt.
|
|
75
|
+
encrypted: undefined,
|
|
76
|
+
protectedHeaders: { ec_version: readInstalledEvolcoreVersion() },
|
|
77
|
+
};
|
|
78
|
+
}
|
|
10
79
|
const EXPLICIT_SCOPE_UPDATE_NAMES = new Set([
|
|
11
80
|
'model',
|
|
12
81
|
'effort',
|
|
@@ -155,6 +224,8 @@ const STABLE_CODES = new Set([
|
|
|
155
224
|
'METHOD_NOT_FOUND', 'NOT_FOUND', 'CONFLICT', 'EXPIRED', 'EXECUTION_TIMEOUT',
|
|
156
225
|
'TEMPORARILY_UNAVAILABLE', 'INTERNAL_ERROR', 'MISSING_SCOPE', 'UPGRADE_REQUIRED',
|
|
157
226
|
'MENU_TOKEN_REQUIRED', 'MENU_TOKEN_REJECTED', 'MENU_TOKEN_ENCRYPTION_REQUIRED',
|
|
227
|
+
'INVALID_CONTEXT', 'SCHEMA_VERSION_UNSUPPORTED', 'DEPENDENCY_UNAVAILABLE',
|
|
228
|
+
'RATE_LIMITED', 'CATALOG_STALE',
|
|
158
229
|
]);
|
|
159
230
|
export function normalizeMenuError(error) {
|
|
160
231
|
const source = error && typeof error === 'object' ? error : {};
|
|
@@ -114,7 +114,9 @@ export async function guardIdleCommand(opts) {
|
|
|
114
114
|
catch {
|
|
115
115
|
// Runner mismatch should not block recovery commands such as /baseagent.
|
|
116
116
|
}
|
|
117
|
-
const isBusy = hasActiveStream
|
|
117
|
+
const isBusy = hasActiveStream
|
|
118
|
+
|| opts.messageQueue?.isProcessing(threadSession.id)
|
|
119
|
+
|| (opts.messageQueue?.getQueueLength(threadSession.id) ?? 0) > 0;
|
|
118
120
|
if (isBusy) {
|
|
119
121
|
return { kind: 'command.error', text: '⚠️ 当前正在处理消息,请稍后再试\n使用 /stop 中断当前任务后重试' };
|
|
120
122
|
}
|
|
@@ -122,7 +124,8 @@ export async function guardIdleCommand(opts) {
|
|
|
122
124
|
}
|
|
123
125
|
else if (opts.activeSession) {
|
|
124
126
|
const isBusy = (opts.activeAgent?.hasActiveStream(opts.activeSession.id) ?? false) ||
|
|
125
|
-
opts.messageQueue?.isProcessing(opts.activeSession.id)
|
|
127
|
+
opts.messageQueue?.isProcessing(opts.activeSession.id) ||
|
|
128
|
+
(opts.messageQueue?.getQueueLength(opts.activeSession.id) ?? 0) > 0;
|
|
126
129
|
if (isBusy) {
|
|
127
130
|
return { kind: 'command.error', text: '⚠️ 当前正在处理消息,请稍后再试\n使用 /stop 中断当前任务后重试' };
|
|
128
131
|
}
|
|
@@ -21,9 +21,9 @@ import { filterModelsForRole, validateModelSelectionForRole } from '../model/mod
|
|
|
21
21
|
import { displaySessionTitle, isSyntheticCliPrompt } from '../session/session-title.js';
|
|
22
22
|
import { chatmodeFieldForPeer, resolveChatModeForField } from '../message/peer-mode.js';
|
|
23
23
|
import { normalizePermissionMode as normalizePermissionModeContract, PUBLIC_PERMISSION_MODES } from '../permission/mode.js';
|
|
24
|
-
import { dispatchToMentionMode } from '../../config/mention-mode.js';
|
|
25
24
|
import { isManagementRole } from '../../config/builtin-roles.js';
|
|
26
25
|
import { isSystemControlChannel } from '../system-channels.js';
|
|
26
|
+
import { spawnDetachedNode } from '../../utils/cross-platform.js';
|
|
27
27
|
import { guardIdleCommand, guardKnownCommand, guardRoleCommand, guardThreadCommand, isRecognizedSlashCommand, normalizeSlashContent, } from './slash-gate.js';
|
|
28
28
|
const allEfforts = ['low', 'medium', 'high', 'xhigh', 'max'];
|
|
29
29
|
const PERMISSION_MODE_KEYS = PUBLIC_PERMISSION_MODES;
|
|
@@ -259,7 +259,8 @@ async function getGitWorkingDirInfo(projectPath) {
|
|
|
259
259
|
cwd: projectPath,
|
|
260
260
|
encoding: 'utf8',
|
|
261
261
|
timeout: 1000,
|
|
262
|
-
stdio: ['ignore', 'pipe', 'ignore']
|
|
262
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
263
|
+
windowsHide: process.platform === 'win32',
|
|
263
264
|
}).trim();
|
|
264
265
|
if (isInsideWorkTree !== 'true')
|
|
265
266
|
return null;
|
|
@@ -268,14 +269,16 @@ async function getGitWorkingDirInfo(projectPath) {
|
|
|
268
269
|
cwd: projectPath,
|
|
269
270
|
encoding: 'utf8',
|
|
270
271
|
timeout: 1000,
|
|
271
|
-
stdio: ['ignore', 'pipe', 'ignore']
|
|
272
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
273
|
+
windowsHide: process.platform === 'win32',
|
|
272
274
|
}).trim();
|
|
273
275
|
if (!branch) {
|
|
274
276
|
branch = execFileSync('git', ['rev-parse', '--abbrev-ref', 'HEAD'], {
|
|
275
277
|
cwd: projectPath,
|
|
276
278
|
encoding: 'utf8',
|
|
277
279
|
timeout: 1000,
|
|
278
|
-
stdio: ['ignore', 'pipe', 'ignore']
|
|
280
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
281
|
+
windowsHide: process.platform === 'win32',
|
|
279
282
|
}).trim();
|
|
280
283
|
}
|
|
281
284
|
if (!branch)
|
|
@@ -285,7 +288,8 @@ async function getGitWorkingDirInfo(projectPath) {
|
|
|
285
288
|
cwd: projectPath,
|
|
286
289
|
encoding: 'utf8',
|
|
287
290
|
timeout: 1000,
|
|
288
|
-
stdio: ['ignore', 'pipe', 'ignore']
|
|
291
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
292
|
+
windowsHide: process.platform === 'win32',
|
|
289
293
|
});
|
|
290
294
|
// 解析文件状态统计
|
|
291
295
|
const stats = { modified: 0, added: 0, deleted: 0, untracked: 0 };
|
|
@@ -328,7 +332,13 @@ async function getGitWorkingDirInfo(projectPath) {
|
|
|
328
332
|
}
|
|
329
333
|
// 获取 ahead/behind 信息
|
|
330
334
|
try {
|
|
331
|
-
const revOutput = execFileSync('git', ['rev-list', '--left-right', '--count', '@{upstream}...HEAD'], {
|
|
335
|
+
const revOutput = execFileSync('git', ['rev-list', '--left-right', '--count', '@{upstream}...HEAD'], {
|
|
336
|
+
cwd: projectPath,
|
|
337
|
+
timeout: 1000,
|
|
338
|
+
encoding: 'utf8',
|
|
339
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
340
|
+
windowsHide: process.platform === 'win32',
|
|
341
|
+
});
|
|
332
342
|
const revParts = revOutput.trim().split(/\s+/);
|
|
333
343
|
if (revParts.length === 2) {
|
|
334
344
|
const behind = parseInt(revParts[0], 10) || 0;
|
|
@@ -1641,6 +1651,10 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
|
|
|
1641
1651
|
if (!isDaemonOwner && aidArg && aidArg !== selfAid) {
|
|
1642
1652
|
return { kind: 'command.error', text: '❌ 无权限:跨 agent reload 仅限 daemon owner 使用' };
|
|
1643
1653
|
}
|
|
1654
|
+
const targetAid = aidArg ?? selfAid;
|
|
1655
|
+
if (!targetAid) {
|
|
1656
|
+
return { kind: 'command.error', text: '❌ 无法确定目标 agent,请指定 aid:/reload <aid>' };
|
|
1657
|
+
}
|
|
1644
1658
|
const reloadScope = 'agent';
|
|
1645
1659
|
const authDenied = await authorizeIntent({
|
|
1646
1660
|
intent: {
|
|
@@ -1664,10 +1678,6 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
|
|
|
1664
1678
|
if (!isDaemonOwner && !isAdmin) {
|
|
1665
1679
|
return { kind: 'command.error', text: '❌ 无权限:/reload 仅限 daemon owner 或 agent owner/admin 使用' };
|
|
1666
1680
|
}
|
|
1667
|
-
const targetAid = aidArg ?? selfAid;
|
|
1668
|
-
if (!targetAid) {
|
|
1669
|
-
return { kind: 'command.error', text: '❌ 无法确定目标 agent,请指定 aid:/reload <aid>' };
|
|
1670
|
-
}
|
|
1671
1681
|
// 繁忙检查(同 menu /agent reload)
|
|
1672
1682
|
const busyInfo = getAgentBusyInfo(this, targetAid);
|
|
1673
1683
|
if (busyInfo && busyInfo.count > 0) {
|
|
@@ -1924,8 +1934,7 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
|
|
|
1924
1934
|
if ('error' in mentionTarget) {
|
|
1925
1935
|
return { kind: 'command.error', text: `❌ ${mentionTarget.error}` };
|
|
1926
1936
|
}
|
|
1927
|
-
|
|
1928
|
-
const mentionFallback = dispatchToMentionMode(mentionSession.metadata?.dispatchMode) ?? null;
|
|
1937
|
+
const mentionFallback = mentionSession.metadata?.mentionMode ?? null;
|
|
1929
1938
|
const currentMode = readSlashMentionMode(mentionTarget, mentionFallback);
|
|
1930
1939
|
if (!arg) {
|
|
1931
1940
|
const displayMode = currentMode ?? '未设置(跟随群设置)';
|
|
@@ -2121,20 +2130,19 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
|
|
|
2121
2130
|
selfAID,
|
|
2122
2131
|
role: sessionRole,
|
|
2123
2132
|
});
|
|
2124
|
-
|
|
2125
|
-
const mentionModeFallback = dispatchToMentionMode(session.metadata?.dispatchMode) ?? null;
|
|
2133
|
+
const mentionModeFallback = session.metadata?.mentionMode ?? null;
|
|
2126
2134
|
const mentionMode = 'error' in mentionModeTarget
|
|
2127
2135
|
? (mentionModeFallback ?? '未设置(跟随群设置)')
|
|
2128
2136
|
: (readSlashMentionMode(mentionModeTarget, mentionModeFallback) ?? '未设置(跟随群设置)');
|
|
2129
2137
|
const chatModeLine = `会话模式: ${chatMode}`;
|
|
2130
|
-
const
|
|
2138
|
+
const mentionModeLine = session.chatType === 'group' ? `@ 处理模式: ${mentionMode}` : null;
|
|
2131
2139
|
if (isAdmin) {
|
|
2132
2140
|
const gitInfo = await getGitWorkingDirInfo(session.projectPath);
|
|
2133
2141
|
lines.push(`📊 ${isThread ? '话题' : '会话'}状态 (Agent: ${agentName}):`, `渠道: ${this.resolveChannelType(channel)} / 项目: ${projectName} / 会话: ${displaySessionTitle(session.name, '(未命名)')}`, `会话ID: ${session.id}`, `项目路径: ${session.projectPath}`);
|
|
2134
2142
|
if (gitInfo) {
|
|
2135
2143
|
lines.push(`Git: ${gitInfo}`);
|
|
2136
2144
|
}
|
|
2137
|
-
lines.push(`会话状态: ${sessionStatus} / 轮数: ${sessionTurns}`, sessionRoleLine, chatModeLine, ...(
|
|
2145
|
+
lines.push(`会话状态: ${sessionStatus} / 轮数: ${sessionTurns}`, sessionRoleLine, chatModeLine, ...(mentionModeLine ? [mentionModeLine] : []));
|
|
2138
2146
|
if (health.consecutiveErrors > 0) {
|
|
2139
2147
|
lines.push(`异常计数: ${health.consecutiveErrors}`);
|
|
2140
2148
|
}
|
|
@@ -2142,7 +2150,7 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
|
|
|
2142
2150
|
}
|
|
2143
2151
|
else {
|
|
2144
2152
|
lines.push(`📊 ${isThread ? '话题' : '会话'}状态 (Agent: ${agentName}):`, `渠道: ${channel} / 项目: ${projectName} / ${session.baseagent}会话`);
|
|
2145
|
-
lines.push(`状态: ${sessionStatus} / 轮数: ${sessionTurns}`, sessionRoleLine, chatModeLine, ...(
|
|
2153
|
+
lines.push(`状态: ${sessionStatus} / 轮数: ${sessionTurns}`, sessionRoleLine, chatModeLine, ...(mentionModeLine ? [mentionModeLine] : []), `最后活跃: ${timeStr}`);
|
|
2146
2154
|
}
|
|
2147
2155
|
if (health.lastError) {
|
|
2148
2156
|
lines.push('');
|
|
@@ -2171,7 +2179,7 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
|
|
|
2171
2179
|
...(previousMetadata.channelKey ? { channelKey: previousMetadata.channelKey } : {}),
|
|
2172
2180
|
groupId: previousMetadata.groupId || channelId,
|
|
2173
2181
|
...(previousMetadata.groupName ? { groupName: previousMetadata.groupName } : {}),
|
|
2174
|
-
...(previousMetadata.
|
|
2182
|
+
...(previousMetadata.mentionMode ? { mentionMode: previousMetadata.mentionMode } : {}),
|
|
2175
2183
|
}
|
|
2176
2184
|
: {
|
|
2177
2185
|
...(previousMetadata.channelKey ? { channelKey: previousMetadata.channelKey } : {}),
|
|
@@ -2180,6 +2188,21 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
|
|
|
2180
2188
|
...(previousMetadata.peerType ? { peerType: previousMetadata.peerType } : {}),
|
|
2181
2189
|
};
|
|
2182
2190
|
const newSession = await this.sessionManager.createNewSession(channel, channelId, projectPath, sessionName, newSessionBaseagent, newSessionIdentityMetadata);
|
|
2191
|
+
const previousSession = session || activeSession;
|
|
2192
|
+
const handoffSelfAid = previousSession?.selfAID
|
|
2193
|
+
|| selfAID
|
|
2194
|
+
|| this.getOwningAgent(channel)?.aid
|
|
2195
|
+
|| this.resolveSelfAID(channel);
|
|
2196
|
+
let handoffDiscardWarning;
|
|
2197
|
+
if (previousSession?.id && handoffSelfAid && this.handoffRuntime) {
|
|
2198
|
+
try {
|
|
2199
|
+
await this.handoffRuntime.discardSession(handoffSelfAid, previousSession.id);
|
|
2200
|
+
}
|
|
2201
|
+
catch (error) {
|
|
2202
|
+
handoffDiscardWarning = '⚠️ 原会话 Handoff 清理失败,请检查 daemon 日志';
|
|
2203
|
+
logger.error(`[Handoff] failed to discard handoffs after /new: session=${previousSession.id}`, error);
|
|
2204
|
+
}
|
|
2205
|
+
}
|
|
2183
2206
|
const previousAgent = getActiveAgentIfAvailable();
|
|
2184
2207
|
if (session && previousAgent) {
|
|
2185
2208
|
// Reset agent backend state so the new
|
|
@@ -2214,7 +2237,7 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
|
|
|
2214
2237
|
?? newRunner.getEffort?.()
|
|
2215
2238
|
?? newAgent?.effort;
|
|
2216
2239
|
const backendBits = [newBaseagent, backendModel, backendEffort].filter(Boolean).join(' · ');
|
|
2217
|
-
return { kind: 'command.result', text: `✓ 已创建新会话${sessionName ? `: ${sessionName}` : ''}\n 项目: ${this.getProjectName(projectPath)}\n 后端: ${backendBits}\n 之前的对话历史已保留,可通过 /s
|
|
2240
|
+
return { kind: 'command.result', text: `✓ 已创建新会话${sessionName ? `: ${sessionName}` : ''}\n 项目: ${this.getProjectName(projectPath)}\n 后端: ${backendBits}\n 之前的对话历史已保留,可通过 /s 查看${handoffDiscardWarning ? `\n${handoffDiscardWarning}` : ''}` };
|
|
2218
2241
|
}
|
|
2219
2242
|
// /check 命令:检查 EvolAgent 实例健康(visitor/member 可用,详情仅 admin)
|
|
2220
2243
|
if (normalizedContent === '/check' || normalizedContent.startsWith('/check ')) {
|
|
@@ -2485,12 +2508,9 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
|
|
|
2485
2508
|
const controlDir = daemonControlDir();
|
|
2486
2509
|
fs.mkdirSync(controlDir, { recursive: true });
|
|
2487
2510
|
fs.writeFileSync(path.join(controlDir, 'restart-pending.json'), JSON.stringify(restartInfo));
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
stdio: 'ignore',
|
|
2492
|
-
env: { ...process.env, EVOLCORE_HOME: resolvePaths().root }
|
|
2493
|
-
}).unref();
|
|
2511
|
+
spawnDetachedNode([path.join(getPackageRoot(), 'dist', 'cli', 'index.js'), 'restart-monitor'], {
|
|
2512
|
+
EVOLCORE_HOME: resolvePaths().root,
|
|
2513
|
+
});
|
|
2494
2514
|
}
|
|
2495
2515
|
else {
|
|
2496
2516
|
logger.info('[System] Suppressed real restart in test runtime');
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* - 只缓存"文件 → 解析后内容",不缓存按 vars 渲染后的结果。
|
|
11
11
|
*
|
|
12
12
|
* 策略(reload/重启永远全量失效,无视策略;策略只决定"平时每次读怎么检查"):
|
|
13
|
-
* - on-reload:平时不检查,直接用缓存(kits
|
|
13
|
+
* - on-reload:平时不检查,直接用缓存(kits 文件)。靠 reload/重启刷新。
|
|
14
14
|
* - manual:同 on-reload,额外支持显式 invalidate(file) 单刷。
|
|
15
15
|
* - mtime:每次读 statSync 门控 size + mtime,变了自动重读(working.md、config.json)。
|
|
16
16
|
*
|
|
@@ -35,6 +35,7 @@ function newCounters() {
|
|
|
35
35
|
*/
|
|
36
36
|
const GROUP_CAPS = {
|
|
37
37
|
'relation-prefs': 512,
|
|
38
|
+
'eck-runtime': 512,
|
|
38
39
|
};
|
|
39
40
|
export class FileCache {
|
|
40
41
|
cache = new Map();
|
|
@@ -1303,7 +1303,10 @@ function verifyArchiveContains(archivePath, sourcePaths) {
|
|
|
1303
1303
|
.replace(/\/+$/, '');
|
|
1304
1304
|
let entries;
|
|
1305
1305
|
try {
|
|
1306
|
-
entries = new Set(execFileSync(tar, ['-tzf', archivePath], {
|
|
1306
|
+
entries = new Set(execFileSync(tar, ['-tzf', archivePath], {
|
|
1307
|
+
encoding: 'utf8',
|
|
1308
|
+
windowsHide: process.platform === 'win32',
|
|
1309
|
+
})
|
|
1307
1310
|
.split('\n')
|
|
1308
1311
|
.map(normalizeArchivePath)
|
|
1309
1312
|
.filter(Boolean));
|
|
@@ -1401,7 +1404,11 @@ export function finalizeDataMigration(root, manifest) {
|
|
|
1401
1404
|
const temporary = `${archivePath}.${process.pid}.${crypto.randomBytes(6).toString('hex')}.tmp`;
|
|
1402
1405
|
const tar = process.platform === 'win32' ? 'tar.exe' : 'tar';
|
|
1403
1406
|
try {
|
|
1404
|
-
execFileSync(tar, ['-czf', temporary, ...sourcePaths], {
|
|
1407
|
+
execFileSync(tar, ['-czf', temporary, ...sourcePaths], {
|
|
1408
|
+
cwd: root,
|
|
1409
|
+
stdio: 'pipe',
|
|
1410
|
+
windowsHide: process.platform === 'win32',
|
|
1411
|
+
});
|
|
1405
1412
|
verifyArchiveContains(temporary, sourcePaths);
|
|
1406
1413
|
fs.renameSync(temporary, archivePath);
|
|
1407
1414
|
}
|
|
@@ -5,7 +5,7 @@ import { logger } from '../utils/logger.js';
|
|
|
5
5
|
import { agentPersonalDir } from '../paths.js';
|
|
6
6
|
import { invalidateAgentDisplayName, resolveAgentDisplayName } from '../aun/aid/agentmd.js';
|
|
7
7
|
import { loadAllAgents, ensureAgentDirSkeleton, loadAgent, validateAgentConfig, } from '../config-store.js';
|
|
8
|
-
import { resolveEffective } from '../config/config-manager.js';
|
|
8
|
+
import { ConfigTarget, read as readConfig, resolveEffective } from '../config/config-manager.js';
|
|
9
9
|
// ── Channel Fingerprint ───────────────────────────────────────────────────
|
|
10
10
|
// 用于检测多 agent 之间复用同一外部凭证的冲突(appId、aid、token 等)。
|
|
11
11
|
// 格式:{type}:{primaryKey}
|
|
@@ -84,18 +84,20 @@ export class EvolAgentRegistry {
|
|
|
84
84
|
this.skipped = [];
|
|
85
85
|
const { agents: rawAgents, skipped, invalidAgents = [] } = loadAllAgents({ includeInvalid: true });
|
|
86
86
|
this.skipped = skipped;
|
|
87
|
-
for (const
|
|
87
|
+
for (const expandedRaw of rawAgents) {
|
|
88
88
|
try {
|
|
89
|
-
const
|
|
89
|
+
const raw = readConfig(ConfigTarget.Agent, { self: expandedRaw.aid }, { cache: true }) ?? expandedRaw;
|
|
90
|
+
const merged = resolveEffective({ self: expandedRaw.aid }, { expand: true });
|
|
90
91
|
const agent = new EvolAgent(raw, merged);
|
|
91
|
-
ensureAgentDirSkeleton(
|
|
92
|
+
ensureAgentDirSkeleton(expandedRaw.aid);
|
|
92
93
|
this.agents.set(agent.aid, agent);
|
|
93
94
|
}
|
|
94
95
|
catch (e) {
|
|
95
|
-
logger.warn(`[EvolAgentRegistry] failed to construct agent ${
|
|
96
|
+
logger.warn(`[EvolAgentRegistry] failed to construct agent ${expandedRaw.aid}: ${e}`);
|
|
96
97
|
}
|
|
97
98
|
}
|
|
98
|
-
for (const { agent:
|
|
99
|
+
for (const { agent: expandedRaw, reason } of invalidAgents) {
|
|
100
|
+
const raw = readConfig(ConfigTarget.Agent, { self: expandedRaw.aid }, { cache: true }) ?? expandedRaw;
|
|
99
101
|
this.registerErrorAgent(raw, reason);
|
|
100
102
|
}
|
|
101
103
|
this.detectAndFlagConflicts();
|
|
@@ -119,7 +121,7 @@ export class EvolAgentRegistry {
|
|
|
119
121
|
}
|
|
120
122
|
resolveMergedForErrorAgent(raw, reason) {
|
|
121
123
|
try {
|
|
122
|
-
return resolveEffective({ self: raw.aid });
|
|
124
|
+
return resolveEffective({ self: raw.aid }, { expand: true });
|
|
123
125
|
}
|
|
124
126
|
catch (e) {
|
|
125
127
|
const message = e instanceof Error ? e.message : String(e);
|
|
@@ -225,9 +227,9 @@ export class EvolAgentRegistry {
|
|
|
225
227
|
logger.info(`[EvolAgentRegistry] agent ${aid} already loaded, skipping`);
|
|
226
228
|
return this.agents.get(aid);
|
|
227
229
|
}
|
|
228
|
-
let
|
|
230
|
+
let expandedRaw = null;
|
|
229
231
|
try {
|
|
230
|
-
|
|
232
|
+
expandedRaw = loadAgent(aid);
|
|
231
233
|
}
|
|
232
234
|
catch (e) {
|
|
233
235
|
const reason = e instanceof Error ? e.message : String(e);
|
|
@@ -239,22 +241,23 @@ export class EvolAgentRegistry {
|
|
|
239
241
|
channels: [],
|
|
240
242
|
}, reason);
|
|
241
243
|
}
|
|
242
|
-
if (!
|
|
244
|
+
if (!expandedRaw) {
|
|
243
245
|
logger.warn(`[EvolAgentRegistry] loadNewAgent: ${aid}/config.json not found`);
|
|
244
246
|
return null;
|
|
245
247
|
}
|
|
246
|
-
const errs = validateAgentConfig(
|
|
248
|
+
const errs = validateAgentConfig(expandedRaw);
|
|
247
249
|
if (errs.length > 0) {
|
|
248
250
|
const reason = errs.join('; ');
|
|
249
251
|
logger.warn(`[EvolAgentRegistry] loadNewAgent ${aid}: ${reason}`);
|
|
250
|
-
return this.registerErrorAgent(
|
|
252
|
+
return this.registerErrorAgent(expandedRaw, reason);
|
|
251
253
|
}
|
|
252
|
-
const conflict = this.checkConflictForReload(
|
|
254
|
+
const conflict = this.checkConflictForReload(expandedRaw, aid);
|
|
253
255
|
if (conflict) {
|
|
254
256
|
logger.warn(`[EvolAgentRegistry] loadNewAgent ${aid}: ${conflict}`);
|
|
255
|
-
return this.registerErrorAgent(
|
|
257
|
+
return this.registerErrorAgent(expandedRaw, `Channel conflict: ${conflict}`);
|
|
256
258
|
}
|
|
257
|
-
const
|
|
259
|
+
const raw = readConfig(ConfigTarget.Agent, { self: aid }, { cache: true }) ?? expandedRaw;
|
|
260
|
+
const merged = resolveEffective({ self: aid }, { expand: true });
|
|
258
261
|
const agent = new EvolAgent(raw, merged);
|
|
259
262
|
ensureAgentDirSkeleton(aid);
|
|
260
263
|
this.agents.set(aid, agent);
|
|
@@ -268,13 +271,14 @@ export class EvolAgentRegistry {
|
|
|
268
271
|
const oldAgent = this.agents.get(aidOrName);
|
|
269
272
|
if (!oldAgent)
|
|
270
273
|
throw new Error(`Agent "${aidOrName}" not found`);
|
|
271
|
-
const
|
|
272
|
-
if (!
|
|
274
|
+
const expandedRaw = loadAgent(oldAgent.aid);
|
|
275
|
+
if (!expandedRaw)
|
|
273
276
|
throw new Error(`Agent ${oldAgent.aid}/config.json missing on reload`);
|
|
274
|
-
const errs = validateAgentConfig(
|
|
277
|
+
const errs = validateAgentConfig(expandedRaw);
|
|
275
278
|
if (errs.length > 0)
|
|
276
279
|
throw new Error(`Invalid config after edit: ${errs.join('; ')}`);
|
|
277
|
-
const
|
|
280
|
+
const raw = readConfig(ConfigTarget.Agent, { self: oldAgent.aid }, { cache: true }) ?? expandedRaw;
|
|
281
|
+
const merged = resolveEffective({ self: raw.aid }, { expand: true });
|
|
278
282
|
if (oldAgent.status === 'disabled' && raw.enabled !== false) {
|
|
279
283
|
oldAgent.swapConfig(raw, merged);
|
|
280
284
|
const hotLoad = globalThis.__evolcore_hotLoadAgent;
|
|
@@ -289,32 +293,69 @@ export class EvolAgentRegistry {
|
|
|
289
293
|
}
|
|
290
294
|
if (oldAgent.status !== 'disabled' && raw.enabled === false) {
|
|
291
295
|
let prepared = false;
|
|
296
|
+
let runnerTransaction;
|
|
297
|
+
const disconnectedChannels = [];
|
|
298
|
+
const previousConfig = oldAgent.captureConfigSnapshot();
|
|
299
|
+
const previousStatus = oldAgent.status;
|
|
300
|
+
const previousError = oldAgent.error;
|
|
292
301
|
try {
|
|
293
302
|
await hooks.prepareHandoffReload?.(oldAgent.aid);
|
|
294
303
|
prepared = true;
|
|
304
|
+
runnerTransaction = await hooks.stageAgentRunners?.(new EvolAgent(raw, merged));
|
|
295
305
|
for (const ch of oldAgent.channelInstanceNames()) {
|
|
296
306
|
try {
|
|
297
307
|
await hooks.drainChannel(ch);
|
|
298
308
|
}
|
|
299
309
|
catch { }
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
}
|
|
303
|
-
catch { }
|
|
310
|
+
await hooks.disconnectChannel(ch);
|
|
311
|
+
disconnectedChannels.push(ch);
|
|
304
312
|
}
|
|
305
313
|
oldAgent.swapConfig(raw, merged);
|
|
306
314
|
oldAgent.status = 'disabled';
|
|
307
315
|
this.channelIndex.clear();
|
|
308
316
|
this.buildChannelIndex();
|
|
317
|
+
runnerTransaction?.commit();
|
|
318
|
+
try {
|
|
319
|
+
await runnerTransaction?.finalize();
|
|
320
|
+
}
|
|
321
|
+
catch (error) {
|
|
322
|
+
logger.warn(`[Reload] Failed to dispose previous runners after disabling "${aidOrName}": ${error instanceof Error ? error.message : String(error)}`);
|
|
323
|
+
}
|
|
324
|
+
prepared = false;
|
|
309
325
|
logger.info(`[Reload] Agent "${aidOrName}" disabled`);
|
|
310
326
|
return;
|
|
311
327
|
}
|
|
312
328
|
catch (error) {
|
|
329
|
+
const rollbackErrors = [];
|
|
330
|
+
oldAgent.swapConfig(previousConfig.rawAgent, previousConfig.merged);
|
|
331
|
+
oldAgent.invalidatePersonaCache();
|
|
332
|
+
oldAgent.status = previousStatus;
|
|
333
|
+
oldAgent.error = previousError;
|
|
334
|
+
await runnerTransaction?.rollback().catch(rollbackError => {
|
|
335
|
+
rollbackErrors.push(`runners: ${rollbackError instanceof Error ? rollbackError.message : String(rollbackError)}`);
|
|
336
|
+
});
|
|
337
|
+
for (const ch of disconnectedChannels) {
|
|
338
|
+
try {
|
|
339
|
+
await hooks.startChannel(oldAgent, ch);
|
|
340
|
+
}
|
|
341
|
+
catch (restartError) {
|
|
342
|
+
rollbackErrors.push(`restart ${ch}: ${restartError instanceof Error ? restartError.message : String(restartError)}`);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
this.channelIndex.clear();
|
|
346
|
+
this.buildChannelIndex();
|
|
313
347
|
if (prepared) {
|
|
314
348
|
try {
|
|
315
349
|
await hooks.completeHandoffReload?.(oldAgent.aid);
|
|
316
350
|
}
|
|
317
|
-
catch {
|
|
351
|
+
catch (resumeError) {
|
|
352
|
+
rollbackErrors.push(`handoff: ${resumeError instanceof Error ? resumeError.message : String(resumeError)}`);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
if (rollbackErrors.length > 0) {
|
|
356
|
+
oldAgent.status = 'error';
|
|
357
|
+
oldAgent.error = `Disable failed; rollback incomplete: ${rollbackErrors.join('; ')}`;
|
|
358
|
+
logger.error(`[Reload] Disable rollback incomplete for "${aidOrName}": ${rollbackErrors.join('; ')}`);
|
|
318
359
|
}
|
|
319
360
|
throw error;
|
|
320
361
|
}
|
|
@@ -324,13 +365,18 @@ export class EvolAgentRegistry {
|
|
|
324
365
|
throw new Error(`Channel conflict: ${conflict}`);
|
|
325
366
|
const removedSuccessfully = [];
|
|
326
367
|
const addedSuccessfully = [];
|
|
368
|
+
let runnerTransaction;
|
|
327
369
|
let prepared = false;
|
|
370
|
+
const previousConfig = oldAgent.captureConfigSnapshot();
|
|
371
|
+
const previousStatus = oldAgent.status;
|
|
372
|
+
const previousError = oldAgent.error;
|
|
328
373
|
try {
|
|
329
374
|
await hooks.prepareHandoffReload?.(oldAgent.aid);
|
|
330
375
|
prepared = true;
|
|
376
|
+
runnerTransaction = await hooks.stageAgentRunners?.(new EvolAgent(raw, merged));
|
|
331
377
|
const oldChannels = new Set(oldAgent.channelInstanceNames());
|
|
332
378
|
const aunKey = oldAgent.effectiveChannelName('aun', 'main');
|
|
333
|
-
const otherKeys =
|
|
379
|
+
const otherKeys = merged.channels.filter(c => c.type !== 'aun').map(c => oldAgent.effectiveChannelName(c.type, c.name));
|
|
334
380
|
const newChannels = new Set([aunKey, ...otherKeys]);
|
|
335
381
|
const toRemove = [...oldChannels].filter(c => !newChannels.has(c));
|
|
336
382
|
const toAdd = [...newChannels].filter(c => !oldChannels.has(c));
|
|
@@ -338,7 +384,7 @@ export class EvolAgentRegistry {
|
|
|
338
384
|
const credentialsChanged = [];
|
|
339
385
|
for (const ch of kept) {
|
|
340
386
|
const oldInst = oldAgent.findChannelInstance(ch);
|
|
341
|
-
const newInst = findInstanceByKey(
|
|
387
|
+
const newInst = findInstanceByKey(merged, oldAgent, ch);
|
|
342
388
|
if (oldInst && newInst && JSON.stringify(oldInst) !== JSON.stringify(newInst)) {
|
|
343
389
|
credentialsChanged.push(ch);
|
|
344
390
|
}
|
|
@@ -360,28 +406,72 @@ export class EvolAgentRegistry {
|
|
|
360
406
|
oldAgent.status = 'running';
|
|
361
407
|
this.channelIndex.clear();
|
|
362
408
|
this.buildChannelIndex();
|
|
363
|
-
|
|
364
|
-
|
|
409
|
+
runnerTransaction?.commit();
|
|
410
|
+
try {
|
|
411
|
+
await hooks.completeHandoffReload?.(oldAgent.aid);
|
|
412
|
+
}
|
|
413
|
+
catch (error) {
|
|
414
|
+
logger.warn(`[Reload] Handoff recovery failed after commit for "${aidOrName}": ${error instanceof Error ? error.message : String(error)}`);
|
|
415
|
+
}
|
|
416
|
+
finally {
|
|
417
|
+
prepared = false;
|
|
418
|
+
}
|
|
419
|
+
try {
|
|
420
|
+
await hooks.afterReload?.(oldAgent);
|
|
421
|
+
}
|
|
422
|
+
catch (error) {
|
|
423
|
+
logger.warn(`[Reload] Post-reload hook failed after commit for "${aidOrName}": ${error instanceof Error ? error.message : String(error)}`);
|
|
424
|
+
}
|
|
425
|
+
try {
|
|
426
|
+
await runnerTransaction?.finalize();
|
|
427
|
+
}
|
|
428
|
+
catch (error) {
|
|
429
|
+
logger.warn(`[Reload] Failed to dispose previous runners for "${aidOrName}": ${error instanceof Error ? error.message : String(error)}`);
|
|
430
|
+
}
|
|
365
431
|
}
|
|
366
432
|
catch (err) {
|
|
367
|
-
logger.error(`[Reload] Failed: ${err}. Attempting rollback for "${aidOrName}".`);
|
|
368
|
-
|
|
433
|
+
logger.error(`[Reload] Failed before commit: ${err}. Attempting rollback for "${aidOrName}".`);
|
|
434
|
+
const rollbackErrors = [];
|
|
435
|
+
for (const ch of [...addedSuccessfully].reverse()) {
|
|
369
436
|
try {
|
|
370
437
|
await hooks.disconnectChannel(ch);
|
|
371
438
|
}
|
|
372
|
-
catch {
|
|
439
|
+
catch (error) {
|
|
440
|
+
rollbackErrors.push(`disconnect ${ch}: ${error instanceof Error ? error.message : String(error)}`);
|
|
441
|
+
}
|
|
373
442
|
}
|
|
443
|
+
oldAgent.swapConfig(previousConfig.rawAgent, previousConfig.merged);
|
|
444
|
+
oldAgent.invalidatePersonaCache();
|
|
445
|
+
await runnerTransaction?.rollback().catch(error => {
|
|
446
|
+
rollbackErrors.push(`runners: ${error instanceof Error ? error.message : String(error)}`);
|
|
447
|
+
});
|
|
448
|
+
for (const ch of [...new Set(removedSuccessfully)]) {
|
|
449
|
+
try {
|
|
450
|
+
await hooks.startChannel(oldAgent, ch);
|
|
451
|
+
}
|
|
452
|
+
catch (error) {
|
|
453
|
+
rollbackErrors.push(`restart ${ch}: ${error instanceof Error ? error.message : String(error)}`);
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
this.channelIndex.clear();
|
|
457
|
+
this.buildChannelIndex();
|
|
374
458
|
if (prepared) {
|
|
375
459
|
try {
|
|
376
460
|
await hooks.completeHandoffReload?.(oldAgent.aid);
|
|
377
461
|
}
|
|
378
462
|
catch (resumeError) {
|
|
379
|
-
|
|
463
|
+
rollbackErrors.push(`handoff: ${resumeError instanceof Error ? resumeError.message : String(resumeError)}`);
|
|
380
464
|
}
|
|
381
465
|
}
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
466
|
+
if (rollbackErrors.length === 0) {
|
|
467
|
+
oldAgent.status = previousStatus;
|
|
468
|
+
oldAgent.error = previousError;
|
|
469
|
+
}
|
|
470
|
+
else {
|
|
471
|
+
oldAgent.status = 'error';
|
|
472
|
+
oldAgent.error = `Reload failed; rollback incomplete: ${rollbackErrors.join('; ')}`;
|
|
473
|
+
logger.error(`[Reload] Rollback incomplete for "${aidOrName}": ${rollbackErrors.join('; ')}`);
|
|
474
|
+
}
|
|
385
475
|
throw err;
|
|
386
476
|
}
|
|
387
477
|
}
|