feat(downloads): fold download plans into download panel (DL-3)
- Waiting plans (approval / disk space / automation off / queued) are
active-segment rows on /download-tasks/board, merged by created_at,
counted, with approve / cancel actions; ?plan= deep links and legacy
/downloads/plans routes redirect to the panel
- Download detail shows "why this download" (source, purpose, replaced
version, reasons); raw evaluation in tech info
- Failed / cancelled / expired plans appear in the entity download
history; rejected / revoked / expired approvals close draft plans
- New GET /acquisition-plans/{id}/why, POST /acquisition-plans/{id}/cancel
- User-facing copy no longer says 下载计划; glossary updated
Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
c0522f2531
commit
a9147e647d
50 files changed
+1535
-348
No files matched your search
@@ -298,7 +298,7 @@ export function OptionsCard({ runId, view, waiting }: { runId: string; view: Opt
|
||||
|
||||
const RISK_LABEL: Record<RiskLevel, string> = { low: "低", medium: "中", high: "高" };
|
||||
const PLAN_LABEL: Record<string, string> = {
|
||||
acquisition_plan: "下载计划",
|
||||
acquisition_plan: "下载",
|
||||
organize_plan: "整理计划",
|
||||
policy_revision: "策略版本",
|
||||
};
|
||||
|
||||
@@ -189,6 +189,8 @@ describe("聚合:进度行用能力元数据的人话计数", () => {
|
||||
expect(activitySummary([call({ capabilityId: "download.inspect" }), call({ capabilityId: "site.list" })])).toBe(
|
||||
"查了 1 个下载任务和站点",
|
||||
);
|
||||
// DL-3:不再说"生成了 1 份下载计划"
|
||||
expect(activitySummary([call({ capabilityId: "download.plan" })])).toBe("准备下载 1 个资源");
|
||||
});
|
||||
it("一回合的全部调用聚成一行(1 次也折叠),展开前看不到能力 ID", () => {
|
||||
const activity: ActivityItem[] = [{ kind: "tool", key: "c1", call: call({ capabilityId: "site.list" }) }];
|
||||
|
||||
@@ -43,7 +43,7 @@ export function deniedReason(call: ToolCallView): { text: string; approvable: bo
|
||||
|
||||
// ── 进度行的人话计数 ──
|
||||
|
||||
type Group = { verb: string; nouns: { noun: string; unit: string | null; n: number }[] };
|
||||
type Group = { verb: string; bare: boolean; nouns: { noun: string; unit: string | null; n: number }[] };
|
||||
|
||||
function activityOf(id: string) {
|
||||
return (
|
||||
@@ -73,7 +73,7 @@ export function activitySummary(calls: Pick<ToolCallView, "capabilityId" | "stat
|
||||
if (c.status === "denied" || c.status === "failed" || authDenial(c)) continue;
|
||||
let g = groups.find((x) => x.verb === a.verb);
|
||||
if (!g) {
|
||||
g = { verb: a.verb, nouns: [] };
|
||||
g = { verb: a.verb, bare: !!a.bare, nouns: [] };
|
||||
groups.push(g);
|
||||
}
|
||||
const n = g.nouns.find((x) => x.noun === a.noun);
|
||||
@@ -81,7 +81,10 @@ export function activitySummary(calls: Pick<ToolCallView, "capabilityId" | "stat
|
||||
else g.nouns.push({ noun: a.noun, unit: a.unit, n: 1 });
|
||||
}
|
||||
const text = (g: Group) =>
|
||||
cjkJoin(`${g.verb}了`, joinNouns(g.nouns.map((x) => (x.unit ? `${x.n} ${x.unit}${x.noun}` : x.noun))));
|
||||
cjkJoin(
|
||||
g.bare ? g.verb : `${g.verb}了`,
|
||||
joinNouns(g.nouns.map((x) => (x.unit ? `${x.n} ${x.unit}${x.noun}` : x.noun))),
|
||||
);
|
||||
if (groups.length > 4) return `${groups.slice(0, 3).map(text).join(",")}等 ${groups.length} 类操作`;
|
||||
return groups.map(text).join(",");
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@ function script(goal: string, opts: AgentSessionOptions): Step[] {
|
||||
const steps: AgentPlanStep[] = [
|
||||
{ id: "s1", title: "查本地库存和收录版本", status: "running", note: null },
|
||||
{ id: "s2", title: "在站点搜 4K 候选", status: "pending", note: null },
|
||||
{ id: "s3", title: "选资源并生成下载计划", status: "pending", note: null },
|
||||
{ id: "s3", title: "选资源并准备下载", status: "pending", note: null },
|
||||
{ id: "s4", title: "提交下载", status: "pending", note: null },
|
||||
];
|
||||
let revision = 0;
|
||||
@@ -324,7 +324,7 @@ function checkupSteps(goal: string, report: string): Step[] {
|
||||
]);
|
||||
}
|
||||
|
||||
/** 例 3:方案绑定下载计划(选定即批准),选了之后直接提交 */
|
||||
/** 例 3:方案绑定计划(选定即批准),选了之后直接提交 */
|
||||
const DUNE_PLAN = "00000000-0000-4000-8000-00000000b003";
|
||||
const DUNE_DECISION = "00000000-0000-4000-8000-00000000d003";
|
||||
function optionSteps(goal: string): Step[] {
|
||||
@@ -338,7 +338,7 @@ function optionSteps(goal: string): Step[] {
|
||||
revision: 1,
|
||||
steps: [
|
||||
{ id: "s1", title: "找 1080p 资源", status: "done", note: null },
|
||||
{ id: "s2", title: "生成下载计划", status: "done", note: null },
|
||||
{ id: "s2", title: "准备下载", status: "done", note: null },
|
||||
{ id: "s3", title: "等你选方案", status: "running", note: null },
|
||||
{ id: "s4", title: "提交下载", status: "pending", note: null },
|
||||
],
|
||||
|
||||
@@ -56,7 +56,7 @@ describe("humanizeReport(汇报兜底)", () => {
|
||||
names,
|
||||
);
|
||||
expect(r.text).toBe(
|
||||
"检测站点 被拒;查看下载任务 无法调用(返回 参数不对);生成下载计划 返回 plan=null,拒绝原因 下载盘用量未知",
|
||||
"检测站点 被拒;查看下载任务 无法调用(返回 参数不对);准备下载 返回 plan=null,拒绝原因 下载盘用量未知",
|
||||
);
|
||||
expect(r.text).not.toMatch(/site\.health_check|invalid_input|volume_state_unknown/);
|
||||
expect(humanizeReport("被规则拒绝(unauthorized),未重试", names).text).toBe("被规则拒绝(需要你批准),未重试");
|
||||
|
||||
Reference in new issue
Block a user