feat(roundtable): 会商席位「技能识别强化」改为按需开关(智能体级 + 链条每席位级)
弱模型补偿指令从全域生效改为默认关闭、两个 opt-in 开关取 OR: - 智能体级: AgentConfig.seat_skill_directive(config.yaml),在智能体编辑弹窗 (agent-card / agent-detail-panel)开关;agents 路由 create/update/读写全程穿。 - 业务链条每席位级: 存进链条 seats JSON(ChainSeatSchema.seat_skill_directive, 无需 DB 列/迁移),在链条编辑器每个席位卡片右侧 ✨ 图标→下拉框(开关+说明)配置; 按 agentId 查表穿到前台 useStep2Orchestration 各席位调用 + 后台作业 seat_skill_directives:{agentId:bool} → InProcessRoundtableGateway.run_seat。 - ROUNDTABLE_SEAT_SKILL_DIRECTIVE 降级为总熔断开关(默认放行,不再 force-on)。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
a8c06ca7dc
commit
9a1b674950
24 files changed
+298
-29
No files matched your search
@@ -229,6 +229,10 @@ export function AgentCard({ agent, layout = "card", onPublisherClick }: AgentCar
|
||||
);
|
||||
const [editPublished, setEditPublished] = useState(agent.published);
|
||||
const [editIsPrivate, setEditIsPrivate] = useState(agent.is_private);
|
||||
// 会商席位「技能识别强化」开关(弱模型补偿)。默认关。
|
||||
const [editSeatSkillDirective, setEditSeatSkillDirective] = useState(
|
||||
agent.seat_skill_directive ?? false,
|
||||
);
|
||||
const [editSquareId, setEditSquareId] = useState(agent.square_id ?? "");
|
||||
const [publishPickerOpen, setPublishPickerOpen] = useState(false);
|
||||
const { data: publishSquares } = usePublishSquares();
|
||||
@@ -274,6 +278,7 @@ export function AgentCard({ agent, layout = "card", onPublisherClick }: AgentCar
|
||||
setEditSquareId(agent.square_id ?? "");
|
||||
setSelectedSkills(agent.skills ?? []);
|
||||
setEditCommonQuestions(normalizeAgentQuestions(agent.common_questions));
|
||||
setEditSeatSkillDirective(agent.seat_skill_directive ?? false);
|
||||
}, [
|
||||
agent.common_questions,
|
||||
agent.description,
|
||||
@@ -282,6 +287,7 @@ export function AgentCard({ agent, layout = "card", onPublisherClick }: AgentCar
|
||||
agent.published,
|
||||
agent.square_id,
|
||||
agent.skills,
|
||||
agent.seat_skill_directive,
|
||||
editOpen,
|
||||
]);
|
||||
|
||||
@@ -295,6 +301,8 @@ export function AgentCard({ agent, layout = "card", onPublisherClick }: AgentCar
|
||||
if (agentDetail) {
|
||||
setEditSoul(agentDetail.soul ?? "");
|
||||
setEditCommonQuestions(normalizeAgentQuestions(agentDetail.common_questions));
|
||||
// 列表响应不带 config 额外字段(seat_skill_directive 为 null),详情才有真值。
|
||||
setEditSeatSkillDirective(agentDetail.seat_skill_directive ?? false);
|
||||
} else {
|
||||
setEditSoul(agent.soul ?? "");
|
||||
}
|
||||
@@ -386,6 +394,8 @@ export function AgentCard({ agent, layout = "card", onPublisherClick }: AgentCar
|
||||
request.skills = selectedSkills;
|
||||
if (JSON.stringify(nextQuestions) !== JSON.stringify(baselineQuestions))
|
||||
request.common_questions = nextQuestions;
|
||||
if (editSeatSkillDirective !== ((agentDetail?.seat_skill_directive ?? agent.seat_skill_directive) ?? false))
|
||||
request.seat_skill_directive = editSeatSkillDirective;
|
||||
|
||||
if (Object.keys(request).length === 0) {
|
||||
setEditOpen(false);
|
||||
@@ -960,6 +970,19 @@ export function AgentCard({ agent, layout = "card", onPublisherClick }: AgentCar
|
||||
onChange={setEditCommonQuestions}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center justify-between gap-3 rounded-md border p-3">
|
||||
<div>
|
||||
<p className="text-sm font-medium">会商技能识别强化</p>
|
||||
<p className="text-muted-foreground text-xs">
|
||||
作为会商席位时,把已配置技能(含 SKILL.md 路径与「先 read_file
|
||||
再用」硬指令)拼进任务,补偿弱模型不识别技能。也可在业务链条配置里打开(任一即生效)。
|
||||
</p>
|
||||
</div>
|
||||
<Switch
|
||||
checked={editSeatSkillDirective}
|
||||
onCheckedChange={setEditSeatSkillDirective}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center justify-between rounded-md border p-3">
|
||||
<div>
|
||||
<p className="text-sm font-medium">公开发布</p>
|
||||
|
||||
@@ -76,6 +76,8 @@ interface AgentDraft {
|
||||
published: boolean;
|
||||
featured: boolean;
|
||||
templateName: string;
|
||||
/** 会商席位「技能识别强化」开关。 */
|
||||
seatSkillDirective: boolean;
|
||||
}
|
||||
|
||||
function draftFromAgent(agent: Agent): AgentDraft {
|
||||
@@ -88,6 +90,7 @@ function draftFromAgent(agent: Agent): AgentDraft {
|
||||
published: agent.published,
|
||||
featured: agent.featured_order != null,
|
||||
templateName: agent.template_name ?? "",
|
||||
seatSkillDirective: agent.seat_skill_directive ?? false,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -295,6 +298,8 @@ export function AgentDetailPanel({
|
||||
if (draft.soul !== (agent.soul ?? "")) request.soul = draft.soul;
|
||||
if (draft.templateName.trim() !== (agent.template_name ?? ""))
|
||||
request.template_name = draft.templateName.trim();
|
||||
if (draft.seatSkillDirective !== (agent.seat_skill_directive ?? false))
|
||||
request.seat_skill_directive = draft.seatSkillDirective;
|
||||
if (nextPublished !== agent.published) request.published = nextPublished;
|
||||
if (JSON.stringify(draft.skills) !== JSON.stringify(agent.skills ?? []))
|
||||
request.skills = draft.skills;
|
||||
@@ -561,6 +566,21 @@ export function AgentDetailPanel({
|
||||
配置后,对话生成的 Markdown 文件可一键发布到知识入库接口。
|
||||
</p>
|
||||
</div>
|
||||
<div className="bg-background/60 flex items-center justify-between rounded-lg border px-3 py-2">
|
||||
<div className="pr-3">
|
||||
<p className="text-sm font-medium">会商技能识别强化</p>
|
||||
<p className="text-muted-foreground text-xs">
|
||||
作为会商席位时,把已配置技能(含 SKILL.md 路径与「先 read_file
|
||||
再用」硬指令)拼进任务,补偿弱模型不识别技能。也可在业务链条配置中打开(任一即生效)。
|
||||
</p>
|
||||
</div>
|
||||
<Switch
|
||||
checked={draft.seatSkillDirective}
|
||||
onCheckedChange={(checked) =>
|
||||
setDraft({ ...draft, seatSkillDirective: checked })
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div className="bg-background/60 flex items-center justify-between rounded-lg border px-3 py-2">
|
||||
<div>
|
||||
<p className="text-sm font-medium">公开发布</p>
|
||||
|
||||
@@ -58,6 +58,10 @@ export interface Agent {
|
||||
/** 入库模板名(外部知识入库接口的 templateName)。配置后该智能体会话中的
|
||||
* Markdown 产物右上角出现"发布入库"按钮。空/缺省 = 不支持入库。 */
|
||||
template_name?: string | null;
|
||||
/** 会商席位「技能识别强化」开关。作为会商席位运行时,是否把本智能体配置的技能
|
||||
* (名称 + SKILL.md 路径 + 必须先 read_file 的硬指令) 拼进本轮任务以补偿弱模型。
|
||||
* 与业务链条同名开关取 OR。null/false = 关 (默认)。 */
|
||||
seat_skill_directive?: boolean | null;
|
||||
tags?: Tag[];
|
||||
featured_order?: number | null;
|
||||
/** Whether the current user has favorited (收藏) this agent. */
|
||||
@@ -91,6 +95,8 @@ export interface CreateAgentRequest {
|
||||
tool_groups?: string[] | null;
|
||||
common_questions?: AgentQuestion[] | null;
|
||||
template_name?: string | null;
|
||||
/** 会商席位「技能识别强化」开关;缺省/false = 关。 */
|
||||
seat_skill_directive?: boolean | null;
|
||||
}
|
||||
|
||||
export interface UpdateAgentRequest {
|
||||
@@ -106,4 +112,6 @@ export interface UpdateAgentRequest {
|
||||
common_questions?: AgentQuestion[] | null;
|
||||
/** 入库模板名;传空字符串表示清除。 */
|
||||
template_name?: string | null;
|
||||
/** 会商席位「技能识别强化」开关;缺省/false = 关。 */
|
||||
seat_skill_directive?: boolean | null;
|
||||
}
|
||||
@@ -83,6 +83,8 @@ export default function NewAgentPage() {
|
||||
const [modelInput, setModelInput] = useState<string>("");
|
||||
const [chatModelInput, setChatModelInput] = useState<string>("");
|
||||
const [templateNameInput, setTemplateNameInput] = useState("");
|
||||
// 会商席位「技能识别强化」开关(弱模型补偿)。默认关。
|
||||
const [seatSkillDirectiveInput, setSeatSkillDirectiveInput] = useState(false);
|
||||
const [formError, setFormError] = useState("");
|
||||
const [agentId, setAgentId] = useState("");
|
||||
const [agent, setAgent] = useState<Agent | null>(null);
|
||||
@@ -230,6 +232,7 @@ export default function NewAgentPage() {
|
||||
skills: selectedSkills,
|
||||
model: modelOverride,
|
||||
template_name: templateNameInput.trim(),
|
||||
seat_skill_directive: seatSkillDirectiveInput,
|
||||
});
|
||||
setAgent(updated);
|
||||
return updated;
|
||||
@@ -251,6 +254,7 @@ export default function NewAgentPage() {
|
||||
skills: selectedSkills,
|
||||
model: modelOverride,
|
||||
template_name: templateNameInput.trim(),
|
||||
seat_skill_directive: seatSkillDirectiveInput,
|
||||
});
|
||||
setAgentId(created.id);
|
||||
setAgent(created);
|
||||
@@ -281,6 +285,7 @@ export default function NewAgentPage() {
|
||||
selectedSkills,
|
||||
soulInput,
|
||||
templateNameInput,
|
||||
seatSkillDirectiveInput,
|
||||
t.agents.nameStepCheckError,
|
||||
t.agents.nameStepNetworkError,
|
||||
validateForm,
|
||||
|
||||
@@ -234,6 +234,12 @@ export interface StreamMultiAgentRequest {
|
||||
reasoningEffort?: "minimal" | "low" | "medium" | "high";
|
||||
/** 仅 special 用;ultra 档置真,给席位解锁 `task` 子代理工具。 */
|
||||
subagentEnabled?: boolean;
|
||||
/**
|
||||
* 仅 special 用。会商席位「技能识别强化」开关(沿用所选业务链条配置)。置 true 时后端把该
|
||||
* 席位配置的技能(含 SKILL.md 路径 + 必须先 read_file 的硬指令)拼进本轮任务,补偿弱模型。
|
||||
* 与该席位 agent 自身 config.yaml 的同名开关取 OR。省略 = false(关)。
|
||||
*/
|
||||
seatSkillDirective?: boolean;
|
||||
skillStopNames?: string[];
|
||||
/**
|
||||
* 仅 leader 用。置 true 表示这是「派活后让总控判断继续/综合」的轮次:后端会把各已交付
|
||||
@@ -485,6 +491,8 @@ export async function streamMultiAgent(
|
||||
thinking_enabled: req.thinkingEnabled,
|
||||
reasoning_effort: req.reasoningEffort,
|
||||
subagent_enabled: req.subagentEnabled,
|
||||
// 仅 special 用;会商技能识别强化(沿用所选业务链条开关)。省略 → 后端按 false(关)处理。
|
||||
seat_skill_directive: req.seatSkillDirective ?? false,
|
||||
skill_stop_names: req.skillStopNames ?? [],
|
||||
// 仅 leader 用;综合轮让后端注入各席位完整交付正文。省略 → 后端按 false 处理。
|
||||
synthesis_mode: req.synthesisMode ?? false,
|
||||
|
||||
@@ -98,6 +98,9 @@ function toStartBody(p: StartJobPayload) {
|
||||
seedMessage: p.seedMessage,
|
||||
threadIds: p.threadIds ?? null,
|
||||
coordinatorName: p.coordinatorName ?? "roundtable-coordinator",
|
||||
// 每席位技能识别强化映射 {agentId: bool}:沿用所选业务链条里各席位的开关;缺省 {}。
|
||||
// 后端 run_seat 按 agentId 查表,与各席位 agent 自身开关取 OR。
|
||||
seat_skill_directives: p.seatSkillDirectives ?? {},
|
||||
...(seat
|
||||
? {
|
||||
thinking_enabled: seat.thinking_enabled,
|
||||
|
||||
@@ -150,18 +150,21 @@ export function BusinessChainPicker({
|
||||
// 席位模型覆盖(链条配置):按 agent_id 取,空/缺省 = 跟随全局模型。
|
||||
const modelById = new Map(selectedChain.seats.map((s) => [s.agent_id, s.model ?? null]));
|
||||
const modelOf = (id: string) => modelById.get(id) ?? null;
|
||||
// 席位「会商技能识别强化」开关(链条配置):按 agent_id 取,缺省 = 关。
|
||||
const skillById = new Map(selectedChain.seats.map((s) => [s.agent_id, s.seat_skill_directive ?? false]));
|
||||
const skillOf = (id: string) => skillById.get(id) ?? false;
|
||||
|
||||
// 链条带分层、过滤存活后仍并行 → dag 编排(携带快速/沙箱子模式);否则退回线性 chain。
|
||||
if (dagPlan) {
|
||||
// dag:席位顺序按 stage 展平(与 plan 一致)。
|
||||
const agents = dagPlan.stages
|
||||
.flatMap((st) => st.agentIds)
|
||||
.map((id) => ({ agent_id: id, name: nameOf(id), model: modelOf(id) }));
|
||||
.map((id) => ({ agent_id: id, name: nameOf(id), model: modelOf(id), seat_skill_directive: skillOf(id) }));
|
||||
onConfirm({ agents, mode: "dag", chain: chainMeta, plan: dagPlan, dagExecMode });
|
||||
return;
|
||||
}
|
||||
|
||||
const agents = survivingSeats.map((s) => ({ agent_id: s.agent_id, name: nameOf(s.agent_id), model: modelOf(s.agent_id) }));
|
||||
const agents = survivingSeats.map((s) => ({ agent_id: s.agent_id, name: nameOf(s.agent_id), model: modelOf(s.agent_id), seat_skill_directive: skillOf(s.agent_id) }));
|
||||
onConfirm({ agents, mode: "chain", chain: chainMeta });
|
||||
};
|
||||
|
||||
|
||||
@@ -279,6 +279,12 @@ export interface UseStep2OrchestrationOptions {
|
||||
* 智能体」弹窗确认时的 `SeatSelectionResult.seatMode` 决定。缺省 `pro`。
|
||||
*/
|
||||
selectedMode: SeatMode;
|
||||
/**
|
||||
* 每席位「技能识别强化」开关映射 {agentId: bool}(沿用所选业务链条里每个席位的配置)。
|
||||
* 按 agentId 查表后透传给该 special 席位 run,与该席位 agent 自身 config.yaml 的同名开关取
|
||||
* OR。recommend / 未选链条 → 空 {}(各席位仅靠其自身开关)。
|
||||
*/
|
||||
seatSkillDirectives?: Record<string, boolean>;
|
||||
/**
|
||||
* 编排模式:
|
||||
* - `recommend`(默认)—— 总控自由派活循环(`runOrchestration`);
|
||||
@@ -434,6 +440,7 @@ export function useStep2Orchestration(
|
||||
currentStep,
|
||||
selectedModel,
|
||||
selectedMode,
|
||||
seatSkillDirectives = {},
|
||||
orchestrationMode,
|
||||
orchestrationPlan,
|
||||
dagExecMode = "fast",
|
||||
@@ -567,6 +574,8 @@ export function useStep2Orchestration(
|
||||
const selectedModelRef = useRef<string>(selectedModel);
|
||||
// 席位执行模式同样打到 ref,让席位调用在编排循环里读到最新值而不重建回调。
|
||||
const selectedModeRef = useRef<SeatMode>(selectedMode);
|
||||
// 每席位技能识别强化映射(所选业务链条各席位开关)同样打到 ref,席位调用按 agentId 读最新值。
|
||||
const seatSkillDirectivesRef = useRef<Record<string, boolean>>(seatSkillDirectives);
|
||||
const agentConfigsRef = useRef<Record<number, AgentConfig>>(agentConfigs);
|
||||
const agentNameToRoleRef = useRef(agentNameToRole);
|
||||
const threadIdsRef = useRef<ThreadIdMap | null>(threadIds);
|
||||
@@ -606,6 +615,9 @@ export function useStep2Orchestration(
|
||||
useEffect(() => {
|
||||
selectedModeRef.current = selectedMode;
|
||||
}, [selectedMode]);
|
||||
useEffect(() => {
|
||||
seatSkillDirectivesRef.current = seatSkillDirectives;
|
||||
}, [seatSkillDirectives]);
|
||||
useEffect(() => {
|
||||
agentConfigsRef.current = agentConfigs;
|
||||
}, [agentConfigs]);
|
||||
@@ -1602,6 +1614,9 @@ export function useStep2Orchestration(
|
||||
model: perSeatModel || selectedModelRef.current || undefined,
|
||||
// 席位执行模式(快速/思考/专业问答/多智能体问答)→ thinking/reasoning/subagent。
|
||||
...seatModeToRequestFields(selectedModeRef.current),
|
||||
// 会商技能识别强化(该席位在所选业务链条里的开关,按 agentId 查表);与该席位
|
||||
// agent 自身开关取 OR。
|
||||
seatSkillDirective: seatSkillDirectivesRef.current[a.agentId] ?? false,
|
||||
signal: subAborter.signal,
|
||||
onTextDelta: (text) =>
|
||||
a.isParallel ? ingestParallelDelta(a.bubbleId, text) : appendToDialogue(a.bubbleId, text),
|
||||
@@ -2031,6 +2046,9 @@ export function useStep2Orchestration(
|
||||
model: perSeatModel || selectedModelRef.current || undefined,
|
||||
// 席位执行模式 → thinking/reasoning/subagent(与推荐/DAG 路径一致)。
|
||||
...seatModeToRequestFields(selectedModeRef.current),
|
||||
// 会商技能识别强化(该席位在所选业务链条里的开关,按 agentId 查表);与该席位
|
||||
// agent 自身开关取 OR。
|
||||
seatSkillDirective: seatSkillDirectivesRef.current[seat.agent_id] ?? false,
|
||||
signal: subAborter.signal,
|
||||
onTextDelta: (text) => appendToDialogue(subBubbleId, text),
|
||||
onPhaseChange: (phase, toolName, args) => {
|
||||
@@ -2371,6 +2389,9 @@ export function useStep2Orchestration(
|
||||
model: perSeatModel || selectedModelRef.current || undefined,
|
||||
// 席位执行模式 → thinking/reasoning/subagent(与推荐/链条路径一致)。
|
||||
...seatModeToRequestFields(selectedModeRef.current),
|
||||
// 会商技能识别强化(该席位在所选业务链条里的开关,按 agentId 查表);与该
|
||||
// 席位 agent 自身开关取 OR。
|
||||
seatSkillDirective: seatSkillDirectivesRef.current[agentId] ?? false,
|
||||
signal: subAborter.signal,
|
||||
// §6:并行席位走 ref 全量累积 + 节流 flush(只渲染选中的);单节点
|
||||
// stage 实际串行,1 路流直接 setState 不卡,沿用 appendToDialogue。
|
||||
|
||||
@@ -29,6 +29,13 @@ export interface ChainSeat {
|
||||
* 的 `handleConfirmAgents`)→ 走现有 per-seat 模型链路。
|
||||
*/
|
||||
model?: string | null;
|
||||
/**
|
||||
* 可选「会商技能识别强化」开关(弱模型补偿)。true = 该席位作为会商子智能体执行时,把其
|
||||
* 配置的技能(名称 + SKILL.md 路径 + 必须先 read_file 的硬指令)拼进本轮任务。与该席位
|
||||
* 智能体自身的同名开关(智能体编辑里的开关)取 OR。缺省 / false = 关。随 `seats` JSON 落库
|
||||
* (无需 DB 列),在链条编辑器里逐席位开关。
|
||||
*/
|
||||
seat_skill_directive?: boolean;
|
||||
}
|
||||
|
||||
/** 一条可复用的业务链条(与后端 `/api/roundtable-chains` 记录对应)。 */
|
||||
@@ -80,8 +87,9 @@ export interface SeatSelectionResult {
|
||||
/**
|
||||
* 有序席位 —— 与现有 `handleConfirmAgents` 入参兼容。dag 模式下按 stage 展平排序。
|
||||
* `model` 为该席位在来源链条里配置的模型覆盖(空 / 缺省 = 跟随全局模型)。
|
||||
* `seat_skill_directive` 为该席位在来源链条里配置的「技能识别强化」开关(缺省 = 关)。
|
||||
*/
|
||||
agents: Array<{ agent_id: string; name: string; model?: string | null }>;
|
||||
agents: Array<{ agent_id: string; name: string; model?: string | null; seat_skill_directive?: boolean }>;
|
||||
/**
|
||||
* 决定 Step 2 走哪种编排:
|
||||
* - `recommend` 总控自由派活;
|
||||
@@ -135,6 +143,9 @@ export function buildChainSeatSelection(
|
||||
// 席位模型覆盖(链条配置):按 agent_id 取,空/缺省 = 跟随全局。
|
||||
const modelById = new Map(chain.seats.map((s) => [s.agent_id, s.model ?? null]));
|
||||
const modelOf = (id: string) => modelById.get(id) ?? null;
|
||||
// 席位「技能识别强化」开关(链条配置):按 agent_id 取,缺省 = 关。
|
||||
const skillById = new Map(chain.seats.map((s) => [s.agent_id, s.seat_skill_directive ?? false]));
|
||||
const skillOf = (id: string) => skillById.get(id) ?? false;
|
||||
|
||||
const survivingSeats = chain.seats.filter((s) => existingIds.has(s.agent_id));
|
||||
if (survivingSeats.length < CHAIN_MIN_SEATS) return null;
|
||||
@@ -156,12 +167,12 @@ export function buildChainSeatSelection(
|
||||
if (hasParallel || hasGoal || hasCoord) {
|
||||
const agents = plan.stages
|
||||
.flatMap((st) => st.agentIds)
|
||||
.map((id) => ({ agent_id: id, name: nameOf(id), model: modelOf(id) }));
|
||||
.map((id) => ({ agent_id: id, name: nameOf(id), model: modelOf(id), seat_skill_directive: skillOf(id) }));
|
||||
return { agents, mode: "dag", chain: chainMeta, plan, dagExecMode: "fast", seatMode: chain.seatMode };
|
||||
}
|
||||
}
|
||||
|
||||
const agents = survivingSeats.map((s) => ({ agent_id: s.agent_id, name: nameOf(s.agent_id), model: modelOf(s.agent_id) }));
|
||||
const agents = survivingSeats.map((s) => ({ agent_id: s.agent_id, name: nameOf(s.agent_id), model: modelOf(s.agent_id), seat_skill_directive: skillOf(s.agent_id) }));
|
||||
return { agents, mode: "chain", chain: chainMeta, seatMode: chain.seatMode };
|
||||
}
|
||||
|
||||
|
||||
@@ -126,6 +126,20 @@ export function setSeatModel(groups: StageGroups, agentId: string, model: string
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置某个席位的「会商技能识别强化」开关(不可变,供编辑器席位下拉 onChange)。
|
||||
* 按 agent_id 定位,跨 stage 适用。随 `seats` JSON 落库。
|
||||
*/
|
||||
export function setSeatSkillDirective(groups: StageGroups, agentId: string, enabled: boolean): StageGroups {
|
||||
return groups.map((g) => {
|
||||
if (!g.seats.some((s) => s.agent_id === agentId)) return g;
|
||||
return {
|
||||
...g,
|
||||
seats: g.seats.map((s) => (s.agent_id === agentId ? { ...s, seat_skill_directive: enabled } : s)),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
// ── 变换(均返回新结构,不可变)──────────────────────────────────────────────
|
||||
|
||||
/** 末尾追加一个**新单席位 stage**(线性追加,pool 点击加入默认行为)。 */
|
||||
|
||||
@@ -132,6 +132,11 @@ export interface StartJobPayload {
|
||||
* reasoning/subagent 三字段发给后端。
|
||||
*/
|
||||
seatMode?: SeatMode;
|
||||
/**
|
||||
* 每席位「技能识别强化」开关映射 {agentId: bool}(沿用所选业务链条里每个席位的配置)。
|
||||
* 后台席位 run 按 agentId 查表(与各席位智能体自身的同名开关取 OR)决定是否注入。缺省 {}。
|
||||
*/
|
||||
seatSkillDirectives?: Record<string, boolean>;
|
||||
}
|
||||
|
||||
/** 终态集合——SSE/轮询判定作业是否结束。 */
|
||||
|
||||
@@ -37,6 +37,7 @@ import {
|
||||
Plus,
|
||||
Save,
|
||||
Sliders,
|
||||
Sparkles,
|
||||
SplitSquareVertical,
|
||||
Trash2,
|
||||
UserPlus,
|
||||
@@ -63,6 +64,12 @@ import {
|
||||
import { CSS } from "@dnd-kit/utilities";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
import { useDesensitizeSubtree } from "@/roundtable-planning/lib/use-desensitize-subtree";
|
||||
import { TagSearchBar } from "@/components/workspace/tags/tag-search-bar";
|
||||
import { ModelNameSelect } from "@/components/workspace/model-name-select";
|
||||
@@ -105,6 +112,7 @@ import {
|
||||
removeSeat as removeSeatFromGroups,
|
||||
seatExistsInGroups,
|
||||
setSeatModel,
|
||||
setSeatSkillDirective,
|
||||
setStageGoal,
|
||||
splitSeatToNewStage,
|
||||
stageGroupsToSeats,
|
||||
@@ -366,6 +374,11 @@ export default function BusinessChainEditorPage() {
|
||||
setStageGroups((prev) => setSeatModel(prev, agentId, model));
|
||||
}, []);
|
||||
|
||||
// 席位「会商技能识别强化」开关:编辑器席位下拉 onChange。
|
||||
const setSeatSkillDirectiveCb = useCallback((agentId: string, enabled: boolean) => {
|
||||
setStageGroups((prev) => setSeatSkillDirective(prev, agentId, enabled));
|
||||
}, []);
|
||||
|
||||
// ── 提示词 AI 优化(总控提示词 / 各 Stage 阶段目标)────────────────────────
|
||||
const [optimizingCoordinator, setOptimizingCoordinator] = useState(false);
|
||||
const [goalOptimizingIdx, setGoalOptimizingIdx] = useState<number | null>(null);
|
||||
@@ -564,6 +577,8 @@ export default function BusinessChainEditorPage() {
|
||||
description: live?.description ?? s.description ?? "",
|
||||
// 席位模型覆盖:随席位一起落库(空 → null = 跟随全局)。
|
||||
model: s.model ?? null,
|
||||
// 席位「会商技能识别强化」开关:随席位一起落库(缺省 = false 关)。
|
||||
seat_skill_directive: s.seat_skill_directive ?? false,
|
||||
};
|
||||
});
|
||||
// 总控编排提示:trim 后空串落 null(清空),与后端「空=不注入」一致。
|
||||
@@ -1131,6 +1146,7 @@ export default function BusinessChainEditorPage() {
|
||||
onMergeSeat={mergeSeatToStage}
|
||||
onSplitSeat={splitSeat}
|
||||
onSeatModelChange={setSeatModelCb}
|
||||
onSeatSkillDirectiveChange={setSeatSkillDirectiveCb}
|
||||
onStageGoalChange={setGoal}
|
||||
goalOptimizingIdx={goalOptimizingIdx}
|
||||
onOptimizeGoal={handleOptimizeStageGoal}
|
||||
@@ -1309,6 +1325,7 @@ function StagesColumn({
|
||||
onMergeSeat,
|
||||
onSplitSeat,
|
||||
onSeatModelChange,
|
||||
onSeatSkillDirectiveChange,
|
||||
onStageGoalChange,
|
||||
goalOptimizingIdx,
|
||||
onOptimizeGoal,
|
||||
@@ -1325,6 +1342,7 @@ function StagesColumn({
|
||||
onMergeSeat: (agentId: string, targetStageIdx: number) => void;
|
||||
onSplitSeat: (agentId: string) => void;
|
||||
onSeatModelChange: (agentId: string, model: string | null) => void;
|
||||
onSeatSkillDirectiveChange: (agentId: string, enabled: boolean) => void;
|
||||
onStageGoalChange: (stageIdx: number, goal: string) => void;
|
||||
/** 正在 AI 优化阶段目标的 stage 下标(null = 没有进行中的优化)。 */
|
||||
goalOptimizingIdx: number | null;
|
||||
@@ -1423,6 +1441,7 @@ function StagesColumn({
|
||||
onMergeSeat={onMergeSeat}
|
||||
onSplitSeat={onSplitSeat}
|
||||
onSeatModelChange={onSeatModelChange}
|
||||
onSeatSkillDirectiveChange={onSeatSkillDirectiveChange}
|
||||
onGoalChange={(goal) => onStageGoalChange(stageIdx, goal)}
|
||||
goalOptimizing={goalOptimizingIdx === stageIdx}
|
||||
anyGoalOptimizing={goalOptimizingIdx !== null}
|
||||
@@ -1452,6 +1471,7 @@ function StageSection({
|
||||
onMergeSeat,
|
||||
onSplitSeat,
|
||||
onSeatModelChange,
|
||||
onSeatSkillDirectiveChange,
|
||||
onGoalChange,
|
||||
goalOptimizing,
|
||||
anyGoalOptimizing,
|
||||
@@ -1470,6 +1490,7 @@ function StageSection({
|
||||
onMergeSeat: (agentId: string, targetStageIdx: number) => void;
|
||||
onSplitSeat: (agentId: string) => void;
|
||||
onSeatModelChange: (agentId: string, model: string | null) => void;
|
||||
onSeatSkillDirectiveChange: (agentId: string, enabled: boolean) => void;
|
||||
onGoalChange: (goal: string) => void;
|
||||
/** 本 Stage 的阶段目标正在 AI 优化中。 */
|
||||
goalOptimizing: boolean;
|
||||
@@ -1631,6 +1652,7 @@ function StageSection({
|
||||
onMergeDown={() => onMergeSeat(seat.agent_id, stageIdx + 1)}
|
||||
onSplit={() => onSplitSeat(seat.agent_id)}
|
||||
onModelChange={(model) => onSeatModelChange(seat.agent_id, model)}
|
||||
onSkillDirectiveChange={(enabled) => onSeatSkillDirectiveChange(seat.agent_id, enabled)}
|
||||
onDetail={() => onSeatDetail(seat.agent_id, seat.name)}
|
||||
/>
|
||||
))}
|
||||
@@ -1654,6 +1676,7 @@ function SeatStrip({
|
||||
onMergeDown,
|
||||
onSplit,
|
||||
onModelChange,
|
||||
onSkillDirectiveChange,
|
||||
onDetail,
|
||||
}: {
|
||||
seat: ChainSeat;
|
||||
@@ -1668,6 +1691,8 @@ function SeatStrip({
|
||||
onSplit: () => void;
|
||||
/** 席位模型覆盖变更(null = 清除,跟随全局)。 */
|
||||
onModelChange: (model: string | null) => void;
|
||||
/** 席位「会商技能识别强化」开关变更。 */
|
||||
onSkillDirectiveChange: (enabled: boolean) => void;
|
||||
onDetail: () => void;
|
||||
}) {
|
||||
const { attributes, listeners, setNodeRef, transform, transition, isDragging } =
|
||||
@@ -1739,8 +1764,41 @@ function SeatStrip({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 控制:查看详情 / 并入上一阶段 / 独立成阶段 / 并入下一阶段 / 移除 */}
|
||||
{/* 控制:技能识别强化 / 查看详情 / 并入上一阶段 / 独立成阶段 / 并入下一阶段 / 移除 */}
|
||||
<div className="flex shrink-0 items-center gap-0.5 border-l border-border px-2">
|
||||
{/* 会商技能识别强化:小图标 → 下拉框(开关 + 说明)。开启 = 高亮。 */}
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<button
|
||||
type="button"
|
||||
title="会商技能识别强化(弱模型补偿)"
|
||||
className={cn(
|
||||
"p-1 transition",
|
||||
R_CTRL,
|
||||
seat.seat_skill_directive
|
||||
? "bg-primary/10 text-primary"
|
||||
: "text-muted-foreground hover:bg-muted hover:text-foreground",
|
||||
)}
|
||||
>
|
||||
<Sparkles className="size-3.5" />
|
||||
</button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end" className="w-64 p-3">
|
||||
<div className="flex items-start justify-between gap-3">
|
||||
<div className="space-y-0.5">
|
||||
<p className="text-xs font-semibold text-foreground">会商技能识别强化</p>
|
||||
<p className="text-[11px] leading-relaxed text-muted-foreground">
|
||||
开启后,该席位作为会商子智能体执行时,会把已配置的技能(含 SKILL.md
|
||||
路径与「先 read_file 再用」硬指令)拼进任务,补偿弱模型不识别技能。也可在该智能体编辑里打开(任一即生效)。
|
||||
</p>
|
||||
</div>
|
||||
<Switch
|
||||
checked={seat.seat_skill_directive ?? false}
|
||||
onCheckedChange={onSkillDirectiveChange}
|
||||
/>
|
||||
</div>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
<button
|
||||
type="button"
|
||||
onClick={onDetail}
|
||||
|
||||
@@ -312,6 +312,9 @@ export default function RoundtablePlanningPage() {
|
||||
// Step 2 席位执行模式(快速/思考/专业问答/多智能体问答)——决定每个子智能体席位的
|
||||
// 推理深度。由「选择参会智能体」弹窗确认时的 SeatSelectionResult.seatMode 决定。
|
||||
const [seatMode, setSeatMode] = useState<SeatMode>(DEFAULT_SEAT_MODE);
|
||||
// Step 2 每席位「技能识别强化」开关映射 {agentId: bool}:由所选业务链条里**每个席位**的同名
|
||||
// 配置决定(recommend 无链条 → 空 {})。透传给每个席位 run,与各席位智能体自身的开关取 OR。
|
||||
const [seatSkillDirectives, setSeatSkillDirectives] = useState<Record<string, boolean>>({});
|
||||
// Step 2 编排模式:recommend = 总控自由派活;chain = 总控按业务链条顺序派活;
|
||||
// dag = 分层 DAG(stage 内并行、stage 间串行,配合 orchestrationPlan)。
|
||||
// 由推荐弹窗确认时的 SeatSelectionResult.mode 决定(见 handleConfirmAgents)。
|
||||
@@ -467,6 +470,7 @@ export default function RoundtablePlanningPage() {
|
||||
currentStep,
|
||||
selectedModel,
|
||||
selectedMode: seatMode,
|
||||
seatSkillDirectives,
|
||||
orchestrationMode,
|
||||
orchestrationPlan,
|
||||
dagExecMode,
|
||||
@@ -944,6 +948,11 @@ export default function RoundtablePlanningPage() {
|
||||
setOrchestrationMode(result.mode);
|
||||
// 席位执行模式:弹窗未带(旧调用)时回退默认 pro,保证行为可预期。
|
||||
setSeatMode(result.seatMode ?? DEFAULT_SEAT_MODE);
|
||||
// 会商技能识别强化:取所选业务链条里**每席位**的开关 → {agentId: bool} 映射
|
||||
// (recommend / 旧调用未带 → 空 {},各席位仅靠其智能体自身开关)。
|
||||
setSeatSkillDirectives(
|
||||
Object.fromEntries(result.agents.map((a) => [a.agent_id, a.seat_skill_directive ?? false])),
|
||||
);
|
||||
setChainMeta(result.mode === "recommend" ? null : result.chain ?? null);
|
||||
setOrchestrationPlan(result.mode === "dag" ? result.plan ?? null : null);
|
||||
setDagExecMode(result.mode === "dag" ? result.dagExecMode ?? "fast" : "fast");
|
||||
@@ -1518,6 +1527,7 @@ export default function RoundtablePlanningPage() {
|
||||
agents,
|
||||
model: selectedModel || undefined, // 模型沿用第二步选择
|
||||
seatMode, // 席位执行模式沿用第二步选择(后台续跑同样按此模式跑席位)
|
||||
seatSkillDirectives, // 每席位技能识别强化沿用所选业务链条里各席位的开关
|
||||
// 后台作业引擎支持 dag(后端 _run_dag:stage 串行 / stage 内并行)——
|
||||
// 直接把当前编排模式透传,业务链的分层 / 并行 / 阶段目标 / 总控提示词全部保留。
|
||||
orchestrationMode,
|
||||
@@ -1534,6 +1544,7 @@ export default function RoundtablePlanningPage() {
|
||||
drafts,
|
||||
selectedModel,
|
||||
seatMode,
|
||||
seatSkillDirectives,
|
||||
orchestrationMode,
|
||||
orchestrationPlan,
|
||||
chainMeta,
|
||||
@@ -1557,6 +1568,7 @@ export default function RoundtablePlanningPage() {
|
||||
const intent = buildInitialIntent();
|
||||
const model = selectedModel || undefined;
|
||||
const seatModeSnap = seatMode;
|
||||
const seatSkillDirectivesSnap = seatSkillDirectives;
|
||||
const mode = orchestrationMode;
|
||||
const chainSnap = chainMeta;
|
||||
const planSnap = mode === "dag" ? orchestrationPlan : null;
|
||||
@@ -1591,6 +1603,7 @@ export default function RoundtablePlanningPage() {
|
||||
agents,
|
||||
model,
|
||||
seatMode: seatModeSnap,
|
||||
seatSkillDirectives: seatSkillDirectivesSnap,
|
||||
orchestrationMode: mode,
|
||||
chain: chainSnap,
|
||||
orchestrationPlan: planSnap,
|
||||
@@ -1614,6 +1627,7 @@ export default function RoundtablePlanningPage() {
|
||||
buildInitialIntent,
|
||||
selectedModel,
|
||||
seatMode,
|
||||
seatSkillDirectives,
|
||||
orchestrationMode,
|
||||
orchestrationPlan,
|
||||
chainMeta,
|
||||
@@ -1654,6 +1668,7 @@ export default function RoundtablePlanningPage() {
|
||||
agents,
|
||||
model: selectedModel || undefined,
|
||||
seatMode,
|
||||
seatSkillDirectives,
|
||||
orchestrationMode,
|
||||
chain: chainMeta,
|
||||
orchestrationPlan: orchestrationMode === "dag" ? orchestrationPlan : null,
|
||||
|
||||
Reference in new issue
Block a user