fix: 会商第二步只对可预览成品出文件卡/开沙箱,去掉干预并发送按钮
This commit is contained in:
1 parent
7066798ab0
commit
b92a26cdd4
3 files changed
+41
-29
No files matched your search
@@ -22,6 +22,7 @@ import {
|
||||
ChainOfThoughtStep,
|
||||
} from "@/components/ai-elements/chain-of-thought";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { languageOfStreamingArtifact } from "@/core/artifacts/streaming-write-preview";
|
||||
import { cn } from "@/lib/utils";
|
||||
import {
|
||||
getStepDisplay,
|
||||
@@ -177,13 +178,17 @@ function ToolStep({
|
||||
const display = getStepDisplay(step.key, step.label, step.args);
|
||||
const argNode = renderStepArgs(step.key, step.args, resolveAgentName);
|
||||
const [, toolName] = step.key.split(":");
|
||||
// 只让「自己生成/展示的文件」可点(write_file / str_replace / present_files),
|
||||
// 与主聊天页一致。read_file 不可点 —— 否则智能体调用技能时读取的 SOUL.md、
|
||||
// 执行的 .py 脚本等也会被渲染成可点击文件名,点开却没有意义。
|
||||
// 只让「自己生成/展示的文件」可点,与主聊天页一致。read_file 不可点 —— 否则智能体
|
||||
// 调用技能时读取的 SOUL.md、执行的 .py 脚本等也会被渲染成可点击文件名,点开却没有意义。
|
||||
// write_file / str_replace 还要过与文件卡同一道门槛(`languageOfStreamingArtifact`,
|
||||
// 只认 .md/.html/.mermaid 等可预览成品):席位写的过程文件(fetch.py / r1.txt)与
|
||||
// 畸形路径(mnt/user-data/outputs)只作步骤展示、点了也不会打开沙箱。
|
||||
// present_files 由后端校验"outputs 下真实存在",保持可点。
|
||||
const stepPath = typeof step.args?.path === "string" ? step.args.path : "";
|
||||
const isArtifactStep =
|
||||
toolName === "write_file" ||
|
||||
toolName === "str_replace" ||
|
||||
toolName === "present_files";
|
||||
toolName === "present_files" ||
|
||||
((toolName === "write_file" || toolName === "str_replace") &&
|
||||
!!languageOfStreamingArtifact(stepPath));
|
||||
|
||||
return (
|
||||
<ChainOfThoughtStep
|
||||
|
||||
@@ -43,6 +43,7 @@ const SHOW_BACKGROUND_SUSPEND = true;
|
||||
import { AutoGrowTextarea } from "@/components/ui/auto-grow-textarea";
|
||||
import { PositionIntentInput } from "@/position-roundtable/components/PositionIntentInput";
|
||||
import { useLocalSettings } from "@/core/settings";
|
||||
import { languageOfStreamingArtifact } from "@/core/artifacts/streaming-write-preview";
|
||||
import { cn } from "@/lib/utils";
|
||||
import type { MessageMetric } from "@/components/workspace/messages/use-message-metrics";
|
||||
|
||||
@@ -985,14 +986,6 @@ function Step2LegacyComposer({
|
||||
</span>
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
type="button"
|
||||
onClick={onSend}
|
||||
title="记录干预指令并立即恢复会商执行"
|
||||
className="shrink-0 cursor-pointer rounded-xl bg-primary px-4 py-2.5 text-xs font-bold text-white shadow-md shadow-primary/10 transition hover:bg-[var(--brand-900)] active:scale-95"
|
||||
>
|
||||
干预并发送
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={onProceed}
|
||||
@@ -1163,11 +1156,17 @@ function RoundtableSubtaskCard({
|
||||
/**
|
||||
* 收集气泡里所有"写了文件"的产出路径,按出现顺序去重,聚合到一行文件卡片。
|
||||
*
|
||||
* 三类来源都算:
|
||||
* - `present_files`:总控呈现文件,args.filepaths 是数组(可一次多个);
|
||||
* 三类来源:
|
||||
* - `present_files`:总控呈现文件,args.filepaths 是数组(可一次多个)。后端
|
||||
* present_files 已强制"必须在 /mnt/user-data/outputs 下且真实存在",与主聊天
|
||||
* `chats/:threadId` 的产物面板同源,原样放行;
|
||||
* - `write_file` / `str_replace`:子智能体被后端 excluded_tools 禁用了 present_files,
|
||||
* 只能 write_file 写文件,单文件 path 在 args.path —— 也聚合进卡片,保证"凡是写了
|
||||
* 文件就出可点的卡片",而不是只有 present_files 才出。
|
||||
* 成品文件就出可点的卡片"。
|
||||
*
|
||||
* write_file/str_replace 的 path 需过 `languageOfStreamingArtifact`(只认 .md/.html/
|
||||
* .mermaid 等可预览产物,与主聊天 `ArtifactWriteToolCall` 的流式预览门槛同一套):
|
||||
* 席位在沙箱里写的过程文件(fetch.py / r1.txt 等)与无扩展名的畸形路径不再出卡。
|
||||
*/
|
||||
function collectPresentedFiles(
|
||||
steps: Step2Dialogue["steps"],
|
||||
@@ -1175,22 +1174,23 @@ function collectPresentedFiles(
|
||||
if (!Array.isArray(steps) || steps.length === 0) return [];
|
||||
const out: string[] = [];
|
||||
const seen = new Set<string>();
|
||||
const add = (fp: unknown) => {
|
||||
const add = (fp: unknown, requirePreviewable = false) => {
|
||||
if (typeof fp !== "string" || !fp || seen.has(fp)) return;
|
||||
if (requirePreviewable && !languageOfStreamingArtifact(fp)) return;
|
||||
seen.add(fp);
|
||||
out.push(fp);
|
||||
};
|
||||
for (const step of steps) {
|
||||
if (step.key === "tool_calling:present_files") {
|
||||
const fps = step.args?.filepaths;
|
||||
if (Array.isArray(fps)) fps.forEach(add);
|
||||
if (Array.isArray(fps)) fps.forEach((fp) => add(fp));
|
||||
continue;
|
||||
}
|
||||
if (
|
||||
step.key === "tool_calling:write_file" ||
|
||||
step.key === "tool_calling:str_replace"
|
||||
) {
|
||||
add(step.args?.path);
|
||||
add(step.args?.path, true);
|
||||
}
|
||||
}
|
||||
return out;
|
||||
|
||||
@@ -29,6 +29,7 @@ import {
|
||||
} from "@/roundtable-planning/api/multi-agent";
|
||||
import { isValidSeatDelivery, stripReasoning } from "@/roundtable-planning/lib/reasoning";
|
||||
import { uploadFiles } from "@/core/uploads/api";
|
||||
import { languageOfStreamingArtifact } from "@/core/artifacts/streaming-write-preview";
|
||||
import { useUpdateSubtask } from "@/core/tasks/context";
|
||||
|
||||
/** 干预消息携带的上传文件引用(与 streamMultiAgent 的 files 字段同形)。 */
|
||||
@@ -924,6 +925,9 @@ export function useStep2Orchestration(
|
||||
? ((tc.args as { path?: string }).path as string)
|
||||
: "";
|
||||
if (!path) continue;
|
||||
// 同 handleArtifactToolPhase 的门槛:跳过 .py/.txt 等过程文件与畸形路径,
|
||||
// 沙箱跟随只落在该席位最近的**成品**文件上。
|
||||
if (!languageOfStreamingArtifact(path)) continue;
|
||||
const virtualUrl = buildWriteFileVirtualUrl(path, stub.id, tc.id);
|
||||
// manual:false —— 沙箱跟随是系统行为,强制打开但**不**关闭 autoSelect(否则毒化
|
||||
// 后续实时自动打开,见 openSeatVirtualArtifact 注释)。
|
||||
@@ -1065,6 +1069,8 @@ export function useStep2Orchestration(
|
||||
if (toolName === "write_file" || toolName === "str_replace") {
|
||||
const path = step.args?.path;
|
||||
if (typeof path !== "string" || !step.args) return;
|
||||
// 与 MessageStepsCard 的可点判定同一道门槛:过程文件/畸形路径点了不开沙箱。
|
||||
if (!languageOfStreamingArtifact(path)) return;
|
||||
const { messageId, toolCallId } = upsertSeatToolCall(
|
||||
seatThreadId,
|
||||
toolName,
|
||||
@@ -1116,6 +1122,10 @@ export function useStep2Orchestration(
|
||||
}
|
||||
return;
|
||||
}
|
||||
// 与主聊天 ArtifactWriteToolCall 的流式预览同一道门槛:只有 .md/.html/.mermaid
|
||||
// 等可预览产物才自动打开沙箱。席位写的过程文件(fetch.py / r*.txt)与无扩展名的
|
||||
// 畸形路径不再触发自动预览,免得右侧沙箱被无意义文件反复抢占。
|
||||
if (!languageOfStreamingArtifact(path)) return;
|
||||
const { messageId, toolCallId } = upsertSeatToolCall(
|
||||
seatThreadId,
|
||||
toolName as string,
|
||||
@@ -1972,11 +1982,9 @@ export function useStep2Orchestration(
|
||||
// 第 2 轮起(已有席位交付)才是「综合轮」:让后端注入各席位完整交付供总控收口。
|
||||
// 首轮纯派活、无交付,传 false 保持上下文精简。
|
||||
synthesisMode: safetyCounter > 1,
|
||||
// 削减重复广播(2026-06-20):首轮(safetyCounter==1)保留预广播——它把任务背景播进各席位
|
||||
// thread,为席位打底;第 2 轮起的续轮/综合广播是「总控收到:<续轮 prompt>」纯噪声 + N 次
|
||||
// 读写巨大席位 thread 的纯浪费(还会让弱模型从历史模仿派活),故 suppress。席位的前序交付
|
||||
// 另经「席位交付广播」(broadcastSeatDeliveryToOthers)送达,不受影响。
|
||||
suppressPreBroadcast: safetyCounter > 1,
|
||||
// 总控 prompt 含派活/编排指令,只能进入总控 thread。任何轮次都禁止广播到席位历史,
|
||||
// 否则弱模型会模仿总控并声称要加载编排技能。席位上下文由具体 task 与交付广播提供。
|
||||
suppressPreBroadcast: true,
|
||||
// 用户随干预上传的参考文件(consume-once:仅首轮 leader 带,之后清空)。
|
||||
files: takeOnce(pendingInjectFilesRef),
|
||||
model: selectedModelRef.current || undefined,
|
||||
@@ -2257,10 +2265,9 @@ export function useStep2Orchestration(
|
||||
dAgentThreadId: ids,
|
||||
agentName: coord,
|
||||
newMessage: leaderTask,
|
||||
// 削减重复广播(2026-06-20):首个席位(i===0)保留预广播为其打底任务背景;从第 2 个席位起
|
||||
// suppress——后续席位的前序交付已由「席位交付广播」送达其 thread,总控派活 prompt 再播一遍
|
||||
// 是 N 次读写巨大 thread 的纯浪费(还会让弱模型模仿派活)。
|
||||
suppressPreBroadcast: i > 0,
|
||||
// 链条首轮同样不能把「请总控调用 agent_orchestration」广播进席位 thread。
|
||||
// 当前席位拿到的是总控生成的具体 task,后续席位再通过交付广播承接上下文。
|
||||
suppressPreBroadcast: true,
|
||||
// 用户随干预上传的参考文件(consume-once:仅首轮 leader 带,之后清空)。
|
||||
files: takeOnce(pendingInjectFilesRef),
|
||||
model: selectedModelRef.current || undefined,
|
||||
|
||||
Reference in new issue
Block a user