evolcore 0.0.24 → 0.0.26
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 +59 -0
- package/bin/ec.js +12 -1
- package/bin/install-codex-managed-hooks.mjs +28 -1
- package/dist/agents/baseagent.js +96 -46
- package/dist/agents/claude-runner.js +54 -13
- package/dist/agents/codex/app-server-client.js +13 -5
- package/dist/agents/codex/catalog.js +1 -1
- package/dist/agents/codex/diagnostics.js +34 -1
- package/dist/agents/codex-runner.js +128 -42
- package/dist/agents/ecagent-runner.js +14 -18
- package/dist/agents/gemini-runner.js +24 -9
- package/dist/agents/runner-types.js +25 -17
- package/dist/aun/aid/agentmd.js +135 -10
- package/dist/aun/aid/client.js +1 -1
- package/dist/aun/aid/identity.js +1 -1
- package/dist/aun/aid/index.js +2 -1
- package/dist/aun/aid/managed-operation.js +53 -1
- package/dist/aun/aid/store.js +17 -13
- package/dist/aun/menu-client.js +74 -0
- package/dist/aun/msg/group.js +1 -1
- package/dist/aun/msg/managed-operation.js +1 -1
- package/dist/aun/msg/p2p.js +6 -1
- package/dist/aun/msg/upload.js +30 -22
- package/dist/aun/storage/client.js +4 -1
- package/dist/aun/storage/index.js +1 -1
- package/dist/channels/aun.js +329 -94
- package/dist/cli/agent-command.js +125 -5
- package/dist/cli/agent-create.js +151 -11
- package/dist/cli/agent.js +119 -6
- package/dist/cli/aun-commands.js +74 -7
- package/dist/cli/config.js +27 -8
- package/dist/cli/contact.js +63 -6
- package/dist/cli/daemon-commands.js +90 -25
- package/dist/cli/diag.js +31 -0
- package/dist/cli/handoff-command.js +35 -3
- package/dist/cli/index.js +7 -1
- package/dist/cli/managed-command-guard.js +3 -0
- package/dist/cli/menu-client.js +38 -23
- package/dist/cli/restart-monitor.js +57 -10
- package/dist/config/builtin-roles.js +2 -0
- package/dist/config/config-manager.js +15 -0
- package/dist/config/config-operation-service.js +165 -3
- package/dist/config/contact-operation-service.js +96 -7
- package/dist/config/resolved-config-op.js +66 -8
- package/dist/core/agent-application-service.js +2 -1
- package/dist/core/auth/authorization-audit.js +20 -12
- package/dist/core/auth/operation-authorizer.js +87 -8
- package/dist/core/auth/operation-catalog.js +51 -4
- package/dist/core/auth/remote-access-store.js +40 -1
- package/dist/core/bootstrap-service.js +1 -1
- package/dist/core/command/agent-control.js +54 -25
- package/dist/core/command/cli-intent-parser.js +18 -0
- package/dist/core/command/command-handler.js +152 -20
- package/dist/core/command/diag-service.js +118 -0
- package/dist/core/command/menu-catalog.js +13 -3
- package/dist/core/command/menu-handler.js +88 -31
- package/dist/core/command/menu-protocol.js +6 -0
- package/dist/core/command/menu-token-store.js +41 -8
- package/dist/core/command/slash-gate.js +34 -4
- package/dist/core/command/slash-handler.js +77 -12
- package/dist/core/event-catalog.js +5 -0
- package/dist/core/evolagent.js +3 -3
- package/dist/core/handoff/runtime.js +205 -27
- package/dist/core/handoff/store.js +193 -4
- package/dist/core/message/im-renderer.js +72 -21
- package/dist/core/message/message-bridge.js +69 -9
- package/dist/core/message/message-queue.js +26 -0
- package/dist/core/message/message-utils.js +1 -0
- package/dist/core/message/response-engine.js +48 -23
- package/dist/core/model/catalog-service.js +79 -49
- package/dist/core/model/context-window-suffix.js +33 -0
- package/dist/core/model/model-catalog.js +1 -1
- package/dist/core/model/model-permission.js +12 -3
- package/dist/core/permission/ec-command-parser.js +51 -12
- package/dist/core/permission/sandbox-runtime.js +36 -14
- package/dist/core/permission/tool-policy.js +47 -7
- package/dist/index.js +195 -31
- package/dist/ipc.js +54 -2
- package/dist/response-system/engines/v1/proactive-flow.js +59 -2
- package/dist/stats/billing.js +41 -15
- package/dist/stats/price-resolver.js +28 -10
- package/dist/utils/avatar-upload.js +282 -23
- package/dist/utils/cross-platform.js +30 -1
- package/dist/utils/error-utils.js +21 -0
- package/dist/utils/evolcore-version.js +129 -0
- package/dist/utils/logger.js +3 -1
- package/dist/utils/model-prices.jsonl +3 -3
- package/dist/utils/npm-ops.js +176 -35
- package/kits/docs/context-assembly.md +1 -0
- package/kits/docs/evolcore/INDEX.md +3 -1
- package/kits/docs/evolcore/agent-create.md +30 -11
- package/kits/docs/evolcore/agent.md +18 -8
- package/kits/docs/evolcore/aid.md +6 -4
- package/kits/docs/evolcore/codex-windows-sandbox.md +146 -0
- package/kits/docs/evolcore/config.md +7 -0
- package/kits/docs/evolcore/contact.md +12 -5
- package/kits/docs/evolcore/menu-client.md +11 -3
- package/kits/docs/evolcore/msg.md +1 -0
- package/kits/docs/prompt-loading-architecture.md +1 -0
- package/kits/docs/venues/group.md +12 -0
- package/kits/eck_manifest.json +12 -0
- package/kits/rules/03-identity.md +3 -1
- package/kits/templates/message-fragments/handoff-request-to-target.md +3 -3
- package/kits/templates/roles/admin.json +14 -2
- package/kits/templates/roles/member.json +3 -2
- package/kits/templates/roles/visitor.json +4 -3
- package/kits/templates/system-fragments/bootstrap.md +16 -5
- package/kits/templates/system-fragments/channel.md +1 -1
- package/kits/templates/system-fragments/session.md +1 -1
- package/package.json +6 -4
- package/skills/eclink/SKILL.md +0 -84
- package/skills/eclink/agents/openai.yaml +0 -4
|
@@ -28,7 +28,7 @@ import { isSystemControlChannel } from '../system-channels.js';
|
|
|
28
28
|
import { launchMonitorAndConfirm, resolveMonitorLaunch, cleanupMonitorStage } from '../../utils/cross-platform.js';
|
|
29
29
|
import { scanInstances } from '../../utils/instance-registry.js';
|
|
30
30
|
import { inspectDataMigrationRequirement } from '../data-migration.js';
|
|
31
|
-
import { guardIdleCommand, guardKnownCommand, guardRoleCommand, guardThreadCommand, isRecognizedSlashCommand, normalizeSlashContent, probeSessionActivity, } from './slash-gate.js';
|
|
31
|
+
import { guardIdleCommand, guardGroupSlashCommand, guardKnownCommand, guardRoleCommand, guardThreadCommand, isRecognizedSlashCommand, normalizeSlashContent, probeSessionActivity, } from './slash-gate.js';
|
|
32
32
|
const allEfforts = ['none', 'minimal', 'low', 'medium', 'high', 'xhigh', 'max'];
|
|
33
33
|
const PERMISSION_MODE_KEYS = PUBLIC_PERMISSION_MODES;
|
|
34
34
|
function defaultSlashChatmode(field) {
|
|
@@ -368,7 +368,7 @@ async function getGitWorkingDirInfo(projectPath) {
|
|
|
368
368
|
return null; // git 命令失败时静默返回 null
|
|
369
369
|
}
|
|
370
370
|
}
|
|
371
|
-
export async function handleSlashCommand(content, channel, channelId, sendMessage, userId, threadId, chatType, source, messageId, selfAID, overrideIdentity, overrideSubject, replyContext) {
|
|
371
|
+
export async function handleSlashCommand(content, channel, channelId, sendMessage, userId, threadId, chatType, source, messageId, selfAID, overrideIdentity, overrideSubject, replyContext, applyGroupSlashPolicy = false) {
|
|
372
372
|
// 卡片回调的 chatType 不可靠(飞书 bot 单聊 chatId 也是 oc_ 前缀),
|
|
373
373
|
// 不应覆盖 session 中已有的正确值
|
|
374
374
|
if (source === 'card-trigger')
|
|
@@ -434,9 +434,18 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
|
|
|
434
434
|
logger.warn(`[CommandHandler] topic session lookup failed: thread=${threadId}: ${error instanceof Error ? error.message : String(error)}`);
|
|
435
435
|
}
|
|
436
436
|
}
|
|
437
|
-
const activeChatType =
|
|
437
|
+
const activeChatType = (applyGroupSlashPolicy
|
|
438
|
+
? replyContext?.delivery?.chatType || overrideSubject?.chatType || chatType
|
|
439
|
+
: undefined)
|
|
440
|
+
|| topicAuthSession?.chatType
|
|
438
441
|
|| activeSession?.chatType
|
|
439
442
|
|| (chatType === 'group' ? 'group' : 'private');
|
|
443
|
+
const groupSlash = applyGroupSlashPolicy && activeChatType === 'group';
|
|
444
|
+
if (applyGroupSlashPolicy) {
|
|
445
|
+
const groupGuard = guardGroupSlashCommand(normalizedContent, activeChatType);
|
|
446
|
+
if (groupGuard)
|
|
447
|
+
return groupGuard;
|
|
448
|
+
}
|
|
440
449
|
const getExistingSessionForCommand = async () => {
|
|
441
450
|
if (threadId) {
|
|
442
451
|
if (topicAuthSessionLookupFailed)
|
|
@@ -485,7 +494,8 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
|
|
|
485
494
|
|| normalizedContent === '/upgrade'
|
|
486
495
|
|| normalizedContent === '/reload'
|
|
487
496
|
|| normalizedContent.startsWith('/reload ')
|
|
488
|
-
|| normalizedContent === '/pair'
|
|
497
|
+
|| normalizedContent === '/pair'
|
|
498
|
+
|| /^\/diag(?:\s|$)/.test(normalizedContent);
|
|
489
499
|
const roleGuard = guardRoleCommand(normalizedContent, activeChatType, isAdmin || (isDaemonOwner && isProcessLevelSlash));
|
|
490
500
|
if (roleGuard)
|
|
491
501
|
return roleGuard;
|
|
@@ -617,6 +627,30 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
|
|
|
617
627
|
};
|
|
618
628
|
// /help 命令不需要会话
|
|
619
629
|
if (normalizedContent === '/help') {
|
|
630
|
+
if (groupSlash) {
|
|
631
|
+
const lines = [
|
|
632
|
+
'群聊可用命令:',
|
|
633
|
+
' /help - 显示帮助',
|
|
634
|
+
' /status - 显示会话状态',
|
|
635
|
+
' /check - 检查 Agent 健康',
|
|
636
|
+
' /model [current|list|check] - 查询模型或检查网关',
|
|
637
|
+
' /effort - 查看推理强度',
|
|
638
|
+
' /baseagent - 查看 Agent 后端',
|
|
639
|
+
' /perm - 查看权限模式',
|
|
640
|
+
' /mentionmode - 查看群聊 @ 处理模式',
|
|
641
|
+
' /ask <选项> - 回答待处理问题',
|
|
642
|
+
' /pause - 在下一次工具调用前暂停任务',
|
|
643
|
+
' /resume - 继续已暂停的任务',
|
|
644
|
+
' /stop - 中断当前任务',
|
|
645
|
+
...(isAdmin ? [
|
|
646
|
+
' /compact - 压缩会话上下文',
|
|
647
|
+
...(threadId ? [' /renew - 轮换当前话题的后端会话'] : []),
|
|
648
|
+
] : []),
|
|
649
|
+
'',
|
|
650
|
+
'模型、推理强度、后端、权限与 @ 模式在群聊中仅支持查询。',
|
|
651
|
+
];
|
|
652
|
+
return { kind: 'command.result', text: lines.join('\n') };
|
|
653
|
+
}
|
|
620
654
|
const canReadModel = canAccessSlashOperation('model.current', 'relation')
|
|
621
655
|
|| canAccessSlashOperation('model.list', 'relation');
|
|
622
656
|
const canUseModel = canAccessSlashOperation('model.use', 'relation');
|
|
@@ -633,7 +667,7 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
|
|
|
633
667
|
const appendProcessCommands = (lines) => {
|
|
634
668
|
if (!isDaemonOwner)
|
|
635
669
|
return;
|
|
636
|
-
lines.push('', '🛠️ 进程级运维:', ' /restart - 重启服务', ' /upgrade - 检查版本更新', ' /reload [aid] - 热重载 Agent 配置', ' /fa <提示词> - 单次使用宿主级 fullaccess 执行(别名: /fullaccess)', ' /pair - 获取远程控制配对码');
|
|
670
|
+
lines.push('', '🛠️ 进程级运维:', ' /restart - 重启服务', ' /upgrade - 检查版本更新', ' /reload [aid] - 热重载 Agent 配置', ' /fa <提示词> - 单次使用宿主级 fullaccess 执行(别名: /fullaccess)', ' /pair - 获取远程控制配对码', ' /diag <问题描述> - 邀请远程诊断;/diag stop 撤销授权');
|
|
637
671
|
};
|
|
638
672
|
if (!isAdmin && activeChatType === 'group') {
|
|
639
673
|
const lines = [
|
|
@@ -773,6 +807,14 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
|
|
|
773
807
|
];
|
|
774
808
|
return { kind: 'command.result', text: lines.join('\n') };
|
|
775
809
|
}
|
|
810
|
+
if (/^\/diag(?:\s|$)/.test(normalizedContent)) {
|
|
811
|
+
if (!isDaemonOwner || !userId || !this.diagService)
|
|
812
|
+
return { kind: 'command.error', text: '❌ /diag 需要本机 DaemonOwner 和可用的诊断入口' };
|
|
813
|
+
const problem = content.trim().slice(5).trim();
|
|
814
|
+
const result = await this.diagService.execute(problem.toLowerCase() === 'stop'
|
|
815
|
+
? { action: 'stop' } : { action: 'start', problem }, userId);
|
|
816
|
+
return { kind: result.ok ? 'command.result' : 'command.error', text: result.message };
|
|
817
|
+
}
|
|
776
818
|
// /pair is also recognized on an Agent channel so it cannot fall through
|
|
777
819
|
// into model input. The daemon control channel has its own fast path; this
|
|
778
820
|
// branch preserves the same daemon-owner boundary for ordinary AUN chats.
|
|
@@ -792,6 +834,26 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
|
|
|
792
834
|
}
|
|
793
835
|
// /evolhelp 命令:返回 JSON 格式的命令列表(供程序解析)
|
|
794
836
|
if (normalizedContent === '/evolhelp') {
|
|
837
|
+
if (groupSlash) {
|
|
838
|
+
const commands = [
|
|
839
|
+
{ command: '/help', description: '显示帮助信息', category: '帮助', roles: ['visitor', 'member', 'admin', 'owner'] },
|
|
840
|
+
{ command: '/status', description: '显示会话状态', category: '运维', roles: ['visitor', 'member', 'admin', 'owner'] },
|
|
841
|
+
{ command: '/check', description: '检查 EvolAgent 实例健康', category: '运维', roles: ['visitor', 'member', 'admin', 'owner'] },
|
|
842
|
+
{ command: '/model', args: '[current|list|check]', description: '查询模型或检查模型网关', category: 'Agent 与模型', roles: [identity.role] },
|
|
843
|
+
{ command: '/effort', description: '查看推理强度', category: 'Agent 与模型', roles: [identity.role] },
|
|
844
|
+
{ command: '/baseagent', description: '查看 Agent 后端', category: 'Agent 与模型', roles: [identity.role] },
|
|
845
|
+
{ command: '/perm', description: '查看当前权限模式', category: '权限管理', roles: [identity.role] },
|
|
846
|
+
{ command: '/mentionmode', description: '查看群聊 @ 处理模式', category: '聊天设置', roles: [identity.role] },
|
|
847
|
+
{ command: '/ask', args: '<选项>', description: '回答 Agent 的交互式问题', category: '运维', roles: ['visitor', 'member', 'admin', 'owner'] },
|
|
848
|
+
{ command: '/pause', description: '在下一次工具调用前暂停当前任务', category: '运维', roles: ['admin', 'owner'] },
|
|
849
|
+
{ command: '/resume', description: '继续已暂停的当前任务', category: '运维', roles: ['admin', 'owner'] },
|
|
850
|
+
{ command: '/stop', description: '中断当前任务', category: '运维', roles: ['admin', 'owner'] },
|
|
851
|
+
];
|
|
852
|
+
if (isAdmin) {
|
|
853
|
+
commands.push({ command: '/compact', description: '压缩会话上下文', category: '会话管理', roles: ['admin', 'owner'] }, ...(threadId ? [{ command: '/renew', description: '轮换当前话题 backend', category: '会话管理', roles: ['admin', 'owner'] }] : []));
|
|
854
|
+
}
|
|
855
|
+
return { kind: 'command.result', text: JSON.stringify({ commands, categories: [...new Set(commands.map(c => c.category))] }) };
|
|
856
|
+
}
|
|
795
857
|
const cmds = [];
|
|
796
858
|
const canReadModel = canAccessSlashOperation('model.current', 'relation')
|
|
797
859
|
|| canAccessSlashOperation('model.list', 'relation');
|
|
@@ -851,6 +913,7 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
|
|
|
851
913
|
cmds.push({ command: '/upgrade', description: '检查版本更新', category: '运维', roles: ['daemon-owner'] });
|
|
852
914
|
cmds.push({ command: '/reload', args: '[aid]', description: '热重载 Agent 配置', category: '运维', roles: ['daemon-owner'] });
|
|
853
915
|
cmds.push({ command: '/pair', description: '获取远程控制配对码', category: '运维', roles: ['daemon-owner'] });
|
|
916
|
+
cmds.push({ command: '/diag', args: '<问题描述>|stop', description: '邀请远程诊断或撤销授权', category: '运维', roles: ['daemon-owner'] });
|
|
854
917
|
}
|
|
855
918
|
if (!isDaemonOwner && isAdmin) {
|
|
856
919
|
cmds.push({ command: '/reload', description: '热重载当前 Agent 配置', category: '运维', roles: ['admin', 'owner'] });
|
|
@@ -1063,7 +1126,7 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
|
|
|
1063
1126
|
if (!args) {
|
|
1064
1127
|
const activeBaseagent = owningAgent?.baseagent || this.parseDefaultBaseagent();
|
|
1065
1128
|
// 尝试发送 CommandCard 卡片
|
|
1066
|
-
if (this.interactionRouter && available.length > 1) {
|
|
1129
|
+
if (!groupSlash && this.interactionRouter && available.length > 1) {
|
|
1067
1130
|
const baseagentSession = await getExistingSessionForCommand();
|
|
1068
1131
|
const interaction = {
|
|
1069
1132
|
type: 'interaction',
|
|
@@ -1090,7 +1153,7 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
|
|
|
1090
1153
|
}
|
|
1091
1154
|
// 降级:文本
|
|
1092
1155
|
const list = available.map((a) => `${a === activeBaseagent ? ' ✓' : ' '} ${a}`).join('\n');
|
|
1093
|
-
const canSwitchAgent = isOwner;
|
|
1156
|
+
const canSwitchAgent = isOwner && !groupSlash;
|
|
1094
1157
|
if (canSwitchAgent) {
|
|
1095
1158
|
return { kind: 'command.result', text: `当前 Baseagent: ${activeBaseagent}\n\n可用:\n${list}\n用法: /baseagent <name>` };
|
|
1096
1159
|
}
|
|
@@ -1348,7 +1411,7 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
|
|
|
1348
1411
|
isDaemonOwner,
|
|
1349
1412
|
});
|
|
1350
1413
|
// 尝试发送 CommandCard 卡片
|
|
1351
|
-
if (!args && canReadModelList && this.interactionRouter && models.length > 0) {
|
|
1414
|
+
if (!groupSlash && !args && canReadModelList && this.interactionRouter && models.length > 0) {
|
|
1352
1415
|
const interaction = {
|
|
1353
1416
|
type: 'interaction',
|
|
1354
1417
|
id: `model-${Date.now()}-${crypto.randomBytes(4).toString('hex')}`,
|
|
@@ -1379,7 +1442,7 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
|
|
|
1379
1442
|
const catalogHint = `[${catalog.source}/${catalog.freshness}] ${catalog.diagnostics.map(d => d.message).join('; ')}`;
|
|
1380
1443
|
const modelList = models.map((m) => ` ${modelMatches(modelAgent, m, currentModel) ? '✓' : ' '} ${modelDisplayLabel(modelAgent, m)}`).join('\n');
|
|
1381
1444
|
const effortHint = efforts.length > 0
|
|
1382
|
-
? `\n推理强度: ${currentEffort === 'auto' ? 'auto (SDK默认)' : currentEffort} (使用 /effort 调整)`
|
|
1445
|
+
? `\n推理强度: ${currentEffort === 'auto' ? 'auto (SDK默认)' : currentEffort}${groupSlash ? '' : ' (使用 /effort 调整)'}`
|
|
1383
1446
|
: '';
|
|
1384
1447
|
if (args === 'current') {
|
|
1385
1448
|
return {
|
|
@@ -1389,7 +1452,7 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
|
|
|
1389
1452
|
};
|
|
1390
1453
|
}
|
|
1391
1454
|
if (canReadModelList) {
|
|
1392
|
-
const usage = !args && isAdmin ? '\n\n用法: /model <模型>' : '';
|
|
1455
|
+
const usage = !groupSlash && !args && isAdmin ? '\n\n用法: /model <模型>' : '';
|
|
1393
1456
|
return {
|
|
1394
1457
|
kind: 'command.result',
|
|
1395
1458
|
text: `当前模型: ${modelDisplayLabel(modelAgent, currentModel)}${effortHint}${rawModels.includes(currentModel) ? '' : ' [当前配置,未列入目录]'}\n${catalogHint}\n\n可用模型:\n${modelList}${usage}`,
|
|
@@ -1659,7 +1722,7 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
|
|
|
1659
1722
|
return { kind: 'command.error', text: '⚠️ 当前模型不支持推理强度设置' };
|
|
1660
1723
|
}
|
|
1661
1724
|
// /effort(无参数):显示当前推理强度 + 发送 CommandCard 卡片
|
|
1662
|
-
if (this.interactionRouter) {
|
|
1725
|
+
if (!groupSlash && this.interactionRouter) {
|
|
1663
1726
|
const allItems = [...efforts, 'auto'];
|
|
1664
1727
|
const interaction = {
|
|
1665
1728
|
type: 'interaction',
|
|
@@ -1687,7 +1750,7 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
|
|
|
1687
1750
|
// 降级:文本
|
|
1688
1751
|
const effortDisplay = currentEffort === 'auto' ? 'auto (SDK默认)' : currentEffort;
|
|
1689
1752
|
const effortOptions = [...efforts, 'auto'].join(' / ');
|
|
1690
|
-
if (isAdmin) {
|
|
1753
|
+
if (isAdmin && !groupSlash) {
|
|
1691
1754
|
return { kind: 'command.result', text: `推理强度: ${effortDisplay} 可选: ${effortOptions} 用法: /effort <level>` };
|
|
1692
1755
|
}
|
|
1693
1756
|
return { kind: 'command.result', text: `推理强度: ${effortDisplay}` };
|
|
@@ -2109,6 +2172,8 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
|
|
|
2109
2172
|
const currentMode = readSlashMentionMode(mentionTarget, mentionFallback);
|
|
2110
2173
|
if (!arg) {
|
|
2111
2174
|
const displayMode = currentMode ?? '未设置(跟随群设置)';
|
|
2175
|
+
if (groupSlash)
|
|
2176
|
+
return { kind: 'command.result', text: `@ 处理模式: ${displayMode}` };
|
|
2112
2177
|
// 尝试发送 CommandCard 卡片
|
|
2113
2178
|
if (isAdmin) {
|
|
2114
2179
|
const modes = [
|
|
@@ -796,6 +796,11 @@ const CATALOG = [
|
|
|
796
796
|
fields: [
|
|
797
797
|
{ path: 'aid', type: 'string' },
|
|
798
798
|
{ path: 'avatar', type: 'string' },
|
|
799
|
+
{ path: 'sha256', type: 'string', optional: true },
|
|
800
|
+
{ path: 'mime', type: 'string', optional: true },
|
|
801
|
+
{ path: 'width', type: 'number', optional: true },
|
|
802
|
+
{ path: 'height', type: 'number', optional: true },
|
|
803
|
+
{ path: 'aspectRatio', type: 'number', optional: true },
|
|
799
804
|
{ path: 'timestamp', type: 'number', optional: true },
|
|
800
805
|
],
|
|
801
806
|
},
|
package/dist/core/evolagent.js
CHANGED
|
@@ -275,9 +275,9 @@ export class EvolAgent {
|
|
|
275
275
|
return `agent-files:${this.aid}`;
|
|
276
276
|
}
|
|
277
277
|
/**
|
|
278
|
-
* 读取 personal/persona.md 内容。走 fileCache(mtime 门控):persona
|
|
279
|
-
*
|
|
280
|
-
* stat 比对、变了自动重读。文件不存在返回 null。
|
|
278
|
+
* 读取 personal/persona.md 内容。走 fileCache(mtime 门控):persona 可由
|
|
279
|
+
* `ec agent persona` 或 agent 自己更新,与 working memory 一样改了即应生效,
|
|
280
|
+
* 故每次读 stat 比对、变了自动重读。文件不存在返回 null。
|
|
281
281
|
*/
|
|
282
282
|
getPersona() {
|
|
283
283
|
const personaPath = path.join(agentPersonalDir(this.aid), 'persona.md');
|
|
@@ -11,6 +11,8 @@ const HANDOFF_STALE_WARN_MS = 72 * 60 * 60 * 1000;
|
|
|
11
11
|
const HANDOFF_RETENTION_MS = 7 * 24 * 60 * 60 * 1000;
|
|
12
12
|
const HANDOFF_LARGE_AGGREGATE_COUNT = 10;
|
|
13
13
|
const HANDOFF_LARGE_AGGREGATE_CHARS = 50_000;
|
|
14
|
+
/** Keep origin-side context bounded; the persisted handoff remains complete. */
|
|
15
|
+
const HANDOFF_ORIGIN_REQUEST_MAX_CHARS = 800;
|
|
14
16
|
export class HandoffTargetBlockedError extends Error {
|
|
15
17
|
blockingHandoffId;
|
|
16
18
|
attentionReason;
|
|
@@ -93,6 +95,8 @@ export class HandoffRuntime {
|
|
|
93
95
|
}, undefined, input.to, targetChatType, origin.baseagent, input.selfAid, 'aun', targetChatType === 'group' ? 'group' : 'agent');
|
|
94
96
|
throwIfAborted(input.signal);
|
|
95
97
|
if (target.id === origin.id) {
|
|
98
|
+
if (input.handoffContext !== undefined)
|
|
99
|
+
throw new Error('handoff context requires a different target session');
|
|
96
100
|
return { crossSession: false, targetSession: target };
|
|
97
101
|
}
|
|
98
102
|
if (!input.explicitReturnPolicy)
|
|
@@ -119,6 +123,7 @@ export class HandoffRuntime {
|
|
|
119
123
|
payload: input.payload,
|
|
120
124
|
encrypt: input.encrypt,
|
|
121
125
|
contextOnly: input.contextOnly,
|
|
126
|
+
handoffContext: input.handoffContext,
|
|
122
127
|
delivery: targetChatType === 'group'
|
|
123
128
|
? { chatType: 'group', groupId: input.to }
|
|
124
129
|
: { chatType: 'private' },
|
|
@@ -193,28 +198,108 @@ export class HandoffRuntime {
|
|
|
193
198
|
}
|
|
194
199
|
async returnHandoff(input) {
|
|
195
200
|
const returnCausation = deriveCausation(normalizeCausation(input.causation) ?? createRootCausation());
|
|
196
|
-
const response = this.
|
|
201
|
+
const response = await this.mutexes.forKey(this.returnKey(input.selfAid, input.handoffId ?? input.currentSessionId))
|
|
202
|
+
.runExclusive(async () => this.store.returnHandoff({ ...input, returnCausation }));
|
|
197
203
|
if (response.ok && !response.idempotent) {
|
|
198
204
|
recordCausationSpan(returnCausation, 'handoff.response', {
|
|
199
205
|
status: 'completed',
|
|
200
|
-
refs: {
|
|
206
|
+
refs: {
|
|
207
|
+
handoffId: response.handoff_id,
|
|
208
|
+
sessionId: input.currentSessionId,
|
|
209
|
+
...(response.result_id ? { resultId: response.result_id } : {}),
|
|
210
|
+
},
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
if (response.ok && !response.idempotent && response.result_id) {
|
|
214
|
+
void this.enqueueOrigin(input.selfAid, response.handoff_id, response.result_id);
|
|
215
|
+
}
|
|
216
|
+
else if (response.ok && !response.idempotent && response.state === 'origin_queued') {
|
|
217
|
+
void this.enqueueOrigin(input.selfAid, response.handoff_id);
|
|
218
|
+
}
|
|
219
|
+
return response;
|
|
220
|
+
}
|
|
221
|
+
/** Recover a return from the original owner session after target-session replacement. */
|
|
222
|
+
async recoverReturnHandoff(input) {
|
|
223
|
+
const instance = this.store.get(input.selfAid, input.handoffId);
|
|
224
|
+
if (!instance || instance.origin_session_id !== input.recoverySessionId) {
|
|
225
|
+
return {
|
|
226
|
+
ok: false,
|
|
227
|
+
code: 'HANDOFF_NOT_FOUND',
|
|
228
|
+
error: 'handoff is not owned by the recovery session',
|
|
229
|
+
handoff_id: input.handoffId,
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
const recovery = {
|
|
233
|
+
actorId: input.actorId,
|
|
234
|
+
recoverySessionId: input.recoverySessionId,
|
|
235
|
+
};
|
|
236
|
+
const returnCausation = deriveCausation(normalizeCausation(input.causation) ?? createRootCausation());
|
|
237
|
+
const response = await this.mutexes.forKey(this.returnKey(input.selfAid, input.handoffId))
|
|
238
|
+
.runExclusive(async () => this.store.returnHandoff({
|
|
239
|
+
selfAid: input.selfAid,
|
|
240
|
+
currentSessionId: input.recoverySessionId,
|
|
241
|
+
handoffId: input.handoffId,
|
|
242
|
+
content: input.content,
|
|
243
|
+
returnCausation,
|
|
244
|
+
recovery,
|
|
245
|
+
}));
|
|
246
|
+
if (response.ok && !response.idempotent) {
|
|
247
|
+
recordCausationSpan(returnCausation, 'handoff.response', {
|
|
248
|
+
status: 'completed',
|
|
249
|
+
refs: {
|
|
250
|
+
handoffId: response.handoff_id,
|
|
251
|
+
sessionId: input.recoverySessionId,
|
|
252
|
+
...(response.result_id ? { resultId: response.result_id } : {}),
|
|
253
|
+
},
|
|
201
254
|
});
|
|
202
255
|
}
|
|
203
|
-
if (response.ok && !response.idempotent && response.
|
|
256
|
+
if (response.ok && !response.idempotent && response.result_id) {
|
|
257
|
+
void this.enqueueOrigin(input.selfAid, response.handoff_id, response.result_id);
|
|
258
|
+
}
|
|
259
|
+
else if (response.ok && !response.idempotent && response.state === 'origin_queued') {
|
|
204
260
|
void this.enqueueOrigin(input.selfAid, response.handoff_id);
|
|
205
261
|
}
|
|
206
262
|
return response;
|
|
207
263
|
}
|
|
208
|
-
async enqueueOrigin(selfAid, handoffId) {
|
|
264
|
+
async enqueueOrigin(selfAid, handoffId, resultId) {
|
|
265
|
+
const operationSequence = ++this.lifecycleSequence;
|
|
266
|
+
await this.mutexes.forKey(this.returnKey(selfAid, handoffId)).runExclusive(async () => {
|
|
267
|
+
await this.enqueueOriginUnlocked(selfAid, handoffId, resultId, operationSequence);
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
async enqueueOriginUnlocked(selfAid, handoffId, resultId, operationSequence) {
|
|
209
271
|
const instance = this.store.get(selfAid, handoffId);
|
|
210
|
-
|
|
272
|
+
const result = resultId
|
|
273
|
+
? instance?.return_results?.find(item => item.result_id === resultId)
|
|
274
|
+
: instance?.return_results?.find(item => item.state === 'origin_queued');
|
|
275
|
+
const resultStream = Array.isArray(instance?.return_results);
|
|
276
|
+
if (resultStream) {
|
|
277
|
+
if (!instance || !result || result.state !== 'origin_queued' || !result.content)
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
else if (!instance || instance.state !== 'origin_queued' || !instance.return_content)
|
|
281
|
+
return;
|
|
282
|
+
const deliveryContent = result?.content ?? instance?.return_content;
|
|
283
|
+
if (!instance || !deliveryContent)
|
|
211
284
|
return;
|
|
212
285
|
const origin = await this.sessionManager.getSessionById(instance.origin_session_id);
|
|
213
286
|
if (!origin) {
|
|
214
|
-
|
|
287
|
+
if (result)
|
|
288
|
+
this.store.recordOriginResultEnqueueFailed(selfAid, handoffId, result.result_id, 'origin session not found');
|
|
289
|
+
else
|
|
290
|
+
this.store.recordOriginEnqueueFailed(selfAid, handoffId, 'origin session not found');
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
if ((this.sessionDiscardSequence.get(this.sessionKey(selfAid, instance.origin_session_id)) ?? 0) > operationSequence) {
|
|
215
294
|
return;
|
|
216
295
|
}
|
|
217
|
-
const
|
|
296
|
+
const deliverable = this.store.get(selfAid, handoffId);
|
|
297
|
+
const deliveryResultId = result?.result_id ?? resultId;
|
|
298
|
+
if (!this.isOriginDeliveryPending(deliverable, deliveryResultId))
|
|
299
|
+
return;
|
|
300
|
+
const messageId = result
|
|
301
|
+
? `origin-deliver:${handoffId}:${result.result_id}:${origin.id}`
|
|
302
|
+
: `origin-deliver:${handoffId}:${origin.id}`;
|
|
218
303
|
const message = {
|
|
219
304
|
channel: origin.channel,
|
|
220
305
|
channelType: origin.channelType,
|
|
@@ -226,21 +311,24 @@ export class HandoffRuntime {
|
|
|
226
311
|
peerId: origin.channelId,
|
|
227
312
|
peerName: origin.metadata?.peerName || origin.channelId,
|
|
228
313
|
peerType: origin.metadata?.peerType,
|
|
229
|
-
content:
|
|
314
|
+
content: deliveryContent,
|
|
230
315
|
messageId,
|
|
231
316
|
timestamp: Date.now(),
|
|
232
317
|
source: 'handoff',
|
|
233
318
|
groupId: origin.chatType === 'group' ? (origin.metadata?.groupId || origin.channelId) : undefined,
|
|
234
319
|
replyContext: replyContextFromSession(origin),
|
|
235
320
|
boundSessionId: origin.id,
|
|
236
|
-
causation:
|
|
237
|
-
? deriveCausation(
|
|
238
|
-
: instance.
|
|
239
|
-
? deriveCausation(instance.
|
|
240
|
-
:
|
|
321
|
+
causation: result?.return_causation
|
|
322
|
+
? deriveCausation(result.return_causation)
|
|
323
|
+
: instance.return_causation
|
|
324
|
+
? deriveCausation(instance.return_causation)
|
|
325
|
+
: instance.causation
|
|
326
|
+
? deriveCausation(instance.causation)
|
|
327
|
+
: undefined,
|
|
241
328
|
handoffDelivery: {
|
|
242
329
|
direction: 'origin',
|
|
243
330
|
handoffId,
|
|
331
|
+
...(result ? { resultId: result.result_id } : {}),
|
|
244
332
|
...(instance.origin_authorization
|
|
245
333
|
? { originAuthorization: { ...instance.origin_authorization } }
|
|
246
334
|
: {}),
|
|
@@ -255,19 +343,42 @@ export class HandoffRuntime {
|
|
|
255
343
|
sessionKeyField: origin.sessionKey,
|
|
256
344
|
selfAID: origin.selfAID,
|
|
257
345
|
onPersisted: () => {
|
|
258
|
-
|
|
259
|
-
|
|
346
|
+
const current = this.store.get(selfAid, handoffId);
|
|
347
|
+
if (result) {
|
|
348
|
+
if (!this.isOriginDeliveryPending(current, result.result_id)) {
|
|
349
|
+
throw new Error(`handoff result is no longer deliverable: ${handoffId}/${result.result_id}`);
|
|
350
|
+
}
|
|
351
|
+
this.store.recordOriginResultEnqueued(selfAid, handoffId, result.result_id, messageId);
|
|
352
|
+
}
|
|
353
|
+
else {
|
|
354
|
+
if (current?.state !== 'origin_queued') {
|
|
355
|
+
throw new Error(`handoff is no longer deliverable: ${handoffId}`);
|
|
356
|
+
}
|
|
357
|
+
this.store.recordOriginEnqueued(selfAid, handoffId, messageId);
|
|
260
358
|
}
|
|
261
|
-
this.store.recordOriginEnqueued(selfAid, handoffId, messageId);
|
|
262
359
|
},
|
|
263
360
|
});
|
|
264
361
|
}
|
|
265
362
|
catch (error) {
|
|
266
|
-
|
|
363
|
+
const current = this.store.get(selfAid, handoffId);
|
|
364
|
+
if (result) {
|
|
365
|
+
if (current?.return_results?.find(item => item.result_id === result.result_id)?.state === 'origin_queued') {
|
|
366
|
+
this.store.recordOriginResultEnqueueFailed(selfAid, handoffId, result.result_id, error instanceof Error ? error.message : String(error));
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
else if (current?.state === 'origin_queued') {
|
|
267
370
|
this.store.recordOriginEnqueueFailed(selfAid, handoffId, error instanceof Error ? error.message : String(error));
|
|
268
371
|
}
|
|
269
372
|
}
|
|
270
373
|
}
|
|
374
|
+
isOriginDeliveryPending(instance, resultId) {
|
|
375
|
+
if (!instance || instance.state === 'discarded' || instance.state === 'failed')
|
|
376
|
+
return false;
|
|
377
|
+
if (resultId) {
|
|
378
|
+
return instance.return_results?.find(item => item.result_id === resultId)?.state === 'origin_queued';
|
|
379
|
+
}
|
|
380
|
+
return instance.state === 'origin_queued' && !!instance.return_content;
|
|
381
|
+
}
|
|
271
382
|
buildPromptItem(message, fallbackPeerRole) {
|
|
272
383
|
return this.buildPromptItems(message, fallbackPeerRole)[0] ?? null;
|
|
273
384
|
}
|
|
@@ -297,6 +408,17 @@ export class HandoffRuntime {
|
|
|
297
408
|
if (instance.state !== expectedState)
|
|
298
409
|
continue;
|
|
299
410
|
}
|
|
411
|
+
else if (Array.isArray(instance.return_results)) {
|
|
412
|
+
const resultIds = delivery.resultIds?.length
|
|
413
|
+
? delivery.resultIds
|
|
414
|
+
: (delivery.resultId ? [delivery.resultId] : []);
|
|
415
|
+
const results = resultIds
|
|
416
|
+
.map(resultId => instance.return_results.find(result => result.result_id === resultId))
|
|
417
|
+
.filter((result) => !!result && result.state === 'origin_delivered');
|
|
418
|
+
if (results.length === 0)
|
|
419
|
+
continue;
|
|
420
|
+
instance.__deliveryResults = results;
|
|
421
|
+
}
|
|
300
422
|
else if (instance.state !== 'origin_delivered') {
|
|
301
423
|
continue;
|
|
302
424
|
}
|
|
@@ -307,6 +429,13 @@ export class HandoffRuntime {
|
|
|
307
429
|
if (delivery.direction === 'origin') {
|
|
308
430
|
this.warnIfLargeAggregate(message, instances, 'origin');
|
|
309
431
|
const first = instances[0];
|
|
432
|
+
const deliveryResultRefs = instances.flatMap(instance => ((instance.__deliveryResults ?? [])
|
|
433
|
+
.map(result => ({ handoffId: instance.handoff_id, resultId: result.result_id, result }))));
|
|
434
|
+
const originRequestSummaries = new Map();
|
|
435
|
+
const originRequestShown = new Set();
|
|
436
|
+
for (const instance of instances) {
|
|
437
|
+
originRequestSummaries.set(instance.handoff_id, this.originRequestSummary(instance));
|
|
438
|
+
}
|
|
310
439
|
return [{
|
|
311
440
|
kind: 'handoff',
|
|
312
441
|
peerId: message.peerId,
|
|
@@ -329,11 +458,26 @@ export class HandoffRuntime {
|
|
|
329
458
|
kind: 'response_to_origin',
|
|
330
459
|
handoffId: first.handoff_id,
|
|
331
460
|
handoffIds: instances.map(instance => instance.handoff_id),
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
461
|
+
...(deliveryResultRefs.length > 0
|
|
462
|
+
? {
|
|
463
|
+
resultIds: deliveryResultRefs.map(({ result }) => result.result_id),
|
|
464
|
+
resultRefs: deliveryResultRefs.map(({ handoffId, result }) => ({ handoffId, resultId: result.result_id })),
|
|
465
|
+
}
|
|
466
|
+
: {}),
|
|
467
|
+
originEntries: deliveryResultRefs.length > 0
|
|
468
|
+
? deliveryResultRefs.map(({ result, handoffId }) => ({
|
|
469
|
+
previousContent: originRequestShown.has(handoffId)
|
|
470
|
+
? '[同一 handoff 的后续结果,原请求见上方]'
|
|
471
|
+
: (originRequestShown.add(handoffId), originRequestSummaries.get(handoffId) ?? this.originRequestSummary(first)),
|
|
472
|
+
responseContent: result.content,
|
|
473
|
+
previousMessageId: (instances.find(instance => instance.handoff_id === handoffId) ?? first).target_message_id,
|
|
474
|
+
resultId: result.result_id,
|
|
475
|
+
}))
|
|
476
|
+
: instances.map(instance => ({
|
|
477
|
+
previousContent: originRequestSummaries.get(instance.handoff_id) ?? this.originRequestSummary(instance),
|
|
478
|
+
responseContent: instance.return_content ?? '',
|
|
479
|
+
previousMessageId: instance.target_message_id,
|
|
480
|
+
})),
|
|
337
481
|
previousContent: this.requestSummary(first),
|
|
338
482
|
previousMessageId: first.target_message_id,
|
|
339
483
|
returnPolicy: first.return_policy,
|
|
@@ -385,9 +529,14 @@ export class HandoffRuntime {
|
|
|
385
529
|
},
|
|
386
530
|
}];
|
|
387
531
|
}
|
|
388
|
-
completeOriginContext(selfAid, handoffId) {
|
|
532
|
+
completeOriginContext(selfAid, handoffId, resultId) {
|
|
389
533
|
const instance = this.store.get(selfAid, handoffId);
|
|
390
|
-
if (
|
|
534
|
+
if (resultId) {
|
|
535
|
+
if (instance?.return_results?.some(result => result.result_id === resultId && result.state === 'origin_delivered')) {
|
|
536
|
+
this.store.completeOriginResult(selfAid, handoffId, resultId);
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
else if (instance?.state === 'origin_delivered')
|
|
391
540
|
this.store.completeOriginContext(selfAid, handoffId);
|
|
392
541
|
}
|
|
393
542
|
completeTargetContext(selfAid, handoffId) {
|
|
@@ -535,6 +684,11 @@ export class HandoffRuntime {
|
|
|
535
684
|
else if (instance.state === 'origin_queued') {
|
|
536
685
|
originEnqueues.push(this.enqueueOrigin(selfAid, instance.handoff_id));
|
|
537
686
|
}
|
|
687
|
+
else if (Array.isArray(instance.return_results)) {
|
|
688
|
+
for (const result of instance.return_results.filter(item => item.state === 'origin_queued')) {
|
|
689
|
+
originEnqueues.push(this.enqueueOrigin(selfAid, instance.handoff_id, result.result_id));
|
|
690
|
+
}
|
|
691
|
+
}
|
|
538
692
|
}
|
|
539
693
|
}
|
|
540
694
|
await Promise.all(originEnqueues);
|
|
@@ -559,10 +713,20 @@ export class HandoffRuntime {
|
|
|
559
713
|
return true;
|
|
560
714
|
}
|
|
561
715
|
async cancelOriginDelivery(instance) {
|
|
716
|
+
const resultDeliveries = instance.return_results?.filter(result => (result.state === 'origin_queued' || result.state === 'origin_delivered')) ?? [];
|
|
717
|
+
if (resultDeliveries.length > 0) {
|
|
718
|
+
for (const result of resultDeliveries) {
|
|
719
|
+
await this.cancelOriginMessage(instance, result.origin_delivery_message_id
|
|
720
|
+
?? `origin-deliver:${instance.handoff_id}:${result.result_id}:${instance.origin_session_id}`);
|
|
721
|
+
}
|
|
722
|
+
return;
|
|
723
|
+
}
|
|
562
724
|
if (instance.state !== 'origin_queued' && instance.state !== 'origin_delivered')
|
|
563
725
|
return;
|
|
564
|
-
|
|
565
|
-
?? `origin-deliver:${instance.handoff_id}:${instance.origin_session_id}
|
|
726
|
+
await this.cancelOriginMessage(instance, instance.origin_delivery_message_id
|
|
727
|
+
?? `origin-deliver:${instance.handoff_id}:${instance.origin_session_id}`);
|
|
728
|
+
}
|
|
729
|
+
async cancelOriginMessage(instance, messageId) {
|
|
566
730
|
const queue = this.messageQueue;
|
|
567
731
|
try {
|
|
568
732
|
const queued = queue.cancelMessageByIdInSession?.(instance.origin_session_id, messageId) ?? false;
|
|
@@ -592,6 +756,9 @@ export class HandoffRuntime {
|
|
|
592
756
|
sessionKey(selfAid, sessionId) {
|
|
593
757
|
return `${selfAid}\u0000${sessionId}`;
|
|
594
758
|
}
|
|
759
|
+
returnKey(selfAid, handoffOrSessionId) {
|
|
760
|
+
return `${selfAid}\u0000handoff-return\u0000${handoffOrSessionId}`;
|
|
761
|
+
}
|
|
595
762
|
warnIfLargeAggregate(message, instances, direction) {
|
|
596
763
|
const chars = message.content.length
|
|
597
764
|
+ instances.reduce((total, instance) => total + this.requestSummary(instance).length, 0);
|
|
@@ -606,7 +773,18 @@ export class HandoffRuntime {
|
|
|
606
773
|
+ ` count=${instances.length} chars=${chars} responseMessage=${message.messageId ?? '<none>'}`);
|
|
607
774
|
}
|
|
608
775
|
requestSummary(instance) {
|
|
609
|
-
|
|
776
|
+
const sent = classifyAunPayloadForLog(instance.request.payload).content || '[payload]';
|
|
777
|
+
return instance.request.context ? `${sent}\n\n[本地交接上下文]\n${instance.request.context}` : sent;
|
|
778
|
+
}
|
|
779
|
+
originRequestSummary(instance) {
|
|
780
|
+
const content = this.requestSummary(instance);
|
|
781
|
+
if (content.length <= HANDOFF_ORIGIN_REQUEST_MAX_CHARS)
|
|
782
|
+
return content;
|
|
783
|
+
const marker = `\n\n[原请求过长,回流提示仅保留首尾;完整内容共 ${content.length} 字,已省略 ${content.length - HANDOFF_ORIGIN_REQUEST_MAX_CHARS} 字]\n\n`;
|
|
784
|
+
const available = HANDOFF_ORIGIN_REQUEST_MAX_CHARS - marker.length;
|
|
785
|
+
const head = Math.ceil(available * 0.6);
|
|
786
|
+
const tail = available - head;
|
|
787
|
+
return `${content.slice(0, head)}${marker}${content.slice(-tail)}`;
|
|
610
788
|
}
|
|
611
789
|
}
|
|
612
790
|
function throwIfAborted(signal) {
|