diff --git a/apps/server/src/http/auth.ts b/apps/server/src/http/auth.ts index f35dd64a..7c6dc8b4 100644 --- a/apps/server/src/http/auth.ts +++ b/apps/server/src/http/auth.ts @@ -183,7 +183,7 @@ export function apiKeyAuth(opts: AuthOptions): MiddlewareHandler { if (!isPublic && WRITE_METHODS.has(c.req.method)) { const provided = c.req.header(CSRF_HEADER); if (!provided || !safeEqual(provided, deriveCsrfToken(token))) { - throw new ApiException("forbidden", "CSRF 校验失败,刷新页面后重试"); + throw new ApiException("forbidden", "CSRF 校验失败,刷新页面后重试"); } } setActor(c, { kind: "admin_session", id: s.adminUserId, name: s.username, scopes: ["admin"] }, "admin", { @@ -197,7 +197,7 @@ export function apiKeyAuth(opts: AuthOptions): MiddlewareHandler { } } if (isPublic) return next(); - throw new ApiException("unauthorized", token ? "会话已过期或已注销,重新登录" : "需要登录或 Bearer API Key"); + throw new ApiException("unauthorized", token ? "会话已过期或已注销,重新登录" : "需要登录或 Bearer API Key"); }; } diff --git a/apps/server/src/main.ts b/apps/server/src/main.ts index 899466d1..b9f2a083 100644 --- a/apps/server/src/main.ts +++ b/apps/server/src/main.ts @@ -113,7 +113,7 @@ async function runOneShot(r: OneShotRole): Promise { switch (r) { case "rotate-master-key": { if (!config.SECRET_MASTER_KEY_NEW) { - logger.error("缺少环境变量 SECRET_MASTER_KEY_NEW(新主密钥);SECRET_MASTER_KEY 保持旧密钥"); + logger.error("缺少环境变量 SECRET_MASTER_KEY_NEW(新主密钥);SECRET_MASTER_KEY 保持旧密钥"); return 2; } const { rotateMasterKey } = await import("./modules/auth-settings/provided.ts"); diff --git a/apps/server/src/modules/acquisition-organize/routes/library.ts b/apps/server/src/modules/acquisition-organize/routes/library.ts index 17999cd5..8816e67f 100644 --- a/apps/server/src/modules/acquisition-organize/routes/library.ts +++ b/apps/server/src/modules/acquisition-organize/routes/library.ts @@ -60,7 +60,7 @@ export async function queryMediaServer( let timer: ReturnType | undefined; const timeout = new Promise((_, reject) => { timer = setTimeout( - () => reject(new Error(`timeout: 媒体服务器 ${limits.timeoutMs / 1000}s 内未响应`)), + () => reject(new Error(`timeout:媒体服务器 ${limits.timeoutMs / 1000}s 内未响应`)), limits.timeoutMs, ); }); @@ -131,7 +131,7 @@ export function registerLibraryRoutes(app: ModuleApp, d: AoDeps) { method: "get", path: "/library", scope: "read", - summary: "媒体库列表(已登记资产或正在追踪的作品)", + summary: "媒体库列表(已登记资产或正在追踪的作品)", query: LibraryListQuery, response: cursorPage(LibraryItemSchema), }, @@ -232,7 +232,7 @@ export function registerLibraryRoutes(app: ModuleApp, d: AoDeps) { method: "get", path: "/library/{id}", scope: "read", - summary: "作品详情:实际路径、身份映射、元数据来源、各 Slot、候选拒绝理由、下载历史与校验结果", + summary: "作品详情:实际路径、身份映射、元数据来源、各 Slot、候选拒绝理由、下载历史与校验结果", params: IdParam, response: LibraryDetail, }, @@ -349,7 +349,7 @@ export function registerLibraryRoutes(app: ModuleApp, d: AoDeps) { method: "get", path: "/inventory", scope: "read", - summary: "库存叠加(站点工作台):按作品返回已有版本 / Slot 缺口 / 下载中", + summary: "库存叠加(站点工作台):按作品返回已有版本 / Slot 缺口 / 下载中", query: InventoryQuery, response: InventoryResponse, }, @@ -469,7 +469,7 @@ export function registerLibraryRoutes(app: ModuleApp, d: AoDeps) { method: "get", path: "/inventory/media-servers", scope: "read", - summary: "Emby 兼容库存查询(多实例;接口失败/截断 = unknown,不等于缺失)", + summary: "Emby 兼容库存查询(多实例;接口失败/截断 = unknown,不等于缺失)", query: MediaServerInventoryQuery, response: MediaServerInventory, }, diff --git a/apps/server/src/modules/acquisition-organize/routes/rule-groups.ts b/apps/server/src/modules/acquisition-organize/routes/rule-groups.ts index 5b1e1bfe..24836353 100644 --- a/apps/server/src/modules/acquisition-organize/routes/rule-groups.ts +++ b/apps/server/src/modules/acquisition-organize/routes/rule-groups.ts @@ -91,7 +91,7 @@ export async function checkSuppression(trx: Trx, fingerprint: string, source: st .where("active", "=", true) .executeTakeFirst(); if (!sup) return; - if (source === "agent") conflict("用户已停用或删除过同一语义的规则,Agent 不能自动加回"); + if (source === "agent") conflict("用户已停用或删除过同一语义的规则,Agent 不能自动加回"); await liftSuppressions(trx, fingerprint, by); } @@ -114,7 +114,7 @@ async function checkIdsExist(exec: Trx | AoDeps["ctx"]["db"], ids: readonly stri .execute(); const have = new Set(rows.map((r) => r.id)); const missing = ids.filter((x) => !have.has(x)); - if (missing.length) throw new ApiException("bad_request", `规则组不存在:${missing.join(", ")}`); + if (missing.length) throw new ApiException("bad_request", `规则组不存在:${missing.join(", ")}`); } /** 版本目标 / 订阅引用的规则组 id 校验(供 slots 路由复用) */ @@ -253,7 +253,7 @@ export function registerRuleGroupRoutes(app: ModuleApp, d: AoDeps) { Api.ruleAtoms.create, async (_c, { body }) => { const key = String(body.key).toUpperCase(); - if (BUILTIN_ATOM_MAP.has(key)) conflict(`${key} 是内置关键字,不能覆盖`); + if (BUILTIN_ATOM_MAP.has(key)) conflict(`${key} 是内置关键字,不能覆盖`); checkAtomPatterns(body.include); checkAtomPatterns(body.exclude); const r = await db() @@ -318,7 +318,7 @@ export function registerRuleGroupRoutes(app: ModuleApp, d: AoDeps) { (e) => atomsOf(e).has(a!.key), ), ); - if (users.length) conflict(`自定义条件 ${a!.key} 被规则组引用:${users.map((u) => u.name).join("、")}`); + if (users.length) conflict(`自定义条件 ${a!.key} 被规则组引用:${users.map((u) => u.name).join("、")}`); await db().deleteFrom("rule_atom").where("id", "=", params.id).execute(); return { ok: true as const }; }, @@ -434,7 +434,7 @@ export function registerRuleGroupRoutes(app: ModuleApp, d: AoDeps) { .executeTakeFirst(); if (!cur) notFound("规则组"); if (b.expectedRevision !== undefined && b.expectedRevision !== cur!.revision) - conflict(`revision 不匹配(当前 ${cur!.revision})`); + conflict(`revision 不匹配(当前 ${cur!.revision})`); if (b.name !== undefined && b.name !== cur!.name) { const dupName = await trx .selectFrom("rule_group") @@ -458,7 +458,7 @@ export function registerRuleGroupRoutes(app: ModuleApp, d: AoDeps) { ladderRequired: b.ladderRequired ?? cur!.ladder_required, }; if (b.kind !== undefined && b.kind !== cur!.kind && cur!.default_order !== null) - conflict("该规则组在默认链里,先从默认值移出再改类型"); + conflict("该规则组在默认链里,先从默认值移出再改类型"); // 偏好标签迁来的组沿用旧指纹口径,规则没变就不重算 const semanticChanged = b.kind !== undefined || @@ -528,7 +528,7 @@ export function registerRuleGroupRoutes(app: ModuleApp, d: AoDeps) { .executeTakeFirst(); if (!cur) notFound("规则组"); const refs = await referenceCount(trx, cur!.id); - if (refs > 0) conflict(`规则组被 ${refs} 个版本目标或订阅引用,先解除引用`); + if (refs > 0) conflict(`规则组被 ${refs} 个版本目标或订阅引用,先解除引用`); const row = await trx .updateTable("rule_group") .set({ diff --git a/apps/server/src/modules/acquisition-organize/routes/subscriptions.ts b/apps/server/src/modules/acquisition-organize/routes/subscriptions.ts index d4d3e71f..fa396220 100644 --- a/apps/server/src/modules/acquisition-organize/routes/subscriptions.ts +++ b/apps/server/src/modules/acquisition-organize/routes/subscriptions.ts @@ -103,7 +103,7 @@ async function assertNoDuplicate( q = input.seasonUnitId ? q.where("season_unit_id", "=", input.seasonUnitId) : q.where("season_unit_id", "is", null); if (input.excludeId) q = q.where("id", "!=", input.excludeId); const hit = await q.executeTakeFirst(); - if (hit) conflict(`该作品同一季已有订阅:${hit.id}`); + if (hit) conflict(`该作品同一季已有订阅:${hit.id}`); } /** 没有收录策略模板、表单给了质量预设时,新订阅按这个模板建一个版本目标(预设随后写入) */ @@ -497,12 +497,12 @@ export function registerSubscriptionRoutes(app: ModuleApp, d: AoDeps) { const pause = async (sub: SubscriptionRow) => { if (sub.status === "paused") return sub; if (!(TRACKING_SUB_STATUSES as readonly string[]).includes(sub.status)) - conflict(`订阅状态为 ${sub.status},不能暂停`); + conflict(`订阅状态为 ${sub.status},不能暂停`); return setStatus(sub, "paused", "user_paused"); }; const resume = async (sub: SubscriptionRow) => { if ((TRACKING_SUB_STATUSES as readonly string[]).includes(sub.status)) return sub; - if (sub.status !== "paused") conflict(`订阅状态为 ${sub.status},已完成/已取消的订阅用"重新订阅"`); + if (sub.status !== "paused") conflict(`订阅状态为 ${sub.status},已完成/已取消的订阅用"重新订阅"`); const ready = await tmdbReady(db(), sub.media_entity_id, sub.media_kind); const r = await setStatus(sub, ready ? "active" : "pending", ready ? "resumed" : "metadata_pending", { nextSearchNow: true, @@ -511,11 +511,11 @@ export function registerSubscriptionRoutes(app: ModuleApp, d: AoDeps) { return r; }; const search = async (sub: SubscriptionRow) => { - if (!sub.media_entity_id) throw new ApiException("bad_request", "订阅未关联作品,无法搜索"); + if (!sub.media_entity_id) throw new ApiException("bad_request", "订阅未关联作品,无法搜索"); try { return await enqueueSubscriptionSearch(d, sub.id, "manual"); } catch (err) { - throw new ApiException("upstream_unavailable", `搜索任务入队失败:${errMessage(err)}`); + throw new ApiException("upstream_unavailable", `搜索任务入队失败:${errMessage(err)}`); } }; @@ -623,7 +623,7 @@ export function registerSubscriptionRoutes(app: ModuleApp, d: AoDeps) { async (_c, { params, body }) => { const sub = await load(params.id); if (!(TRACKING_SUB_STATUSES as readonly string[]).includes(sub.status)) - conflict("订阅不在追踪中(暂停/已完成/已取消),先恢复或重新订阅"); + conflict("订阅不在追踪中(暂停/已完成/已取消),先恢复或重新订阅"); const slots = sub.media_entity_id ? await db() .selectFrom("acquisition_slot") @@ -641,7 +641,7 @@ export function registerSubscriptionRoutes(app: ModuleApp, d: AoDeps) { { idempotencyKey: `fill:${params.slotId}:${d.ports.now().getTime()}`, traceId: params.slotId }, ); } catch (err) { - throw new ApiException("upstream_unavailable", `补全任务入队失败:${errMessage(err)}`); + throw new ApiException("upstream_unavailable", `补全任务入队失败:${errMessage(err)}`); } }, { tag: TAG, status: 202 }, diff --git a/apps/server/src/modules/acquisition-organize/services/acquisition.ts b/apps/server/src/modules/acquisition-organize/services/acquisition.ts index 29135b35..fb627f7a 100644 --- a/apps/server/src/modules/acquisition-organize/services/acquisition.ts +++ b/apps/server/src/modules/acquisition-organize/services/acquisition.ts @@ -801,7 +801,7 @@ async function evaluateForSlot(c: Ctx, p: SlotInput): Promise<{ planId: string | }).then( ({ taskId }) => db.updateTable("acquisition_plan").set({ task_id: taskId }).where("id", "=", planId).execute(), (err) => - d.ctx.logger.warn({ err: { message: (err as Error).message }, planId }, "submitDownload 入队失败,留给巡检补投"), + d.ctx.logger.warn({ err: { message: (err as Error).message }, planId }, "submitDownload 入队失败,留给巡检补投"), ); return { planId, deferred: false }; } @@ -932,7 +932,7 @@ async function gates( reasons: [b.decision === "unknown" ? "budget_unknown_size" : "budget_exceeded"], rejected: b.violations.map((v) => ({ code: `budget.${v.kind}`, - message: `${v.kind}: 上限 ${v.limit},当前 ${v.current},请求 ${v.requested}`, + message: `${v.kind}:上限 ${v.limit},当前 ${v.current},请求 ${v.requested}`, })), }; } @@ -949,7 +949,7 @@ async function gates( rejected: [ { code: `cache.${dv.decision}`, - message: `缓存卷 ${vol.volumeKey} 使用率 ${dv.usedPct?.toFixed(1) ?? "未知"}%,预测 ${dv.projectedPct?.toFixed(1) ?? "未知"}%`, + message: `缓存卷 ${vol.volumeKey} 使用率 ${dv.usedPct?.toFixed(1) ?? "未知"}%,预测 ${dv.projectedPct?.toFixed(1) ?? "未知"}%`, }, ], }; diff --git a/apps/server/src/modules/acquisition-organize/services/downloads.ts b/apps/server/src/modules/acquisition-organize/services/downloads.ts index fe8f6c3f..608a47fd 100644 --- a/apps/server/src/modules/acquisition-organize/services/downloads.ts +++ b/apps/server/src/modules/acquisition-organize/services/downloads.ts @@ -151,7 +151,7 @@ export async function applySlotEvent( return false; } if (!next) { - d.ctx.logger.warn({ slotId, event: event.type, from: slot.status }, "Slot 状态迁移被拒绝(已被人工处理?)"); + d.ctx.logger.warn({ slotId, event: event.type, from: slot.status }, "Slot 状态迁移被拒绝(已被人工处理?)"); return false; } const ended = PLAN_EVENTS.has(event.type) && stageAfter(event.type) === null; @@ -288,7 +288,7 @@ export async function submitDownload(d: AoDeps, planId: string, opts: { runId?: return { kind: "already_exists", ...view(out.existing), tags: [...new Set([...out.existing.tags, tag])] }; } case "accepted_unconfirmed": - if (!out.hash) throw new Error("qB 已收单但未能确认任务(无 hash),稍后按探针标签对账"); + if (!out.hash) throw new Error("qB 已收单但未能确认任务(无 hash),稍后按探针标签对账"); return { kind: "accepted_unconfirmed", hash: out.hash, @@ -351,7 +351,7 @@ export async function submitDownload(d: AoDeps, planId: string, opts: { runId?: // 季包/多集包:按缺集选文件(接管的已有任务不改文件选择);失败不影响提交结果 if (result.kind === "added" && plan.unit_keys.length > 0 && plan.file_selection === null) { await selectPackFiles(d, client, hash, plan, rel).catch((err) => - d.ctx.logger.warn({ planId: plan.id, err: { message: errMessage(err) } }, "季包选文件失败,按整包下载"), + d.ctx.logger.warn({ planId: plan.id, err: { message: errMessage(err) } }, "季包选文件失败,按整包下载"), ); } if (!dtId) { @@ -754,7 +754,7 @@ async function observeLocked( // 无进展:只记证据 + 待处理,保留原任务,不换源(REQ §8.4.2) await upsertDecision(d, trx, { kind: "download_stalled", - title: `下载无进展:${t.name}`, + title: `下载无进展:${t.name}`, summary: `已 ${Math.round((now.getTime() - (noProgressSince?.getTime() ?? now.getTime())) / 60000)} 分钟没有新增字节`, subjectType: "download_task", subjectId: row.id, @@ -771,7 +771,7 @@ async function observeLocked( if ((merged.status === "failed" || merged.status === "missing") && changed) { await upsertDecision(d, trx, { kind: "download_failed", - title: `下载异常:${t.name}`, + title: `下载异常:${t.name}`, summary: `qB 状态 ${t.rawState}`, subjectType: "download_task", subjectId: row.id, @@ -1133,7 +1133,7 @@ export async function cleanupDownloadCache( kind: "space_insufficient", severity: "critical", title: `下载缓存卷 ${v.volumeKey} 空间不足`, - summary: `使用率 ${pct.toFixed(1)}%,可清理任务已用尽;新下载已暂停`, + summary: `使用率 ${pct.toFixed(1)}%,可清理任务已用尽;新下载已暂停`, subjectType: "cache_volume", dedupeKey: `space_insufficient:${v.volumeKey}`, evidence: { usedPercent: pct, locked: entry.skipped }, @@ -1146,7 +1146,7 @@ export async function cleanupDownloadCache( /** qB 删种 + 删数据:必经 assertSafeToDeleteData(本机视图与下载器视图各查一次) */ async function removeWithData(d: AoDeps, cand: CleanupCandidate, libraryRootsLocal: string[]): Promise { const { row, topo } = cand; - if (!row.content_path) throw new Error("content_path 未知,不能删数据"); + if (!row.content_path) throw new Error("content_path 未知,不能删数据"); const localContent = hostPathOf(topo, row.content_path); if (!localContent) throw new Error("content_path 无法映射到本机路径"); assertSafeToDeleteData( diff --git a/apps/server/src/modules/acquisition-organize/services/rules.ts b/apps/server/src/modules/acquisition-organize/services/rules.ts index 3ca7497e..3f750587 100644 --- a/apps/server/src/modules/acquisition-organize/services/rules.ts +++ b/apps/server/src/modules/acquisition-organize/services/rules.ts @@ -217,14 +217,14 @@ export function ruleFingerprint(g: { } const fmtErrors = (errs: readonly RuleParseError[]) => - errs.map((e) => `${e.message}(第 ${e.layer + 1} 层,位置 ${e.position})`).join(";"); + errs.map((e) => `${e.message}(第 ${e.layer + 1} 层,位置 ${e.position})`).join(";"); /** 表达式里的原子都要存在(内置且支持,或自定义) */ function checkAtoms(e: RuleExpr, custom: ReadonlySet): string[] { const bad: string[] = []; for (const id of atomsOf(e)) { const b = BUILTIN_ATOM_MAP.get(id); - if (b ? !b.supported : !custom.has(id)) bad.push(b ? `${id}(不支持)` : `${id}(不存在)`); + if (b ? !b.supported : !custom.has(id)) bad.push(b ? `${id}(不支持)` : `${id}(不存在)`); } return bad; } @@ -240,7 +240,7 @@ export function normalizeGroupInput( if (body.ruleString.trim() === "") out.layers = []; else { const p = parseRuleString(body.ruleString, { customAtoms: custom }); - if (p.errors.length) throw new ApiException("validation_failed", `规则串解析失败:${fmtErrors(p.errors)}`); + if (p.errors.length) throw new ApiException("validation_failed", `规则串解析失败:${fmtErrors(p.errors)}`); out.layers = p.layers; } } else if (body.layers !== undefined) { @@ -252,16 +252,16 @@ export function normalizeGroupInput( if (body.hardExcludes !== undefined) { out.hardExcludes = body.hardExcludes.map((x: string | ApiRuleExpr, i: number) => { if (typeof x !== "string") return normalizeExpr(x as RuleExpr); - if (x.includes(">")) throw new ApiException("validation_failed", `硬排除第 ${i + 1} 条不能分层(去掉 >)`); + if (x.includes(">")) throw new ApiException("validation_failed", `硬排除第 ${i + 1} 条不能分层(去掉 >)`); const p = parseExpr(x, { customAtoms: custom }); - if (!p.expr) throw new ApiException("validation_failed", `硬排除第 ${i + 1} 条解析失败:${fmtErrors(p.errors)}`); + if (!p.expr) throw new ApiException("validation_failed", `硬排除第 ${i + 1} 条解析失败:${fmtErrors(p.errors)}`); return p.expr; }); } const bad = [...(out.layers ?? []).map((l) => l.expr), ...(out.hardExcludes ?? [])].flatMap((e) => checkAtoms(e, custom), ); - if (bad.length) throw new ApiException("validation_failed", `条件无效:${[...new Set(bad)].join("、")}`); + if (bad.length) throw new ApiException("validation_failed", `条件无效:${[...new Set(bad)].join("、")}`); return out; } diff --git a/apps/server/src/modules/acquisition-organize/services/subscriptions-params.ts b/apps/server/src/modules/acquisition-organize/services/subscriptions-params.ts index f140e116..499b6b9f 100644 --- a/apps/server/src/modules/acquisition-organize/services/subscriptions-params.ts +++ b/apps/server/src/modules/acquisition-organize/services/subscriptions-params.ts @@ -83,7 +83,7 @@ async function idsByKey( .execute(); return values.map((v) => { const hit = rows.find((r) => r.id === v || r.k === v); - if (!hit) bad(`${label}不存在:${v}`); + if (!hit) bad(`${label}不存在:${v}`); return hit!.id; }); } @@ -454,14 +454,14 @@ export async function normalizeSubscriptionParams( const parsed = SubscriptionParams.safeParse(src); if (!parsed.success) { const i = parsed.error.issues[0]; - bad(`params.${i?.path.join(".") ?? ""} 无效:${i?.message ?? ""}`); + bad(`params.${i?.path.join(".") ?? ""} 无效:${i?.message ?? ""}`); } const p = parsed.data as SubscriptionParams; // ── 库根存在性与正则白名单 ── if (p.libraryRootId) { const r = await exec.selectFrom("library_root").select("id").where("id", "=", p.libraryRootId).executeTakeFirst(); - if (!r) bad(`库根不存在:${p.libraryRootId}`); + if (!r) bad(`库根不存在:${p.libraryRootId}`); } for (const k of ["include", "exclude"] as const) for (const pat of p[k] ?? []) { @@ -470,7 +470,7 @@ export async function normalizeSubscriptionParams( try { new RegExp(pat, "i"); } catch { - bad(`${k} 正则语法错误:${pat}`); + bad(`${k} 正则语法错误:${pat}`); } } if (p.stopAfter && Number.isNaN(Date.parse(p.stopAfter))) bad("stopAfter 不是有效时间"); diff --git a/apps/server/src/modules/acquisition-organize/services/subscriptions.ts b/apps/server/src/modules/acquisition-organize/services/subscriptions.ts index 3b1cc089..323a797b 100644 --- a/apps/server/src/modules/acquisition-organize/services/subscriptions.ts +++ b/apps/server/src/modules/acquisition-organize/services/subscriptions.ts @@ -474,7 +474,7 @@ export async function afterSlotVerified( kind: "other", severity: "info", title: "疑似缺集", - summary: `已入库最高集之前缺:${desc}。总集数未知,订阅继续追踪`.slice(0, 500), + summary: `已入库最高集之前缺:${desc}。总集数未知,订阅继续追踪`.slice(0, 500), subjectType: "acquisition_slot", subjectId: slot.id, dedupeKey: `suspected_missing:${slot.id}`, @@ -549,7 +549,7 @@ export async function completeSubscription( completed_at: d.ports.now(), }) .execute(); - d.ctx.logger.info({ subscriptionId: sub.id, reason }, "订阅完成,已写历史"); + d.ctx.logger.info({ subscriptionId: sub.id, reason }, "订阅完成,已写历史"); return true; } diff --git a/apps/server/src/modules/agent/authorize.ts b/apps/server/src/modules/agent/authorize.ts index 508d1dcb..df971c63 100644 --- a/apps/server/src/modules/agent/authorize.ts +++ b/apps/server/src/modules/agent/authorize.ts @@ -111,7 +111,7 @@ export function createAuthorizer(deps: { // 自动化总开关 / 只读对账:自动来源(agent / 后台任务)一律不执行写操作 if (ctx.source !== "web" && d.kind === "execute") { const a = await deps.settings.get("automation"); - if (!a.enabled) return { allowed: false, reason: "自动化总开关已关闭,不自动执行" }; + if (!a.enabled) return { allowed: false, reason: "自动化总开关已关闭,不自动执行" }; if (a.readOnlyReconcile) return { allowed: false, reason: "只读对账模式下不自动执行" }; } if (ctx.source === "web" && /^(user|admin):/.test(ctx.actorId)) return { allowed: true }; @@ -154,9 +154,9 @@ export function createAuthorizer(deps: { if (a.status !== "approved") return { allowed: false, reason: `审批 ${approvalId} 状态为 ${a.status}` }; if (a.plan_type !== ap.planType || a.plan_id !== planId) return { allowed: false, reason: "审批绑定的计划与本次调用不一致" }; - if (a.payload_hash !== target.payloadHash) return { allowed: false, reason: "计划内容已变化,旧审批失效" }; + if (a.payload_hash !== target.payloadHash) return { allowed: false, reason: "计划内容已变化,旧审批失效" }; if (a.expected_revision !== target.revision) - return { allowed: false, reason: "计划 revision 已变化,旧审批失效" }; + return { allowed: false, reason: "计划 revision 已变化,旧审批失效" }; if (a.expires_at && a.expires_at.getTime() <= now().getTime()) return { allowed: false, reason: "审批已过期" }; return { allowed: true, approvalId: a.id }; } @@ -227,7 +227,7 @@ export function createAuthorizer(deps: { } return { allowed: false, - reason: reasons.length > 0 ? `没有可用的授权:${reasons.join(";")}` : `缺少 ${d.id} 的持续授权或有效的单次审批`, + reason: reasons.length > 0 ? `没有可用的授权:${reasons.join(";")}` : `缺少 ${d.id} 的持续授权或有效的单次审批`, }; }; } diff --git a/apps/server/src/modules/agent/capabilities-download.ts b/apps/server/src/modules/agent/capabilities-download.ts index 178f0b79..b63dfcde 100644 --- a/apps/server/src/modules/agent/capabilities-download.ts +++ b/apps/server/src/modules/agent/capabilities-download.ts @@ -39,7 +39,7 @@ async function checkGrantScope(ctx: ServerContext, grantId: string, planId: stri .where("p.id", "=", planId) .executeTakeFirst() : undefined; - if (!e) throw new CapabilityError("unauthorized", `授权 ${grantId} 限定了对象范围,但下载任务没有关联作品`); + if (!e) throw new CapabilityError("unauthorized", `授权 ${grantId} 限定了对象范围,但下载任务没有关联作品`); if (scope.mediaEntityIds?.length && !scope.mediaEntityIds.includes(e.id)) throw new CapabilityError("unauthorized", `授权 ${grantId} 的对象范围不含目标作品`); if (scope.mediaKinds?.length) { @@ -87,7 +87,7 @@ export function registerDownloadRecoveryCapabilities( if (input.action === "recheck") await client.recheck([dt.torrent_hash]); else await client.resume([dt.torrent_hash]); } catch (err) { - throw new CapabilityError("internal", `下载器请求失败:${(err as Error).message}`); + throw new CapabilityError("internal", `下载器请求失败:${(err as Error).message}`); } return { downloadTaskId: dt.id, diff --git a/apps/server/src/modules/agent/capabilities.ts b/apps/server/src/modules/agent/capabilities.ts index 289e0f7c..6f212b61 100644 --- a/apps/server/src/modules/agent/capabilities.ts +++ b/apps/server/src/modules/agent/capabilities.ts @@ -58,7 +58,7 @@ export async function taskTimeline(ctx: ServerContext, t: TaskRow) { out.push({ at: t.finished_at.toISOString(), type: `task.${t.status}`, - summary: t.status === "failed" ? `失败:${JSON.stringify(t.last_error ?? {}).slice(0, 200)}` : t.status, + summary: t.status === "failed" ? `失败:${JSON.stringify(t.last_error ?? {}).slice(0, 200)}` : t.status, }); if (t.subject_type === "acquisition_plan" && t.subject_id) { const p = await db @@ -82,7 +82,7 @@ export async function taskTimeline(ctx: ServerContext, t: TaskRow) { out.push({ at: dt.updated_at.toISOString(), type: "download.status", - summary: `下载任务 ${dt.status}(进度 ${Math.round(Number(dt.progress ?? 0) * 100)}%)`, + summary: `下载任务 ${dt.status}(进度 ${Math.round(Number(dt.progress ?? 0) * 100)}%)`, data: { downloadTaskId: dt.id, status: dt.status }, }); const recs = dts.length @@ -125,7 +125,7 @@ export async function retryTask(ctx: ServerContext, taskId: string, by: string, const t = await ctx.db.selectFrom("task").selectAll().where("id", "=", taskId).executeTakeFirst(); if (!t) throw new CapabilityError("not_found", "任务不存在"); if (!["failed", "dead", "cancelled"].includes(t.status)) - throw new CapabilityError("precondition", `只能重试失败/死信/已取消的任务(当前 ${t.status})`); + throw new CapabilityError("precondition", `只能重试失败/死信/已取消的任务(当前 ${t.status})`); const input = t.kind === "agentRun" ? { agentRunId: t.subject_id } : { ...((t.input ?? {}) as Record), taskId }; const { runId } = await ctx.engine.enqueue(engineTaskName(t.kind), input, { diff --git a/apps/server/src/modules/agent/fake-scripts.ts b/apps/server/src/modules/agent/fake-scripts.ts index 3fd604ff..ac6f335f 100644 --- a/apps/server/src/modules/agent/fake-scripts.ts +++ b/apps/server/src/modules/agent/fake-scripts.ts @@ -79,7 +79,7 @@ export const DEMO_SCRIPTS: FakeScript[] = [ finalText: (results) => { const r = results.find((x) => x.capabilityId === "inventory.query"); const n = (r?.json as { items?: unknown[] } | undefined)?.items?.length ?? 0; - return `[fake] 演示模式:库存查询返回 ${n} 个作品。演示模型不提交下载;要真实执行,在设置里配置 Agent 上游。`; + return `[fake] 演示模式:库存查询返回 ${n} 个作品。演示模型不提交下载;要真实执行,在设置里配置 Agent 上游。`; }, }, ]; diff --git a/apps/server/src/modules/agent/interaction.ts b/apps/server/src/modules/agent/interaction.ts index 6d8ee9dd..0dc0defe 100644 --- a/apps/server/src/modules/agent/interaction.ts +++ b/apps/server/src/modules/agent/interaction.ts @@ -289,7 +289,7 @@ async function planSummary(db: Kysely, planType: string, planId: string): Pr } if (planType === "organize_plan") { const r = await db.selectFrom("artifact").select("title").where("id", "=", planId).executeTakeFirst(); - if (r) return safe(`整理:${r.title}`, 300); + if (r) return safe(`整理:${r.title}`, 300); } if (planType === "policy_revision") { const r = await db.selectFrom("policy_revision").select("revision").where("id", "=", planId).executeTakeFirst(); @@ -353,8 +353,8 @@ export async function inlineApproval( return { allowed: false, reason: "审批绑定的计划与本次调用不一致" }; } if (existing.payload_hash !== b.payloadHash || existing.expected_revision !== b.revision) { - await resolved("invalid", "计划在审批期间变化,旧审批失效"); - return { allowed: false, reason: "计划在审批期间变化,旧审批失效;重新生成计划后再提交" }; + await resolved("invalid", "计划在审批期间变化,旧审批失效"); + return { allowed: false, reason: "计划在审批期间变化,旧审批失效;重新生成计划后再提交" }; } if (existing.expires_at && existing.expires_at.getTime() <= now.getTime()) { await resolved("expired", null); @@ -383,7 +383,7 @@ export async function inlineApproval( if (existing.status === "expired" || (existing.expires_at && existing.expires_at.getTime() <= now.getTime())) { await expireApproval(sctx, existing.id); await resolved("expired", null); - return { allowed: false, reason: "审批已过期,没有执行" }; + return { allowed: false, reason: "审批已过期,没有执行" }; } // 仍在等待:重新挂起(崩溃重放 / 兜底轮询提前醒来) if (live?.suspend && !inv.startsWith("reconcile:")) @@ -405,7 +405,7 @@ export async function inlineApproval( const summary = await planSummary(db, plan.planType, plan.planId); const decision = await insertDecision(sctx, { kind: "approval_required", - title: `Agent 请求审批:${CAPABILITY_CATALOG[d.id as KnownCapabilityId]?.title ?? d.id}`, + title: `Agent 请求审批:${CAPABILITY_CATALOG[d.id as KnownCapabilityId]?.title ?? d.id}`, summary, runId, dedupeKey: `${APPROVAL_KEY}:${runId}:${inv}`, @@ -450,7 +450,7 @@ export async function inlineApproval( deadline: expiresAt.toISOString(), approvalId: a.id, }); - return { allowed: false, reason: "已在步骤处发起审批,等待管理员批准" }; + return { allowed: false, reason: "已在步骤处发起审批,等待管理员批准" }; } async function expireApproval(ctx: ServerContext, approvalId: string) { @@ -524,7 +524,7 @@ export async function proposeFirstBlock(db: Kysely, runId: string): Promise< .where("dedupe_key", "like", `${OPTIONS_KEY}:%`) .where("status", "=", "resolved") .executeTakeFirst(); - return chosen ? null : "本会话要求先看方案再执行:先用 agent.propose_options 给出方案,用户选定后才能执行"; + return chosen ? null : "本会话要求先看方案再执行:先用 agent.propose_options 给出方案,用户选定后才能执行"; } // ── 挂起唤醒条件(runAgent 的 waitForFact) ── @@ -569,7 +569,7 @@ export function registerInteractionCapabilities(registry: CapabilityRegistry, ct registry.register(capabilitySpec("agent.plan"), async (input, c) => { const run = await runOf(ctx, c); const live = liveRun(run.id); - if (!live) throw new CapabilityError("precondition", "会话不在运行中,不能更新步骤清单"); + if (!live) throw new CapabilityError("precondition", "会话不在运行中,不能更新步骤清单"); const cur = await currentPlan(ctx, live, run.id); const byId = new Map(input.replace ? [] : cur.steps.map((s) => [s.id, s] as const)); const order = input.replace ? [] : cur.steps.map((s) => s.id); @@ -583,7 +583,7 @@ export function registerInteractionCapabilities(registry: CapabilityRegistry, ct }); if (!order.includes(s.id)) order.push(s.id); } - if (order.length > 30) throw new CapabilityError("invalid_input", "步骤最多 30 个;合并细碎步骤后再更新"); + if (order.length > 30) throw new CapabilityError("invalid_input", "步骤最多 30 个;合并细碎步骤后再更新"); const next = { steps: order.map((id) => byId.get(id)!), revision: cur.revision + 1 }; live.plan = next; live.handle.appendEvent({ type: "plan_updated", steps: next.steps, revision: next.revision }); @@ -594,7 +594,7 @@ export function registerInteractionCapabilities(registry: CapabilityRegistry, ct registry.register({ ...capabilitySpec("agent.propose_options"), replay: "safe" }, async (input, c) => { const run = await runOf(ctx, c); if (!canAskUser(run)) - throw new CapabilityError("precondition", "子会话和后台会话不能让用户选方案:自己按证据决定,在汇报里说明取舍"); + throw new CapabilityError("precondition", "子会话和后台会话不能让用户选方案:自己按证据决定,在汇报里说明取舍"); const live = liveRun(run.id); const inv = c.invocationId; if (!live?.suspend || !inv) throw new CapabilityError("precondition", "会话不在运行中"); @@ -625,7 +625,7 @@ export function registerInteractionCapabilities(registry: CapabilityRegistry, ct const stepId = await currentStepId(ctx, live, run.id); d = await insertDecision(ctx, { kind: "other", - title: `Agent 等你选方案:${clip(input.question, 80)}`, + title: `Agent 等你选方案:${clip(input.question, 80)}`, summary: input.options.map((o) => `${o.id}. ${o.title}`).join(";"), runId: run.id, dedupeKey, @@ -660,8 +660,8 @@ export function registerInteractionCapabilities(registry: CapabilityRegistry, ct const pick = ev.recommendedId ?? ev.options[0]?.id ?? null; const res: OptionsResolution = ev.timeoutAction === "recommended" && pick - ? { optionKey: pick, outcome: "chosen", reason: "超时未选,按设置自动选推荐项", timeout: true } - : { optionKey: null, outcome: "stop", reason: "超时未选,按设置停止", timeout: true }; + ? { optionKey: pick, outcome: "chosen", reason: "超时未选,按设置自动选推荐项", timeout: true } + : { optionKey: null, outcome: "stop", reason: "超时未选,按设置停止", timeout: true }; await closeDecision(ctx, d.id, res.outcome === "chosen" ? "resolved" : "expired", { ...res }, "system:timeout"); result = optionsResult(ev, res.outcome === "chosen" ? "resolved" : "expired", res, "timeout"); } diff --git a/apps/server/src/modules/agent/investigate.ts b/apps/server/src/modules/agent/investigate.ts index da100dd1..f1e4341a 100644 --- a/apps/server/src/modules/agent/investigate.ts +++ b/apps/server/src/modules/agent/investigate.ts @@ -127,9 +127,9 @@ async function relatedTask(ctx: ServerContext, d: DecisionRow) { function suggestionFor(d: DecisionRow, reason: string): string { if (d.kind === "identity_ambiguous") - return `${reason}。可在待处理中手动选择身份,或为 identity.resolve 发放持续授权。`; + return `${reason}。可在待处理中手动选择身份,或为 identity.resolve 发放持续授权。`; return ( - `${reason}。可在待处理中选择"强制重新校验"(download.recheck),或为 download.recheck / task.retry 发放持续授权;` + + `${reason}。可在待处理中选择"强制重新校验"(download.recheck),或为 download.recheck / task.retry 发放持续授权;` + `换源、删除需人工决定。` ); } @@ -144,7 +144,7 @@ async function addRecheckOption(ctx: ServerContext, d: DecisionRow) { { key: "recheck", label: "强制重新校验", - description: "让下载器重新校验已有文件(不换源、不删除)", + description: "让下载器重新校验已有文件(不换源、不删除)", capabilityId: "download.recheck", input: { downloadTaskId: d.subject_id, action: "recheck" }, }, @@ -184,7 +184,7 @@ export async function autoInvestigate( and (evidence->'autoRecover'->>'recoverAttempted')::boolean is true`.execute(ctx.db); attempt = (prior.rows[0]?.n ?? 0) + 1; if (attempt > s.investigateMaxAttempts) { - const reason = `同一根因已自动恢复 ${attempt - 1} 次(上限 ${s.investigateMaxAttempts}),不再自动处理`; + const reason = `同一根因已自动恢复 ${attempt - 1} 次(上限 ${s.investigateMaxAttempts}),不再自动处理`; await mergeEvidence(ctx, d.id, { mode: "investigate", requestedMode: requested, @@ -229,14 +229,14 @@ export async function autoInvestigate( const taskId = await relatedTask(ctx, d); const goal = [ `调查待处理事项 ${d.id}(${d.kind}):${d.title}。`, - d.summary ? `摘要:${d.summary}。` : "", - d.subject_type && d.subject_id ? `对象:${d.subject_type} ${d.subject_id}。` : "", - dt ? `下载任务 ${dt.id}「${dt.name ?? ""}」当前 ${dt.status}(qB 状态 ${dt.raw_state ?? "未知"})。` : "", + d.summary ? `摘要:${d.summary}。` : "", + d.subject_type && d.subject_id ? `对象:${d.subject_type} ${d.subject_id}。` : "", + dt ? `下载任务 ${dt.id}「${dt.name ?? ""}」当前 ${dt.status}(qB 状态 ${dt.raw_state ?? "未知"})。` : "", taskId ? `关联任务 ${taskId}。` : "", mode === "recover" - ? "在授权范围内做有界恢复:先用 task.inspect 等只读能力查证,再按需调用 download.recheck(强制重新校验 / 恢复暂停)、" + - "task.retry(重试失败任务)或 identity.resolve,每种最多一次;不换源、不删除、不清理;恢复结果由系统核实。" - : "只做只读调查:查证据,给出结论与处理建议;不提交下载,不改文件。", + ? "在授权范围内做有界恢复:先用 task.inspect 等只读能力查证,再按需调用 download.recheck(强制重新校验 / 恢复暂停)、" + + "task.retry(重试失败任务)或 identity.resolve,每种最多一次;不换源、不删除、不清理;恢复结果由系统核实。" + : "只做只读调查:查证据,给出结论与处理建议;不提交下载,不改文件。", ].join(""); const { run, created } = await startRun(ctx, { goal, @@ -380,16 +380,16 @@ export async function applyRecoveryOutcome( const what = done.map((a) => a.capabilityId).join(" + "); const summary = outcome === "recovered" - ? `已恢复(系统核实):执行 ${what} 后业务状态已恢复。` + ? `已恢复(系统核实):执行 ${what} 后业务状态已恢复。` : outcome === "failed" - ? `未恢复:执行 ${what} 后业务状态仍为失败,已退回待处理。` + ? `未恢复:执行 ${what} 后业务状态仍为失败,已退回待处理。` : outcome === "unverified" - ? `未恢复:执行 ${what} 后在核实时限内业务状态未恢复,已退回待处理。` + ? `未恢复:执行 ${what} 后在核实时限内业务状态未恢复,已退回待处理。` : outcome === "budget_exceeded" - ? `未恢复:调查超出预算(${res.message ?? ""}),未执行恢复动作,已退回待处理。` + ? `未恢复:调查超出预算(${res.message ?? ""}),未执行恢复动作,已退回待处理。` : outcome === "cancelled" - ? "未恢复:运行已取消。" - : `未恢复:没有执行恢复动作${acts.denied.length ? `(被拒绝:${acts.denied.map((x) => x.capabilityId).join(", ")})` : ""},已退回待处理。`; + ? "未恢复:运行已取消。" + : `未恢复:没有执行恢复动作${acts.denied.length ? `(被拒绝:${acts.denied.map((x) => x.capabilityId).join(", ")})` : ""},已退回待处理。`; const evidence: Partial = { runId, outcome, diff --git a/apps/server/src/modules/agent/invoke-limits.ts b/apps/server/src/modules/agent/invoke-limits.ts index 5728774b..245e5122 100644 --- a/apps/server/src/modules/agent/invoke-limits.ts +++ b/apps/server/src/modules/agent/invoke-limits.ts @@ -31,7 +31,7 @@ export function externalUpstream(capabilityId: string, input: unknown): Upstream export class UpstreamBusyError extends Error { constructor(readonly upstream: Upstream) { - super(`${upstream === "tmdb" ? "TMDB" : "站点"}并发已满,稍后重试`); + super(`${upstream === "tmdb" ? "TMDB" : "站点"}并发已满,稍后重试`); } } diff --git a/apps/server/src/modules/agent/report.ts b/apps/server/src/modules/agent/report.ts index aea7a22a..8bd6c9b6 100644 --- a/apps/server/src/modules/agent/report.ts +++ b/apps/server/src/modules/agent/report.ts @@ -102,11 +102,11 @@ export async function buildGoalReport( const done = records.filter((r) => r.status === "succeeded" || r.status === "partial"); const summary = state === "no_actions" - ? "本次没有提交下载或整理,结论见模型汇报。" + ? "本次没有提交下载或整理,结论见模型汇报。" : [ - `已提交下载 ${submitted} 个,已入库 ${imported} 个,进行中 ${inProgress} 个,失败 ${failed} 个。`, - state === "waiting" ? "已提交≠已入库:等待下载完成并核验入库。" : "", - done.length ? `整理记录:${done.map((r) => `${r.id}(${r.status})`).join("、")}。` : "", + `已提交下载 ${submitted} 个,已入库 ${imported} 个,进行中 ${inProgress} 个,失败 ${failed} 个。`, + state === "waiting" ? "已提交≠已入库:等待下载完成并核验入库。" : "", + done.length ? `整理记录:${done.map((r) => `${r.id}(${r.status})`).join("、")}。` : "", ] .filter(Boolean) .join(""); diff --git a/apps/server/src/modules/agent/routes.ts b/apps/server/src/modules/agent/routes.ts index 2fbc03ed..6ae8ed0f 100644 --- a/apps/server/src/modules/agent/routes.ts +++ b/apps/server/src/modules/agent/routes.ts @@ -224,7 +224,7 @@ export function registerAgentRoutes( const run = await db().selectFrom("agent_run").selectAll().where("id", "=", params.id).executeTakeFirst(); if (!run) throw new ApiException("not_found", "Agent 运行不存在"); if (body.optionId === null && !body.reason?.trim()) - throw new ApiException("validation_failed", "都不要时写一句原因,Agent 按它调整方案"); + throw new ApiException("validation_failed", "都不要时写一句原因,Agent 按它调整方案"); const r = await answerOptions(ctx, run.id, body, actorId); if (r === "not_found") throw new ApiException("not_found", "这个会话没有这项方案选择"); if (r === "bad_option") throw new ApiException("validation_failed", "没有这个方案"); @@ -287,7 +287,7 @@ export function registerAgentRoutes( const cur = await db().selectFrom("automation_grant").selectAll().where("id", "=", params.id).executeTakeFirst(); if (!cur) throw new ApiException("not_found", "授权不存在"); if (body.expiresAt && cur.expires_at && new Date(body.expiresAt) > cur.expires_at) - throw new ApiException("validation_failed", "只能缩短有效期;扩大范围需新建授权"); + throw new ApiException("validation_failed", "只能缩短有效期;扩大范围需新建授权"); const r = await db() .updateTable("automation_grant") .set({ @@ -362,7 +362,7 @@ export function registerAgentRoutes( .executeTakeFirst(); if (!cur) throw new ApiException("not_found", "审批不存在"); if (cur.payload_hash !== body.payloadHash) - throw new ApiException("conflict", "计划已变化(payloadHash 不一致),重新申请审批"); + throw new ApiException("conflict", "计划已变化(payloadHash 不一致),重新申请审批"); const allowed = body.status === "revoked" ? ["pending", "approved"] : ["pending"]; if (!allowed.includes(cur.status)) throw new ApiException("conflict", `审批已是 ${cur.status}`); return trx @@ -388,7 +388,7 @@ export function registerAgentRoutes( })); route(app, Api.capabilities.invoke, async ({ params, body, actor, actorId, c }) => { const entry = ctx.capabilities.get(params.capabilityId); - if (!entry) throw new ApiException("not_found", `能力未注册:${params.capabilityId}`); + if (!entry) throw new ApiException("not_found", `能力未注册:${params.capabilityId}`); const scopes = (c.get("principal")?.scopes ?? actor?.scopes ?? []) as string[]; if (!hasScope(scopes, entry.descriptor.scope)) throw new ApiException("forbidden", `调用 ${entry.descriptor.id} 需要 scope: ${entry.descriptor.scope}`); @@ -431,7 +431,8 @@ export function registerAgentRoutes( slotIds = [s.id]; } else { const entityId = body.entityId ?? rel.media_entity_id; - if (!entityId) throw new ApiException("precondition_failed", "资源还没有关联本地作品,先在“身份与元数据”指定作品"); + if (!entityId) + throw new ApiException("precondition_failed", "资源还没有关联本地作品,先在「身份与元数据」指定作品"); slotIds = ( await db() .selectFrom("acquisition_slot") @@ -443,7 +444,7 @@ export function registerAgentRoutes( } const rejected: { slotId: string | null; code: string; message: string }[] = []; if (slotIds.length === 0) - rejected.push({ slotId: null, code: "no_slot", message: "这个作品没有版本目标,先在订阅里新建" }); + rejected.push({ slotId: null, code: "no_slot", message: "这个作品没有版本目标,先在订阅里新建" }); const invokeCtx = { actorId, taskId: `http:${c.get("requestId") ?? "req"}`, diff --git a/apps/server/src/modules/agent/runner.ts b/apps/server/src/modules/agent/runner.ts index 14a704a6..121469e7 100644 --- a/apps/server/src/modules/agent/runner.ts +++ b/apps/server/src/modules/agent/runner.ts @@ -86,7 +86,7 @@ export function scopeFor(mode: "execute" | "plan_only", allowlist?: readonly str const d = CAPABILITY_CATALOG[id as KnownCapabilityId]; return !d || !PLAN_ONLY_KINDS.includes(d.kind); }); - if (bad.length) throw new Error(`plan_only 模式只允许 read/preview/propose 类能力,不允许:${bad.join(", ")}`); + if (bad.length) throw new Error(`plan_only 模式只允许 read/preview/propose 类能力,不允许:${bad.join(", ")}`); return { capabilities: allowlist?.length ? [...allowlist] @@ -252,23 +252,24 @@ function systemPromptFor( sub: { canSpawn: boolean; maxConcurrent: number; maxDepth: number }, ): string { const constraints: string[] = [ - `可用能力:${scope.capabilities.join(", ")}`, - "有副作用的能力(提交下载、执行整理、发布策略、重试/取消任务)需要服务端授权;被拒时说明缺少哪项授权。", + `可用能力:${scope.capabilities.join(", ")}`, + "有副作用的能力(提交下载、执行整理、发布策略、重试/取消任务)需要服务端授权;被拒时说明缺少哪项授权。", ]; - if (run.mode === "plan_only") constraints.push("本次只读:只调查与规划,不提交下载、不改文件、不发布策略。"); + if (run.mode === "plan_only") constraints.push("本次只读:只调查与规划,不提交下载、不改文件、不发布策略。"); if (sub.canSpawn) constraints.push( - `可以用 agent.spawn 把彼此独立的有界子目标交给子会话(同时最多 ${sub.maxConcurrent} 个,派生层数上限 ${sub.maxDepth});` + - "子会话的能力、模式与预算都不会超过本会话,且会占用本会话预算;派生后用 agent.await 等结果再汇总,不要为小事派生。", + `可以用 agent.spawn 把彼此独立的有界子目标交给子会话(同时最多 ${sub.maxConcurrent} 个,派生层数上限 ${sub.maxDepth});` + + "子会话的能力、模式与预算都不会超过本会话,且会占用本会话预算;派生后用 agent.await 等结果再汇总,不要为小事派生。", ); - if (run.parent_run_id) constraints.push("你是子会话:只完成本目标,完成后简要汇报结论与证据(对象 ID),由父会话汇总。"); + if (run.parent_run_id) + constraints.push("你是子会话:只完成本目标,完成后简要汇报结论与证据(对象 ID),由父会话汇总。"); if (run.trigger === "event" && run.mode === "execute") constraints.push( - "本任务由后台事件触发,只做授权内的有界恢复:先查证据,再在授权范围内执行恢复(task.retry / download.recheck / identity.resolve)," + - "每种动作最多一次;不换源、不删除、不清理;是否恢复由系统按业务状态核实,不要自行宣称已恢复。", + "本任务由后台事件触发,只做授权内的有界恢复:先查证据,再在授权范围内执行恢复(task.retry / download.recheck / identity.resolve)," + + "每种动作最多一次;不换源、不删除、不清理;是否恢复由系统按业务状态核实,不要自行宣称已恢复。", ); else if (run.trigger === "event") - constraints.push("本任务由后台事件触发,只做只读调查:收集证据,给出结论与建议,不要反复重试同一操作。"); + constraints.push("本任务由后台事件触发,只做只读调查:收集证据,给出结论与建议,不要反复重试同一操作。"); const opts = sessionOptions(run); return buildSystemPrompt({ role: run.trigger === "event" ? "媒体库异常调查员" : "媒体库自动化 Agent", @@ -340,7 +341,7 @@ export async function drive( let settings: AgentSettings; try { const resolved = await resolveRunUpstream(ctx, run.upstream_id); - if (!resolved) throw new Error("未配置 Agent 上游(设置 → Agent → 上游)"); + if (!resolved) throw new Error("未配置 Agent 上游(设置 → Agent → 上游)"); upstream = resolved; fake = upstream.row.provider === "fake"; settings = agentSettings(s, (run.budget ?? {}) as AgentBudget, fake, upstream); @@ -373,7 +374,7 @@ export async function drive( status: "failed", reason: "error", budget: null, - message: `fakeScript 不合法:${(err as Error).message}`, + message: `fakeScript 不合法:${(err as Error).message}`, finalText: "", }; } diff --git a/apps/server/src/modules/agent/service.ts b/apps/server/src/modules/agent/service.ts index 7564c096..69b8a4f3 100644 --- a/apps/server/src/modules/agent/service.ts +++ b/apps/server/src/modules/agent/service.ts @@ -142,7 +142,7 @@ async function enqueueRun(ctx: ServerContext, run: AgentRunRow, key: string) { } catch (err) { await ctx.db .updateTable("agent_run") - .set({ status: "failed", error: JSON.stringify({ message: `入队失败:${(err as Error).message}` }) }) + .set({ status: "failed", error: JSON.stringify({ message: `入队失败:${(err as Error).message}` }) }) .where("id", "=", run.id) .execute(); throw err; @@ -237,13 +237,13 @@ export async function finalize( let summary: string; if (res.status === "cancelled" || run.cancel_requested_at) { status = "cancelled"; - summary = `已停止(后续步骤不再执行,已完成的动作不撤销)。${report.state === "no_actions" ? "" : report.summary}`; + summary = `已停止(后续步骤不再执行,已完成的动作不撤销)。${report.state === "no_actions" ? "" : report.summary}`; } else if (res.reason === "budget") { status = "budget_exceeded"; - summary = `超出预算:${res.message ?? ""}。${report.state === "no_actions" ? "" : report.summary}`; + summary = `超出预算:${res.message ?? ""}。${report.state === "no_actions" ? "" : report.summary}`; } else if (res.status === "failed") { status = "failed"; - summary = `运行失败:${res.message ?? ""}`; + summary = `运行失败:${res.message ?? ""}`; } else if (report.state === "waiting") { status = "waiting"; summary = report.summary; @@ -252,7 +252,7 @@ export async function finalize( summary = report.summary; } // 模型文本只作参考:附在业务核查结论之后,明确标注未经核查 - if (res.finalText && res.status !== "failed") summary += `\n模型汇报(未经核查):${res.finalText}`; + if (res.finalText && res.status !== "failed") summary += `\n 模型汇报(未经核查):${res.finalText}`; // 子代理:父会话总预算含子会话用量 const kids = await descendants(ctx, runId); if (kids.length) { @@ -264,7 +264,7 @@ export async function finalize( .executeTakeFirst(); const ownTokens = ((fresh?.usage ?? own) as { totalTokens?: number }).totalTokens ?? 0; const ext = await descendantUsage(ctx, runId); - summary += `\n子会话 ${kids.length} 个;会话树合计工具调用 ${(fresh?.tool_call_count ?? run.tool_call_count) + ext.toolCalls} 次、token ${ownTokens + ext.totalTokens}。`; + summary += `\n 子会话 ${kids.length} 个;会话树合计工具调用 ${(fresh?.tool_call_count ?? run.tool_call_count) + ext.toolCalls} 次、token ${ownTokens + ext.totalTokens}。`; } // 系统核实结论(授权内恢复)与终态同一次写入:观察者看到终态时汇总已是最终文本 if (opts.summaryPrefix) summary = `${opts.summaryPrefix}\n${summary}`; @@ -391,9 +391,9 @@ export async function autoInvestigate( if (!s.autoInvestigate) return { runId: null, created: false }; const goal = [ `调查待处理事项 ${d.id}(${d.kind}):${d.title}。`, - d.summary ? `摘要:${d.summary}。` : "", - d.subject_type && d.subject_id ? `对象:${d.subject_type} ${d.subject_id}。` : "", - "只做只读调查:查证据,给出结论与处理建议;不提交下载,不改文件。", + d.summary ? `摘要:${d.summary}。` : "", + d.subject_type && d.subject_id ? `对象:${d.subject_type} ${d.subject_id}。` : "", + "只做只读调查:查证据,给出结论与处理建议;不提交下载,不改文件。", ].join(""); const { run, created } = await startRun(ctx, { goal, diff --git a/apps/server/src/modules/agent/subagents.ts b/apps/server/src/modules/agent/subagents.ts index 4e257270..b1bbce07 100644 --- a/apps/server/src/modules/agent/subagents.ts +++ b/apps/server/src/modules/agent/subagents.ts @@ -75,7 +75,7 @@ const safe = (s: string | null | undefined, n = 1500) => clip(redactString(s ?? */ export class AwaitSuspendedError extends CapabilityError { constructor() { - super("precondition", "会话已挂起等待子会话结束,恢复后重放本调用"); + super("precondition", "会话已挂起等待子会话结束,恢复后重放本调用"); } } @@ -321,7 +321,7 @@ export async function cascadeCancel(ctx: ServerContext, runId: string, reason: s .updateTable("agent_run") .set({ cancel_requested_at: now, - error: JSON.stringify({ abortReason: `父会话已取消:${reason}`, by: `agent:${runId}` }), + error: JSON.stringify({ abortReason: `父会话已取消:${reason}`, by: `agent:${runId}` }), }) .where("id", "in", ids) .where("cancel_requested_at", "is", null) @@ -330,7 +330,7 @@ export async function cascadeCancel(ctx: ServerContext, runId: string, reason: s if (taskIds.length) await ctx.db .updateTable("task") - .set({ cancel_requested_at: now, cancel_reason: `父会话已取消:${reason}` }) + .set({ cancel_requested_at: now, cancel_reason: `父会话已取消:${reason}` }) .where("id", "in", taskIds) .where("cancel_requested_at", "is", null) .execute(); @@ -489,7 +489,7 @@ export async function allocateBudget( if (want !== undefined) out[k] = want; return; } - if (left < min) throw new CapabilityError("budget", `本会话剩余${label}不足,不能再派生子会话(剩余 ${left})`); + if (left < min) throw new CapabilityError("budget", `本会话剩余${label}不足,不能再派生子会话(剩余 ${left})`); if (want !== undefined && want > left) throw new CapabilityError("budget", `子会话${label} ${want} 超过本会话剩余预算 ${left}`); const v = want ?? (k === "maxCostUsd" ? left / 2 : Math.max(min, Math.floor(left / 2))); @@ -498,7 +498,7 @@ export async function allocateBudget( pick("maxToolCalls", remain.maxToolCalls, "工具调用次数", 1); pick("maxTokens", remain.maxTokens, " token", 1); pick("maxCostUsd", remain.maxCostUsd, "费用", 0.0001); - pick("maxDurationSec", remain.maxDurationSec, "时长(秒)", 5); + pick("maxDurationSec", remain.maxDurationSec, "时长(秒)", 5); return out; } @@ -528,10 +528,10 @@ export function spawnScope( const kindOf = (id: string) => CAPABILITY_CATALOG[id as KnownCapabilityId]?.kind; if (requested?.length) { const outside = requested.filter((id) => !parentCaps.includes(id)); - if (outside.length) throw new CapabilityError("unauthorized", `子会话能力超出本会话白名单:${outside.join(", ")}`); + if (outside.length) throw new CapabilityError("unauthorized", `子会话能力超出本会话白名单:${outside.join(", ")}`); const tooStrong = requested.filter((id) => !kinds.includes(kindOf(id) as CapabilityKind)); if (tooStrong.length) - throw new CapabilityError("unauthorized", `子会话模式/授权上限不允许这些能力:${tooStrong.join(", ")}`); + throw new CapabilityError("unauthorized", `子会话模式/授权上限不允许这些能力:${tooStrong.join(", ")}`); } const caps = (requested?.length ? [...new Set(requested)] : parentCaps).filter((id) => kinds.includes(kindOf(id) as CapabilityKind), @@ -587,7 +587,7 @@ export async function awaitChildren( const rows = await q.orderBy("created_at").execute(); if (ids?.length && rows.length !== new Set(ids).size) { const missing = ids.filter((id) => !rows.some((r) => r.id === id)); - throw new CapabilityError("not_found", `不是本会话派生的子会话:${missing.join(", ")}`); + throw new CapabilityError("not_found", `不是本会话派生的子会话:${missing.join(", ")}`); } await liveRun(parentId)?.pump(); const allDone = rows.every((r) => SETTLED.includes(r.status)); @@ -644,7 +644,7 @@ export async function wakeParent(ctx: ServerContext, childRunId: string): Promis await ctx.engine.signal(w.engineRunId, w.fact, { childRunId }); return true; } catch (err) { - ctx.logger.warn({ childRunId, err: { message: (err as Error).message } }, "唤醒父会话失败(父会话会兜底轮询)"); + ctx.logger.warn({ childRunId, err: { message: (err as Error).message } }, "唤醒父会话失败(父会话会兜底轮询)"); return false; } } @@ -662,7 +662,7 @@ export async function wakeRun( await ctx.engine.signal(w.engineRunId, w.fact, payload); return true; } catch (err) { - ctx.logger.warn({ runId, err: { message: (err as Error).message } }, "唤醒会话失败(会话会兜底轮询)"); + ctx.logger.warn({ runId, err: { message: (err as Error).message } }, "唤醒会话失败(会话会兜底轮询)"); return false; } } @@ -719,9 +719,9 @@ export function registerSubagentCapabilities(registry: CapabilityRegistry, ctx: const parent = await parentOf(ctx, c); const s = await ctx.settings.all(); const cfg = subagentSettings(s); - if (!cfg.enabled) throw new CapabilityError("precondition", "子会话未开启(设置 → Agent → 允许子会话)"); + if (!cfg.enabled) throw new CapabilityError("precondition", "子会话未开启(设置 → Agent → 允许子会话)"); if (parent.cancel_requested_at || TERMINAL.includes(parent.status)) - throw new CapabilityError("precondition", "本会话已取消或已结束,不能派生子会话"); + throw new CapabilityError("precondition", "本会话已取消或已结束,不能派生子会话"); const dedupeKey = `subagent:${c.idempotencyKey ?? `${parent.id}:${Date.now()}`}`; const existing = await ctx.db .selectFrom("agent_run") @@ -731,7 +731,7 @@ export function registerSubagentCapabilities(registry: CapabilityRegistry, ctx: let child = existing; if (!child) { if (parent.depth + 1 > cfg.maxDepth) - throw new CapabilityError("precondition", `派生层数已达上限 ${cfg.maxDepth},子会话不能再派生`); + throw new CapabilityError("precondition", `派生层数已达上限 ${cfg.maxDepth},子会话不能再派生`); const mode: AgentRunMode = input.mode ?? parent.mode; if (parent.mode === "plan_only" && mode === "execute") throw new CapabilityError("unauthorized", "只给方案的会话只能派生只给方案的子会话"); @@ -812,7 +812,7 @@ export async function exceedsAncestors( for (const a of chain) { const sc = (a.scope ?? {}) as Partial; if (sc.capabilities?.length && !sc.capabilities.includes(capabilityId)) - return `超出父会话 ${a.id} 的能力白名单:${capabilityId}`; + return `超出父会话 ${a.id} 的能力白名单:${capabilityId}`; if (sc.allowedKinds && !sc.allowedKinds.includes(kind)) return `父会话 ${a.id} 不允许 ${kind} 类能力`; } return null; diff --git a/apps/server/src/modules/auth-settings/auth-service.ts b/apps/server/src/modules/auth-settings/auth-service.ts index 342f5442..2af30426 100644 --- a/apps/server/src/modules/auth-settings/auth-service.ts +++ b/apps/server/src/modules/auth-settings/auth-service.ts @@ -82,7 +82,7 @@ export async function ensureSetupToken(deps: Pick): P returning id`.execute(deps.db); if (!r.rows.length) return null; // 注意:logger 会把名为 *token* 的字段脱敏,所以 token 放在消息文本里 - deps.logger.warn(`管理员尚未初始化。一次性 setup token(打开控制台登录页完成初始化;重启会重新生成):${token}`); + deps.logger.warn(`管理员尚未初始化。一次性 setup token(打开控制台登录页完成初始化;重启会重新生成):${token}`); return token; } @@ -104,7 +104,7 @@ async function recentFailures(db: Kysely, col: "ip" | "username", value: str export async function assertNotRateLimited(db: Kysely, ip: string, username: string): Promise { 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 分钟后再试"); } } @@ -363,7 +363,7 @@ function otpauthUri(secret: string, account: string): string { } export async function totpBegin(deps: AuthDeps, admin: AdminRow): Promise { - if (admin.totp_enabled_at) throw new ApiException("conflict", "TOTP 已启用,先停用再重新绑定"); + if (admin.totp_enabled_at) throw new ApiException("conflict", "TOTP 已启用,先停用再重新绑定"); const secret = base32Encode(randomBytes(20)); const ref = await deps.secrets.create({ purpose: "other", plaintext: secret, label: `admin-totp:${randomToken(6)}` }); const old = admin.totp_pending_secret_ref; @@ -375,7 +375,7 @@ export async function totpBegin(deps: AuthDeps, admin: AdminRow): Promise { - if (admin.totp_enabled_at) throw new ApiException("conflict", "TOTP 已启用,先停用再重新绑定"); + if (admin.totp_enabled_at) throw new ApiException("conflict", "TOTP 已启用,先停用再重新绑定"); if (admin.totp_pending_secret_ref) { try { const secret = await deps.secrets.resolve(admin.totp_pending_secret_ref); diff --git a/apps/server/src/modules/auth-settings/engine-routes.ts b/apps/server/src/modules/auth-settings/engine-routes.ts index ced23e85..5d994a63 100644 --- a/apps/server/src/modules/auth-settings/engine-routes.ts +++ b/apps/server/src/modules/auth-settings/engine-routes.ts @@ -25,7 +25,7 @@ const D = { method: "get", path: "/engine/runs", scope: "admin", - summary: "引擎 runs(状态/名称/时间筛选,新→旧)", + summary: "引擎 runs(状态/名称/时间筛选,新→旧)", query: EngineRunQuery, response: cursorPage(EngineRun), tags, @@ -61,7 +61,7 @@ const D = { method: "post", path: "/engine/runs/{id}/retry", scope: "admin", - summary: "按原任务名与输入重新入队(新幂等键 retry::<序号>)", + summary: "按原任务名与输入重新入队(新幂等键 retry::<序号>)", params: RunIdParam, response: z.object({ runId: z.string(), retryOf: z.string(), attempt: z.number().int().positive() }), tags, @@ -316,7 +316,7 @@ export function registerEngineRoutes(app: ModuleApp, ctx: ServerContext, opts: { const run = r.rows[0]; if (!run) throw new ApiException("not_found", `run ${params.id} 不存在`); if (!RETRYABLE.has(run.status)) - throw new ApiException("conflict", `run 状态为 ${run.status},只能重跑已结束的 run(可先取消)`); + throw new ApiException("conflict", `run 状态为 ${run.status},只能重跑已结束的 run(可先取消)`); const env = (run.input ?? {}) as { input?: unknown; traceId?: string; priority?: number }; const prefix = `retry:${params.id}:`; const prev = await sql<{ n: number }>` @@ -330,7 +330,7 @@ export function registerEngineRoutes(app: ModuleApp, ctx: ServerContext, opts: { ...(typeof env.priority === "number" ? { priority: env.priority } : {}), })); } catch (err) { - throw new ApiException("precondition_failed", `无法重新入队:${(err as Error).message}`); + throw new ApiException("precondition_failed", `无法重新入队:${(err as Error).message}`); } await audit( ctx, diff --git a/apps/server/src/modules/auth-settings/instances-routes.ts b/apps/server/src/modules/auth-settings/instances-routes.ts index aa26a7e4..9b6619e3 100644 --- a/apps/server/src/modules/auth-settings/instances-routes.ts +++ b/apps/server/src/modules/auth-settings/instances-routes.ts @@ -130,7 +130,7 @@ export function registerInstanceRoutes(app: ModuleApp, ctx: ServerContext) { .deleteFrom(table) .where("id", "=", id) .execute() - .catch((e: unknown) => pgConflict(e, `${NOUN[table]}仍被引用,不能删除`)); + .catch((e: unknown) => pgConflict(e, `${NOUN[table]}仍被引用,不能删除`)); if (row.secret_ref) await ctx.secrets.remove(row.secret_ref).catch(() => {}); await audit(ctx, actor, action, { type: table, id }); return { ok: true as const }; @@ -142,7 +142,7 @@ export function registerInstanceRoutes(app: ModuleApp, ctx: ServerContext) { .values(values) .returningAll() .executeTakeFirstOrThrow() - .catch((e: unknown) => pgConflict(e, `${NOUN[table]}的名称或路径重复,或关联的对象不存在`))) as Record< + .catch((e: unknown) => pgConflict(e, `${NOUN[table]}的名称或路径重复,或关联的对象不存在`))) as Record< string, unknown > & { @@ -157,7 +157,7 @@ export function registerInstanceRoutes(app: ModuleApp, ctx: ServerContext) { .where("id", "=", id) .returningAll() .executeTakeFirst() - .catch((e: unknown) => pgConflict(e, `${NOUN[table]}的名称或路径重复,或关联的对象不存在`)); + .catch((e: unknown) => pgConflict(e, `${NOUN[table]}的名称或路径重复,或关联的对象不存在`)); if (!r) throw new ApiException("not_found", `${NOUN[table]}不存在`); return r as Record & { id: string }; }; @@ -181,7 +181,7 @@ export function registerInstanceRoutes(app: ModuleApp, ctx: ServerContext) { } catch (e) { throw new ApiException( "precondition_failed", - `库根不可写(需要目录存在且可写入哨兵文件 ${name}):${errMessage(e)}`, + `库根不可写(需要目录存在且可写入哨兵文件 ${name}):${errMessage(e)}`, ); } return name; diff --git a/apps/server/src/modules/auth-settings/keys-routes.ts b/apps/server/src/modules/auth-settings/keys-routes.ts index 18c76935..f6aa2ecd 100644 --- a/apps/server/src/modules/auth-settings/keys-routes.ts +++ b/apps/server/src/modules/auth-settings/keys-routes.ts @@ -35,7 +35,7 @@ const D = { method: "delete", path: "/secrets/{id}", scope: "admin", - summary: "删除凭证(被引用时 409)", + summary: "删除凭证(被引用时 409)", params: IdParam, response: OkResult, tags: ["secrets"], @@ -44,7 +44,7 @@ const D = { method: "get", path: "/audit-events", scope: "admin", - summary: "审计事件(按 actor/action/target 过滤,新→旧)", + summary: "审计事件(按 actor/action/target 过滤,新→旧)", query: AuditEventQuery, response: cursorPage(AuditEvent), tags: ["audit"], @@ -174,7 +174,7 @@ export function registerKeyRoutes(app: ModuleApp, ctx: ServerContext) { .where("id", "=", params.id) .returning("id") .executeTakeFirst() - .catch((e) => pgConflict(e, "凭证仍被实例、站点账号、Feed 或设置引用,先解除引用")); + .catch((e) => pgConflict(e, "凭证仍被实例、站点账号、Feed 或设置引用,先解除引用")); if (!r) throw new ApiException("not_found", "凭证不存在"); await audit(ctx, actor, "secret.delete", { type: "secret", id: params.id }); return { ok: true as const }; diff --git a/apps/server/src/modules/auth-settings/passkey.ts b/apps/server/src/modules/auth-settings/passkey.ts index 386bace7..0d8f29fe 100644 --- a/apps/server/src/modules/auth-settings/passkey.ts +++ b/apps/server/src/modules/auth-settings/passkey.ts @@ -180,7 +180,7 @@ export async function verifyRegistration( const response = input.response as unknown as RegistrationResponseJSON; const origins = expectedOrigins(rp, response); const challenge = await takeChallenge(db, input.challengeId, "register", admin.id); - if (!challenge) throw new ApiException("precondition_failed", "注册挑战已过期或已用过,重新开始"); + if (!challenge) throw new ApiException("precondition_failed", "注册挑战已过期或已用过,重新开始"); let v: Awaited>; try { v = await verifyRegistrationResponse({ @@ -191,7 +191,7 @@ export async function verifyRegistration( requireUserVerification: false, }); } catch (err) { - throw new ApiException("unauthorized", `Passkey 校验失败:${(err as Error).message}`); + throw new ApiException("unauthorized", `Passkey 校验失败:${(err as Error).message}`); } if (!v.verified) throw new ApiException("unauthorized", "Passkey 校验失败"); const { credential, credentialDeviceType, credentialBackedUp } = v.registrationInfo; @@ -220,7 +220,7 @@ export async function verifyRegistration( export async function authenticationOptions(db: Kysely, admin: AdminRow, rp: RpInfo): Promise { const creds = await listPasskeys(db, admin.id); - if (!creds.length) throw new ApiException("precondition_failed", "还没有登记 Passkey,用密码登录"); + if (!creds.length) throw new ApiException("precondition_failed", "还没有登记 Passkey,用密码登录"); const options = await generateAuthenticationOptions({ rpID: rp.rpID, userVerification: "preferred", @@ -240,7 +240,7 @@ export async function verifyAuthentication( const response = input.response as unknown as AuthenticationResponseJSON; const origins = expectedOrigins(rp, response); const challenge = await takeChallenge(db, input.challengeId, "login", null); - if (!challenge) throw new ApiException("precondition_failed", "登录挑战已过期或已用过,重试"); + if (!challenge) throw new ApiException("precondition_failed", "登录挑战已过期或已用过,重试"); const cred = await db .selectFrom("admin_passkey") .selectAll() @@ -264,7 +264,7 @@ export async function verifyAuthentication( requireUserVerification: false, }); } catch (err) { - throw new ApiException("unauthorized", `Passkey 校验失败:${(err as Error).message}`); + throw new ApiException("unauthorized", `Passkey 校验失败:${(err as Error).message}`); } if (!v.verified) throw new ApiException("unauthorized", "Passkey 校验失败"); // 计数器只增不减(条件更新:并发重放同一个断言只有一个生效;计数器恒为 0 的认证器照常放行) @@ -281,6 +281,6 @@ export async function verifyAuthentication( .where(sql`(counter < ${next} or (counter = 0 and ${next} = 0))`) .returningAll() .executeTakeFirst(); - if (!u) throw new ApiException("unauthorized", "Passkey 签名计数器回退(可能被克隆),已拒绝"); + if (!u) throw new ApiException("unauthorized", "Passkey 签名计数器回退(可能被克隆),已拒绝"); return u; } diff --git a/apps/server/src/modules/auth-settings/provided.ts b/apps/server/src/modules/auth-settings/provided.ts index 8cb2f91b..743c407c 100644 --- a/apps/server/src/modules/auth-settings/provided.ts +++ b/apps/server/src/modules/auth-settings/provided.ts @@ -22,7 +22,7 @@ export class SecretError extends Error { readonly secretRef: string, ) { super( - kind === "not_found" ? `secret ${secretRef} 不存在` : `secret ${secretRef} 解密失败(主密钥不匹配或密文被篡改)`, + kind === "not_found" ? `secret ${secretRef} 不存在` : `secret ${secretRef} 解密失败(主密钥不匹配或密文被篡改)`, ); this.name = "SecretError"; } @@ -221,7 +221,7 @@ export async function rotateMasterKey( newKey: string, opts: { progressEvery?: number } = {}, ): Promise { - if (oldKey === newKey) throw new Error("新旧主密钥相同,不需要轮换"); + if (oldKey === newKey) throw new Error("新旧主密钥相同,不需要轮换"); const { db, logger } = deps; const newKid = masterKeyId(newKey); const every = opts.progressEvery ?? 50; @@ -266,7 +266,7 @@ export async function rotateMasterKey( else if (r === "skipped") out.skipped++; else if (r === "failed") { out.failed.push(id); - logger.warn({ secretId: id }, "这条 secret 新旧主密钥都解不开,已跳过(保持原样)"); + logger.warn({ secretId: id }, "这条 secret 新旧主密钥都解不开,已跳过(保持原样)"); } done++; if (done % every === 0 || done === ids.length) @@ -295,7 +295,7 @@ export async function rotateMasterKey( .catch((err) => logger.warn({ err: { message: (err as Error).message } }, "主密钥轮换审计写入失败")); logger.info( { total: out.total, rotated: out.rotated, skipped: out.skipped, failed: out.failed.length }, - out.failed.length ? "主密钥轮换结束,有解不开的行" : "主密钥轮换完成", + out.failed.length ? "主密钥轮换结束,有解不开的行" : "主密钥轮换完成", ); return out; } @@ -337,7 +337,7 @@ export function createSettingsReader(deps: { const v = stored.get(key); const merged = v && typeof v === "object" && !Array.isArray(v) ? { ...def, ...(v as object) } : def; const parsed = GlobalSettings.shape[key].safeParse(merged); - if (!parsed.success) deps.logger.warn({ key }, "settings 值不合法,回退缺省"); + if (!parsed.success) deps.logger.warn({ key }, "settings 值不合法,回退缺省"); out[key] = parsed.success ? parsed.data : def; } return out as GlobalSettings; diff --git a/apps/server/src/modules/browser/capabilities.ts b/apps/server/src/modules/browser/capabilities.ts index 4b109fcd..9adecc3e 100644 --- a/apps/server/src/modules/browser/capabilities.ts +++ b/apps/server/src/modules/browser/capabilities.ts @@ -33,7 +33,7 @@ export function registerBrowserCapabilities(registry: CapabilityRegistry, svc: ( const resolveSession = async (input: { sessionId?: string | undefined; siteId?: string | undefined }) => { if (input.sessionId) return input.sessionId; const row = input.siteId ? await svc().liveSessionFor(input.siteId) : undefined; - if (!row) throw new BrowserServiceError("not_found", "该站点没有浏览器会话,先用 browser.navigate 打开页面"); + if (!row) throw new BrowserServiceError("not_found", "该站点没有浏览器会话,先用 browser.navigate 打开页面"); return row.id; }; @@ -176,12 +176,12 @@ export function registerBrowserCapabilities(registry: CapabilityRegistry, svc: ( sourceReleaseId: v.sourceReleaseId, duplicate: !!v.duplicate, note: v.duplicate - ? "同一会话已捕获过这个文件;先查已有下载计划,不要重复提交" + ? "同一会话已捕获过这个文件;先查已有下载计划,不要重复提交" : !v.verification.valid ? "不是有效的种子文件" : v.sourceReleaseId - ? "已暂存核验;用 download.plan 提交这个 sourceReleaseId" - : "资源不在本地索引;先 release.search 该站点后再提交", + ? "已暂存核验;用 download.plan 提交这个 sourceReleaseId" + : "资源不在本地索引;先 release.search 该站点后再提交", }; }), ); diff --git a/apps/server/src/modules/browser/routes.ts b/apps/server/src/modules/browser/routes.ts index b5643407..fc95d62d 100644 --- a/apps/server/src/modules/browser/routes.ts +++ b/apps/server/src/modules/browser/routes.ts @@ -244,20 +244,20 @@ export function registerBrowserRoutes(app: ModuleApp, svc: () => BrowserService) const v = await svc().verifyDownload(params.id, artifact); let submission: ManualAcquisitionResult | null = null; let note: string | null = null; - if (v.duplicate) note = "同一会话已捕获过这个文件,未重复提交"; - else if (!v.verification.valid) note = "不是有效的种子文件,未提交"; - else if (!v.sourceReleaseId) note = "资源不在本地索引(先快照或搜索该站点),未提交"; + if (v.duplicate) note = "同一会话已捕获过这个文件,未重复提交"; + else if (!v.verification.valid) note = "不是有效的种子文件,未提交"; + else if (!v.sourceReleaseId) note = "资源不在本地索引(先快照或搜索该站点),未提交"; else if (body.submit) { try { submission = await dispatch.call(c, "POST", "/acquisition-plans", { ...body.submit, sourceReleaseId: v.sourceReleaseId, }); - if (!submission.submitted) note = "提交未生成下载计划,见 rejected"; + if (!submission.submitted) note = "提交未生成下载计划,见 rejected"; } catch (e) { // 与 POST /acquisition-plans 相同的拒绝(未识别、无授权等):产物已暂存,结果照常返回 if (!(e instanceof ApiException) || e.code === "internal") throw e; - note = `提交未通过:${e.message}`; + note = `提交未通过:${e.message}`; } } await svc().recordDownload(params.id, { diff --git a/apps/server/src/modules/browser/service.ts b/apps/server/src/modules/browser/service.ts index 16a72cc4..24b79f82 100644 --- a/apps/server/src/modules/browser/service.ts +++ b/apps/server/src/modules/browser/service.ts @@ -86,7 +86,7 @@ export function fromBrowserError(e: unknown): unknown { case "rate_limited": return new BrowserServiceError("rate_limited", e.message); default: - return new BrowserServiceError("upstream", `浏览器服务不可用:${e.message}`); + return new BrowserServiceError("upstream", `浏览器服务不可用:${e.message}`); } } @@ -218,7 +218,8 @@ export class BrowserService { .where("kind", "<>", "none"); if (id) q = q.where("id", "=", id); const row = await q.orderBy("created_at").executeTakeFirst(); - if (!row) throw new BrowserServiceError("precondition", "没有启用的浏览器服务(camofox),见 docs/runbook/browser.md"); + if (!row) + throw new BrowserServiceError("precondition", "没有启用的浏览器服务(camofox),见 docs/runbook/browser.md"); return row; } @@ -329,13 +330,13 @@ export class BrowserService { .executeTakeFirst(); if (!prow?.enabled) { await this.closeRow(row, "provider_disabled"); - throw new BrowserServiceError("precondition", "浏览器服务已停用,会话已关闭"); + throw new BrowserServiceError("precondition", "浏览器服务已停用,会话已关闭"); } const site = await this.liveSite(row.site_id).catch(() => null); if (!site?.enabled) { // 站点关闭即停止其网络访问(REQ §4.6、§4.7.3) await this.closeRow(row, "site_disabled", prow); - throw new BrowserServiceError("precondition", "站点已停用,浏览器会话已关闭"); + throw new BrowserServiceError("precondition", "站点已停用,浏览器会话已关闭"); } const provider = this.providerFor(prow); if (!provider.sessionInfo(row.id)) { @@ -611,8 +612,8 @@ export class BrowserService { kind: "other", status: "awaiting_user", severity: "warning", - title: `${site?.name ?? "站点"}:原站需要人工处理`, - summary: `${REASON_TEXT[reason] ?? reason}。在站点详情的原站视图接管会话,完成后交还。`, + title: `${site?.name ?? "站点"}:原站需要人工处理`, + summary: `${REASON_TEXT[reason] ?? reason}。在站点详情的原站视图接管会话,完成后交还。`, subject_type: "browser_session", subject_id: row.id, dedupe_key: `browser.waiting:${row.id}`, @@ -783,8 +784,8 @@ export class BrowserService { throw new BrowserServiceError( "conflict", row.controller === "human" - ? "会话正由人工接管,自动化动作已停止" - : `会话在等待人工处理(${REASON_TEXT[rs.waitingReason ?? ""] ?? rs.waitingReason ?? "未知原因"}),自动化动作已停止`, + ? "会话正由人工接管,自动化动作已停止" + : `会话在等待人工处理(${REASON_TEXT[rs.waitingReason ?? ""] ?? rs.waitingReason ?? "未知原因"}),自动化动作已停止`, ); } } @@ -803,7 +804,7 @@ export class BrowserService { if (e instanceof BrowserError && e.kind === "not_found" && e.httpStatus === 404) { const r = await loaded.provider.reconcileTabs(id).catch(() => null); await this.persist(id, loaded.provider, { needsSnapshot: true }); - if (r?.lost.length) throw new BrowserServiceError("conflict", "标签页已被浏览器服务回收,重新打开页面"); + if (r?.lost.length) throw new BrowserServiceError("conflict", "标签页已被浏览器服务回收,重新打开页面"); } await this.persist(id, loaded.provider).catch(() => {}); throw fromBrowserError(e); diff --git a/apps/server/src/modules/pi/oauth.ts b/apps/server/src/modules/pi/oauth.ts index 08dc9d9e..26afd5f5 100644 --- a/apps/server/src/modules/pi/oauth.ts +++ b/apps/server/src/modules/pi/oauth.ts @@ -60,12 +60,12 @@ export function readableLoginError(provider: string, err: unknown): string { const raw = err instanceof Error ? err.message : String(err); if (/EADDRINUSE|address already in use/i.test(raw)) { const port = OAUTH_CALLBACK_PORTS[provider] ?? raw.match(/:(\d{2,5})\b/)?.[1]; - return `服务端回调端口${port ? ` ${port}` : ""} 被占用。结束占用该端口的进程(如另一个登录流程、Claude Code / Codex CLI)后重试`; + return `服务端回调端口${port ? ` ${port}` : ""} 被占用。结束占用该端口的进程(如另一个登录流程、Claude Code / Codex CLI)后重试`; } if (/cancel|abort/i.test(raw)) return "登录已取消"; - if (/state mismatch/i.test(raw)) return "粘贴的地址不属于本次登录(state 不一致)。用本次打开的授权链接重新登录"; + if (/state mismatch/i.test(raw)) return "粘贴的地址不属于本次登录(state 不一致)。用本次打开的授权链接重新登录"; if (/\b404\b/.test(raw) && provider === "openai-codex") - return "这个账号未开启设备码登录(Codex 返回 404)。改用浏览器登录"; + return "这个账号未开启设备码登录(Codex 返回 404)。改用浏览器登录"; return classifyUpstreamError(err).message; } @@ -111,7 +111,7 @@ export class OAuthSessions { if (!info.oauth.modes.includes(mode)) throw new ApiException( "validation_failed", - `${info.name} 不支持 ${mode} 登录,可用:${info.oauth.modes.join(" / ")}`, + `${info.name} 不支持 ${mode} 登录,可用:${info.oauth.modes.join(" / ")}`, ); const busy = this.active(row.provider); @@ -119,7 +119,7 @@ export class OAuthSessions { if (busy.upstreamId !== row.id) throw new ApiException( "conflict", - `${info.name} 已有登录进行中。完成它,或等到 ${new Date(busy.expiresAt).toISOString()} 过期后重试`, + `${info.name} 已有登录进行中。完成它,或等到 ${new Date(busy.expiresAt).toISOString()} 过期后重试`, ); this.cancel(busy, "cancelled", "已被新的登录请求取代"); await busy.done.catch(() => undefined); @@ -144,7 +144,7 @@ export class OAuthSessions { pasteWaiters: [], firstEvent: Promise.withResolvers(), done: Promise.resolve(), - timer: setTimeout(() => this.cancel(s, "expired", "登录会话已超时,重新开始登录"), ttl), + timer: setTimeout(() => this.cancel(s, "expired", "登录会话已超时,重新开始登录"), ttl), }; (s.timer as { unref?: () => void }).unref?.(); this.sessions.set(s.id, s); @@ -192,7 +192,7 @@ export class OAuthSessions { for (const w of s.pasteWaiters.splice(0)) w(); }); } - throw new Error("这个登录流程需要额外输入(secret),服务端不支持"); + throw new Error("这个登录流程需要额外输入(secret),服务端不支持"); } private notify(s: Session, e: UpstreamAuthEvent) { @@ -238,11 +238,11 @@ export class OAuthSessions { return { sessionId: req.sessionId, state: "expired", - message: "登录会话不存在或已过期,重新开始登录", + message: "登录会话不存在或已过期,重新开始登录", progress: [], }; if (req.pastedUrlOrCode && s.state === "pending") { - if (s.mode !== "browser") throw new ApiException("bad_request", "设备码登录不需要粘贴,等待授权完成"); + if (s.mode !== "browser") throw new ApiException("bad_request", "设备码登录不需要粘贴,等待授权完成"); if (!s.paste) { // manual_code 提示可能还没发出:短暂等待 await Promise.race([ diff --git a/apps/server/src/modules/sources-identity/service/account.ts b/apps/server/src/modules/sources-identity/service/account.ts index 32060e8d..52aecb8e 100644 --- a/apps/server/src/modules/sources-identity/service/account.ts +++ b/apps/server/src/modules/sources-identity/service/account.ts @@ -342,7 +342,7 @@ export async function refreshSiteAccount( decisionId: null, }); const def = getSiteDefinition(site.key); - if (!def || site.deleted_at) return unchanged("unsupported", "站点没有适配器定义,无法校验登录"); + if (!def || site.deleted_at) return unchanged("unsupported", "站点没有适配器定义,无法校验登录"); const defaults = await rt.siteDefaults(); const ua = acc.user_agent ?? defaults.userAgent; let limiter: ReturnType = null; @@ -402,7 +402,7 @@ export async function refreshSiteAccount( const succeed = (info: SiteUserInfo | null, note?: string) => markSuccess(rt, acc, site, info, actor, note); try { - if (acc.auth_kind === "passkey") return unchanged("unsupported", "Passkey 只用于 RSS 和下载,不参与登录"); + if (acc.auth_kind === "passkey") return unchanged("unsupported", "Passkey 只用于 RSS 和下载,不参与登录"); if (!acc.secret_ref) return unchanged("unsupported", "账号没有凭证"); if (acc.auth_kind === "password") { if (umt.kind !== "nexusphp" || !umt.login) { @@ -434,14 +434,14 @@ export async function refreshSiteAccount( if (r.kind === "ok") return await succeed(r.info); return await succeed( null, - `登录成功;用户数据未取到:${r.kind === "no_parser" ? "站点没有用户数据解析器" : r.message}`, + `登录成功;用户数据未取到:${r.kind === "no_parser" ? "站点没有用户数据解析器" : r.message}`, ); } const plain = await secrets.resolve(acc.secret_ref); let r = await fetchUserData(acc.auth_kind === "cookie" ? { cookie: plain } : { apikey: plain }); if (r.kind === "no_parser") r = await viaHealth(); if (r.kind === "ok") return await succeed(r.info); - if (r.kind === "no_parser") return await succeed(null, "站点没有用户数据解析器,只校验了登录态"); + if (r.kind === "no_parser") return await succeed(null, "站点没有用户数据解析器,只校验了登录态"); if (r.kind === "network") return await fail("network", r.message); if (r.kind === "challenge") return await fail("challenge", r.message); return await fail( @@ -580,11 +580,11 @@ async function upsertUserDataDay(trx: Exec, accountId: string, now: Date, info: const DECISION_TEXT: Record string; summary: string }> = { challenge: { title: (s) => `站点「${s}」登录需要人工验证`, - summary: "登录页要求验证码 / 二次验证,无法自动登录。改用 Cookie 登录,或填写 TOTP 密钥后重试", + summary: "登录页要求验证码 / 二次验证,无法自动登录。改用 Cookie 登录,或填写 TOTP 密钥后重试", }, invalid: { title: (s) => `站点「${s}」登录失败`, - summary: "用户名或密码错误,或账号被封禁 / 未确认。更新凭证后重新刷新登录", + summary: "用户名或密码错误,或账号被封禁 / 未确认。更新凭证后重新刷新登录", }, expired: { title: (s) => `站点「${s}」登录失效`, @@ -619,7 +619,7 @@ async function markFailure( const text = DECISION_TEXT[status]!; const summary = reason === "challenge" - ? "站点返回 Cloudflare 挑战页,无法自动登录。改用 Cookie 登录" + ? "站点返回 Cloudflare 挑战页,无法自动登录。改用 Cookie 登录" : status === "expired" || reason === "account_blocked" || reason === "bad_credentials" || @@ -719,7 +719,7 @@ export async function checkSiteHealth( let message: string | null = null; const def = getSiteDefinition(site.key); if (!def) { - message = "站点没有适配器定义,无法检测"; + message = "站点没有适配器定义,无法检测"; status = "unsupported"; } else { await rt.siteDefaults(); diff --git a/apps/server/test/e2e/e2-agent-delegate.test.ts b/apps/server/test/e2e/e2-agent-delegate.test.ts index c0ea6333..ce77c88e 100644 --- a/apps/server/test/e2e/e2-agent-delegate.test.ts +++ b/apps/server/test/e2e/e2-agent-delegate.test.ts @@ -105,7 +105,7 @@ describe.skipIf(!hasPg)("E2 Agent 委托闭环(fake 模型 + 真实能力/授权 expect(slot.status).toBe("empty"); // 汇报不采信模型文本:没有可核查的业务变更 expect(run.summary).toContain("没有提交下载"); - expect(run.summary).toContain("模型汇报(未经核查)"); + expect(run.summary).toContain("模型汇报(未经核查)"); }, 90_000); it("有持续授权:提交成功 → 已提交≠已入库(waiting)→ 下载完成、核验入库后 succeeded,汇报来自整理记录", async () => { diff --git a/apps/server/test/e2e/e6-auto-recover.test.ts b/apps/server/test/e2e/e6-auto-recover.test.ts index 4bae8572..1709bb25 100644 --- a/apps/server/test/e2e/e6-auto-recover.test.ts +++ b/apps/server/test/e2e/e6-auto-recover.test.ts @@ -246,8 +246,8 @@ describe.skipIf(!hasPg)("E6 授权内异常调查与恢复", () => { const done = await finished(run.id); expect(done.status).toBe("succeeded"); - expect(done.summary).toMatch(/^已恢复\(系统核实\)/); - expect(done.summary).toContain("模型汇报(未经核查)"); + expect(done.summary).toMatch(/^已恢复(系统核实)/); + expect(done.summary).toContain("模型汇报(未经核查)"); expect(await toolStarts(run.id)).toEqual(["task.inspect", "download.recheck"]); expect(await toolRejected(run.id)).toEqual(["download__submit"]); const inv = await e.db @@ -339,7 +339,7 @@ describe.skipIf(!hasPg)("E6 授权内异常调查与恢复", () => { expect(run.mode).toBe("execute"); const done = await finished(run.id); expect(done.status).toBe("budget_exceeded"); - expect(done.summary).toMatch(/^未恢复:调查超出预算/); + expect(done.summary).toMatch(/^未恢复:调查超出预算/); expect(await toolStarts(run.id)).toEqual(["task.inspect"]); expect(e.qb.callsTo("torrents/recheck")).toHaveLength(1); const d2 = await waitFor( diff --git a/apps/web/app/api/agent.ts b/apps/web/app/api/agent.ts index 42f3da3e..93b758a0 100644 --- a/apps/web/app/api/agent.ts +++ b/apps/web/app/api/agent.ts @@ -271,7 +271,7 @@ export const REPORT_STATE: Record< { label: string; tone: "success" | "warning" | "destructive" | "neutral" } > = { no_actions: { label: "没有产生业务动作", tone: "neutral" }, - waiting: { label: "已提交,等待下载/入库", tone: "warning" }, + waiting: { label: "已提交,等待下载/入库", tone: "warning" }, completed: { label: "已核验入库", tone: "success" }, partial: { label: "部分完成", tone: "warning" }, failed: { label: "未达成", tone: "destructive" }, diff --git a/apps/web/app/api/inventory.ts b/apps/web/app/api/inventory.ts index 6c717c16..3b1230fb 100644 --- a/apps/web/app/api/inventory.ts +++ b/apps/web/app/api/inventory.ts @@ -49,7 +49,7 @@ export function inventoryFlags( const wants4k = entry.gaps.some((g) => /2160|4k|uhd/i.test(g.name)); if (wants4k && !has4k) flags.push({ key: "no4k", label: "缺 4K", tone: "warning" }); for (const g of entry.gaps.filter((g) => !/2160|4k|uhd/i.test(g.name) || has4k)) { - flags.push({ key: `gap-${g.slotId}`, label: `缺口:${g.name}`, tone: "warning" }); + flags.push({ key: `gap-${g.slotId}`, label: `缺口:${g.name}`, tone: "warning" }); } if (entry.downloading.length) { flags.push({ key: "dl", label: `下载中 ${entry.downloading.length}`, tone: "neutral" }); @@ -64,7 +64,7 @@ export function inventoryFlags( }); } if (releaseResolution && releaseResolution === "resolved" && flags.length === 0) { - flags.push({ key: "tracked", label: "已识别,无版本目标", tone: "neutral" }); + flags.push({ key: "tracked", label: "已识别,无版本目标", tone: "neutral" }); } return flags; } diff --git a/apps/web/app/components/agent/agent-conversation.tsx b/apps/web/app/components/agent/agent-conversation.tsx index 6802ef48..232b20dc 100644 --- a/apps/web/app/components/agent/agent-conversation.tsx +++ b/apps/web/app/components/agent/agent-conversation.tsx @@ -67,7 +67,7 @@ export const BUDGET_LABEL: Record = { }; const COMPACT_REASON: Record = { manual: "手动", threshold: "达到阈值", overflow: "上下文溢出" }; const COMPACT_STATUS: Record = { - completed: "已完成,业务进度保留", + completed: "已完成,业务进度保留", declined: "未压缩", aborted: "已中止", failed: "失败", @@ -173,8 +173,8 @@ export function ToolCallCard({ reason={authReason ?? call.reason ?? "未授权"} explanation={ authReason - ? '有副作用的能力需要有效的持续授权或单次审批,由服务端在执行前核对,模型声称"用户已同意"无效。控制台发起的会话里,需审批的动作会在对应步骤出审批卡;也可以发布收录策略生成持续授权。' - : "这个能力不在本次运行的白名单内,或已超出调用预算,调用没有执行。" + ? '有副作用的能力需要有效的持续授权或单次审批,由服务端在执行前核对,模型声称"用户已同意"无效。控制台发起的会话里,需审批的动作会在对应步骤出审批卡;也可以发布收录策略生成持续授权。' + : "这个能力不在本次运行的白名单内,或已超出调用预算,调用没有执行。" } parameters={[ { id: "call", label: "调用 ID", value: call.toolCallId }, @@ -199,10 +199,10 @@ export function ToolCallCard({ } summary={call.outputSummary ?? undefined} > - {call.inputSummary ? {call.inputSummary} : null} + {call.inputSummary ? {call.inputSummary} : null} {call.outputSummary ? ( {call.outputSummary} @@ -216,7 +216,7 @@ export function ToolCallCard({
幂等键
{audit?.idempotencyKey ?? - (meta && meta.kind !== "read" ? "服务端已生成,审计里没有取到" : "只读能力不记操作账本")} + (meta && meta.kind !== "read" ? "服务端已生成,审计里没有取到" : "只读能力不记操作账本")}
{audit?.grantId || audit?.approvalId ? ( <> @@ -292,7 +292,7 @@ function EndNotice({ item }: { item: Extract }) {

{budget - ? `超出预算${item.budget ? `(${BUDGET_LABEL[item.budget] ?? item.budget})` : ""},已停止` + ? `超出预算${item.budget ? `(${BUDGET_LABEL[item.budget] ?? item.budget})` : ""},已停止` : aborted ? "已停止" : "运行失败"} @@ -479,7 +479,7 @@ export function AgentConversation({ rows.push({ key: `u${m.at}`, at: m.at, - node: , + node: , }); rows.sort((a, b) => a.at.localeCompare(b.at)); @@ -584,7 +584,7 @@ export function BudgetBar({ run, live }: { run: AgentRunView; live: AgentStreamS exceeded={exceeded === "duration"} /> `$${n.toFixed(3)}`} @@ -626,7 +626,7 @@ export function ApprovalItem({ approval: a }: { approval: Approval }) { {risk ? : null} @@ -654,7 +654,7 @@ export function ApprovalItem({ approval: a }: { approval: Approval }) {

到期
{a.expiresAt ? formatDateTime(a.expiresAt) : "不过期"} - {expired ? "(已过期)" : ""} + {expired ? "(已过期)" : ""}
@@ -721,12 +721,12 @@ export function FinalReport({

- 模型说的(未经核查) + 模型说的(未经核查)

- {said || "(没有文字汇报)"} + {said || "(没有文字汇报)"} -

仅供参考,不作为完成依据。

+

仅供参考,不作为完成依据。

@@ -788,12 +788,12 @@ export function FinalReport({ /> ) : null}

- 取自下载计划、下载任务和整理记录(生成于 {formatDateTime(report.generatedAt)} - )。"已提交"指已进入下载器,整理成功才算入库。 + 取自下载计划、下载任务和整理记录(生成于 {formatDateTime(report.generatedAt)} + )。"已提交"指已进入下载器,整理成功才算入库。

) : ( -

服务端汇报还没有生成,以任务和整理记录的实际状态为准。

+

服务端汇报还没有生成,以任务和整理记录的实际状态为准。

)} {run.error ? (

diff --git a/apps/web/app/components/agent/agent-drawer.tsx b/apps/web/app/components/agent/agent-drawer.tsx index 7901f814..f7f00e50 100644 --- a/apps/web/app/components/agent/agent-drawer.tsx +++ b/apps/web/app/components/agent/agent-drawer.tsx @@ -93,9 +93,9 @@ export function AgentDrawer() { Agent - {context?.label ? `当前对象:${context.label}` : "交代目标,Agent 在授权范围内执行并汇报。"} + {context?.label ? `当前对象:${context.label}` : "交代目标,Agent 在授权范围内执行并汇报。"} - ({agentShortcutLabel()} 开关,Esc 关闭) + ({agentShortcutLabel()} 开关,Esc 关闭) {context ? : null} @@ -105,8 +105,8 @@ export function AgentDrawer() { variant="ghost" size="icon-sm" className="absolute top-4 right-14" - aria-label="最小化(运行继续)" - title="最小化(运行继续)" + aria-label="最小化(运行继续)" + title="最小化(运行继续)" onClick={minimizeAgentDrawer} > @@ -180,7 +180,7 @@ function AgentMiniDock({ runId }: { runId: string }) { ? "思考中…" : last?.kind === "end" ? last.ok - ? "已完成,展开查看核实结果" + ? "已完成,展开查看核实结果" : last.message : "等待输出…"; const status = run.data @@ -205,8 +205,8 @@ function AgentMiniDock({ runId }: { runId: string }) {

) : null} {live.error ? ( @@ -250,7 +250,7 @@ export function AgentRunPanel({ role="alert" className="flex flex-wrap items-center gap-2 rounded-xl border border-destructive/40 bg-destructive/10 px-3 py-2 text-caption text-destructive" > - 事件读取失败:{live.error} + 事件读取失败:{live.error} } title={`回滚到 r${rev.revision}?`} - description={`用 r${rev.revision} 的内容发布一个新版本,当前版本标记为已回滚。${PUBLISH_NOTICE}`} + description={`用 r${rev.revision} 的内容发布一个新版本,当前版本标记为已回滚。${PUBLISH_NOTICE}`} confirmLabel="回滚" onConfirm={() => rollback .mutateAsync(rev.id) - .then((n) => toast.success(`已回滚,r${n.revision} 生效`)) + .then((n) => toast.success(`已回滚,r${n.revision} 生效`)) .then(onClose) .catch((e) => { toast.error(errorMessage(e)); @@ -538,7 +538,7 @@ function PreviewBlock({ value }: { value: JsonObject | null }) { if (!value) return (

- 还没有预览。预览用最近已识别的资源评估模板,不会执行任何操作。 + 还没有预览。预览用最近已识别的资源评估模板,不会执行任何操作。

); const p = PolicyPreview.safeParse(value); @@ -548,7 +548,7 @@ function PreviewBlock({ value }: { value: JsonObject | null }) {
{!v.valid ? (

- 规则未通过校验,预览按空模板计算,无法发布。 + 规则未通过校验,预览按空模板计算,无法发布。

) : null}
@@ -565,7 +565,7 @@ function PreviewBlock({ value }: { value: JsonObject | null }) { ))}

- 计算于 {formatDateTime(v.evaluatedAt)};接受/排除按"资源 × 模板"计数。 + 计算于 {formatDateTime(v.evaluatedAt)};接受/排除按"资源 × 模板"计数。

{v.samples.length ? (
    @@ -660,7 +660,7 @@ function PolicyEditorSheet({ onOpenChange={(o) => !o && !pending && onClose()} width="lg" title={editId ? `编辑草案 r${base?.revision}` : base ? `基于 r${base.revision} 新建草案` : "新建收录策略草案"} - description="保存为草案,发布后才生效。" + description="保存为草案,发布后才生效。" footer={ <>
- +