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

@@ -103,7 +103,7 @@ async function recentFailures(db: Kysely<DB>, col: "ip" | "username", value: str
export async function assertNotRateLimited(db: Kysely<DB>, ip: string, username: string): Promise<void> {
const [byIp, byAccount] = await Promise.all([recentFailures(db, "ip", ip), recentFailures(db, "username", username)]);
if (byIp >= MAX_FAILURES_PER_IP || byAccount >= MAX_FAILURES_PER_ACCOUNT) {
throw new ApiException("rate_limited", "失败次数过多,请 15 分钟后再试");
throw new ApiException("rate_limited", "失败次数过多,15 分钟后再试");
}
}
@@ -190,7 +190,7 @@ export async function setupAdmin(
const admin = await loadAdmin(deps.db);
if (admin?.password_hash) throw new ApiException("conflict", "管理员已初始化");
await recordAttempt(deps.db, { ip, username: "(setup)", success: false, reason: "bad_setup_token" });
throw new ApiException("unauthorized", "setup token 无效(以服务端日志最新打印的为准)");
throw new ApiException("unauthorized", "setup token 无效。使用服务端日志里最新打印的那个");
}
const admin = await loadAdmin(deps.db);
if (!admin) throw new Error("admin_user 行丢失");
@@ -206,7 +206,7 @@ export async function verifyLogin(
): Promise<LoginOutcome> {
await assertNotRateLimited(deps.db, ip, input.username);
const admin = await loadAdmin(deps.db);
if (!admin?.password_hash) throw new ApiException("precondition_failed", "管理员尚未初始化,请先完成初始化");
if (!admin?.password_hash) throw new ApiException("precondition_failed", "管理员尚未初始化");
const userOk = admin.username === input.username;
// 用户名不对也跑一次 argon2,避免用时差探测账号
let pwOk = false;
@@ -284,7 +284,7 @@ export async function totpBegin(deps: AuthDeps, admin: AdminRow): Promise<{ uri:
}
export async function totpEnable(deps: AuthDeps, admin: AdminRow, code: string): Promise<void> {
if (!admin.totp_pending_secret_ref) throw new ApiException("precondition_failed", "请先生成 TOTP 密钥");
if (!admin.totp_pending_secret_ref) throw new ApiException("precondition_failed", "先生成 TOTP 密钥");
const secret = await deps.secrets.resolve(admin.totp_pending_secret_ref);
const counter = verifyTotp(secret, code, null);
if (counter === null) throw new ApiException("unauthorized", "验证码错误");