evolcore 0.0.22 → 0.0.24
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 +74 -14
- package/README.md +4 -0
- package/bin/codex-managed-acl.ps1 +160 -0
- package/bin/codex-managed-hook.mjs +127 -10
- package/bin/ec-safe-output.js +85 -3
- package/bin/install-codex-managed-hooks.mjs +384 -95
- package/dist/agents/baseagent-availability.js +48 -0
- package/dist/agents/baseagent.js +30 -2
- package/dist/agents/claude-runner.js +179 -240
- package/dist/agents/{codex-app-server-client.js → codex/app-server-client.js} +90 -20
- package/dist/{utils/codex-app-server-registry.js → agents/codex/app-server-registry.js} +2 -2
- package/dist/agents/codex/catalog.js +70 -0
- package/dist/agents/codex/diagnostics.js +369 -0
- package/dist/agents/codex-runner.js +839 -192
- package/dist/agents/ecagent-runner.js +27 -34
- package/dist/agents/gemini-runner.js +62 -8
- package/dist/agents/runner-types.js +1 -1
- package/dist/aun/aid/managed-operation.js +28 -6
- package/dist/aun/aid/store.js +43 -0
- package/dist/aun/menu-client.js +272 -0
- package/dist/aun/msg/group.js +65 -4
- package/dist/aun/msg/index.js +1 -1
- package/dist/aun/msg/managed-operation.js +57 -5
- package/dist/aun/outbox.js +56 -18
- package/dist/aun/rpc/client.js +7 -1
- package/dist/channels/aun.js +178 -77
- package/dist/channels/daemon.js +7 -1
- package/dist/channels/dingtalk.js +1 -0
- package/dist/channels/feishu.js +10 -4
- package/dist/channels/wechat.js +81 -10
- package/dist/channels/wecom.js +2 -1
- package/dist/cli/agent-command.js +29 -3
- package/dist/cli/agent-create.js +538 -0
- package/dist/cli/agent.js +26 -675
- package/dist/cli/aun-commands.js +108 -24
- package/dist/cli/command-log.js +21 -2
- package/dist/cli/ctl-command.js +2 -0
- package/dist/cli/daemon-commands.js +289 -61
- package/dist/cli/index.js +22 -2
- package/dist/cli/init.js +2 -10
- package/dist/cli/managed-command-guard.js +3 -0
- package/dist/cli/menu-client.js +95 -0
- package/dist/cli/model.js +23 -8
- package/dist/cli/restart-monitor.js +432 -8
- package/dist/cli/stats.js +110 -0
- package/dist/cli/task-context.js +12 -2
- package/dist/cli/trigger-command.js +9 -1
- package/dist/config/builtin-roles.js +2 -0
- package/dist/config/schema-migration.js +14 -2
- package/dist/core/auth/agent-delegation.js +10 -7
- package/dist/core/auth/auth-gateway.js +3 -2
- package/dist/core/auth/authorization-audit.js +75 -18
- package/dist/core/auth/operation-authorizer.js +63 -3
- package/dist/core/auth/operation-catalog.js +14 -2
- package/dist/core/auth/remote-access-store.js +136 -0
- package/dist/core/baseagent-loader.js +4 -4
- package/dist/core/bootstrap-messages.js +39 -18
- package/dist/core/bootstrap-service.js +32 -26
- package/dist/core/capability/capability-manager.js +8 -4
- package/dist/core/capability/providers/codex-capability-provider.js +1 -1
- package/dist/core/command/agent-control.js +7 -2
- package/dist/core/command/command-handler.js +128 -30
- package/dist/core/command/daemon-diagnostics.js +448 -0
- package/dist/core/command/menu-catalog.js +6 -1
- package/dist/core/command/menu-handler.js +410 -98
- package/dist/core/command/menu-protocol.js +123 -0
- package/dist/core/command/slash-gate.js +4 -6
- package/dist/core/command/slash-handler.js +199 -70
- package/dist/core/data-migration.js +5 -15
- package/dist/core/event-catalog.js +3 -0
- package/dist/core/handoff/runtime.js +3 -1
- package/dist/core/handoff/store.js +7 -1
- package/dist/core/message/im-renderer.js +13 -6
- package/dist/core/message/message-bridge.js +5 -36
- package/dist/core/message/message-queue.js +20 -15
- package/dist/core/message/message-utils.js +6 -3
- package/dist/core/message/response-engine.js +147 -94
- package/dist/core/model/catalog-service.js +310 -0
- package/dist/core/model/model-catalog.js +134 -334
- package/dist/core/model/model-diagnostics.js +4 -5
- package/dist/core/model/model-fallback.js +9 -16
- package/dist/core/model/model-permission.js +5 -2
- package/dist/core/permission/approval-gateway.js +183 -32
- package/dist/core/permission/protected-paths.js +63 -4
- package/dist/core/permission/sandbox-runtime.js +4 -2
- package/dist/core/permission/tool-policy.js +257 -31
- package/dist/core/runtime-lock.js +13 -8
- package/dist/core/session/adapters/codex-session-file-adapter.js +71 -3
- package/dist/core/session/session-manager.js +49 -13
- package/dist/core/session/session-renew.js +1 -0
- package/dist/index.js +214 -57
- package/dist/ipc.js +191 -102
- package/dist/paths.js +23 -1
- package/dist/stats/billing.js +18 -4
- package/dist/trigger/manager.js +3 -1
- package/dist/trigger/parser.js +77 -13
- package/dist/trigger/patch.js +104 -1
- package/dist/utils/atomic-write.js +26 -2
- package/dist/utils/cross-platform.js +436 -1
- package/dist/utils/ecweb-supervisor.js +11 -2
- package/dist/utils/ecweb-utils.js +8 -26
- package/dist/utils/error-utils.js +16 -2
- package/dist/utils/model-prices.jsonl +23 -20
- package/dist/utils/npm-ops.js +70 -15
- package/dist/utils/windows-autostart.js +1 -1
- package/kits/docs/evolcore/INDEX.md +1 -0
- package/kits/docs/evolcore/agent-create.md +13 -0
- package/kits/docs/evolcore/group-collaboration.md +7 -2
- package/kits/docs/evolcore/menu-client.md +34 -0
- package/kits/migrations/migrate-contact-book-v2.mjs +9 -1
- package/kits/migrations/migrate-role-config-v4.mjs +11 -5
- package/kits/migrations/migrate-role-config-v5.mjs +8 -2
- package/kits/templates/roles/admin.json +2 -0
- package/kits/templates/roles/visitor.json +1 -0
- package/kits/templates/system-fragments/bootstrap.md +8 -1
- package/package.json +16 -4
- package/dist/utils/codex-cli.js +0 -43
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { hasModelSwitcher, hasPermissionController } from '../../agents/runner-types.js';
|
|
2
|
-
import { getCodexEfforts } from '../../agents/codex-runner.js';
|
|
3
2
|
import { normalizeBaseagent } from '../../agents/baseagent.js';
|
|
4
3
|
import { buildEnvelope, isDeliveryTargetForChannel } from '../message/message-utils.js';
|
|
5
4
|
import { resolvePaths, getPackageRoot, daemonControlDir } from '../../paths.js';
|
|
@@ -10,13 +9,14 @@ import path from 'path';
|
|
|
10
9
|
import fs from 'fs';
|
|
11
10
|
import { checkLatestVersion, getLocalVersion, isLinkedInstall, compareVersions } from '../../utils/npm-ops.js';
|
|
12
11
|
import { loadDaemonConfig } from '../../config-store.js';
|
|
12
|
+
import { remoteAccessService } from '../auth/remote-access-store.js';
|
|
13
13
|
import { read as cfgRead, resolveEffective, routeFieldPath, write as cfgWrite, } from '../../config/config-manager.js';
|
|
14
14
|
import { execAgentAction } from './agent-control.js';
|
|
15
15
|
import { authorizeOperation, buildAuthSubject } from '../auth/auth-gateway.js';
|
|
16
16
|
import { authorizationNextStep, formatAuthorizationDenial } from '../auth/authorization-denial.js';
|
|
17
17
|
import { resolvePermissionMode, writeScope } from '../model/config-scope.js';
|
|
18
18
|
import { formatPeerKey } from '../relation/peer-identity.js';
|
|
19
|
-
import { modelMatches } from '../model/model-catalog.js';
|
|
19
|
+
import { modelMatches, getCatalog, assertCatalogSelectable, resolveCatalogModel } from '../model/model-catalog.js';
|
|
20
20
|
import { formatModelCheck, runModelCheck } from '../model/model-diagnostics.js';
|
|
21
21
|
import { constrainResolvedModelForRole, filterModelsForRole, validateModelSelectionForRole } from '../model/model-permission.js';
|
|
22
22
|
import { resolveRuntimeStringField } from '../role/runtime-policy.js';
|
|
@@ -25,10 +25,11 @@ import { chatmodeFieldForPeer, resolveChatModeForField } from '../message/peer-m
|
|
|
25
25
|
import { normalizePermissionMode as normalizePermissionModeContract, PUBLIC_PERMISSION_MODES } from '../permission/mode.js';
|
|
26
26
|
import { isManagementRole } from '../../config/builtin-roles.js';
|
|
27
27
|
import { isSystemControlChannel } from '../system-channels.js';
|
|
28
|
-
import {
|
|
28
|
+
import { launchMonitorAndConfirm, resolveMonitorLaunch, cleanupMonitorStage } from '../../utils/cross-platform.js';
|
|
29
|
+
import { scanInstances } from '../../utils/instance-registry.js';
|
|
29
30
|
import { inspectDataMigrationRequirement } from '../data-migration.js';
|
|
30
31
|
import { guardIdleCommand, guardKnownCommand, guardRoleCommand, guardThreadCommand, isRecognizedSlashCommand, normalizeSlashContent, probeSessionActivity, } from './slash-gate.js';
|
|
31
|
-
const allEfforts = ['low', 'medium', 'high', 'xhigh', 'max'];
|
|
32
|
+
const allEfforts = ['none', 'minimal', 'low', 'medium', 'high', 'xhigh', 'max'];
|
|
32
33
|
const PERMISSION_MODE_KEYS = PUBLIC_PERMISSION_MODES;
|
|
33
34
|
function defaultSlashChatmode(field) {
|
|
34
35
|
return field === 'private' ? 'interactive' : 'proactive';
|
|
@@ -127,12 +128,8 @@ function resolveSlashRelationTarget(params) {
|
|
|
127
128
|
role: params.role,
|
|
128
129
|
};
|
|
129
130
|
}
|
|
130
|
-
function getAvailableEfforts(agent, model) {
|
|
131
|
-
|
|
132
|
-
return allEfforts;
|
|
133
|
-
if (agent.name === 'codex')
|
|
134
|
-
return getCodexEfforts(model);
|
|
135
|
-
return [];
|
|
131
|
+
async function getAvailableEfforts(agent, model) {
|
|
132
|
+
return (await agent.getAvailableEfforts?.(model) ?? []);
|
|
136
133
|
}
|
|
137
134
|
function modelDisplayLabel(agent, model) {
|
|
138
135
|
const full = agent.resolveModelId?.(model);
|
|
@@ -487,7 +484,8 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
|
|
|
487
484
|
const isProcessLevelSlash = normalizedContent === '/restart'
|
|
488
485
|
|| normalizedContent === '/upgrade'
|
|
489
486
|
|| normalizedContent === '/reload'
|
|
490
|
-
|| normalizedContent.startsWith('/reload ')
|
|
487
|
+
|| normalizedContent.startsWith('/reload ')
|
|
488
|
+
|| normalizedContent === '/pair';
|
|
491
489
|
const roleGuard = guardRoleCommand(normalizedContent, activeChatType, isAdmin || (isDaemonOwner && isProcessLevelSlash));
|
|
492
490
|
if (roleGuard)
|
|
493
491
|
return roleGuard;
|
|
@@ -635,7 +633,7 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
|
|
|
635
633
|
const appendProcessCommands = (lines) => {
|
|
636
634
|
if (!isDaemonOwner)
|
|
637
635
|
return;
|
|
638
|
-
lines.push('', '🛠️ 进程级运维:', ' /restart - 重启服务', ' /upgrade - 检查版本更新', ' /reload [aid] - 热重载 Agent 配置', ' /fa <提示词> - 单次使用宿主级 fullaccess 执行(别名: /fullaccess)');
|
|
636
|
+
lines.push('', '🛠️ 进程级运维:', ' /restart - 重启服务', ' /upgrade - 检查版本更新', ' /reload [aid] - 热重载 Agent 配置', ' /fa <提示词> - 单次使用宿主级 fullaccess 执行(别名: /fullaccess)', ' /pair - 获取远程控制配对码');
|
|
639
637
|
};
|
|
640
638
|
if (!isAdmin && activeChatType === 'group') {
|
|
641
639
|
const lines = [
|
|
@@ -775,6 +773,23 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
|
|
|
775
773
|
];
|
|
776
774
|
return { kind: 'command.result', text: lines.join('\n') };
|
|
777
775
|
}
|
|
776
|
+
// /pair is also recognized on an Agent channel so it cannot fall through
|
|
777
|
+
// into model input. The daemon control channel has its own fast path; this
|
|
778
|
+
// branch preserves the same daemon-owner boundary for ordinary AUN chats.
|
|
779
|
+
if (normalizedContent === '/pair') {
|
|
780
|
+
if (!isDaemonOwner) {
|
|
781
|
+
return {
|
|
782
|
+
kind: 'command.error',
|
|
783
|
+
text: '❌ /pair 仅限 daemon-owner 使用;请向 daemon 控制 AID 发送 /pair。',
|
|
784
|
+
};
|
|
785
|
+
}
|
|
786
|
+
const pair = remoteAccessService.getPairingCode(userId);
|
|
787
|
+
const mins = Math.max(0, Math.round((pair.expiresAt - Date.now()) / 60000));
|
|
788
|
+
return {
|
|
789
|
+
kind: 'command.result',
|
|
790
|
+
text: `远程控制配对码:${pair.code}(约 ${mins} 分钟内有效)\n可用于 ECWeb 登录或远程 Menu 客户端授权`,
|
|
791
|
+
};
|
|
792
|
+
}
|
|
778
793
|
// /evolhelp 命令:返回 JSON 格式的命令列表(供程序解析)
|
|
779
794
|
if (normalizedContent === '/evolhelp') {
|
|
780
795
|
const cmds = [];
|
|
@@ -835,6 +850,7 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
|
|
|
835
850
|
cmds.push({ command: '/restart', description: '重启服务', category: '运维', roles: ['daemon-owner'] });
|
|
836
851
|
cmds.push({ command: '/upgrade', description: '检查版本更新', category: '运维', roles: ['daemon-owner'] });
|
|
837
852
|
cmds.push({ command: '/reload', args: '[aid]', description: '热重载 Agent 配置', category: '运维', roles: ['daemon-owner'] });
|
|
853
|
+
cmds.push({ command: '/pair', description: '获取远程控制配对码', category: '运维', roles: ['daemon-owner'] });
|
|
838
854
|
}
|
|
839
855
|
if (!isDaemonOwner && isAdmin) {
|
|
840
856
|
cmds.push({ command: '/reload', description: '热重载当前 Agent 配置', category: '运维', roles: ['admin', 'owner'] });
|
|
@@ -1048,11 +1064,12 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
|
|
|
1048
1064
|
const activeBaseagent = owningAgent?.baseagent || this.parseDefaultBaseagent();
|
|
1049
1065
|
// 尝试发送 CommandCard 卡片
|
|
1050
1066
|
if (this.interactionRouter && available.length > 1) {
|
|
1067
|
+
const baseagentSession = await getExistingSessionForCommand();
|
|
1051
1068
|
const interaction = {
|
|
1052
1069
|
type: 'interaction',
|
|
1053
1070
|
id: `agent-${Date.now()}-${crypto.randomBytes(4).toString('hex')}`,
|
|
1054
1071
|
channelId,
|
|
1055
|
-
sessionId:
|
|
1072
|
+
sessionId: baseagentSession?.id || `agent-${Date.now()}`,
|
|
1056
1073
|
initiatorId: userId,
|
|
1057
1074
|
kind: {
|
|
1058
1075
|
kind: 'command-card',
|
|
@@ -1065,7 +1082,7 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
|
|
|
1065
1082
|
})),
|
|
1066
1083
|
},
|
|
1067
1084
|
};
|
|
1068
|
-
const replyCtx =
|
|
1085
|
+
const replyCtx = baseagentSession ? this.getReplyContext(baseagentSession) : replyContext;
|
|
1069
1086
|
const cardResult = await this.sendCommandCard({ channel, channelId, interaction, replyCtx, canWrite: isOwner });
|
|
1070
1087
|
if (cardResult === null)
|
|
1071
1088
|
return null;
|
|
@@ -1085,29 +1102,79 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
|
|
|
1085
1102
|
if (!owningAgent) {
|
|
1086
1103
|
return { kind: 'command.error', text: '❌ 当前 channel 无绑定 agent,无法设置 active_baseagent' };
|
|
1087
1104
|
}
|
|
1088
|
-
|
|
1089
|
-
const
|
|
1105
|
+
const currentSession = await getExistingSessionForCommand();
|
|
1106
|
+
const previousBaseagent = owningAgent.baseagent || this.parseDefaultBaseagent();
|
|
1107
|
+
if (currentSession?.threadId) {
|
|
1108
|
+
const rotation = await withIdleTopicBackend(currentSession.id, currentSession.threadId, async (current) => {
|
|
1109
|
+
try {
|
|
1110
|
+
owningAgent.setActiveBaseagent(args);
|
|
1111
|
+
}
|
|
1112
|
+
catch (error) {
|
|
1113
|
+
return { ok: false, error: error instanceof Error ? error.message : String(error) };
|
|
1114
|
+
}
|
|
1115
|
+
try {
|
|
1116
|
+
const result = await this.sessionManager.rotateTopicBackend({
|
|
1117
|
+
sessionId: current.id,
|
|
1118
|
+
expectedThreadId: current.threadId,
|
|
1119
|
+
baseagent: args,
|
|
1120
|
+
});
|
|
1121
|
+
return { ok: true, result };
|
|
1122
|
+
}
|
|
1123
|
+
catch (error) {
|
|
1124
|
+
try {
|
|
1125
|
+
owningAgent.setActiveBaseagent(previousBaseagent);
|
|
1126
|
+
}
|
|
1127
|
+
catch { }
|
|
1128
|
+
return { ok: false, error: error instanceof Error ? error.message : String(error) };
|
|
1129
|
+
}
|
|
1130
|
+
}, currentSession);
|
|
1131
|
+
if (!rotation.ok) {
|
|
1132
|
+
if (rotation.reason === 'busy')
|
|
1133
|
+
return { kind: 'command.error', text: '⚠️ 当前话题会话忙碌,无法切换 baseagent,请稍后再试' };
|
|
1134
|
+
if (rotation.reason === 'unknown')
|
|
1135
|
+
return { kind: 'command.error', text: '⚠️ 当前话题会话状态未知,无法安全切换 baseagent,请稍后再试' };
|
|
1136
|
+
return { kind: 'command.error', text: `❌ baseagent 切换失败:${rotation.reason === 'missing' ? '当前话题会话不存在' : '状态未知'}` };
|
|
1137
|
+
}
|
|
1138
|
+
if (!rotation.value.ok)
|
|
1139
|
+
return { kind: 'command.error', text: `❌ baseagent 切换失败:${rotation.value.error}` };
|
|
1140
|
+
this.eventBus.publish({ type: 'agent:baseagent-changed', aid: owningAgent.aid, baseagent: args, previousBaseagent, scope: 'agent', timestamp: Date.now() });
|
|
1141
|
+
return {
|
|
1142
|
+
kind: 'system.notice',
|
|
1143
|
+
text: `✓ 当前话题已切换 Baseagent: ${previousBaseagent} → ${args}\n 话题会话 ID 保持不变: ${currentSession.id}`,
|
|
1144
|
+
subtype: 'session.created',
|
|
1145
|
+
};
|
|
1146
|
+
}
|
|
1147
|
+
await interruptPausedSessionBeforeReplacement(currentSession);
|
|
1090
1148
|
try {
|
|
1091
1149
|
owningAgent.setActiveBaseagent(args);
|
|
1092
1150
|
}
|
|
1093
1151
|
catch (error) {
|
|
1094
1152
|
return { kind: 'command.error', text: `❌ ${error instanceof Error ? error.message : String(error)}` };
|
|
1095
1153
|
}
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1154
|
+
let newSession;
|
|
1155
|
+
try {
|
|
1156
|
+
newSession = await this.sessionManager.createNewSession(channel, channelId, owningAgent.projectPath, undefined, args, currentSession?.metadata, { previousBaseagent, baseagentChanged: previousBaseagent !== args });
|
|
1157
|
+
}
|
|
1158
|
+
catch (error) {
|
|
1159
|
+
try {
|
|
1160
|
+
owningAgent.setActiveBaseagent(previousBaseagent);
|
|
1161
|
+
}
|
|
1162
|
+
catch { }
|
|
1163
|
+
return { kind: 'command.error', text: `❌ 创建新会话失败:${error instanceof Error ? error.message : String(error)}` };
|
|
1164
|
+
}
|
|
1165
|
+
if (currentSession) {
|
|
1166
|
+
const previousAgent = getActiveAgentIfAvailable();
|
|
1167
|
+
if (previousAgent) {
|
|
1168
|
+
await previousAgent.clearSession(currentSession.id, currentSession.agentSessionId || '', currentSession.projectPath);
|
|
1169
|
+
await previousAgent.closeSession(currentSession.id);
|
|
1170
|
+
}
|
|
1171
|
+
}
|
|
1172
|
+
this.eventBus.publish({ type: 'agent:baseagent-changed', aid: owningAgent.aid, baseagent: args, previousBaseagent, scope: 'agent', timestamp: Date.now() });
|
|
1173
|
+
return {
|
|
1174
|
+
kind: 'system.notice',
|
|
1175
|
+
text: `✓ 已切换 Baseagent: ${previousBaseagent} → ${args}\n 已创建新的主会话: ${newSession.id}\n 之前的对话历史已保留,可通过 /s 查看`,
|
|
1176
|
+
subtype: 'session.created',
|
|
1177
|
+
};
|
|
1111
1178
|
}
|
|
1112
1179
|
// /model 命令:查看或切换模型/推理强度
|
|
1113
1180
|
if (normalizedContent.startsWith('/model')) {
|
|
@@ -1260,12 +1327,14 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
|
|
|
1260
1327
|
catch { }
|
|
1261
1328
|
const configuredModel = modelConfig.model;
|
|
1262
1329
|
const configuredEffort = modelConfig.effort;
|
|
1263
|
-
const
|
|
1330
|
+
const catalog = await getCatalog(modelSelector.self, fallbackBaseagent);
|
|
1331
|
+
assertCatalogSelectable(catalog);
|
|
1332
|
+
const rawModels = catalog.models.map(m => m.id);
|
|
1264
1333
|
const models = hasModelSwitcher(modelAgent)
|
|
1265
|
-
? filterModelsForRole(identity.role, fallbackBaseagent, rawModels,
|
|
1334
|
+
? filterModelsForRole(identity.role, fallbackBaseagent, rawModels, model => resolveCatalogModel(catalog, model), modelSelector.self)
|
|
1266
1335
|
: rawModels;
|
|
1267
1336
|
const currentModel = hasModelSwitcher(modelAgent) ? (configuredModel || modelAgent.getModel()) : modelAgent.name;
|
|
1268
|
-
const efforts = getAvailableEfforts(modelAgent, currentModel);
|
|
1337
|
+
const efforts = await getAvailableEfforts(modelAgent, currentModel);
|
|
1269
1338
|
const currentEffort = configuredEffort ?? modelAgent.getEffort?.() ?? 'auto';
|
|
1270
1339
|
const canReadModelList = await canReadSlashModelList({
|
|
1271
1340
|
subject: authSubject,
|
|
@@ -1290,7 +1359,7 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
|
|
|
1290
1359
|
kind: 'command-card',
|
|
1291
1360
|
title: '🤖 切换模型',
|
|
1292
1361
|
buttons: models.map((m) => {
|
|
1293
|
-
const display = modelDisplayLabel(modelAgent, m);
|
|
1362
|
+
const display = modelDisplayLabel(modelAgent, m) + (catalog.models.find(e => e.id === m)?.verification === 'unverified' ? ' [unverified]' : '');
|
|
1294
1363
|
return {
|
|
1295
1364
|
label: modelMatches(modelAgent, m, currentModel) ? `✓ ${display}` : display,
|
|
1296
1365
|
command: `/model ${m}`,
|
|
@@ -1300,13 +1369,14 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
|
|
|
1300
1369
|
}),
|
|
1301
1370
|
},
|
|
1302
1371
|
};
|
|
1303
|
-
const replyCtx = modelSession ? this.getReplyContext(modelSession) :
|
|
1372
|
+
const replyCtx = modelSession ? this.getReplyContext(modelSession) : replyContext;
|
|
1304
1373
|
const cardResult = await this.sendCommandCard({ channel, channelId, interaction, replyCtx, canWrite: isAdmin });
|
|
1305
1374
|
if (cardResult === null)
|
|
1306
1375
|
return null;
|
|
1307
1376
|
return { kind: 'command.result', text: cardResult };
|
|
1308
1377
|
}
|
|
1309
1378
|
// 降级:文本
|
|
1379
|
+
const catalogHint = `[${catalog.source}/${catalog.freshness}] ${catalog.diagnostics.map(d => d.message).join('; ')}`;
|
|
1310
1380
|
const modelList = models.map((m) => ` ${modelMatches(modelAgent, m, currentModel) ? '✓' : ' '} ${modelDisplayLabel(modelAgent, m)}`).join('\n');
|
|
1311
1381
|
const effortHint = efforts.length > 0
|
|
1312
1382
|
? `\n推理强度: ${currentEffort === 'auto' ? 'auto (SDK默认)' : currentEffort} (使用 /effort 调整)`
|
|
@@ -1322,8 +1392,8 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
|
|
|
1322
1392
|
const usage = !args && isAdmin ? '\n\n用法: /model <模型>' : '';
|
|
1323
1393
|
return {
|
|
1324
1394
|
kind: 'command.result',
|
|
1325
|
-
text: `当前模型: ${modelDisplayLabel(modelAgent, currentModel)}${effortHint}\n\n可用模型:\n${modelList}${usage}`,
|
|
1326
|
-
...(args === 'list' ? { structured: { ok: true, model: currentModel, effort: currentEffort, baseagent: fallbackBaseagent, models } } : {}),
|
|
1395
|
+
text: `当前模型: ${modelDisplayLabel(modelAgent, currentModel)}${effortHint}${rawModels.includes(currentModel) ? '' : ' [当前配置,未列入目录]'}\n${catalogHint}\n\n可用模型:\n${modelList}${usage}`,
|
|
1396
|
+
...(args === 'list' ? { structured: { ok: true, model: currentModel, effort: currentEffort, baseagent: fallbackBaseagent, models, revision: catalog.revision, source: catalog.source, freshness: catalog.freshness, diagnostics: catalog.diagnostics } } : {}),
|
|
1327
1397
|
};
|
|
1328
1398
|
}
|
|
1329
1399
|
return { kind: 'command.result', text: `当前模型: ${modelDisplayLabel(modelAgent, currentModel)}${effortHint}` };
|
|
@@ -1384,7 +1454,7 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
|
|
|
1384
1454
|
catch { }
|
|
1385
1455
|
const models = hasModelSwitcher(modelAgent) ? await modelAgent.listModels() : [];
|
|
1386
1456
|
const allowedModels = hasModelSwitcher(modelAgent)
|
|
1387
|
-
? filterModelsForRole(identity.role, fallbackBaseagent, models, modelAgent.resolveModelId?.bind(modelAgent))
|
|
1457
|
+
? filterModelsForRole(identity.role, fallbackBaseagent, models, modelAgent.resolveModelId?.bind(modelAgent), selfAID ?? this.resolveSelfAID(channel))
|
|
1388
1458
|
: models;
|
|
1389
1459
|
const parts = args.split(/\s+/);
|
|
1390
1460
|
let newModel;
|
|
@@ -1392,7 +1462,7 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
|
|
|
1392
1462
|
if (parts.length === 1) {
|
|
1393
1463
|
const arg = parts[0];
|
|
1394
1464
|
const currentModel = hasModelSwitcher(modelAgent) ? (configuredModel || modelAgent.getModel()) : modelAgent.name;
|
|
1395
|
-
const efforts = getAvailableEfforts(modelAgent, currentModel);
|
|
1465
|
+
const efforts = await getAvailableEfforts(modelAgent, currentModel);
|
|
1396
1466
|
// effort 相关参数统一转发到 /effort
|
|
1397
1467
|
if (efforts.includes(arg) || arg === 'auto') {
|
|
1398
1468
|
const delegated = await this.handle(`/effort ${arg}`, channel, channelId, undefined, userId, threadId);
|
|
@@ -1425,7 +1495,7 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
|
|
|
1425
1495
|
if (!models.includes(modelArg)) {
|
|
1426
1496
|
return { kind: 'command.error', text: `❌ 无效的模型ID: ${modelArgRaw}` };
|
|
1427
1497
|
}
|
|
1428
|
-
const targetEfforts = getAvailableEfforts(modelAgent, modelArg);
|
|
1498
|
+
const targetEfforts = await getAvailableEfforts(modelAgent, modelArg);
|
|
1429
1499
|
if (targetEfforts.length === 0) {
|
|
1430
1500
|
return { kind: 'command.error', text: `⚠️ ${modelArg} 不支持推理强度设置` };
|
|
1431
1501
|
}
|
|
@@ -1583,7 +1653,7 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
|
|
|
1583
1653
|
const configuredModel = modelConfig.model;
|
|
1584
1654
|
const configuredEffort = modelConfig.effort;
|
|
1585
1655
|
const currentModel = hasModelSwitcher(effortAgent) ? (configuredModel || effortAgent.getModel()) : effortAgent.name;
|
|
1586
|
-
const efforts = getAvailableEfforts(effortAgent, currentModel);
|
|
1656
|
+
const efforts = await getAvailableEfforts(effortAgent, currentModel);
|
|
1587
1657
|
const currentEffort = configuredEffort ?? effortAgent.getEffort?.() ?? 'auto';
|
|
1588
1658
|
if (efforts.length === 0) {
|
|
1589
1659
|
return { kind: 'command.error', text: '⚠️ 当前模型不支持推理强度设置' };
|
|
@@ -1608,7 +1678,7 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
|
|
|
1608
1678
|
})),
|
|
1609
1679
|
},
|
|
1610
1680
|
};
|
|
1611
|
-
const replyCtx = effortSession ? this.getReplyContext(effortSession) :
|
|
1681
|
+
const replyCtx = effortSession ? this.getReplyContext(effortSession) : replyContext;
|
|
1612
1682
|
const cardResult = await this.sendCommandCard({ channel, channelId, interaction, replyCtx, canWrite: isAdmin });
|
|
1613
1683
|
if (cardResult === null)
|
|
1614
1684
|
return null;
|
|
@@ -1645,7 +1715,7 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
|
|
|
1645
1715
|
const configuredModel = modelConfig.model;
|
|
1646
1716
|
const configuredEffort = modelConfig.effort;
|
|
1647
1717
|
const currentModel = hasModelSwitcher(effortAgent) ? (configuredModel || effortAgent.getModel()) : effortAgent.name;
|
|
1648
|
-
const efforts = getAvailableEfforts(effortAgent, currentModel);
|
|
1718
|
+
const efforts = await getAvailableEfforts(effortAgent, currentModel);
|
|
1649
1719
|
const currentEffort = configuredEffort ?? effortAgent.getEffort?.() ?? 'auto';
|
|
1650
1720
|
if (efforts.length === 0) {
|
|
1651
1721
|
return { kind: 'command.error', text: '⚠️ 当前模型不支持推理强度设置' };
|
|
@@ -1861,7 +1931,10 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
|
|
|
1861
1931
|
})),
|
|
1862
1932
|
},
|
|
1863
1933
|
};
|
|
1864
|
-
|
|
1934
|
+
// A topic session's persisted route is authoritative; an inbound
|
|
1935
|
+
// replyContext may be a stale/main-session snapshot (notably for card
|
|
1936
|
+
// callbacks), so prefer the resolved session context whenever present.
|
|
1937
|
+
const replyCtx = activitySession ? this.getReplyContext(activitySession) : replyContext;
|
|
1865
1938
|
const cardResult = await this.sendCommandCard({ channel, channelId, interaction, replyCtx, canWrite: isOwner });
|
|
1866
1939
|
if (cardResult === null)
|
|
1867
1940
|
return null;
|
|
@@ -1929,7 +2002,7 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
|
|
|
1929
2002
|
})),
|
|
1930
2003
|
},
|
|
1931
2004
|
};
|
|
1932
|
-
const replyCtx = existingChatmodeSession ? this.getReplyContext(existingChatmodeSession) :
|
|
2005
|
+
const replyCtx = existingChatmodeSession ? this.getReplyContext(existingChatmodeSession) : replyContext;
|
|
1933
2006
|
const cardResult = await this.sendCommandCard({
|
|
1934
2007
|
channel,
|
|
1935
2008
|
channelId,
|
|
@@ -2235,7 +2308,7 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
|
|
|
2235
2308
|
}
|
|
2236
2309
|
return {
|
|
2237
2310
|
kind: 'system.notice',
|
|
2238
|
-
text:
|
|
2311
|
+
text: '已切换到新会话。下一条消息将在新会话中处理。',
|
|
2239
2312
|
subtype: 'session.renewed',
|
|
2240
2313
|
};
|
|
2241
2314
|
}
|
|
@@ -2272,7 +2345,10 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
|
|
|
2272
2345
|
// The runner instance's model is only the final fallback; without this
|
|
2273
2346
|
// override Claude's /compact command could resume with a stale agent
|
|
2274
2347
|
// default (for example glm-5.3) while the turn itself used gpt-5.6-sol.
|
|
2275
|
-
const compactBaseagentRaw = session.baseagent
|
|
2348
|
+
const compactBaseagentRaw = session.baseagent;
|
|
2349
|
+
if (!compactBaseagentRaw) {
|
|
2350
|
+
return { kind: 'command.error', text: '❌ 当前会话缺少 baseagent 绑定,拒绝隐式切换' };
|
|
2351
|
+
}
|
|
2276
2352
|
const compactBaseagentNormalized = normalizeBaseagent(compactBaseagentRaw);
|
|
2277
2353
|
const compactBaseagent = compactBaseagentNormalized.canonical === 'unknown'
|
|
2278
2354
|
? compactBaseagentRaw
|
|
@@ -2502,6 +2578,8 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
|
|
|
2502
2578
|
if (session && previousAgent) {
|
|
2503
2579
|
// Reset agent backend state so the new
|
|
2504
2580
|
// session starts with a fresh conversation history
|
|
2581
|
+
// Clearing is a pure backend lifecycle operation; model selection is
|
|
2582
|
+
// resolved only when the new session performs its next turn.
|
|
2505
2583
|
await previousAgent.clearSession(session.id, session.agentSessionId || '', session.projectPath);
|
|
2506
2584
|
await previousAgent.closeSession(session.id);
|
|
2507
2585
|
}
|
|
@@ -2590,7 +2668,10 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
|
|
|
2590
2668
|
groups.set(type, []);
|
|
2591
2669
|
groups.get(type).push({ name, status });
|
|
2592
2670
|
}
|
|
2593
|
-
|
|
2671
|
+
// ECWeb 的系统控制面需要结构化健康数据(尤其是 daemon.aun.connected)。
|
|
2672
|
+
// 这里不能把全局 system 通道降级成仅文本摘要,否则前端会因缺少
|
|
2673
|
+
// daemonStatusProvider 返回值而把已连接的控制 AID 显示为 disconnect。
|
|
2674
|
+
if (!isAdmin && !isDaemonOwner && !isGlobalCheckChannel) {
|
|
2594
2675
|
// visitor/member: 仅显示实例通道摘要
|
|
2595
2676
|
const total = [...groups.values()].flat().length;
|
|
2596
2677
|
const healthy = [...groups.values()].flat().filter(i => i.status.includes('✓')).length;
|
|
@@ -2743,6 +2824,7 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
|
|
|
2743
2824
|
// 进程级操作:必须是 daemon owner(daemon.json.owners),与 menu 协议 /system restart 一致。
|
|
2744
2825
|
// agent-channel 的 owner/admin 角色不足以重启整个 daemon。
|
|
2745
2826
|
const restartSelfAid = this.agentRegistry?.resolveByChannel(channel)?.aid;
|
|
2827
|
+
const commandSession = await getExistingSessionForCommand();
|
|
2746
2828
|
const authDenied = await authorizeIntent({
|
|
2747
2829
|
intent: {
|
|
2748
2830
|
operation: 'system.restart',
|
|
@@ -2752,7 +2834,7 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
|
|
|
2752
2834
|
dangerous: true,
|
|
2753
2835
|
},
|
|
2754
2836
|
identity,
|
|
2755
|
-
session: activeSession,
|
|
2837
|
+
session: commandSession ?? activeSession,
|
|
2756
2838
|
channel,
|
|
2757
2839
|
channelId,
|
|
2758
2840
|
userId,
|
|
@@ -2764,7 +2846,10 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
|
|
|
2764
2846
|
const selfAid = this.agentRegistry?.resolveByChannel(channel)?.aid;
|
|
2765
2847
|
// 排除当前会话(如果存在)。如果 activeSession 不存在,说明还没建立会话,
|
|
2766
2848
|
// 此时检查所有任务;如果有其他会话在处理,仍然阻塞重启。
|
|
2767
|
-
|
|
2849
|
+
// A topic command owns its topic session even when the channel's active
|
|
2850
|
+
// session remains the main conversation. Exclude that exact queue key
|
|
2851
|
+
// from the busy check so the current topic cannot block its own restart.
|
|
2852
|
+
const excludeKey = commandSession?.id ?? activeSession?.id;
|
|
2768
2853
|
const busyInfo = getAgentBusyInfo(this, selfAid, excludeKey);
|
|
2769
2854
|
if (busyInfo && busyInfo.count > 0) {
|
|
2770
2855
|
// busyInfo.count > 0 说明【除当前会话外】还有其他会话的任务在执行
|
|
@@ -2789,10 +2874,14 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
|
|
|
2789
2874
|
const suppressRealRestart = shouldSuppressRealRestart();
|
|
2790
2875
|
let restartReplyContext = replyContext;
|
|
2791
2876
|
try {
|
|
2792
|
-
|
|
2877
|
+
// A topic's persisted session context is authoritative for card
|
|
2878
|
+
// callbacks and for inbound messages that only carry a partial route.
|
|
2879
|
+
// Do not let a stale/main-session replyContext win merely because it
|
|
2880
|
+
// happens to contain a delivery target.
|
|
2881
|
+
if (threadId) {
|
|
2793
2882
|
const threadSession = await this.sessionManager.getThreadSession(channel, channelId, threadId);
|
|
2794
2883
|
if (threadSession)
|
|
2795
|
-
restartReplyContext = this.getReplyContext(threadSession);
|
|
2884
|
+
restartReplyContext = { ...(replyContext ?? {}), ...(this.getReplyContext(threadSession) ?? {}), threadId };
|
|
2796
2885
|
}
|
|
2797
2886
|
else if (!restartReplyContext?.delivery && activeSession) {
|
|
2798
2887
|
restartReplyContext = this.getReplyContext(activeSession);
|
|
@@ -2834,16 +2923,39 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
|
|
|
2834
2923
|
channel,
|
|
2835
2924
|
channelId,
|
|
2836
2925
|
timestamp: Date.now(),
|
|
2926
|
+
...(restartReplyContext?.threadId ? { threadId: restartReplyContext.threadId } : {}),
|
|
2837
2927
|
...(restartReplyContext?.delivery ? { delivery: restartReplyContext.delivery } : {}),
|
|
2838
2928
|
...(restartReplyContext?.replyToMessageId ? { rootId: restartReplyContext.replyToMessageId } : {}),
|
|
2839
2929
|
};
|
|
2840
2930
|
if (!suppressRealRestart) {
|
|
2931
|
+
// 这条路径上 daemon 会在下面 kill 掉自己,所以必须先确认 monitor 真的活起来:
|
|
2932
|
+
// 只查文件存在不够 —— 开发仓下 monitor 走 tsx,源码语法错误会让它加载即退出;
|
|
2933
|
+
// 安装包下 monitor 先复制到 stage 再启动,避免升级替换掉自己脚下的文件。
|
|
2934
|
+
const monitorEntry = resolveMonitorLaunch(getPackageRoot(), path.join(resolvePaths().root, 'data', 'monitor-stage'));
|
|
2935
|
+
if (monitorEntry.note)
|
|
2936
|
+
logger.info(`[System] ${monitorEntry.note}`);
|
|
2937
|
+
if (!fs.existsSync(monitorEntry.entry)) {
|
|
2938
|
+
logger.error(`[System] Refusing restart: restart-monitor entry not found: ${monitorEntry.entry}`);
|
|
2939
|
+
return { code: 'RESTART_ENTRY_MISSING', entry: monitorEntry.entry };
|
|
2940
|
+
}
|
|
2841
2941
|
const controlDir = daemonControlDir();
|
|
2842
2942
|
fs.mkdirSync(controlDir, { recursive: true });
|
|
2843
2943
|
fs.writeFileSync(path.join(controlDir, 'restart-pending.json'), JSON.stringify(restartInfo));
|
|
2844
|
-
|
|
2845
|
-
|
|
2846
|
-
|
|
2944
|
+
const confirmation = await launchMonitorAndConfirm([...monitorEntry.args, 'restart-monitor'], { EVOLCORE_HOME: resolvePaths().root, ...monitorEntry.env }, pid => scanInstances().restartMonitors.some(m => m.alive && m.record.pid === pid), pid => scanInstances().restartMonitors.some(m => m.alive && m.record.pid !== pid));
|
|
2945
|
+
if (!confirmation.ok) {
|
|
2946
|
+
// 没有看门狗就不要停服务:撤掉回执文件,让本进程继续跑。
|
|
2947
|
+
if (monitorEntry.stageDir)
|
|
2948
|
+
cleanupMonitorStage(monitorEntry.stageDir);
|
|
2949
|
+
try {
|
|
2950
|
+
fs.rmSync(path.join(controlDir, 'restart-pending.json'), { force: true });
|
|
2951
|
+
}
|
|
2952
|
+
catch { }
|
|
2953
|
+
logger.error(`[System] Refusing restart: restart-monitor did not come up (${confirmation.reason})`);
|
|
2954
|
+
for (const line of (confirmation.stderrTail ?? '').split(/\r?\n/).filter(Boolean).slice(-8)) {
|
|
2955
|
+
logger.error(`[System] | ${line}`);
|
|
2956
|
+
}
|
|
2957
|
+
return { code: 'RESTART_MONITOR_DEAD', reason: confirmation.reason ?? 'unknown' };
|
|
2958
|
+
}
|
|
2847
2959
|
}
|
|
2848
2960
|
else {
|
|
2849
2961
|
logger.info('[System] Suppressed real restart in test runtime');
|
|
@@ -2874,15 +2986,10 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
|
|
|
2874
2986
|
logger.error('[System] Failed to send restart notification:', err);
|
|
2875
2987
|
}
|
|
2876
2988
|
}
|
|
2877
|
-
//
|
|
2878
|
-
//
|
|
2879
|
-
//
|
|
2880
|
-
|
|
2881
|
-
setTimeout(() => {
|
|
2882
|
-
logger.info('[System] Restarting by user command...');
|
|
2883
|
-
process.kill(process.pid, 'SIGTERM');
|
|
2884
|
-
}, 1000);
|
|
2885
|
-
}
|
|
2989
|
+
// restart-monitor owns the old daemon shutdown and the replacement
|
|
2990
|
+
// startup. Do not kill this process here: the monitor may have exited
|
|
2991
|
+
// after a read-only migration check, in which case the current service
|
|
2992
|
+
// must remain available.
|
|
2886
2993
|
return true;
|
|
2887
2994
|
};
|
|
2888
2995
|
// 文本确认流程
|
|
@@ -2911,7 +3018,21 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
|
|
|
2911
3018
|
if (typeof restarted === 'object' && restarted.code === 'DATA_MIGRATION_REQUIRED') {
|
|
2912
3019
|
return {
|
|
2913
3020
|
kind: 'command.error',
|
|
2914
|
-
text: `❌ 待处理的用户数据迁移(${restarted.operationCount}
|
|
3021
|
+
text: `❌ 待处理的用户数据迁移(${restarted.operationCount} 项)阻止重启;本次仅检查,未停止当前服务。请先运行 ec data migrate --dry-run,然后运行 ec data migrate --apply。`,
|
|
3022
|
+
reason: restarted.code,
|
|
3023
|
+
};
|
|
3024
|
+
}
|
|
3025
|
+
if (typeof restarted === 'object' && restarted.code === 'RESTART_ENTRY_MISSING') {
|
|
3026
|
+
return {
|
|
3027
|
+
kind: 'command.error',
|
|
3028
|
+
text: `❌ 重启已中止:找不到 restart-monitor 入口 ${restarted.entry}。服务未重启,当前进程继续运行。请先执行 npm run build 重建产物。`,
|
|
3029
|
+
reason: restarted.code,
|
|
3030
|
+
};
|
|
3031
|
+
}
|
|
3032
|
+
if (typeof restarted === 'object' && restarted.code === 'RESTART_MONITOR_DEAD') {
|
|
3033
|
+
return {
|
|
3034
|
+
kind: 'command.error',
|
|
3035
|
+
text: `❌ 重启已中止:看门狗进程未能启动(${restarted.reason})。服务未停止,仍在运行当前版本。开发仓下这通常是源码语法错误导致 tsx 无法加载,请执行 npx tsc --noEmit 检查。`,
|
|
2915
3036
|
reason: restarted.code,
|
|
2916
3037
|
};
|
|
2917
3038
|
}
|
|
@@ -3079,11 +3200,11 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
|
|
|
3079
3200
|
? { delivery: { chatType: 'private' } }
|
|
3080
3201
|
: sendSession
|
|
3081
3202
|
? this.getReplyContext(sendSession)
|
|
3082
|
-
: chatType === 'group'
|
|
3203
|
+
: replyContext ?? (chatType === 'group'
|
|
3083
3204
|
? { delivery: { chatType: 'group', groupId: channelId } }
|
|
3084
3205
|
: chatType === 'private'
|
|
3085
3206
|
? { delivery: { chatType: 'private' } }
|
|
3086
|
-
: undefined;
|
|
3207
|
+
: undefined);
|
|
3087
3208
|
await targetAdapter.send(buildEnvelope({ channel: targetAdapter.channelName, channelId: targetChannelId, replyContext: replyCtx }), { kind: 'result.file', filePath: realPath });
|
|
3088
3209
|
const sizeStr = stat.size < 1024 ? `${stat.size} B`
|
|
3089
3210
|
: stat.size < 1024 * 1024 ? `${(stat.size / 1024).toFixed(1)} KB`
|
|
@@ -3309,7 +3430,12 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
|
|
|
3309
3430
|
projectPaths.unshift(session.projectPath);
|
|
3310
3431
|
}
|
|
3311
3432
|
for (const projectPath of projectPaths) {
|
|
3312
|
-
const currentBaseagent = session?.baseagent
|
|
3433
|
+
const currentBaseagent = session?.baseagent
|
|
3434
|
+
?? this.agentRegistry?.resolveByChannel(channel)?.baseagent
|
|
3435
|
+
?? (this.agentMap?.size === 1 ? this.parseDefaultBaseagent() : undefined);
|
|
3436
|
+
if (!currentBaseagent) {
|
|
3437
|
+
return { kind: 'command.error', text: '❌ 无法确定当前会话的 baseagent,拒绝导入 CLI 会话' };
|
|
3438
|
+
}
|
|
3313
3439
|
const cliSessions = currentBaseagent === 'ecagent'
|
|
3314
3440
|
? await this.sessionManager.listImportableCliSessions(projectPath, currentBaseagent, selfAID ?? session?.selfAID)
|
|
3315
3441
|
: await this.sessionManager.listImportableCliSessions(projectPath, currentBaseagent);
|
|
@@ -3484,7 +3610,10 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
|
|
|
3484
3610
|
const session = await getExistingSessionForCommand();
|
|
3485
3611
|
if (!session)
|
|
3486
3612
|
return { kind: 'command.error', text: '❌ 当前没有活跃会话' };
|
|
3487
|
-
const rewindBaseagent = session.baseagent
|
|
3613
|
+
const rewindBaseagent = session.baseagent;
|
|
3614
|
+
if (!rewindBaseagent) {
|
|
3615
|
+
return { kind: 'command.error', text: '❌ 当前会话缺少 baseagent 绑定,拒绝隐式切换' };
|
|
3616
|
+
}
|
|
3488
3617
|
const rewindAgent = this.getAgent(channel, rewindBaseagent);
|
|
3489
3618
|
if (!session.agentSessionId) {
|
|
3490
3619
|
return { kind: 'command.error', text: '❌ 当前会话无历史记录\n\n请先发送一条消息,然后再使用 /rewind' };
|
|
@@ -752,21 +752,11 @@ export function planDataMigration(root) {
|
|
|
752
752
|
}
|
|
753
753
|
}
|
|
754
754
|
catch { }
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
const aid = entry.name.slice(0, -'.jsonl'.length);
|
|
761
|
-
if (!aids.has(aid)) {
|
|
762
|
-
warnings.push(`Unresolved outbox owner retained: ${entry.name}`);
|
|
763
|
-
continue;
|
|
764
|
-
}
|
|
765
|
-
const operationId = nextId('outbox');
|
|
766
|
-
addCopy(operations, operationId, path.join(legacyOutbox, entry.name), path.join(root, 'agents', aid, 'data', 'outbox.jsonl'), stageFor(operationId));
|
|
767
|
-
}
|
|
768
|
-
}
|
|
769
|
-
catch { }
|
|
755
|
+
// Outbox layouts are imported lazily by src/aun/outbox.ts when a queue is
|
|
756
|
+
// first read or written. They are append-only delivery state, so including
|
|
757
|
+
// them in the maintenance migration would unnecessarily block startup and
|
|
758
|
+
// could race a live connector. Keep the explicit migration reserved for
|
|
759
|
+
// data that needs operator review or an exclusive maintenance window.
|
|
770
760
|
const contactAudit = path.join(root, 'data', 'contact-book-audit.jsonl');
|
|
771
761
|
// The legacy audit file may be provisioned as an empty placeholder (for
|
|
772
762
|
// example by a protected-path sandbox). An empty source has nothing to
|
|
@@ -89,6 +89,9 @@ const CATALOG = [
|
|
|
89
89
|
{ path: 'name', type: 'string', optional: true },
|
|
90
90
|
{ path: 'chatType', type: 'string', optional: true },
|
|
91
91
|
{ path: 'threadId', type: 'string', optional: true },
|
|
92
|
+
{ path: 'baseagent', type: 'string', optional: true },
|
|
93
|
+
{ path: 'previousBaseagent', type: 'string', optional: true },
|
|
94
|
+
{ path: 'baseagentChanged', type: 'boolean', optional: true },
|
|
92
95
|
{ path: 'cause', type: 'string', optional: true },
|
|
93
96
|
{ path: 'sourceSessionId', type: 'string', optional: true },
|
|
94
97
|
{ path: 'renewSource', type: 'string', optional: true },
|
|
@@ -118,6 +118,7 @@ export class HandoffRuntime {
|
|
|
118
118
|
targetSessionId: target.id,
|
|
119
119
|
payload: input.payload,
|
|
120
120
|
encrypt: input.encrypt,
|
|
121
|
+
contextOnly: input.contextOnly,
|
|
121
122
|
delivery: targetChatType === 'group'
|
|
122
123
|
? { chatType: 'group', groupId: input.to }
|
|
123
124
|
: { chatType: 'private' },
|
|
@@ -131,7 +132,8 @@ export class HandoffRuntime {
|
|
|
131
132
|
status: 'started',
|
|
132
133
|
refs: { handoffId: handoff.handoff_id, sessionId: origin.id, messageId: input.originMessageId },
|
|
133
134
|
});
|
|
134
|
-
|
|
135
|
+
if (!input.contextOnly)
|
|
136
|
+
await this.dispatcher.drain(input.selfAid, target.id);
|
|
135
137
|
const dispatched = this.store.get(input.selfAid, handoff.handoff_id) ?? handoff;
|
|
136
138
|
const replacedSessionId = [origin.id, target.id].find(sessionId => ((this.sessionDiscardSequence.get(this.sessionKey(input.selfAid, sessionId)) ?? 0) > operationSequence));
|
|
137
139
|
if (dispatched.state === 'discarded' || replacedSessionId) {
|