docs(plan): v0.2 adds Pi subagents and scheduled-jobs board

Co-Authored-By: Claude Fable 5.1 <[email protected]>
This commit is contained in:
rootandClaude Fable 5.1 committed 2026-09-28 02:09:39 +08:00
1 parent b839ed4056
commit 16ab2a6c84
97 files changed
+592 -648

No files matched your search

+12 -12
View File
@@ -155,13 +155,13 @@ const BLOCK_ERR: Record<
NonNullable<FileDeleteCheck["blocked"]>,
[ConstructorParameters<typeof FilesError>[0], string]
> = {
download_active: ["download_active", "属于纳管下载任务(未完成或做种义务未满足)"],
is_root: ["bad_request", "不能删除根本身或只读登记根下的内容"],
download_active: ["download_active", "属于未完成或做种义务未满足的下载任务"],
is_root: ["bad_request", "不能删除根目录或只读根下的内容"],
needs_retire: ["needs_retire", "含已登记资产,需勾选“同时退役资产”"],
needs_unbind: ["needs_unbind", "是作品/季的绑定目录,需勾选“解除绑定”"],
root_unavailable: ["root_unavailable", "根不可用,不当作已删除"],
root_unavailable: ["root_unavailable", "根不可用"],
not_found: ["not_found", "不存在"],
escape: ["path_escape", "路径逃出根"],
escape: ["path_escape", "路径指向根外"],
};
export async function deleteFiles(
@@ -171,7 +171,7 @@ export async function deleteFiles(
): Promise<FilesDeleteResult> {
if (body.mode === "permanent") {
if (!d.options.allowPermanentDelete)
throw new FilesError("permanent_disabled", "永久删除未开启(FILES_ALLOW_PERMANENT_DELETE),请使用回收站");
throw new FilesError("permanent_disabled", "未开启永久删除(FILES_ALLOW_PERMANENT_DELETE)。改用回收站");
if (!isAdminSession(principal)) throw new FilesError("forbidden", "永久删除只允许控制台管理员会话");
}
const downloads = await managedDownloads(d);
@@ -276,8 +276,8 @@ export async function deleteFiles(
for (const s of slots)
await upsertDecision(d.ao, trx, {
kind: "other",
title: `Slot 满足资产已被删除:${s.name}`,
summary: "文件管理删除了满足该 Slot 的资产(进回收站或永久删除),Slot 退回 attention,请确认是否重新获取",
title: `版本目标的文件已删除:${s.name}`,
summary: "满足该版本目标的资产已在文件管理中删除,版本目标改为需关注。确认是否重新获取",
subjectType: "acquisition_slot",
subjectId: s.id,
dedupeKey: `files_delete_slot:${s.id}`,
@@ -396,7 +396,7 @@ async function moveToTrash(
if (check.size > d.options.trashCrossMaxBytes)
throw new FilesError(
"bad_request",
`无法同卷移入回收站,且大小 ${check.size} 超过跨卷上限 ${d.options.trashCrossMaxBytes};请使用永久删除`,
`无法同卷移入回收站,且大小 ${check.size} 超过跨卷上限 ${d.options.trashCrossMaxBytes}。改用永久删除`,
);
await rm(join(ctx.rootReal!, TRASH_DIR, stamp, id), { recursive: true, force: true }).catch(() => undefined);
trashPath = join(resolve(d.options.dataDir), "trash", stamp, id, check.path);
@@ -487,10 +487,10 @@ export async function mkdirWithin(rootReal: string, rel: string): Promise<void>
throw e;
});
if (st?.isSymbolicLink())
throw new FilesError("path_escape", `还原路径中的 ${relOfRoot(rootReal, cur)} 是符号链接,拒绝`);
throw new FilesError("path_escape", `还原路径中的 ${relOfRoot(rootReal, cur)} 是符号链接,不能还原`);
if (st && !st.isDirectory()) throw new FilesError("exists", `还原路径中的 ${relOfRoot(rootReal, cur)} 不是目录`);
if (!st) await mkdir(cur);
if (!within(await realpath(cur), rootReal)) throw new FilesError("path_escape", `还原路径逃出根: ${rel}`);
if (!within(await realpath(cur), rootReal)) throw new FilesError("path_escape", `还原路径指向根外: ${rel}`);
}
}
const relOfRoot = (root: string, p: string) => (p === root ? "" : p.slice(root.length + 1));
@@ -509,7 +509,7 @@ export async function restoreTrash(
throw new FilesError("bad_request", "还原目标必须是已存在的目录");
// "还原到…"只能进可写的 managed 库根(与 move/copy 的目标规则一致;register_only / 下载根拒绝,验收 07 S-5)
if (target && !(await isWritableTarget(target.def, target.rootReal)))
throw new FilesError("not_writable", `还原目标根 ${target.def.name} 不可写(仅 managed 库根可作为目标)`);
throw new FilesError("not_writable", `还原目标根 ${target.def.name} 不可写,只能选 managed 库根`);
for (const id of body.ids) {
try {
const row = await d.ctx.db.selectFrom("trash_item").selectAll().where("id", "=", id).executeTakeFirst();
@@ -547,7 +547,7 @@ export async function restoreTrash(
if (bytes > d.options.trashCrossMaxBytes)
throw new FilesError(
"too_large",
`跨卷还原大小 ${bytes} 超过上限 ${d.options.trashCrossMaxBytes};请在同卷还原,或手工处理 ${row.trash_path}`,
`跨卷还原大小 ${bytes} 超过上限 ${d.options.trashCrossMaxBytes}。在同卷还原,或手工处理 ${row.trash_path}`,
);
await copyTree(d, row.trash_path, dst, files);
await rm(row.trash_path, { recursive: true });