feat(parallel-agents): 子智能体禁派活/脚本(防模仿总控) + 面板能力完善

后端 parallel_agents.py:每个并行席位 excluded_tools 增加 agent_orchestration
(派活) 与 bash(脚本逃逸),并在任务消息里加「角色边界」护栏——根治弱模型拿到
带「总控/业务链条」语气的任务后模仿总控输出「总控调度指令…」、绕道 glob/bash
翻找派活脚本的问题。新增 /api/parallel-agents/auth/token 取 token 端点
(auth/csrf 豁免)。

parallel-agents-vue 面板:协作澄清卡片、步骤图标/步骤展示、API 与类型完善等。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
董宏凯andClaude Opus 4.8 committed 2026-06-09 13:41:39 +08:00
1 parent 3405795371
commit c230f7a6c6
18 files changed
+1276 -128

No files matched your search

@@ -1,52 +1,95 @@
<script setup lang="ts">
/** 步骤条 / 思维链卡:折叠展示该气泡运行期间的工具调用步骤(对齐 Step2 MessageStepsCard)。 */
import { ref } from "vue";
/**
* 步骤条 / 思维链卡 —— 1:1 复刻圆桌 Step2 的 MessageStepsCard + ChainOfThought:
* slate 边框卡 + 每步「图标列(带竖向连接线) + 中文文案 + 时间 + 参数子节点」。
*/
import { computed, ref } from "vue";
import type { DialogueStep } from "@/types";
import { getStepArgs, getStepDisplay } from "@/lib/step-display";
import StepIcon from "./StepIcon.vue";
defineProps<{ steps: DialogueStep[]; streaming?: boolean }>();
const props = defineProps<{ steps: DialogueStep[]; streaming?: boolean }>();
const open = ref(true);
function argSummary(args?: Record<string, unknown> | null): string {
if (!args) return "";
const path = (args as { path?: unknown }).path;
if (typeof path === "string") return path;
try {
const s = JSON.stringify(args);
return s.length > 60 ? s.slice(0, 60) + "…" : s;
} catch {
return "";
}
}
const rows = computed(() =>
props.steps.map((s, i) => {
const display = getStepDisplay(s.key, s.label, s.args);
return {
step: s,
display,
args: getStepArgs(s.key, s.args),
// 最后一步在 streaming 时为「进行中」(前景色),其余为已完成(次级色)。
active: props.streaming && i === props.steps.length - 1,
isLast: i === props.steps.length - 1,
};
}),
);
</script>
<template>
<div class="pa-surface pa-border mb-2 rounded-lg border p-0.5">
<div class="pa-surface pa-border mb-2 w-full gap-1 rounded-lg border p-0.5">
<button
type="button"
class="flex w-full items-center gap-1.5 rounded-md px-2 py-1 text-left text-[11px] font-semibold pa-text-muted"
@click="open = !open"
>
<span
class="inline-block transition-transform"
:class="open ? 'rotate-90' : ''"
>▶</span
>
<span class="inline-block transition-transform" :class="open ? 'rotate-90' : ''">▶</span>
<span>执行步骤({{ steps.length }})</span>
<span v-if="streaming" class="pa-spin inline-block">◜</span>
</button>
<ol v-show="open" class="space-y-0.5 px-2 pb-1.5 pt-0.5">
<li
v-for="s in steps"
:key="s.id"
class="flex items-start gap-2 text-[11px] pa-text"
<div v-show="open" class="space-y-2 px-3 pb-2 pt-0.5">
<div
v-for="row in rows"
:key="row.step.id"
class="flex gap-2 text-sm"
:class="row.active ? 'pa-text' : 'pa-text-muted'"
>
<span class="mt-1 inline-block size-1.5 shrink-0 rounded-full" style="background: var(--pa-primary)" />
<span class="min-w-0">
<span class="font-semibold">{{ s.label }}</span>
<span v-if="argSummary(s.args)" class="pa-text-muted"> · {{ argSummary(s.args) }}</span>
<span class="pa-text-muted"> · {{ s.ts }}</span>
</span>
</li>
</ol>
<!-- 图标列 + 竖向连接线 -->
<div class="relative mt-0.5">
<StepIcon :name="row.display.icon" :size="16" />
<div
v-if="!row.isLast"
class="absolute left-1/2 -mx-px w-px"
style="top: 26px; bottom: -8px; background: var(--pa-border)"
/>
</div>
<!-- 文案 + 时间 + 参数 -->
<div class="min-w-0 flex-1 space-y-1 overflow-hidden">
<div class="leading-snug">
<span class="font-medium pa-text">{{ row.display.label }}</span>
<span class="ml-2 font-mono text-[11px] pa-text-muted">{{ row.step.ts }}</span>
</div>
<!-- 参数子节点(按 kind 渲染,对齐 renderStepArgs) -->
<template v-if="row.args">
<span
v-if="row.args.kind === 'chip'"
class="pa-surface pa-border inline-block max-w-full truncate rounded border px-1.5 py-0.5 text-[11px] pa-text-muted"
>
{{ row.args.text }}
</span>
<pre
v-else-if="row.args.kind === 'code'"
class="pa-md max-h-60 overflow-auto rounded-md p-2 text-[11px]"
><code>{{ row.args.text }}</code></pre>
<span
v-else-if="row.args.kind === 'text'"
class="line-clamp-2 block text-[11px] leading-snug pa-text-muted"
>
{{ row.args.text }}
</span>
<span v-else-if="row.args.kind === 'kv'" class="flex flex-wrap gap-1">
<span
v-for="p in row.args.pairs"
:key="p.k"
class="pa-surface pa-border inline-block rounded border px-1.5 py-0.5 text-[11px] pa-text-muted"
>
<span class="opacity-60">{{ p.k }}:</span> {{ p.v }}
</span>
</span>
</template>
</div>
</div>
</div>
</div>
</template>