evolcore 0.0.20 → 0.0.22
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 +65 -0
- package/README.md +58 -9
- package/bin/codex-managed-hook.mjs +3 -0
- package/bin/install-codex-managed-hooks.mjs +3 -1
- package/dist/agents/baseagent.js +10 -6
- package/dist/agents/claude-runner.js +393 -108
- package/dist/agents/codex-app-server-client.js +41 -7
- package/dist/agents/codex-runner.js +1292 -220
- package/dist/agents/ecagent-runner.js +171 -61
- package/dist/agents/gemini-runner.js +130 -30
- package/dist/agents/request-identity.js +25 -0
- package/dist/agents/runner-types.js +19 -0
- package/dist/aun/aid/agentmd.js +59 -2
- package/dist/aun/aid/identity.js +4 -1
- package/dist/aun/aid/index.js +1 -1
- package/dist/aun/msg/group.js +72 -6
- package/dist/aun/msg/history.js +213 -36
- package/dist/aun/msg/managed-operation.js +58 -9
- package/dist/aun/msg/p2p.js +5 -0
- package/dist/aun/outbox.js +189 -80
- package/dist/aun/service-proxy.js +43 -25
- package/dist/channels/aun.js +618 -123
- package/dist/channels/daemon.js +6 -1
- package/dist/cli/agent-command.js +4 -3
- package/dist/cli/agent.js +66 -56
- package/dist/cli/aun-commands.js +177 -42
- package/dist/cli/command-log.js +10 -11
- package/dist/cli/contact.js +1 -0
- package/dist/cli/daemon-commands.js +98 -123
- package/dist/cli/init.js +27 -15
- package/dist/cli/task-context.js +50 -0
- package/dist/cli/trigger-command.js +14 -5
- package/dist/cli/watch-logs.js +10 -3
- package/dist/config/builtin-roles.js +1 -0
- package/dist/config/config-field-policy.js +19 -5
- package/dist/config/config-manager.js +167 -22
- package/dist/config/contact-book-store.js +25 -3
- package/dist/config/contact-operation-service.js +32 -1
- package/dist/config/contact-request-service.js +44 -0
- package/dist/config/daemon-services.js +186 -0
- package/dist/config/gateway-config.js +20 -9
- package/dist/config/role-service.js +54 -3
- package/dist/config/schema-migration.js +550 -0
- package/dist/config-store.js +151 -9
- package/dist/core/agent-application-service.js +279 -0
- package/dist/core/audit/log-integrity.js +102 -0
- package/dist/core/auth/agent-delegation.js +43 -1
- package/dist/core/auth/auth-gateway.js +41 -4
- package/dist/core/auth/authorization-audit.js +216 -8
- package/dist/core/auth/operation-authorizer.js +41 -1
- package/dist/core/auth/operation-catalog.js +9 -1
- package/dist/core/bootstrap-messages.js +8 -0
- package/dist/core/bootstrap-service.js +99 -27
- package/dist/core/causation/aun-association.js +7 -4
- package/dist/core/command/agent-control.js +56 -16
- package/dist/core/command/command-handler.js +311 -44
- package/dist/core/command/connect-menu.js +3 -4
- package/dist/core/command/group-menu.js +5 -7
- package/dist/core/command/menu-handler.js +288 -80
- package/dist/core/command/menu-protocol.js +1 -1
- package/dist/core/command/role-menu.js +21 -11
- package/dist/core/command/slash-gate.js +85 -18
- package/dist/core/command/slash-handler.js +377 -36
- package/dist/core/data-migration.js +11 -1
- package/dist/core/event-catalog.js +37 -0
- package/dist/core/evolagent.js +4 -0
- package/dist/core/handoff/dispatcher.js +4 -0
- package/dist/core/handoff/runtime.js +33 -3
- package/dist/core/handoff/store.js +32 -9
- package/dist/core/inference/text-inference.js +7 -15
- package/dist/core/message/im-renderer.js +90 -87
- package/dist/core/message/{inbound-admission.js → message-admission.js} +51 -1
- package/dist/core/message/message-bridge.js +184 -12
- package/dist/core/message/message-log.js +47 -7
- package/dist/core/message/message-queue.js +227 -16
- package/dist/core/message/message-utils.js +12 -5
- package/dist/core/message/response-engine.js +658 -109
- package/dist/core/message/send-receipt.js +1 -0
- package/dist/core/message/stream-debouncer.js +9 -2
- package/dist/core/model/model-catalog.js +23 -15
- package/dist/core/model/model-diagnostics.js +28 -10
- package/dist/core/permission/approval-gateway.js +180 -6
- package/dist/core/permission/ec-command-parser.js +627 -69
- package/dist/core/permission/mode.js +18 -3
- package/dist/core/{protected-paths.js → permission/protected-paths.js} +27 -14
- package/dist/core/permission/readonly-shell-query.js +263 -9
- package/dist/core/permission/sandbox-runtime.js +159 -1
- package/dist/core/permission/tool-error-code.js +12 -0
- package/dist/core/permission/tool-policy.js +618 -23
- package/dist/core/session/session-fs-store.js +154 -5
- package/dist/core/session/session-manager.js +329 -30
- package/dist/core/session/session-renew.js +37 -13
- package/dist/core/session/session-turn-coordinator.js +16 -5
- package/dist/eck/kit-renderer.js +1 -1
- package/dist/index.js +316 -50
- package/dist/ipc.js +459 -29
- package/dist/paths.js +82 -7
- package/dist/response-system/context-builder.js +1 -7
- package/dist/response-system/engines/v1/proactive-flow.js +7 -2
- package/dist/stats/price-resolver.js +4 -0
- package/dist/trigger/anomaly-store.js +1 -0
- package/dist/trigger/feedback.js +70 -7
- package/dist/trigger/history.js +79 -4
- package/dist/trigger/legacy-session-history.js +2 -2
- package/dist/trigger/parser.js +13 -3
- package/dist/trigger/scheduler.js +20 -3
- package/dist/trigger/validation.js +6 -1
- package/dist/utils/atomic-write.js +27 -0
- package/dist/utils/ecweb-utils.js +16 -2
- package/dist/utils/error-utils.js +4 -1
- package/dist/utils/logger.js +30 -6
- package/dist/utils/process-tree-stats.js +24 -4
- package/dist/utils/process-tree-worker.js +31 -0
- package/dist/utils/project-path.js +1 -2
- package/dist/utils/tool-summary.js +59 -0
- package/dist/utils/windows-shell-trust.js +201 -0
- package/kits/docs/INDEX.md +1 -1
- package/kits/docs/evolcore/INDEX.md +3 -3
- package/kits/docs/evolcore/agent-create.md +146 -0
- package/kits/docs/evolcore/agent.md +6 -0
- package/kits/docs/evolcore/contact.md +7 -1
- package/kits/docs/evolcore/group-collaboration.md +251 -0
- package/kits/docs/evolcore/group-rules.md +1 -19
- package/kits/docs/evolcore/group.md +3 -1
- package/kits/docs/evolcore/msg.md +16 -0
- package/kits/docs/evolcore/trigger.md +6 -3
- package/kits/docs/prompt-loading-architecture.md +6 -0
- package/kits/eck_message_manifest.json +6 -6
- package/kits/schemas/_meta.json +7 -4
- package/kits/schemas/agent-config.schema.11.json +13 -0
- package/kits/schemas/agent-config.schema.12.json +427 -0
- package/kits/schemas/daemon.schema.5.json +0 -1
- package/kits/schemas/daemon.schema.6.json +131 -0
- package/kits/schemas/defaults.schema.5.json +15 -3
- package/kits/schemas/migrations/README.md +3 -1
- package/kits/schemas/relation-config.schema.8.json +13 -0
- package/kits/schemas/role-config.schema.1.json +1 -2
- package/kits/schemas/single-session.schema.3.json +32 -0
- package/kits/templates/message-fragments/item.md +1 -1
- package/kits/templates/roles/admin.json +1 -0
- package/kits/templates/roles/member.json +1 -0
- package/kits/templates/roles/visitor.json +1 -0
- package/kits/templates/system-fragments/bootstrap.md +2 -1
- package/kits/templates/system-fragments/commands.md +2 -2
- package/package.json +6 -3
- package/skills/eclink/SKILL.md +2 -0
- package/dist/config/aun-gateway-config.js +0 -2
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { hasModelSwitcher, hasPermissionController } from '../../agents/runner-types.js';
|
|
2
2
|
import { getCodexEfforts } from '../../agents/codex-runner.js';
|
|
3
|
+
import { normalizeBaseagent } from '../../agents/baseagent.js';
|
|
3
4
|
import { buildEnvelope, isDeliveryTargetForChannel } from '../message/message-utils.js';
|
|
4
5
|
import { resolvePaths, getPackageRoot, daemonControlDir } from '../../paths.js';
|
|
5
6
|
import { logger } from '../../utils/logger.js';
|
|
@@ -17,14 +18,16 @@ import { resolvePermissionMode, writeScope } from '../model/config-scope.js';
|
|
|
17
18
|
import { formatPeerKey } from '../relation/peer-identity.js';
|
|
18
19
|
import { modelMatches } from '../model/model-catalog.js';
|
|
19
20
|
import { formatModelCheck, runModelCheck } from '../model/model-diagnostics.js';
|
|
20
|
-
import { filterModelsForRole, validateModelSelectionForRole } from '../model/model-permission.js';
|
|
21
|
+
import { constrainResolvedModelForRole, filterModelsForRole, validateModelSelectionForRole } from '../model/model-permission.js';
|
|
22
|
+
import { resolveRuntimeStringField } from '../role/runtime-policy.js';
|
|
21
23
|
import { displaySessionTitle } from '../session/session-title.js';
|
|
22
24
|
import { chatmodeFieldForPeer, resolveChatModeForField } from '../message/peer-mode.js';
|
|
23
25
|
import { normalizePermissionMode as normalizePermissionModeContract, PUBLIC_PERMISSION_MODES } from '../permission/mode.js';
|
|
24
26
|
import { isManagementRole } from '../../config/builtin-roles.js';
|
|
25
27
|
import { isSystemControlChannel } from '../system-channels.js';
|
|
26
28
|
import { spawnDetachedNode } from '../../utils/cross-platform.js';
|
|
27
|
-
import {
|
|
29
|
+
import { inspectDataMigrationRequirement } from '../data-migration.js';
|
|
30
|
+
import { guardIdleCommand, guardKnownCommand, guardRoleCommand, guardThreadCommand, isRecognizedSlashCommand, normalizeSlashContent, probeSessionActivity, } from './slash-gate.js';
|
|
28
31
|
const allEfforts = ['low', 'medium', 'high', 'xhigh', 'max'];
|
|
29
32
|
const PERMISSION_MODE_KEYS = PUBLIC_PERMISSION_MODES;
|
|
30
33
|
function defaultSlashChatmode(field) {
|
|
@@ -114,7 +117,7 @@ function resolveSlashRelationTarget(params) {
|
|
|
114
117
|
const actualChatType = params.session?.chatType || params.chatType;
|
|
115
118
|
const peerId = actualChatType === 'group'
|
|
116
119
|
? (params.session?.metadata?.groupId || params.channelId)
|
|
117
|
-
: (params.userId || params.session?.metadata?.peerId);
|
|
120
|
+
: (params.userId || params.session?.metadata?.peerId || params.session?.channelId || params.channelId);
|
|
118
121
|
if (!peerId)
|
|
119
122
|
return { error: 'missing current peer id', code: 'MISSING_PEER' };
|
|
120
123
|
const channelType = params.session?.channelType || this.resolveChannelType?.(params.channel) || params.channel.split('#')[0];
|
|
@@ -419,10 +422,30 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
|
|
|
419
422
|
// 权限检查:区分用户级命令和管理级命令
|
|
420
423
|
const isOwner = identity.role === 'owner';
|
|
421
424
|
const isAdmin = isManagementRole(identity.role);
|
|
422
|
-
|
|
425
|
+
// A topic's latest snapshot is authoritative and intentionally strict. Do
|
|
426
|
+
// not let a corrupt committed record turn a backend-management command into
|
|
427
|
+
// an unhandled quick-command failure; /renew will report it as UNKNOWN and
|
|
428
|
+
// leave the binding untouched.
|
|
429
|
+
let topicAuthSession;
|
|
430
|
+
let topicAuthSessionLookupFailed = false;
|
|
431
|
+
if (threadId) {
|
|
432
|
+
try {
|
|
433
|
+
topicAuthSession = await this.sessionManager.getThreadSession(channel, channelId, threadId);
|
|
434
|
+
}
|
|
435
|
+
catch (error) {
|
|
436
|
+
topicAuthSessionLookupFailed = true;
|
|
437
|
+
logger.warn(`[CommandHandler] topic session lookup failed: thread=${threadId}: ${error instanceof Error ? error.message : String(error)}`);
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
const activeChatType = topicAuthSession?.chatType
|
|
441
|
+
|| activeSession?.chatType
|
|
442
|
+
|| (chatType === 'group' ? 'group' : 'private');
|
|
423
443
|
const getExistingSessionForCommand = async () => {
|
|
424
|
-
if (threadId)
|
|
425
|
-
|
|
444
|
+
if (threadId) {
|
|
445
|
+
if (topicAuthSessionLookupFailed)
|
|
446
|
+
return undefined;
|
|
447
|
+
return topicAuthSession ?? await this.sessionManager.getThreadSession(channel, channelId, threadId);
|
|
448
|
+
}
|
|
426
449
|
return activeSession;
|
|
427
450
|
};
|
|
428
451
|
const getEffectiveChatmode = (session, fallbackChatType = activeChatType) => {
|
|
@@ -497,7 +520,102 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
|
|
|
497
520
|
agentName: this.agentRegistry?.resolveByChannel(channel)?.name ?? '<unknown>',
|
|
498
521
|
});
|
|
499
522
|
await this.processor.interruptSession(commandSession.id, 'stop');
|
|
523
|
+
this.processor?.clearPauseSession?.(commandSession.id);
|
|
524
|
+
}
|
|
525
|
+
};
|
|
526
|
+
/**
|
|
527
|
+
* Boundary-scoped activity probe shared by /renew and /compact. The queue
|
|
528
|
+
* snapshot is taken at gate installation; later arrivals are deliberately
|
|
529
|
+
* excluded because they are cutoff-after work and remain durably queued.
|
|
530
|
+
*/
|
|
531
|
+
const probeBackendActivity = (current, gateSnapshot) => {
|
|
532
|
+
const probes = {
|
|
533
|
+
paused: () => this.processor?.isPauseRequested?.(current.id) ?? false,
|
|
534
|
+
activeStream: () => this.getAgent(channel, current.baseagent).hasActiveStream(current.id),
|
|
535
|
+
};
|
|
536
|
+
if (this.permissionGateway) {
|
|
537
|
+
probes.pendingPermission = () => this.permissionGateway.getPendingRequests(current.id).length > 0;
|
|
538
|
+
}
|
|
539
|
+
if (this.interactionRouter) {
|
|
540
|
+
probes.pendingInteraction = () => this.interactionRouter.getPending(current.id).length > 0;
|
|
541
|
+
}
|
|
542
|
+
const selfAid = current.selfAID || selfAID || this.getOwningAgent(channel)?.aid;
|
|
543
|
+
if (this.handoffRuntime) {
|
|
544
|
+
probes.activeHandoff = () => {
|
|
545
|
+
if (!selfAid)
|
|
546
|
+
throw new Error('handoff activity owner unavailable');
|
|
547
|
+
return this.handoffRuntime.hasActiveSessionWork(selfAid, current.id);
|
|
548
|
+
};
|
|
549
|
+
probes.openHandoffTarget = () => {
|
|
550
|
+
if (!selfAid)
|
|
551
|
+
throw new Error('handoff activity owner unavailable');
|
|
552
|
+
return this.handoffRuntime.hasOpenTarget(selfAid, current.id);
|
|
553
|
+
};
|
|
554
|
+
}
|
|
555
|
+
return probeSessionActivity({
|
|
556
|
+
prequeue: gateSnapshot.prequeue,
|
|
557
|
+
active: gateSnapshot.active,
|
|
558
|
+
queued: gateSnapshot.queued,
|
|
559
|
+
dispatching: gateSnapshot.dispatching,
|
|
560
|
+
processing: !!current.processingState,
|
|
561
|
+
activeTurn: !!current.metadata?.turnState?.active,
|
|
562
|
+
}, probes);
|
|
563
|
+
};
|
|
564
|
+
const withIdleTopicBackend = async (sessionId, expectedThreadId, action, fallbackSession) => {
|
|
565
|
+
// Main-session /compact keeps its existing idle guard and provider path.
|
|
566
|
+
// Only topic backend operations require the stronger dequeue barrier.
|
|
567
|
+
if (!expectedThreadId) {
|
|
568
|
+
let current = fallbackSession;
|
|
569
|
+
if (!current) {
|
|
570
|
+
try {
|
|
571
|
+
current = typeof this.sessionManager.getSessionById === 'function'
|
|
572
|
+
? await this.sessionManager.getSessionById(sessionId)
|
|
573
|
+
: undefined;
|
|
574
|
+
}
|
|
575
|
+
catch (error) {
|
|
576
|
+
logger.warn(`[CommandHandler] main session snapshot read failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
577
|
+
return { ok: false, reason: 'unknown' };
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
if (!current || current.threadId)
|
|
581
|
+
return { ok: false, reason: 'missing' };
|
|
582
|
+
// Preserve the legacy main-session /compact exclusion. Topic commands
|
|
583
|
+
// use the stronger barrier below; main sessions retain the historical
|
|
584
|
+
// keyed dequeue lock and provider behavior.
|
|
585
|
+
const releaseLock = typeof this.messageQueue.acquireLock === 'function'
|
|
586
|
+
? this.messageQueue.acquireLock(sessionId)
|
|
587
|
+
: undefined;
|
|
588
|
+
try {
|
|
589
|
+
return { ok: true, value: await action(current) };
|
|
590
|
+
}
|
|
591
|
+
finally {
|
|
592
|
+
releaseLock?.();
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
if (typeof this.messageQueue.withSessionBarrier !== 'function') {
|
|
596
|
+
return { ok: false, reason: 'unknown' };
|
|
500
597
|
}
|
|
598
|
+
return this.messageQueue.withSessionBarrier(sessionId, async (snapshot) => {
|
|
599
|
+
let current;
|
|
600
|
+
try {
|
|
601
|
+
current = typeof this.sessionManager.getSessionById === 'function'
|
|
602
|
+
? await this.sessionManager.getSessionById(sessionId)
|
|
603
|
+
: expectedThreadId
|
|
604
|
+
? await this.sessionManager.getThreadSession(channel, channelId, expectedThreadId)
|
|
605
|
+
: fallbackSession;
|
|
606
|
+
}
|
|
607
|
+
catch (error) {
|
|
608
|
+
logger.warn(`[CommandHandler] session snapshot read failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
609
|
+
return { ok: false, reason: 'unknown' };
|
|
610
|
+
}
|
|
611
|
+
if (!current || (current.threadId || undefined) !== expectedThreadId) {
|
|
612
|
+
return { ok: false, reason: 'missing' };
|
|
613
|
+
}
|
|
614
|
+
const activity = probeBackendActivity(current, snapshot);
|
|
615
|
+
if (activity !== 'idle')
|
|
616
|
+
return { ok: false, reason: activity };
|
|
617
|
+
return { ok: true, value: await action(current, snapshot) };
|
|
618
|
+
});
|
|
501
619
|
};
|
|
502
620
|
// /help 命令不需要会话
|
|
503
621
|
if (normalizedContent === '/help') {
|
|
@@ -517,7 +635,7 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
|
|
|
517
635
|
const appendProcessCommands = (lines) => {
|
|
518
636
|
if (!isDaemonOwner)
|
|
519
637
|
return;
|
|
520
|
-
lines.push('', '🛠️ 进程级运维:', ' /restart - 重启服务', ' /upgrade - 检查版本更新', ' /reload [aid] - 热重载 Agent 配置');
|
|
638
|
+
lines.push('', '🛠️ 进程级运维:', ' /restart - 重启服务', ' /upgrade - 检查版本更新', ' /reload [aid] - 热重载 Agent 配置', ' /fa <提示词> - 单次使用宿主级 fullaccess 执行(别名: /fullaccess)');
|
|
521
639
|
};
|
|
522
640
|
if (!isAdmin && activeChatType === 'group') {
|
|
523
641
|
const lines = [
|
|
@@ -559,6 +677,7 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
|
|
|
559
677
|
' /s [cli|名称|序号|uuid] - 列出或切换会话(cli 查看未导入的 CLI 会话)',
|
|
560
678
|
' /name <新名称> - 重命名当前会话',
|
|
561
679
|
' /del <名称> - 删除指定会话(仅解绑,不删除文件)',
|
|
680
|
+
...(threadId ? [' /renew - 在当前话题中轮换 backend'] : []),
|
|
562
681
|
' /pause - 在下一次工具调用前暂停当前任务',
|
|
563
682
|
' /resume - 继续已暂停的当前任务',
|
|
564
683
|
' /stop - 中断当前任务',
|
|
@@ -608,6 +727,7 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
|
|
|
608
727
|
' /fork [名称] - 分支当前会话(从当前对话点创建分支)',
|
|
609
728
|
' /rewind [N] [chat|file|all] - 查看历史/撤销指定轮次(别名: /rw)',
|
|
610
729
|
' /compact - 压缩会话上下文(减少 token 用量)',
|
|
730
|
+
' /renew - 在话题中轮换 backend(下一条消息懒初始化)',
|
|
611
731
|
' /pause - 在下一次工具调用前暂停当前任务',
|
|
612
732
|
' /resume - 继续已暂停的当前任务',
|
|
613
733
|
' /stop - 中断当前任务',
|
|
@@ -639,6 +759,7 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
|
|
|
639
759
|
...(isDaemonOwner ? [
|
|
640
760
|
' /restart - 重启服务',
|
|
641
761
|
' /reload [aid] - 热重载 Agent 配置',
|
|
762
|
+
' /fa <提示词> - 单次使用宿主级 fullaccess 执行(别名: /fullaccess)',
|
|
642
763
|
] : []),
|
|
643
764
|
...(!isDaemonOwner && isAdmin ? [
|
|
644
765
|
' /reload - 热重载当前 Agent 配置',
|
|
@@ -682,6 +803,9 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
|
|
|
682
803
|
cmds.push({ command: '/rewind', aliases: ['/rw'], args: '[N] [chat|file|all]', description: '查看历史/撤销指定轮次', category: '会话管理', roles: ['admin', 'owner'] });
|
|
683
804
|
cmds.push({ command: '/compact', description: '压缩会话上下文(减少 token 用量)', category: '会话管理', roles: ['admin', 'owner'] });
|
|
684
805
|
}
|
|
806
|
+
if (threadId && (activeChatType !== 'group' || isAdmin)) {
|
|
807
|
+
cmds.push({ command: '/renew', description: '轮换当前话题 backend', category: '会话管理', roles: activeChatType === 'group' ? ['admin', 'owner'] : ['visitor', 'member', 'admin', 'owner'] });
|
|
808
|
+
}
|
|
685
809
|
// Agent 与模型
|
|
686
810
|
if (isAdmin)
|
|
687
811
|
cmds.push({ command: '/baseagent', aliases: ['/base'], args: '[name]', description: '查看或切换 Agent 后端', category: 'Agent 与模型', roles: ['admin', 'owner'] });
|
|
@@ -707,6 +831,7 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
|
|
|
707
831
|
cmds.push({ command: '/observable', args: '[true|false]', description: '查看或切换观察者模式', category: '运维', roles: ['owner'] });
|
|
708
832
|
}
|
|
709
833
|
if (isDaemonOwner) {
|
|
834
|
+
cmds.push({ command: '/fa', aliases: ['/fullaccess'], args: '<提示词>', description: '仅让当前提示词对应的 run 使用宿主级 fullaccess', category: '权限管理', roles: ['daemon-owner'] });
|
|
710
835
|
cmds.push({ command: '/restart', description: '重启服务', category: '运维', roles: ['daemon-owner'] });
|
|
711
836
|
cmds.push({ command: '/upgrade', description: '检查版本更新', category: '运维', roles: ['daemon-owner'] });
|
|
712
837
|
cmds.push({ command: '/reload', args: '[aid]', description: '热重载 Agent 配置', category: '运维', roles: ['daemon-owner'] });
|
|
@@ -962,7 +1087,12 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
|
|
|
962
1087
|
}
|
|
963
1088
|
await interruptPausedSessionBeforeReplacement(await getExistingSessionForCommand());
|
|
964
1089
|
const previousDefaultBaseagent = owningAgent.baseagent || this.parseDefaultBaseagent();
|
|
965
|
-
|
|
1090
|
+
try {
|
|
1091
|
+
owningAgent.setActiveBaseagent(args);
|
|
1092
|
+
}
|
|
1093
|
+
catch (error) {
|
|
1094
|
+
return { kind: 'command.error', text: `❌ ${error instanceof Error ? error.message : String(error)}` };
|
|
1095
|
+
}
|
|
966
1096
|
this.eventBus.publish({
|
|
967
1097
|
type: 'agent:baseagent-changed',
|
|
968
1098
|
aid: owningAgent.aid,
|
|
@@ -1655,7 +1785,7 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
|
|
|
1655
1785
|
}).join('\n');
|
|
1656
1786
|
return { kind: 'command.error', text: `❌ 该 Agent 有 ${busyInfo.count} 个任务执行中,无法 reload。\n\n处理中:\n${processingLines}\n\n等待任务完成后重试,通常 30-60 秒。` };
|
|
1657
1787
|
}
|
|
1658
|
-
const res = await execAgentAction('reload', { aid: targetAid }, userId ?? '', this.eventBus);
|
|
1788
|
+
const res = await execAgentAction('reload', { aid: targetAid }, userId ?? '', this.eventBus, this.agentApplicationService);
|
|
1659
1789
|
if ('error' in res)
|
|
1660
1790
|
return { kind: 'command.error', text: `❌ reload 失败:${res.error}` };
|
|
1661
1791
|
return { kind: 'command.result', text: `✅ Agent ${targetAid} 配置已重载` };
|
|
@@ -2009,41 +2139,228 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
|
|
|
2009
2139
|
if (normalizedContent === '/clear') {
|
|
2010
2140
|
return { kind: 'command.error', text: '⚠️ /clear 已移除\n\n请使用 /new [名称] 创建新会话来开始全新上下文。旧会话会保留,可通过 /s 查看或切换。' };
|
|
2011
2141
|
}
|
|
2012
|
-
// /
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
if (
|
|
2016
|
-
return { kind: '
|
|
2017
|
-
const sessionAgent = this.getAgent(channel, session.baseagent);
|
|
2018
|
-
if (!sessionAgent.capabilities?.compact) {
|
|
2019
|
-
return { kind: 'command.error', text: `❌ 当前 Agent (${sessionAgent.name}) 不支持 /compact` };
|
|
2142
|
+
// /renew:只支持话题会话。它废止当前 backend 绑定;下一条普通消息
|
|
2143
|
+
// 才会在新的 TurnLease 中懒创建并绑定新 backend。
|
|
2144
|
+
if (normalizedContent === '/renew' || /^\/renew\s/.test(normalizedContent)) {
|
|
2145
|
+
if (normalizedContent !== '/renew') {
|
|
2146
|
+
return { kind: 'system.error', text: '❌ /renew 不接受参数,请直接使用 /renew', subtype: 'session.renew.invalid_arguments', recoverable: true };
|
|
2020
2147
|
}
|
|
2021
|
-
if (!
|
|
2022
|
-
return { kind: '
|
|
2148
|
+
if (!threadId) {
|
|
2149
|
+
return { kind: 'system.error', text: '⚠️ /renew 仅支持话题会话', subtype: 'session.renew.unsupported', recoverable: true };
|
|
2023
2150
|
}
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
:
|
|
2028
|
-
|
|
2151
|
+
// Card callbacks use synthetic message IDs on several adapters. They are
|
|
2152
|
+
// suitable for UI correlation, but not for the durable inbound claim.
|
|
2153
|
+
if (source === 'card-trigger') {
|
|
2154
|
+
return { kind: 'system.error', text: '⚠️ /renew 仅支持携带原生消息 ID 的普通消息', subtype: 'session.renew.invalid_source', recoverable: true };
|
|
2155
|
+
}
|
|
2156
|
+
let renewSession;
|
|
2029
2157
|
try {
|
|
2158
|
+
// Re-read rather than trusting the authorization lookup above: only a
|
|
2159
|
+
// fresh strict snapshot may be used to choose the barrier key.
|
|
2160
|
+
renewSession = await this.sessionManager.getThreadSession(channel, channelId, threadId);
|
|
2161
|
+
}
|
|
2162
|
+
catch (error) {
|
|
2163
|
+
logger.warn(`[CommandHandler] /renew topic lookup failed: thread=${threadId}: ${error instanceof Error ? error.message : String(error)}`);
|
|
2164
|
+
return { kind: 'system.error', text: '⚠️ 当前会话状态未知,无法安全轮换,请稍后再试', subtype: 'session.renew.state_unknown', recoverable: true };
|
|
2165
|
+
}
|
|
2166
|
+
if (!renewSession) {
|
|
2167
|
+
return { kind: 'system.error', text: '❌ 找不到当前话题会话', subtype: 'session.renew.session_missing', recoverable: true };
|
|
2168
|
+
}
|
|
2169
|
+
const inboundMessageId = messageId?.trim();
|
|
2170
|
+
if (!inboundMessageId) {
|
|
2171
|
+
return { kind: 'system.error', text: '❌ /renew 缺少稳定的消息 ID,当前渠道不支持安全重试', subtype: 'session.renew.message_id_missing', recoverable: true };
|
|
2172
|
+
}
|
|
2173
|
+
if (typeof this.sessionManager.rotateTopicBackend !== 'function'
|
|
2174
|
+
|| typeof this.messageQueue.withSessionBarrier !== 'function'
|
|
2175
|
+
|| typeof this.messageQueue.claimInbound !== 'function') {
|
|
2176
|
+
return { kind: 'system.error', text: '❌ 当前运行环境不支持安全 backend 轮换', subtype: 'session.renew.unavailable', recoverable: true };
|
|
2177
|
+
}
|
|
2178
|
+
try {
|
|
2179
|
+
const outcome = await this.messageQueue.withSessionBarrier(renewSession.id, async (snapshot) => {
|
|
2180
|
+
let current;
|
|
2181
|
+
try {
|
|
2182
|
+
current = typeof this.sessionManager.getSessionById === 'function'
|
|
2183
|
+
? await this.sessionManager.getSessionById(renewSession.id)
|
|
2184
|
+
: undefined;
|
|
2185
|
+
}
|
|
2186
|
+
catch (error) {
|
|
2187
|
+
logger.warn(`[CommandHandler] /renew strict session read failed: session=${renewSession.id}: ${error instanceof Error ? error.message : String(error)}`);
|
|
2188
|
+
return { kind: 'unknown' };
|
|
2189
|
+
}
|
|
2190
|
+
if (!current || current.threadId !== threadId)
|
|
2191
|
+
return { kind: 'missing' };
|
|
2192
|
+
let claim;
|
|
2193
|
+
try {
|
|
2194
|
+
claim = this.messageQueue.claimInbound(current.id, inboundMessageId, selfAID ?? current.selfAID);
|
|
2195
|
+
}
|
|
2196
|
+
catch (error) {
|
|
2197
|
+
logger.error(`[CommandHandler] /renew inbound claim failed: session=${current.id}: ${error instanceof Error ? error.message : String(error)}`);
|
|
2198
|
+
return { kind: 'claim_failed' };
|
|
2199
|
+
}
|
|
2200
|
+
if (claim === 'duplicate')
|
|
2201
|
+
return { kind: 'duplicate' };
|
|
2202
|
+
const activity = probeBackendActivity(current, snapshot);
|
|
2203
|
+
if (activity !== 'idle')
|
|
2204
|
+
return { kind: activity };
|
|
2205
|
+
const result = await this.sessionManager.rotateTopicBackend({
|
|
2206
|
+
sessionId: current.id,
|
|
2207
|
+
expectedThreadId: threadId,
|
|
2208
|
+
});
|
|
2209
|
+
return {
|
|
2210
|
+
kind: 'rotated',
|
|
2211
|
+
wasBound: result.wasBound,
|
|
2212
|
+
};
|
|
2213
|
+
});
|
|
2214
|
+
if (outcome.kind === 'missing') {
|
|
2215
|
+
return { kind: 'system.error', text: '❌ 当前话题会话已不存在或归属已变化', subtype: 'session.renew.session_changed', recoverable: true };
|
|
2216
|
+
}
|
|
2217
|
+
if (outcome.kind === 'claim_failed') {
|
|
2218
|
+
return { kind: 'system.error', text: '❌ 当前渠道无法安全记录该请求,未执行 backend 轮换', subtype: 'session.renew.claim_failed', recoverable: true };
|
|
2219
|
+
}
|
|
2220
|
+
if (outcome.kind === 'duplicate') {
|
|
2221
|
+
return { kind: 'system.notice', text: 'ℹ️ 该请求已接收过,不再重复执行', subtype: 'session.renew.duplicate' };
|
|
2222
|
+
}
|
|
2223
|
+
if (outcome.kind === 'busy' || outcome.kind === 'unknown') {
|
|
2224
|
+
return {
|
|
2225
|
+
kind: 'system.error',
|
|
2226
|
+
text: outcome.kind === 'busy'
|
|
2227
|
+
? '⚠️ 当前会话忙碌,无法安全轮换,请稍后再试'
|
|
2228
|
+
: '⚠️ 当前会话状态未知,无法安全轮换,请稍后再试',
|
|
2229
|
+
subtype: outcome.kind === 'busy' ? 'session.renew.busy' : 'session.renew.state_unknown',
|
|
2230
|
+
recoverable: true,
|
|
2231
|
+
};
|
|
2232
|
+
}
|
|
2233
|
+
if (outcome.kind !== 'rotated') {
|
|
2234
|
+
return { kind: 'system.error', text: '⚠️ 当前会话状态未知,无法安全轮换,请稍后再试', subtype: 'session.renew.state_unknown', recoverable: true };
|
|
2235
|
+
}
|
|
2236
|
+
return {
|
|
2237
|
+
kind: 'system.notice',
|
|
2238
|
+
text: `✅ backend 已切换${outcome.wasBound ? '' : '(当前未绑定 backend)'};下一条消息将初始化新 backend(阶段一不携带历史摘要)`,
|
|
2239
|
+
subtype: 'session.renewed',
|
|
2240
|
+
};
|
|
2241
|
+
}
|
|
2242
|
+
catch (error) {
|
|
2243
|
+
logger.error(`[CommandHandler] /renew failed: session=${renewSession.id}: ${error instanceof Error ? error.message : String(error)}`);
|
|
2244
|
+
return { kind: 'system.error', text: `❌ backend 轮换失败:${error instanceof Error ? error.message : String(error)}`, subtype: 'session.renew.failed', recoverable: true };
|
|
2245
|
+
}
|
|
2246
|
+
}
|
|
2247
|
+
// /compact 命令:手动压缩会话上下文
|
|
2248
|
+
if (normalizedContent === '/compact') {
|
|
2249
|
+
const session = await getExistingSessionForCommand();
|
|
2250
|
+
if (!session) {
|
|
2251
|
+
return topicAuthSessionLookupFailed
|
|
2252
|
+
? { kind: 'command.error', text: '⚠️ 当前会话状态未知,无法安全压缩,请稍后再试' }
|
|
2253
|
+
: { kind: 'command.error', text: '❌ 当前没有活跃会话,无需压缩' };
|
|
2254
|
+
}
|
|
2255
|
+
if (!session.threadId)
|
|
2256
|
+
await interruptPausedSessionBeforeReplacement(session);
|
|
2257
|
+
const outcome = await withIdleTopicBackend(session.id, session.threadId || undefined, async (session) => {
|
|
2258
|
+
if (!session.agentSessionId) {
|
|
2259
|
+
return { kind: 'command.error', text: '❌ 当前会话没有历史记录,无需压缩' };
|
|
2260
|
+
}
|
|
2261
|
+
const projectPath = path.isAbsolute(session.projectPath)
|
|
2262
|
+
? session.projectPath
|
|
2263
|
+
: path.resolve(process.cwd(), session.projectPath);
|
|
2264
|
+
const currentSessionAgent = this.getAgent(channel, session.baseagent);
|
|
2265
|
+
if (!currentSessionAgent.capabilities?.compact) {
|
|
2266
|
+
return { kind: 'command.error', text: `❌ 当前 Agent (${currentSessionAgent.name}) 不支持 /compact` };
|
|
2267
|
+
}
|
|
2030
2268
|
if (sendMessage) {
|
|
2031
2269
|
await sendMessage(channelId, '⏳ 正在压缩会话上下文...', this.getReplyContext(session));
|
|
2032
2270
|
}
|
|
2033
|
-
|
|
2034
|
-
|
|
2271
|
+
// Resolve the same relation/agent effective model used for normal turns.
|
|
2272
|
+
// The runner instance's model is only the final fallback; without this
|
|
2273
|
+
// override Claude's /compact command could resume with a stale agent
|
|
2274
|
+
// default (for example glm-5.3) while the turn itself used gpt-5.6-sol.
|
|
2275
|
+
const compactBaseagentRaw = session.baseagent || currentSessionAgent.name;
|
|
2276
|
+
const compactBaseagentNormalized = normalizeBaseagent(compactBaseagentRaw);
|
|
2277
|
+
const compactBaseagent = compactBaseagentNormalized.canonical === 'unknown'
|
|
2278
|
+
? compactBaseagentRaw
|
|
2279
|
+
: compactBaseagentNormalized.canonical;
|
|
2280
|
+
const compactChatType = session.chatType;
|
|
2281
|
+
const compactRelation = resolveSlashRelationTarget.call(this, {
|
|
2282
|
+
session,
|
|
2283
|
+
channel,
|
|
2284
|
+
channelId,
|
|
2285
|
+
userId,
|
|
2286
|
+
selfAID,
|
|
2287
|
+
role: session.identity?.role || identity.role,
|
|
2288
|
+
chatType: compactChatType,
|
|
2289
|
+
});
|
|
2290
|
+
const compactSelector = 'error' in compactRelation
|
|
2291
|
+
? { self: selfAID ?? session.selfAID ?? this.resolveSelfAID(channel) }
|
|
2292
|
+
: compactRelation;
|
|
2293
|
+
let compactConfig = {};
|
|
2294
|
+
try {
|
|
2295
|
+
compactConfig = (resolveEffective(compactSelector, { cache: true }).baseagents || {})[compactBaseagent] || {};
|
|
2296
|
+
}
|
|
2297
|
+
catch {
|
|
2298
|
+
compactConfig = {};
|
|
2299
|
+
}
|
|
2300
|
+
const compactModelDecision = constrainResolvedModelForRole({
|
|
2301
|
+
role: session.identity?.role || identity.role,
|
|
2302
|
+
baseagent: compactBaseagent,
|
|
2303
|
+
model: typeof compactConfig.model === 'string' ? compactConfig.model : undefined,
|
|
2304
|
+
resolveModelId: typeof currentSessionAgent.resolveModelId === 'function'
|
|
2305
|
+
? currentSessionAgent.resolveModelId.bind(currentSessionAgent)
|
|
2306
|
+
: undefined,
|
|
2307
|
+
selfAid: compactSelector.self,
|
|
2308
|
+
});
|
|
2309
|
+
const compactModel = compactModelDecision.model
|
|
2310
|
+
|| (typeof currentSessionAgent.getModel === 'function' ? currentSessionAgent.getModel() : undefined);
|
|
2311
|
+
const compactEffortDecision = resolveRuntimeStringField({
|
|
2312
|
+
selfAid: compactSelector.self,
|
|
2313
|
+
role: session.identity?.role || identity.role,
|
|
2314
|
+
field: `baseagents.${compactBaseagent}.effort`,
|
|
2315
|
+
configuredValue: compactConfig.effort,
|
|
2316
|
+
});
|
|
2317
|
+
const compactConfiguredEffort = compactEffortDecision.effectiveValue;
|
|
2318
|
+
const compactEffort = compactConfiguredEffort && compactConfiguredEffort !== 'auto'
|
|
2319
|
+
? compactConfiguredEffort
|
|
2320
|
+
: (compactConfiguredEffort === 'auto' ? undefined : currentSessionAgent.getEffort?.());
|
|
2321
|
+
// Only send an explicit override when config/role resolution decided a
|
|
2322
|
+
// value. If neither scope provides one, the runner's own getModel/getEffort
|
|
2323
|
+
// fallback is the same effective value and preserving an omitted fourth
|
|
2324
|
+
// argument keeps older runner adapters source-compatible.
|
|
2325
|
+
const hasExplicitCompactModel = typeof compactConfig.model === 'string' && compactConfig.model.trim().length > 0
|
|
2326
|
+
|| compactModelDecision.constrained;
|
|
2327
|
+
const hasExplicitCompactEffort = typeof compactConfig.effort === 'string' && compactConfig.effort.trim().length > 0
|
|
2328
|
+
|| compactEffortDecision.decidedBy === 'role';
|
|
2329
|
+
const compactOverride = (hasExplicitCompactModel && compactModel) || (hasExplicitCompactEffort && compactEffort)
|
|
2330
|
+
? {
|
|
2331
|
+
...(hasExplicitCompactModel && compactModel ? { model: compactModel } : {}),
|
|
2332
|
+
...(hasExplicitCompactEffort && compactEffort ? { effort: compactEffort } : {}),
|
|
2333
|
+
}
|
|
2334
|
+
: undefined;
|
|
2335
|
+
const compacted = compactOverride
|
|
2336
|
+
? await currentSessionAgent.compactSession(session.id, session.agentSessionId, projectPath, compactOverride)
|
|
2337
|
+
: await currentSessionAgent.compactSession(session.id, session.agentSessionId, projectPath);
|
|
2338
|
+
const compactResult = compacted;
|
|
2339
|
+
if (compactResult === true || compactResult?.ok === true) {
|
|
2035
2340
|
return {
|
|
2036
2341
|
kind: 'command.result',
|
|
2037
2342
|
text: '✅ 会话压缩完成',
|
|
2038
2343
|
};
|
|
2039
2344
|
}
|
|
2040
2345
|
else {
|
|
2041
|
-
return {
|
|
2346
|
+
return {
|
|
2347
|
+
kind: 'command.error',
|
|
2348
|
+
text: `❌ 会话压缩失败(${compactResult?.code ?? 'sdk_error'}):${compactResult?.message ?? 'compact failed'}`,
|
|
2349
|
+
};
|
|
2042
2350
|
}
|
|
2351
|
+
}, session);
|
|
2352
|
+
if (!outcome.ok) {
|
|
2353
|
+
if (outcome.reason === 'missing') {
|
|
2354
|
+
return { kind: 'command.error', text: '❌ 当前会话已不存在或归属已变化' };
|
|
2355
|
+
}
|
|
2356
|
+
return {
|
|
2357
|
+
kind: 'command.error',
|
|
2358
|
+
text: outcome.reason === 'busy'
|
|
2359
|
+
? '⚠️ 当前会话忙碌,请稍后再试\n使用 /stop 中断当前任务后重试'
|
|
2360
|
+
: '⚠️ 当前会话状态未知,无法安全压缩,请稍后再试',
|
|
2361
|
+
};
|
|
2043
2362
|
}
|
|
2044
|
-
|
|
2045
|
-
releaseLock();
|
|
2046
|
-
}
|
|
2363
|
+
return outcome.value;
|
|
2047
2364
|
}
|
|
2048
2365
|
// 后续命令可读取现有会话,但不应在这里隐式创建新会话。
|
|
2049
2366
|
// 真正需要新会话的命令应显式调用 createNewSession()。
|
|
@@ -2138,13 +2455,17 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
|
|
|
2138
2455
|
if (sessionName) {
|
|
2139
2456
|
const existing = await this.sessionManager.getSessionByName(channel, channelId, sessionName);
|
|
2140
2457
|
if (existing) {
|
|
2141
|
-
return { kind: '
|
|
2458
|
+
return { kind: 'system.error', text: `❌ 会话名称 "${sessionName}" 已存在,请使用其他名称`, subtype: 'session.new.name_conflict', recoverable: true };
|
|
2142
2459
|
}
|
|
2143
2460
|
}
|
|
2144
2461
|
await interruptPausedSessionBeforeReplacement(session || activeSession);
|
|
2145
2462
|
const projectPath = this.getEffectiveDefaultPath(channel);
|
|
2146
2463
|
if (sendMessage && session) {
|
|
2147
|
-
await sendMessage(channelId, `⏳ 正在创建新会话${sessionName ? `: ${sessionName}` : ''}...`, this.getReplyContext(session)
|
|
2464
|
+
await sendMessage(channelId, `⏳ 正在创建新会话${sessionName ? `: ${sessionName}` : ''}...`, this.getReplyContext(session), {
|
|
2465
|
+
kind: 'system.notice',
|
|
2466
|
+
text: `⏳ 正在创建新会话${sessionName ? `: ${sessionName}` : ''}...`,
|
|
2467
|
+
subtype: 'session.creating',
|
|
2468
|
+
});
|
|
2148
2469
|
}
|
|
2149
2470
|
const newSessionBaseagent = this.agentRegistry?.resolveByChannel(channel)?.baseagent || this.parseDefaultBaseagent();
|
|
2150
2471
|
const previousMetadata = session?.metadata || activeSession?.metadata || {};
|
|
@@ -2211,7 +2532,11 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
|
|
|
2211
2532
|
?? newRunner.getEffort?.()
|
|
2212
2533
|
?? newAgent?.effort;
|
|
2213
2534
|
const backendBits = [newBaseagent, backendModel, backendEffort].filter(Boolean).join(' · ');
|
|
2214
|
-
return {
|
|
2535
|
+
return {
|
|
2536
|
+
kind: 'system.notice',
|
|
2537
|
+
text: `✓ 已创建新会话${sessionName ? `: ${sessionName}` : ''}\n 项目: ${this.getProjectName(projectPath)}\n 后端: ${backendBits}\n 之前的对话历史已保留,可通过 /s 查看${handoffDiscardWarning ? `\n${handoffDiscardWarning}` : ''}`,
|
|
2538
|
+
subtype: 'session.created',
|
|
2539
|
+
};
|
|
2215
2540
|
}
|
|
2216
2541
|
// /check 命令:检查 EvolAgent 实例健康(visitor/member 可用,详情仅 admin)
|
|
2217
2542
|
if (normalizedContent === '/check' || normalizedContent.startsWith('/check ')) {
|
|
@@ -2460,7 +2785,6 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
|
|
|
2460
2785
|
const count = this.messageCache.getCount(s.id);
|
|
2461
2786
|
return `${s.projectPath} 有 ${count} 条新消息`;
|
|
2462
2787
|
});
|
|
2463
|
-
// 执行重启逻辑(共用于卡片回调和文本确认)
|
|
2464
2788
|
const executeRestart = async () => {
|
|
2465
2789
|
const suppressRealRestart = shouldSuppressRealRestart();
|
|
2466
2790
|
let restartReplyContext = replyContext;
|
|
@@ -2501,6 +2825,11 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
|
|
|
2501
2825
|
logger.warn(`[System] Refusing restart without a valid explicit delivery route: channel=${channel} channelId=${channelId}`);
|
|
2502
2826
|
return false;
|
|
2503
2827
|
}
|
|
2828
|
+
const migrationRequirement = inspectDataMigrationRequirement(resolvePaths().root);
|
|
2829
|
+
if (migrationRequirement.required) {
|
|
2830
|
+
logger.warn(`[System] Refusing restart while data migration is pending: operations=${migrationRequirement.operationCount}`);
|
|
2831
|
+
return { code: 'DATA_MIGRATION_REQUIRED', operationCount: migrationRequirement.operationCount };
|
|
2832
|
+
}
|
|
2504
2833
|
const restartInfo = {
|
|
2505
2834
|
channel,
|
|
2506
2835
|
channelId,
|
|
@@ -2579,6 +2908,13 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
|
|
|
2579
2908
|
}
|
|
2580
2909
|
}
|
|
2581
2910
|
const restarted = await executeRestart();
|
|
2911
|
+
if (typeof restarted === 'object' && restarted.code === 'DATA_MIGRATION_REQUIRED') {
|
|
2912
|
+
return {
|
|
2913
|
+
kind: 'command.error',
|
|
2914
|
+
text: `❌ 待处理的用户数据迁移(${restarted.operationCount} 项)阻止重启。请先运行 ec data migrate --dry-run,然后运行 ec data migrate --apply。`,
|
|
2915
|
+
reason: restarted.code,
|
|
2916
|
+
};
|
|
2917
|
+
}
|
|
2582
2918
|
if (!restarted) {
|
|
2583
2919
|
return { kind: 'command.error', text: '❌ 无法确定重启通知的出站路由,请从可信会话上下文重试' };
|
|
2584
2920
|
}
|
|
@@ -3203,7 +3539,12 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
|
|
|
3203
3539
|
const backupPath = await backupSessionFile(sessionFile);
|
|
3204
3540
|
const fsPromises = await import('fs/promises');
|
|
3205
3541
|
await fsPromises.unlink(sessionFile);
|
|
3206
|
-
|
|
3542
|
+
// A topic backend binding may only be changed by an explicit boundary
|
|
3543
|
+
// rotation/activation. Repair clears runner-local state, but must not
|
|
3544
|
+
// turn a legacy empty callback into an authoritative binding mutation.
|
|
3545
|
+
if (!repairSession.threadId) {
|
|
3546
|
+
await this.sessionManager.updateAgentSessionIdBySessionId(repairSession.id, '');
|
|
3547
|
+
}
|
|
3207
3548
|
repairAgent.updateSessionId(repairSession.id, '');
|
|
3208
3549
|
await this.sessionManager.resetHealthStatus(repairSession.id);
|
|
3209
3550
|
return { kind: 'command.result', text: `✓ 修复完成\n\n检测到问题:\n${healthCheck.issues.map((i) => `- ${i}`).join('\n')}\n\n修复操作:\n- 已备份损坏文件\n- 已删除损坏文件\n- 已重置异常计数器\n\n备份位置:${backupPath}` };
|
|
@@ -3240,7 +3581,7 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
|
|
|
3240
3581
|
});
|
|
3241
3582
|
if (triggerAuthDenied)
|
|
3242
3583
|
return triggerAuthDenied;
|
|
3243
|
-
const text = await this.handleTrigger(normalizedContent, channel, channelId, userId ?? '', isAdmin, messageId, chatType, threadId);
|
|
3584
|
+
const text = await this.handleTrigger(normalizedContent, channel, channelId, userId ?? '', isAdmin, messageId, chatType, threadId, isDaemonOwner, authSubject.canApprovePersistentFullAccess);
|
|
3244
3585
|
return { kind: 'command.result', text };
|
|
3245
3586
|
}
|
|
3246
3587
|
return null;
|
|
@@ -768,7 +768,17 @@ export function planDataMigration(root) {
|
|
|
768
768
|
}
|
|
769
769
|
catch { }
|
|
770
770
|
const contactAudit = path.join(root, 'data', 'contact-book-audit.jsonl');
|
|
771
|
-
|
|
771
|
+
// The legacy audit file may be provisioned as an empty placeholder (for
|
|
772
|
+
// example by a protected-path sandbox). An empty source has nothing to
|
|
773
|
+
// partition and must not block daemon startup.
|
|
774
|
+
let hasContactAuditRecords = false;
|
|
775
|
+
try {
|
|
776
|
+
const stat = fs.statSync(contactAudit);
|
|
777
|
+
hasContactAuditRecords = stat.isFile()
|
|
778
|
+
&& fs.readFileSync(contactAudit, 'utf8').trim().length > 0;
|
|
779
|
+
}
|
|
780
|
+
catch { }
|
|
781
|
+
if (hasContactAuditRecords) {
|
|
772
782
|
const operationId = nextId('contact-audit');
|
|
773
783
|
operations.push({ id: operationId, kind: 'contact-audit-partition', source: contactAudit, staging: path.join(stagingDir, operationId), status: 'planned' });
|
|
774
784
|
}
|