From 31a54186c7a2cbbf60c0e7cf49c8071977bb6aa1 Mon Sep 17 00:00:00 2001 From: root Date: Mon, 28 Sep 2026 13:29:42 +0800 Subject: [PATCH] copy: second-round copy review across console, settings, agent and server messages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Trim descriptions, empty states, confirm buttons and toasts to facts - Unify terms per glossary: 待处理/待识别/整理记录/运行记录/凭证/子会话/Agent 上游 - Subscription-login termsNote states facts only (borrowed client identity, ToS/ban risk) - Shorter, concrete goal templates for built-in Agent checks - Human-readable nouns instead of table names in instance CRUD errors - Audit labels for pi_upstream / schedule / automation_grant actions - Glossary: add 上游、订阅登录、凭证、定时任务、巡检、运行记录、子会话、停止、持续授权 Co-Authored-By: Claude Opus 5.5 --- apps/server/src/http/auth.ts | 10 +++--- apps/server/src/modules/agent/authorize.ts | 5 +-- apps/server/src/modules/agent/investigate.ts | 7 ++-- apps/server/src/modules/agent/routes.ts | 2 +- apps/server/src/modules/agent/runner.ts | 6 ++-- apps/server/src/modules/agent/service.ts | 2 +- apps/server/src/modules/agent/subagents.ts | 2 +- .../modules/auth-settings/instances-routes.ts | 21 ++++++++---- apps/server/src/modules/pi/oauth.ts | 26 +++++++------- apps/server/src/modules/pi/refresh.ts | 6 ++-- apps/server/src/modules/pi/routes.ts | 2 +- apps/server/src/modules/pi/service.ts | 6 ++-- apps/server/src/modules/pi/store.ts | 2 +- .../src/modules/schedules/agent-checks.ts | 12 +++---- apps/server/src/modules/schedules/store.ts | 4 +-- apps/server/test/e2e/e11-subagents.test.ts | 2 +- apps/web/app/api/agent.ts | 6 ++-- .../components/agent/agent-conversation.tsx | 28 +++++++-------- .../web/app/components/agent/agent-drawer.tsx | 2 +- .../app/components/agent/agent-run-panel.tsx | 22 ++++++------ .../components/agent/agent-session-list.tsx | 2 +- .../components/agent/agent-subagent-tree.tsx | 2 +- apps/web/app/components/agent/quick-goals.ts | 4 +-- .../components/agentui/streaming-response.tsx | 2 +- .../app/components/console/agent-launcher.tsx | 8 ++--- apps/web/app/components/console/nav.ts | 2 +- .../schedules/agent-check-dialog.tsx | 6 ++-- apps/web/app/components/schedules/labels.ts | 2 +- .../app/components/schedules/runs-sheet.tsx | 2 +- .../components/schedules/schedule-card.tsx | 8 ++--- apps/web/app/components/settings/agent.tsx | 6 ++-- apps/web/app/components/settings/api-keys.tsx | 4 +-- .../app/components/settings/automation.tsx | 2 +- .../web/app/components/settings/instances.tsx | 4 +-- apps/web/app/components/settings/metadata.tsx | 2 +- .../app/components/settings/path-mappings.tsx | 2 +- apps/web/app/components/settings/pi-oauth.tsx | 4 +-- .../web/app/components/settings/pi-shared.tsx | 4 +-- apps/web/app/components/settings/pi-test.tsx | 6 ++-- .../app/components/settings/pi-upstreams.tsx | 6 ++-- .../web/app/components/settings/pi-wizard.tsx | 6 ++-- .../components/settings/resource-section.tsx | 2 +- apps/web/app/components/settings/sites.tsx | 2 +- apps/web/app/lib/labels.ts | 12 +++---- apps/web/app/routes/console/agent.tsx | 11 +++--- apps/web/app/routes/console/audit.tsx | 9 +++++ apps/web/app/routes/console/downloads.tsx | 2 +- apps/web/app/routes/console/home.tsx | 34 ++++++++----------- apps/web/app/routes/console/identity.tsx | 2 +- apps/web/app/routes/console/schedules.tsx | 4 +-- apps/web/app/routes/console/settings.tsx | 2 +- apps/web/app/routes/console/sites.$siteId.tsx | 8 ++--- apps/web/app/routes/console/sites.tsx | 2 +- apps/web/app/routes/console/sources.tsx | 2 +- apps/web/app/routes/not-found.tsx | 2 +- docs/design/glossary.md | 13 ++++++- packages/agent/src/pi/catalog.ts | 22 ++++++------ packages/contracts/src/settings.ts | 2 +- 58 files changed, 200 insertions(+), 188 deletions(-) diff --git a/apps/server/src/http/auth.ts b/apps/server/src/http/auth.ts index b9109675..30734eb2 100644 --- a/apps/server/src/http/auth.ts +++ b/apps/server/src/http/auth.ts @@ -177,7 +177,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", { @@ -191,7 +191,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"); }; } @@ -207,7 +207,7 @@ export function hasScope(granted: readonly string[], required: ApiScope): boolea export function requireScope(...anyOf: ApiScope[]): MiddlewareHandler { return async (c, next) => { const p = c.get("principal"); - if (!p) throw new ApiException("unauthorized", "未鉴权"); + if (!p) throw new ApiException("unauthorized", "未登录"); if (!anyOf.some((s) => hasScope(p.scopes, s))) { throw new ApiException("forbidden", `需要 scope: ${anyOf.join(" | ")}`); } @@ -222,8 +222,8 @@ export function requireScope(...anyOf: ApiScope[]): MiddlewareHandler { export function requireAdminSession(): MiddlewareHandler { return async (c, next) => { const p = c.get("principal"); - if (!p) throw new ApiException("unauthorized", "未鉴权"); - if (p.kind !== "admin_session") throw new ApiException("forbidden", "该操作只能在控制台登录后进行"); + if (!p) throw new ApiException("unauthorized", "未登录"); + if (p.kind !== "admin_session") throw new ApiException("forbidden", "这个操作需要在控制台登录后进行"); return next(); }; } diff --git a/apps/server/src/modules/agent/authorize.ts b/apps/server/src/modules/agent/authorize.ts index 35395daf..fa3a1b18 100644 --- a/apps/server/src/modules/agent/authorize.ts +++ b/apps/server/src/modules/agent/authorize.ts @@ -209,10 +209,7 @@ export function createAuthorizer(deps: { } return { allowed: false, - reason: - reasons.length > 0 - ? `没有可用的授权:${reasons.join(";")}` - : `缺少 ${d.id} 的持续授权(automation_grant)或有效的单次审批(approval)`, + reason: reasons.length > 0 ? `没有可用的授权:${reasons.join(";")}` : `缺少 ${d.id} 的持续授权或有效的单次审批`, }; }; } diff --git a/apps/server/src/modules/agent/investigate.ts b/apps/server/src/modules/agent/investigate.ts index a6bd3e09..da100dd1 100644 --- a/apps/server/src/modules/agent/investigate.ts +++ b/apps/server/src/modules/agent/investigate.ts @@ -126,10 +126,11 @@ async function relatedTask(ctx: ServerContext, d: DecisionRow) { } function suggestionFor(d: DecisionRow, reason: string): string { - if (d.kind === "identity_ambiguous") return `${reason}。建议在待处理中人工选择身份,或为 identity.resolve 发放授权。`; + if (d.kind === "identity_ambiguous") + return `${reason}。可在待处理中手动选择身份,或为 identity.resolve 发放持续授权。`; return ( - `${reason}。建议在待处理中选择"强制重新校验"(download.recheck),或为 download.recheck / task.retry 发放持续授权` + - `(可绑定已发布的 automation 策略);换源、删除仍需人工决定。` + `${reason}。可在待处理中选择"强制重新校验"(download.recheck),或为 download.recheck / task.retry 发放持续授权;` + + `换源、删除需人工决定。` ); } diff --git a/apps/server/src/modules/agent/routes.ts b/apps/server/src/modules/agent/routes.ts index d531f45b..9efc74cb 100644 --- a/apps/server/src/modules/agent/routes.ts +++ b/apps/server/src/modules/agent/routes.ts @@ -421,7 +421,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 81eedca5..628e34c0 100644 --- a/apps/server/src/modules/agent/runner.ts +++ b/apps/server/src/modules/agent/runner.ts @@ -129,9 +129,9 @@ async function agentSettings( const pi = s.pi; let apiKey: string | undefined; if (!fake && !upstream) { - if (!pi.apiKeySecretRef) throw new Error("未配置 Pi 上游(设置 → 模型上游)"); + if (!pi.apiKeySecretRef) throw new Error("未配置 Agent 上游(设置 → Agent → 上游)"); apiKey = await env.ctx.secrets.resolve(pi.apiKeySecretRef); - if (!pi.model) throw new Error("未配置模型(设置 → pi.model)"); + if (!pi.model) throw new Error("未配置模型(设置 → Agent → 上游)"); } const maxTokens = budget.maxTokens ?? s.agent.maxTokensPerRun ?? undefined; const maxCost = budget.maxCostUsd ?? s.agent.maxCostUsdPerRun ?? undefined; @@ -278,7 +278,7 @@ function systemPromptFor( else if (run.trigger === "event") constraints.push("本任务由后台事件触发,只做只读调查:收集证据,给出结论与建议,不要反复重试同一操作。"); return buildSystemPrompt({ - role: run.trigger === "event" ? "媒体库异常调查员" : "媒体库自动化助手", + role: run.trigger === "event" ? "媒体库异常调查员" : "媒体库自动化 Agent", goal: run.goal, constraints, }); diff --git a/apps/server/src/modules/agent/service.ts b/apps/server/src/modules/agent/service.ts index 530e5720..668bd8a1 100644 --- a/apps/server/src/modules/agent/service.ts +++ b/apps/server/src/modules/agent/service.ts @@ -222,7 +222,7 @@ 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}`; diff --git a/apps/server/src/modules/agent/subagents.ts b/apps/server/src/modules/agent/subagents.ts index 3ed84fde..fdca137d 100644 --- a/apps/server/src/modules/agent/subagents.ts +++ b/apps/server/src/modules/agent/subagents.ts @@ -583,7 +583,7 @@ 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", "本会话已取消或已结束,不能派生子会话"); const dedupeKey = `subagent:${c.idempotencyKey ?? `${parent.id}:${Date.now()}`}`; diff --git a/apps/server/src/modules/auth-settings/instances-routes.ts b/apps/server/src/modules/auth-settings/instances-routes.ts index dad3f771..362505d7 100644 --- a/apps/server/src/modules/auth-settings/instances-routes.ts +++ b/apps/server/src/modules/auth-settings/instances-routes.ts @@ -64,6 +64,12 @@ const test = (path: string, summary: string, tag: string) => ({ }); type Table = "library_root" | "downloader_instance" | "media_server_instance" | "path_mapping"; +const NOUN: Record = { + library_root: "库根", + downloader_instance: "下载器实例", + media_server_instance: "媒体服务器实例", + path_mapping: "路径映射", +}; const JSON_COLS = ["settings", "version_info", "health_detail", "verify_detail"] as const; const errMessage = (e: unknown) => redactString(String((e as Error)?.message ?? e)).slice(0, 500); @@ -111,7 +117,7 @@ export function registerInstanceRoutes(app: ModuleApp, ctx: ServerContext) { const getRow = async (table: Table, id: string) => { // biome-ignore lint/suspicious/noExplicitAny: 同上 const r = await (db() as any).selectFrom(table).selectAll().where("id", "=", id).executeTakeFirst(); - if (!r) throw new ApiException("not_found", `${table} ${id} 不存在`); + if (!r) throw new ApiException("not_found", `${NOUN[table]}不存在`); return r as Record & { id: string; secret_ref?: string | null }; }; const get = (table: Table, tag: string, def: Def) => @@ -119,7 +125,7 @@ export function registerInstanceRoutes(app: ModuleApp, ctx: ServerContext) { toApi(await getRow(table, (params as { id: string }).id)), ); const remove = (table: Table, tag: string, path: string, action: AuditAction) => - endpoint(app, del(path, `删除 ${table}`, tag), async ({ params, actor }) => { + endpoint(app, del(path, `删除${NOUN[table]}`, tag), async ({ params, actor }) => { const id = (params as { id: string }).id; const row = await getRow(table, id); // biome-ignore lint/suspicious/noExplicitAny: 同上 @@ -127,7 +133,7 @@ export function registerInstanceRoutes(app: ModuleApp, ctx: ServerContext) { .deleteFrom(table) .where("id", "=", id) .execute() - .catch((e: unknown) => pgConflict(e, `${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 }; @@ -139,7 +145,10 @@ export function registerInstanceRoutes(app: ModuleApp, ctx: ServerContext) { .values(values) .returningAll() .executeTakeFirstOrThrow() - .catch((e: unknown) => pgConflict(e, `${table} 名称/路径重复或引用不存在`))) as Record & { + .catch((e: unknown) => pgConflict(e, `${NOUN[table]}的名称或路径重复,或关联的对象不存在`))) as Record< + string, + unknown + > & { id: string; }; const update = async (table: Table, id: string, values: Record) => { @@ -151,8 +160,8 @@ export function registerInstanceRoutes(app: ModuleApp, ctx: ServerContext) { .where("id", "=", id) .returningAll() .executeTakeFirst() - .catch((e: unknown) => pgConflict(e, `${table} 名称/路径重复或引用不存在`)); - if (!r) throw new ApiException("not_found", `${table} ${id} 不存在`); + .catch((e: unknown) => pgConflict(e, `${NOUN[table]}的名称或路径重复,或关联的对象不存在`)); + if (!r) throw new ApiException("not_found", `${NOUN[table]}不存在`); return r as Record & { id: string }; }; diff --git a/apps/server/src/modules/pi/oauth.ts b/apps/server/src/modules/pi/oauth.ts index 3e396fc3..08dc9d9e 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; } @@ -105,10 +105,7 @@ export class OAuthSessions { async start(row: PiUpstreamRow, req: { mode?: PiOAuthMode; enterpriseDomain?: string }): Promise { const info = providerInfo(row.provider); if (row.auth_kind !== "oauth" || !info?.oauth) - throw new ApiException( - "precondition_failed", - "该上游不是订阅登录型(authKind 需为 oauth,且 provider 支持订阅登录)", - ); + throw new ApiException("precondition_failed", "这个上游不是订阅登录类型"); if (!row.enabled) throw new ApiException("precondition_failed", "上游已停用"); const mode = req.mode ?? info.oauth.modes[0]!; if (!info.oauth.modes.includes(mode)) @@ -122,7 +119,7 @@ export class OAuthSessions { if (busy.upstreamId !== row.id) throw new ApiException( "conflict", - `${info.name} 已有登录进行中(同一 provider 的登录需串行),请先完成或等待其过期(${new Date(busy.expiresAt).toISOString()})`, + `${info.name} 已有登录进行中。完成它,或等到 ${new Date(busy.expiresAt).toISOString()} 过期后重试`, ); this.cancel(busy, "cancelled", "已被新的登录请求取代"); await busy.done.catch(() => undefined); @@ -147,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); @@ -195,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,9 +235,14 @@ export class OAuthSessions { ): Promise<{ sessionId: string; state: PiOAuthState; message: string | null; progress: string[] }> { const s = this.sessions.get(req.sessionId); if (!s || s.upstreamId !== upstreamId) - return { sessionId: req.sessionId, state: "expired", message: "登录会话不存在或已过期,请重新开始", progress: [] }; + return { + sessionId: req.sessionId, + state: "expired", + 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/pi/refresh.ts b/apps/server/src/modules/pi/refresh.ts index f79d59bd..7ea7b5c2 100644 --- a/apps/server/src/modules/pi/refresh.ts +++ b/apps/server/src/modules/pi/refresh.ts @@ -24,13 +24,13 @@ const DECISION_KEY = (id: string) => `pi_upstream_expired:${id}`; /** 与其它模块的 upsertDecision 同一写法(跨模块不 import):同键未关闭时只累加 attempts */ async function upsertExpiredDecision(ctx: ServerContext, row: PiUpstreamRow, message: string) { - const title = `模型上游「${row.name}」登录已失效,需要重新登录`; + const title = `Agent 上游「${row.name}」登录已失效,需要重新登录`; await ctx.db.transaction().execute(async (trx) => { const r = await sql<{ id: string; inserted: boolean }>` insert into decision (kind, severity, title, summary, subject_type, subject_id, dedupe_key, evidence, options) values ('other', 'warning', ${title}, ${message}, 'pi_upstream', ${row.id}, ${DECISION_KEY(row.id)}, ${JSON.stringify({ provider: row.provider, upstreamId: row.id, error: message })}::jsonb, - ${JSON.stringify([{ key: "relogin", label: "去设置页重新登录", description: "设置 → 模型上游 → 登录" }])}::jsonb) + ${JSON.stringify([{ key: "relogin", label: "重新登录", description: "设置 → Agent → 上游" }])}::jsonb) on conflict (dedupe_key) where status in ('open', 'investigating', 'awaiting_user') do update set attempts = decision.attempts + 1, evidence = excluded.evidence, summary = excluded.summary, updated_at = now() @@ -102,7 +102,7 @@ export async function refreshPiTokens(ctx: ServerContext, opts: RefreshOptions = if (r.ok) report.refreshed += 1; else { report.failed.push({ upstreamId: row.id, message: r.message }); - ctx.logger.warn({ upstreamId: row.id, provider: row.provider, message: r.message }, "Pi 上游 token 刷新失败"); + ctx.logger.warn({ upstreamId: row.id, provider: row.provider, message: r.message }, "Agent 上游 token 刷新失败"); } } return report; diff --git a/apps/server/src/modules/pi/routes.ts b/apps/server/src/modules/pi/routes.ts index 2acf8ad9..bd73ec30 100644 --- a/apps/server/src/modules/pi/routes.ts +++ b/apps/server/src/modules/pi/routes.ts @@ -109,7 +109,7 @@ export function registerPiRoutes( source: t.source, model: t.model, errorKind: null, - message: t.inCatalog ? null : `模型 ${t.model} 不在随包目录中,将按手填 id 调用`, + message: t.inCatalog ? null : `模型 ${t.model} 不在内置模型目录中,按手填 id 调用`, }; await setStatus(ctx, row.id, { status: "ok", verified: true }); } catch (err) { diff --git a/apps/server/src/modules/pi/service.ts b/apps/server/src/modules/pi/service.ts index a75b12a9..b154995c 100644 --- a/apps/server/src/modules/pi/service.ts +++ b/apps/server/src/modules/pi/service.ts @@ -213,10 +213,10 @@ export async function resolveRunUpstream(ctx: ServerContext, upstreamId?: string const id = upstreamId ?? (await defaultUpstreamId(ctx)); if (!id) return null; const row = await getRow(ctx, id); - if (!row) throw new Error(upstreamId ? `指定的上游 ${upstreamId} 不存在` : "默认上游已不存在,请在设置里重新选择"); + if (!row) throw new Error(upstreamId ? `指定的上游 ${upstreamId} 不存在` : "默认上游已不存在,在设置里重新选择"); if (!row.enabled) throw new Error(`上游「${row.name}」已停用`); - if (row.status === "expired") throw new Error(`上游「${row.name}」登录已失效,请在设置里重新登录`); + if (row.status === "expired") throw new Error(`上游「${row.name}」登录已失效,在设置里重新登录`); if (row.status === "auth_required") - throw new Error(`上游「${row.name}」${row.auth_kind === "oauth" ? "尚未登录" : "未配置 API Key"}`); + throw new Error(`上游「${row.name}」${row.auth_kind === "oauth" ? "未登录" : "未配置 API Key"}`); return { row, spec: { ...specOf(row), extra: parseExtra(row.extra) }, credentials: dbCredentialStore(ctx, row.id) }; } diff --git a/apps/server/src/modules/pi/store.ts b/apps/server/src/modules/pi/store.ts index 62dbe060..746de61c 100644 --- a/apps/server/src/modules/pi/store.ts +++ b/apps/server/src/modules/pi/store.ts @@ -59,7 +59,7 @@ async function readSecret(ctx: Ctx, row: Pick 0) { mode = "execute"; allowlist = [...new Set([...(allowlist.length ? allowlist : INVESTIGATE_CAPABILITIES), ...RECOVER_CAPABILITIES])]; - } else note = "(没有覆盖恢复动作的有效授权,已降级只读)"; + } else note = "(没有覆盖恢复动作的有效授权,改为只读)"; } const goal = renderGoal(cfg.goalTemplate, c.now, f); const dedupeKey = c.trigger === "manual" ? `schedule:${c.jobId}:manual:${c.runId}` : `schedule:${c.jobId}:${c.slot}`; diff --git a/apps/server/src/modules/schedules/store.ts b/apps/server/src/modules/schedules/store.ts index c5271f4b..7ae4b729 100644 --- a/apps/server/src/modules/schedules/store.ts +++ b/apps/server/src/modules/schedules/store.ts @@ -201,7 +201,7 @@ export async function updateJob( }); } if (patch.enabled !== undefined) { - if (patch.enabled && !row.available) throw new ApiException("conflict", "该任务未配置,不能开启"); + if (patch.enabled && !row.available) throw new ApiException("conflict", "任务未配置,不能启用"); set.enabled = patch.enabled; set.customized = true; if (patch.enabled && !row.enabled && !set.next_run_at) { @@ -228,7 +228,7 @@ export async function updateJob( /** 立即执行:置 run_requested_at(已在排队或运行中则合并,不重复) */ export async function requestRun(db: Kysely, id: string): Promise<{ row: ScheduledJobRow; queued: boolean }> { const row = await getJob(db, id); - if (!row.available) throw new ApiException("conflict", "该任务未配置,不能执行"); + if (!row.available) throw new ApiException("conflict", "任务未配置,不能执行"); const upd = await db .updateTable("scheduled_job") .set({ run_requested_at: sql`now()` }) diff --git a/apps/server/test/e2e/e11-subagents.test.ts b/apps/server/test/e2e/e11-subagents.test.ts index b0d80ce7..63b67501 100644 --- a/apps/server/test/e2e/e11-subagents.test.ts +++ b/apps/server/test/e2e/e11-subagents.test.ts @@ -275,7 +275,7 @@ describe.skipIf(!hasPg)("E11 Pi 子代理(agent.spawn / agent.await)", () => { await e.settings({ pi: { subagents: { enabled: false } } }); expect(await spawn(parent.id, { goal: "关闭后" })).toMatchObject({ code: "precondition", - message: expect.stringContaining("子代理未开启"), + message: expect.stringContaining("子会话未开启"), }); await e.settings({ pi: { subagents: { enabled: true } } }); // 以上都没有建出子会话 diff --git a/apps/web/app/api/agent.ts b/apps/web/app/api/agent.ts index c3ed3b08..0c0a9dfb 100644 --- a/apps/web/app/api/agent.ts +++ b/apps/web/app/api/agent.ts @@ -209,8 +209,8 @@ export function useAbortAgentRun() { return useMutation({ mutationFn: async (id: string) => { if (agentMockEnabled()) return mockDetail(id, { abort: true }).run; - return apiMutate(`${AGENT_BASE}/:id/abort`, { pathParams: { id }, body: { reason: "用户在控制台中止" } }).then( - (d) => AgentRun.parse(d), + return apiMutate(`${AGENT_BASE}/:id/abort`, { pathParams: { id }, body: { reason: "在控制台停止" } }).then((d) => + AgentRun.parse(d), ); }, onSuccess: () => qc.invalidateQueries({ queryKey: agentKeys.all }), @@ -236,7 +236,7 @@ export const RUN_STATUS_LABEL: Record = { waiting: "等待中", succeeded: "已完成", failed: "失败", - cancelled: "已中止", + cancelled: "已停止", budget_exceeded: "超出预算", }; diff --git a/apps/web/app/components/agent/agent-conversation.tsx b/apps/web/app/components/agent/agent-conversation.tsx index 08506f3b..c2936383 100644 --- a/apps/web/app/components/agent/agent-conversation.tsx +++ b/apps/web/app/components/agent/agent-conversation.tsx @@ -174,10 +174,10 @@ export function ToolCallCard({ reason={authReason ?? call.reason ?? "未授权"} explanation={ authReason - ? '有副作用的能力必须命中有效的持续授权(automation_grant)或单次审批(approval),由服务端在执行前核对;模型声称"用户已同意"不会被采信。需要放行时到「设置 → 持续授权」发放,或对计划申请单次审批。' + ? '有副作用的能力需要有效的持续授权或单次审批,由服务端在执行前核对,模型声称"用户已同意"无效。发布收录策略会生成持续授权,也可以为计划申请单次审批。' : mode === "plan_only" && writeKind - ? "本次是「只要方案」:白名单只放行只读、预览、提案类能力,执行类调用一律拒绝。要真正执行,请按方案以执行模式重新发起。" - : "这个能力不在本次运行的能力白名单内(或已超出调用预算),调用在到达处理器前就被拦下。Agent 无法绕过。" + ? "本次是「只要方案」,只放行只读、预览、提案类能力。要执行,点「按方案执行」。" + : "这个能力不在本次运行的白名单内,或已超出调用预算,调用没有执行。" } parameters={[ { id: "call", label: "调用 ID", value: call.toolCallId }, @@ -219,9 +219,7 @@ export function ToolCallCard({
幂等键
{audit?.idempotencyKey ?? - (meta && meta.kind !== "read" - ? "由服务端生成(能力:版本:运行:输入哈希),审计未取到" - : "只读能力不记操作账本")} + (meta && meta.kind !== "read" ? "服务端已生成,审计里没有取到" : "只读能力不记操作账本")}
{audit?.grantId || audit?.approvalId ? ( <> @@ -298,13 +296,13 @@ function EndNotice({ item }: { item: Extract }) { {budget ? `超出预算${item.budget ? `(${BUDGET_LABEL[item.budget] ?? item.budget})` : ""},已停止` : aborted - ? "已中止" + ? "已停止" : "运行失败"}

{item.message ?

{item.message}

: null} {budget ? (

- 已完成的外部动作不会撤销;可调大预算后追加消息续跑。 + 已完成的外部动作不会撤销。调大预算后追加消息可继续运行。

) : null} @@ -636,7 +634,7 @@ export function ApprovalItem({ approval: a }: { approval: Approval }) { {risk ? : null} @@ -645,7 +643,7 @@ export function ApprovalItem({ approval: a }: { approval: Approval }) { } onApprove={() => act("approved")} onReject={() => act("rejected")} - disabledReason={admin ? undefined : "只有控制台管理员会话能批准或拒绝"} + disabledReason={admin ? undefined : "只有管理员能批准或拒绝"} result={ a.decidedAt ? `${a.status === "approved" ? "批准" : a.status === "rejected" ? "拒绝" : "处理"}于 ${formatDateTime(a.decidedAt)}` @@ -736,7 +734,7 @@ export function FinalReport({ {said || "(没有文字汇报)"} -

模型的自述,仅供参考,不作为完成依据。

+

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

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

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

) : ( -

服务端汇报尚未生成;以任务页、整理记录的实际状态为准。

+

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

)} {run.error ? (

@@ -833,7 +831,7 @@ export function RunAnnouncer({ status, finished }: { status: AgentRunStatus; fin prev.current = status; }, [status]); useEffect(() => { - if (finished) setMsg("回复完成,最终汇报已生成"); + if (finished) setMsg("最终汇报已生成"); }, [finished]); return ( diff --git a/apps/web/app/components/agent/agent-drawer.tsx b/apps/web/app/components/agent/agent-drawer.tsx index d6a8b33d..e4ec71dc 100644 --- a/apps/web/app/components/agent/agent-drawer.tsx +++ b/apps/web/app/components/agent/agent-drawer.tsx @@ -93,7 +93,7 @@ export function AgentDrawer() { Agent - {context?.label ? `当前对象:${context.label}` : "交代目标,Agent 在授权范围内边执行边汇报。"} + {context?.label ? `当前对象:${context.label}` : "交代目标,Agent 在授权范围内执行并汇报。"} ({agentShortcutLabel()} 开关,Esc 关闭) diff --git a/apps/web/app/components/agent/agent-run-panel.tsx b/apps/web/app/components/agent/agent-run-panel.tsx index 2e03e5bd..88fbe179 100644 --- a/apps/web/app/components/agent/agent-run-panel.tsx +++ b/apps/web/app/components/agent/agent-run-panel.tsx @@ -37,16 +37,16 @@ import { import { SubagentTree } from "./agent-subagent-tree.tsx"; import { useAgentStream } from "./use-agent-stream.ts"; -const TRIGGER_LABEL = { user: "用户发起", event: "事件触发", schedule: "定时", subagent: "子代理" } as const; +const TRIGGER_LABEL = { user: "用户发起", event: "事件触发", schedule: "定时", subagent: "子会话" } as const; export const MODE_HELP: Record = { execute: { label: "授权内执行", - help: "在已授权范围内执行;未授权的动作会被服务端拒绝并在对话里标出。", + help: "未授权的动作会被服务端拒绝并在对话里标出。", }, plan_only: { label: "只要方案", - help: "只调查、出方案,不执行任何写操作;满意后可一键按方案执行。", + help: "只调查、出方案,不执行写操作。之后可按方案执行。", }, }; @@ -134,7 +134,7 @@ export function GoalComposer({

- 试试这些 + 常用目标

    {suggestions.map((s) => ( @@ -267,9 +267,7 @@ export function AgentRunPanel({ className="flex items-center gap-2 rounded-xl border border-warning/40 bg-warning/10 px-3 py-2 text-caption text-warning" > - - 实时连接中断,正在重连;恢复后按 after= 补读漏掉的事件,期间每 2.5 秒轮询兜底。 - + 实时连接中断,正在重连。期间每 2.5 秒轮询一次。
) : null} {live.error ? ( @@ -320,7 +318,7 @@ export function AgentRunPanel({ maxRows={6} maxLength={8000} aria-label="追加消息" - placeholder={streaming ? "补充或纠正(运行中也可追加)" : "追加消息,继续这次会话"} + placeholder={streaming ? "补充或纠正(运行中也可追加)" : "追加消息"} toolbar={ mode === "plan_only" && finished ? ( , @@ -207,7 +207,7 @@ export function AgentNavEntry({ collapsed = false, onNavigate }: { collapsed?: b {count ? : null} - {count ? `${count} 个会话进行中` : "交代目标,随时唤出"} + {count ? `${count} 个会话进行中` : "交代目标,边做边汇报"} {count ? ( @@ -225,8 +225,8 @@ export function AgentNavEntry({ collapsed = false, onNavigate }: { collapsed?: b export function AgentShortcutsHelp() { const shortcut = agentShortcutLabel(); const rows: [string, string][] = [ - [shortcut, "任意页面打开 / 收起 Agent 抽屉(带当前页面上下文)"], - ["Esc", "关闭 Agent 抽屉"], + [shortcut, "打开 / 收起 Agent,附带当前页面"], + ["Esc", "关闭 Agent"], ["Enter", "发送目标;Shift+Enter 换行"], ]; return ( diff --git a/apps/web/app/components/console/nav.ts b/apps/web/app/components/console/nav.ts index dc1773ac..830125e1 100644 --- a/apps/web/app/components/console/nav.ts +++ b/apps/web/app/components/console/nav.ts @@ -174,7 +174,7 @@ export const NAV_GROUPS: NavGroup[] = [ to: "/console/schedules", label: "定时任务", icon: Timer, - description: "周期任务与 Agent 巡检的开关、间隔、立即执行与历史", + description: "周期任务与 Agent 巡检的开关、间隔和运行记录", }, { to: "/console/engine", diff --git a/apps/web/app/components/schedules/agent-check-dialog.tsx b/apps/web/app/components/schedules/agent-check-dialog.tsx index 6112e0b6..07a3fae3 100644 --- a/apps/web/app/components/schedules/agent-check-dialog.tsx +++ b/apps/web/app/components/schedules/agent-check-dialog.tsx @@ -142,7 +142,7 @@ export function AgentCheckDialog({ {job ? "编辑巡检" : "新建巡检"} - 到点按目标入队一次 Agent 会话,只在白名单与预算内行动。 + 到点按目标发起一次 Agent 会话,只在白名单和预算内执行。
set("goal", e.target.value)} /> - +
{READONLY_CAPS.map((c) => { const on = d.caps.includes(c.id); @@ -235,7 +235,7 @@ export function AgentCheckDialog({ label="间隔" htmlFor="ac-every" error={errors.every} - hint={cronJob ? "cron 调度请在卡片上修改" : undefined} + hint={cronJob ? "cron 表达式在卡片上修改" : undefined} >
= { }; export const CHECK_TRIGGER_LABEL: Record = { - always: "每次都跑", + always: "每次运行", open_decisions: "有待处理时", stalled_downloads: "有下载无进展时", subscriptions_active: "有追踪订阅时", diff --git a/apps/web/app/components/schedules/runs-sheet.tsx b/apps/web/app/components/schedules/runs-sheet.tsx index a9bd9fe4..b7672f16 100644 --- a/apps/web/app/components/schedules/runs-sheet.tsx +++ b/apps/web/app/components/schedules/runs-sheet.tsx @@ -17,7 +17,7 @@ export function RunsSheet({ job, onClose }: { job: ScheduledJob | null; onClose: !o && onClose()} - title={job ? `${job.name} · 历史` : "历史"} + title={job ? `${job.name} · 运行记录` : "运行记录"} description={ job ? `${describeSchedule(job.schedule)} · 累计 ${job.runCount} 次,失败 ${job.failCount} 次` : undefined } diff --git a/apps/web/app/components/schedules/schedule-card.tsx b/apps/web/app/components/schedules/schedule-card.tsx index 6b875888..1bb40d94 100644 --- a/apps/web/app/components/schedules/schedule-card.tsx +++ b/apps/web/app/components/schedules/schedule-card.tsx @@ -41,7 +41,7 @@ function AgentCheckSummary({ config }: { config: Partial }) { 预算:{budget.maxToolCalls ? `${budget.maxToolCalls} 次调用` : "默认"} {budget.maxDurationSec ? ` · ${budget.maxDurationSec} 秒` : ""} - 能力:{allow.length ? `${allow.length} 项` : "只读全部"} + 能力:{allow.length ? `${allow.length} 项` : "全部只读能力"} {config.allowRecover ? 允许授权内恢复 : null} {config.reportAsDecision ? 写入待处理 : null}
@@ -156,7 +156,7 @@ export function ScheduleCard({ {job.customized ? ( @@ -184,8 +184,8 @@ export function ScheduleCard({ } title={`删除「${job.name}」?`} - description="运行历史一并删除,不影响已产生的 Agent 会话。" - confirmLabel="删除" + description="运行记录一并删除,已产生的 Agent 会话保留。" + confirmLabel="删除巡检" destructive onConfirm={() => del.mutateAsync({ id: job.id }).then( diff --git a/apps/web/app/components/settings/agent.tsx b/apps/web/app/components/settings/agent.tsx index 42822334..f1678d5a 100644 --- a/apps/web/app/components/settings/agent.tsx +++ b/apps/web/app/components/settings/agent.tsx @@ -10,7 +10,7 @@ const nullNum = (v: FieldValue | undefined) => (str(v) === "" ? null : Number(st const RUNTIME_CARD: CardSpec = { anchor: "runtime", - title: "上下文与子代理", + title: "上下文与子会话", fields: [ { name: "pi.contextBudget", @@ -18,7 +18,7 @@ const RUNTIME_CARD: CardSpec = { kind: "number", hint: "4096–2000000,超过模型窗口时按窗口算", }, - { name: "pi.subagentsEnabled", label: "允许子代理", kind: "switch" }, + { name: "pi.subagentsEnabled", label: "允许子会话", kind: "switch" }, { name: "pi.compaction.enabled", label: "上下文压缩", kind: "switch" }, { name: "pi.compaction.thresholdRatio", @@ -49,7 +49,7 @@ const RUNTIME_CARD: CardSpec = { const BUDGET_CARD: CardSpec = { anchor: "budget", title: "预算", - description: "超出每日预算的运行停在「超预算」,等你放行。", + description: "超出每日预算的运行停在「超出预算」,等你放行。", fields: [ { name: "budget.agentMaxRunsPerDay", label: "每日运行次数", kind: "number" }, { name: "budget.agentMaxCostUsdPerDay", label: "每日费用(USD)", kind: "number" }, diff --git a/apps/web/app/components/settings/api-keys.tsx b/apps/web/app/components/settings/api-keys.tsx index c02fedf6..c50a58fc 100644 --- a/apps/web/app/components/settings/api-keys.tsx +++ b/apps/web/app/components/settings/api-keys.tsx @@ -105,7 +105,7 @@ export function ApiKeysSection() { section="api-keys" anchor="keys" title="API Key" - description="签发给外部程序(媒体服务器插件、脚本)的 Key,按权限范围授权。控制台走登录会话,不用 Key;浏览器本机 Token 在「账户与安全」。" + description="签发给外部程序(媒体服务器插件、脚本)的 Key,按权限范围授权。本机 Token 在「账户与安全」。" plain actions={
diff --git a/apps/web/app/components/settings/instances.tsx b/apps/web/app/components/settings/instances.tsx index 824fd48d..c57fbe92 100644 --- a/apps/web/app/components/settings/instances.tsx +++ b/apps/web/app/components/settings/instances.tsx @@ -87,7 +87,7 @@ function DownloadersTable() { config={{ noun: "下载器实例", description: - "只接管带管理标签的任务。多个实例共用一块缓存盘时填相同的缓存卷标识,水位合并计算。点行编辑实例和它的路径映射。", + "只接管带管理标签的任务。多个实例共用一块缓存盘时填相同的缓存卷标识,水位合并计算。点行编辑实例和路径映射。", emptyHint: "添加 qBittorrent 后才能提交下载。", drawerExtra: (r) => , columns: [ @@ -181,7 +181,7 @@ function MediaServersTable() { hooks={{ list, create, update, remove } as never} config={{ noun: "媒体服务器", - description: "只用来查询作品有无、季集和版本;查不到不等于缺失。点行编辑实例和它的路径映射。", + description: "只用来查询作品有无、季集和版本;查不到不等于缺失。点行编辑实例和路径映射。", drawerExtra: (r) => , columns: [ { diff --git a/apps/web/app/components/settings/metadata.tsx b/apps/web/app/components/settings/metadata.tsx index c7eb8b7f..2ea7797f 100644 --- a/apps/web/app/components/settings/metadata.tsx +++ b/apps/web/app/components/settings/metadata.tsx @@ -11,7 +11,7 @@ import { type CardSpec, SettingsCard, SettingsGate } from "./settings-card.tsx"; const TMDB_CARD: CardSpec = { anchor: "tmdb", title: "TMDB", - description: "这里没填时使用环境变量 TMDB_API_KEY / TMDB_ACCESS_TOKEN / TMDB_BASE_URL。", + description: "留空时用环境变量 TMDB_API_KEY / TMDB_ACCESS_TOKEN / TMDB_BASE_URL。", fields: [ { name: "metadata.tmdbApiKey", diff --git a/apps/web/app/components/settings/path-mappings.tsx b/apps/web/app/components/settings/path-mappings.tsx index ee4e6ac7..6d97bd02 100644 --- a/apps/web/app/components/settings/path-mappings.tsx +++ b/apps/web/app/components/settings/path-mappings.tsx @@ -211,7 +211,7 @@ export function PathMappingsPanel({ onOpenChange={(o) => !o && setDeleting(null)} title="删除路径映射?" description="依赖这条映射的路径换算会失效,磁盘文件不动。" - confirmLabel="删除" + confirmLabel="删除映射" destructive onConfirm={async () => { if (!deleting) return; diff --git a/apps/web/app/components/settings/pi-oauth.tsx b/apps/web/app/components/settings/pi-oauth.tsx index 1f6d8b17..6de463e6 100644 --- a/apps/web/app/components/settings/pi-oauth.tsx +++ b/apps/web/app/components/settings/pi-oauth.tsx @@ -102,7 +102,7 @@ export function PiOAuthLogin({ abortRef.current = ac; while (!ac.signal.aborted) { if (Date.now() > new Date(s.expiresAt).getTime()) { - finish("expired", "超过有效期仍未完成授权。"); + finish("expired", "授权已超时。"); return; } try { @@ -256,7 +256,7 @@ export function PiOAuthLogin({ ) : (

- 在自己的浏览器打开授权链接。授权后会跳到 localhost 地址,页面打不开是正常的,把地址栏里的完整 URL 粘到下面。 + 在浏览器打开授权链接。授权后会跳到 localhost 地址,页面打不开属正常,把地址栏里的完整 URL 粘贴到下面。

{session.authorizeUrl ? (
diff --git a/apps/web/app/components/settings/pi-shared.tsx b/apps/web/app/components/settings/pi-shared.tsx index 578adc5f..2e045692 100644 --- a/apps/web/app/components/settings/pi-shared.tsx +++ b/apps/web/app/components/settings/pi-shared.tsx @@ -7,7 +7,7 @@ import { Input } from "~/components/ui/input.tsx"; export const GROUP_LABEL: Record = { api_key: "API Key", subscription: "订阅登录", - cloud: "云凭据", + cloud: "云凭证", custom: "兼容端点", test: "测试", }; @@ -16,7 +16,7 @@ export const GROUP_ORDER: PiProviderGroup[] = ["api_key", "subscription", "cloud export const AUTH_LABEL: Record = { api_key: "API Key", oauth: "订阅登录", - none: "不需要凭据", + none: "不需要凭证", }; export const THINKING_LABEL: Record = { diff --git a/apps/web/app/components/settings/pi-test.tsx b/apps/web/app/components/settings/pi-test.tsx index fabc5492..c3423589 100644 --- a/apps/web/app/components/settings/pi-test.tsx +++ b/apps/web/app/components/settings/pi-test.tsx @@ -10,7 +10,7 @@ import { errorMessage } from "~/lib/api.ts"; // 上游测试:默认只验凭据与模型(不产生费用);"发一次最小补全"真实调用一次模型 const ERROR_KIND: Record = { - auth: "凭据被拒", + auth: "凭证被拒", oauth: "登录失效", model: "模型不可用", network: "网络错误", @@ -28,7 +28,7 @@ export function PiTestPanel({ upstream, model }: { upstream: PiUpstream; model?: try { const r = await test.mutateAsync({ id: upstream.id, body: { mode, ...(model ? { model } : {}) } }); setResult(r); - if (r.ok) toast.success(mode === "auth" ? "凭据可用" : "补全成功"); + if (r.ok) toast.success(mode === "auth" ? "凭证可用" : "补全成功"); else toast.error(r.message ?? "测试失败"); } catch (err) { setResult(null); @@ -51,7 +51,7 @@ export function PiTestPanel({ upstream, model }: { upstream: PiUpstream; model?:

- 「测试」只检查凭据和模型,不产生费用;最小补全会真实调用一次模型(最多 16 个 token)。 + 「测试」只检查凭证和模型,不产生费用。最小补全会调用一次模型(最多 16 个 token)。

{result ? (
diff --git a/apps/web/app/components/settings/pi-upstreams.tsx b/apps/web/app/components/settings/pi-upstreams.tsx index d7815e80..a2f4e65c 100644 --- a/apps/web/app/components/settings/pi-upstreams.tsx +++ b/apps/web/app/components/settings/pi-upstreams.tsx @@ -51,7 +51,7 @@ export function PiUpstreamsBlock() { section="agent" anchor="upstream" title="上游" - description="Agent 调用的模型上游。没指定上游的运行用默认上游;密钥和登录凭据只写入,不回显。" + description="没指定上游的运行用默认上游。密钥和登录凭证保存后不回显。" plain actions={ @@ -54,8 +51,8 @@ export default function AgentPage() {
-

每一次工具调用都经服务端授权校验:不在白名单或没有授权覆盖的调用会被拒绝,并在对话里醒目标出。

-

运行结束后,汇报分两栏:模型的自述只作参考,"系统核实的结果"取自下载计划、下载任务与整理记录。

+

每次工具调用都经服务端授权校验,不在白名单或没有授权的调用会被拒绝并在对话里标出。

+

汇报分两栏:模型自述仅供参考,"系统核实的结果"取自下载计划、下载任务和整理记录。

diff --git a/apps/web/app/routes/console/audit.tsx b/apps/web/app/routes/console/audit.tsx index fd95a659..8f74c4be 100644 --- a/apps/web/app/routes/console/audit.tsx +++ b/apps/web/app/routes/console/audit.tsx @@ -37,6 +37,12 @@ const ACTION_LABEL: Record = { "secret.delete": "删除凭证", "site_account.credentials_set": "录入站点凭证", "engine.run_cancel": "取消引擎运行", + "pi_upstream.oauth_start": "开始订阅登录", + "pi_upstream.oauth_login": "完成订阅登录", + "pi_upstream.oauth_logout": "退出订阅登录", + "schedule.run_now": "立即执行定时任务", + "schedule.skip": "跳过定时任务", + "acquisition_plan.manual_submit": "手动提交下载计划", }; const VERB: Record = { create: "新建", update: "修改", delete: "删除", test: "测试", verify: "校验" }; const NOUN: Record = { @@ -44,6 +50,9 @@ const NOUN: Record = { downloader_instance: "下载器", media_server_instance: "媒体服务器", path_mapping: "路径映射", + pi_upstream: "Agent 上游", + schedule: "定时任务", + automation_grant: "持续授权", }; function actionLabel(a: string): string { if (ACTION_LABEL[a]) return ACTION_LABEL[a]; diff --git a/apps/web/app/routes/console/downloads.tsx b/apps/web/app/routes/console/downloads.tsx index 173d1d9e..31b49e5e 100644 --- a/apps/web/app/routes/console/downloads.tsx +++ b/apps/web/app/routes/console/downloads.tsx @@ -143,7 +143,7 @@ export default function DownloadsPage() { footer={ open ? ( ) : null } diff --git a/apps/web/app/routes/console/home.tsx b/apps/web/app/routes/console/home.tsx index d86fe59d..6c4e6b1c 100644 --- a/apps/web/app/routes/console/home.tsx +++ b/apps/web/app/routes/console/home.tsx @@ -155,10 +155,10 @@ function AutomationBanner({ a }: { a: Dashboard["automation"] }) {

{paused - ? "自动收录、下载、整理都已停下;查询与手动操作仍可用。" + ? "自动收录、下载、整理已停止,手动操作仍可用。" : readOnly - ? "恢复后的只读对账:只核对不写入,确认无误后再关掉。" - : `${a.activeSubscriptions} 个订阅在追踪,按规则自动收录、下载与入库。`} + ? "只核对,不写入。确认无误后关闭。" + : `${a.activeSubscriptions} 个订阅在追踪,自动收录、下载和整理。`}

@@ -170,7 +170,7 @@ function AutomationBanner({ a }: { a: Dashboard["automation"] }) { checked={readOnly} disabled={update.isPending} onCheckedChange={(v) => - setAutomation({ readOnlyReconcile: v }, v ? "已进入只读对账模式" : "已退出只读对账模式").catch(() => {}) + setAutomation({ readOnlyReconcile: v }, v ? "已开启只读对账" : "已关闭只读对账").catch(() => {}) } aria-label="只读对账模式" /> @@ -193,8 +193,8 @@ function AutomationBanner({ a }: { a: Dashboard["automation"] }) { } title="暂停全部自动化?" - description="自动收录、下载、整理会停下,已提交的下载任务不受影响。随时可以恢复。" - confirmLabel="暂停" + description="自动收录、下载、整理会停止,已提交的下载任务不受影响。可随时恢复。" + confirmLabel="暂停自动化" onConfirm={() => setAutomation({ enabled: false }, "自动化已暂停")} /> )} @@ -278,9 +278,9 @@ function CurrentWork({ w }: { w: Dashboard["currentWork"] }) { function NeedsAttention({ n }: { n: Dashboard["needsAttention"] }) { return ( -
+
{n.total === 0 ? ( - + ) : ( <>

@@ -308,8 +308,8 @@ function CacheSection({ c }: { c: Dashboard["cache"] }) { ) : ( @@ -401,13 +401,7 @@ function SourcesSection({ s }: { s: Dashboard["sources"] }) { return (

{rows.length === 0 ? ( - + ) : (
    {rows.slice(0, 8).map((r) => { @@ -438,7 +432,7 @@ function RecentOutput({ items }: { items: Dashboard["recentOutput"] }) { return (
    {items.length === 0 ? ( - + ) : (
      {items.slice(0, 6).map((r) => { @@ -478,7 +472,7 @@ export default function Home() { <> @@ -499,7 +493,7 @@ export default function Home() { ))} ) : q.isError ? ( - q.refetch()} /> + q.refetch()} /> ) : ( <> diff --git a/apps/web/app/routes/console/identity.tsx b/apps/web/app/routes/console/identity.tsx index 365c94af..d14cc6ad 100644 --- a/apps/web/app/routes/console/identity.tsx +++ b/apps/web/app/routes/console/identity.tsx @@ -280,7 +280,7 @@ export default function IdentityPage() { } diff --git a/apps/web/app/routes/console/schedules.tsx b/apps/web/app/routes/console/schedules.tsx index 53a76af5..691fb275 100644 --- a/apps/web/app/routes/console/schedules.tsx +++ b/apps/web/app/routes/console/schedules.tsx @@ -110,7 +110,7 @@ export default function SchedulesPage() { ) : board.isError ? ( board.refetch()} /> ) : items.length === 0 ? ( - + ) : (
      {GROUP_ORDER.map((g) => { @@ -123,7 +123,7 @@ export default function SchedulesPage() { {jobs.length === 0 ? (

      - {g === "agent" ? "暂无巡检,点「新建巡检」添加。" : "暂无任务。"} + {g === "agent" ? "还没有巡检。" : "还没有定时任务。"}

      ) : ( jobs.map((j) => ( diff --git a/apps/web/app/routes/console/settings.tsx b/apps/web/app/routes/console/settings.tsx index ff85b7b6..2fe3ad9c 100644 --- a/apps/web/app/routes/console/settings.tsx +++ b/apps/web/app/routes/console/settings.tsx @@ -43,7 +43,7 @@ export default function SettingsPage() { return ( <> - +
      {/* 窄屏:顶部下拉 */}
      diff --git a/apps/web/app/routes/console/sites.$siteId.tsx b/apps/web/app/routes/console/sites.$siteId.tsx index 6bc194fb..eb09e983 100644 --- a/apps/web/app/routes/console/sites.$siteId.tsx +++ b/apps/web/app/routes/console/sites.$siteId.tsx @@ -40,7 +40,7 @@ const PROMOS = [ ]; const RESOLUTION: { value: ResolutionStatus; label: string }[] = [ { value: "resolved", label: "已识别" }, - { value: "unresolved", label: "未识别" }, + { value: "unresolved", label: "待识别" }, { value: "ambiguous", label: "有歧义" }, { value: "unidentifiable", label: "无法识别" }, ]; @@ -358,11 +358,7 @@ export default function SiteWorkbenchPage() { - 计划在 M2 提供。资源详情里有站点 ID 和来源时间,可据此到原站查看。} - /> + 资源详情里有站点 ID,可据此到原站查看。} /> diff --git a/apps/web/app/routes/console/sites.tsx b/apps/web/app/routes/console/sites.tsx index 03ef7cf3..7e9f7b4d 100644 --- a/apps/web/app/routes/console/sites.tsx +++ b/apps/web/app/routes/console/sites.tsx @@ -146,7 +146,7 @@ export default function SitesPage() { setRegistering(true)}> diff --git a/apps/web/app/routes/console/sources.tsx b/apps/web/app/routes/console/sources.tsx index addf8fcc..42a185d6 100644 --- a/apps/web/app/routes/console/sources.tsx +++ b/apps/web/app/routes/console/sources.tsx @@ -84,7 +84,7 @@ export default function SourcesPage() { <> setCreating(true)}> diff --git a/apps/web/app/routes/not-found.tsx b/apps/web/app/routes/not-found.tsx index 2c113c27..70c78dc8 100644 --- a/apps/web/app/routes/not-found.tsx +++ b/apps/web/app/routes/not-found.tsx @@ -7,7 +7,7 @@ export default function NotFound() {
      回到首页 diff --git a/docs/design/glossary.md b/docs/design/glossary.md index 4f8f5913..307d13e7 100644 --- a/docs/design/glossary.md +++ b/docs/design/glossary.md @@ -27,7 +27,16 @@ | ExternalIdentity | 外部身份(TMDB/TVDB/IMDb ID) | 外部 ID 映射、外部标识 | | 未确认身份的文件/资源 | 待识别 | 未识别(IA 重构时若改名,全站一起改) | | Trash | 回收站 | 垃圾桶 | -| Agent | Agent;上游模型配置叫"Agent 上游" | 助手、智能体、AI、Pi 上游 | +| Agent | Agent;上游模型配置叫"Agent 上游" | 助手、智能体、AI、Pi 上游、模型上游 | +| PiUpstream | Agent 上游;在 Agent 设置分区和上游页内简称"上游" | Pi 上游、模型上游、Provider 配置 | +| OAuth 型上游的登录 | 订阅登录 | OAuth 登录、账号授权 | +| 密钥、Cookie、密码、令牌的统称 | 凭证 | 凭据、认证信息 | +| ScheduledJob | 定时任务 | 计划任务、周期作业、cron 任务 | +| kind=agent_check 的定时任务 | 巡检 | 巡查、Agent 定时任务 | +| 定时任务每次执行的记录 | 运行记录 | 历史、执行历史 | +| AgentRun(parent_run_id 非空) | 子会话;派生它的叫"父会话" | 子代理、子 Agent、subagent | +| AgentRun 被中止 | 停止;状态"已停止" | 中止、取消 | +| AutomationGrant | 持续授权 | 自动化授权、automation_grant | | 工作流引擎的一次执行 | 运行 | run | ## 文案规则 @@ -42,3 +51,5 @@ 8. 数字和状态来自数据,不写产品没有的承诺。 9. 技术词保留原文:API Key、Cookie、TMDB、infohash、hash。 10. 标题、按钮、标签、Toast 末尾不加句号。 +11. 订阅登录的条款提示只陈述事实:借用哪个官方客户端身份、可能违反条款、账号可能被限流或封禁;不劝阻,不美化。 +12. 给模型看的文字(巡检目标、调查提示)同样适用:具体、短,用界面同一套术语。 diff --git a/packages/agent/src/pi/catalog.ts b/packages/agent/src/pi/catalog.ts index 19a32272..90282a65 100644 --- a/packages/agent/src/pi/catalog.ts +++ b/packages/agent/src/pi/catalog.ts @@ -49,15 +49,15 @@ export const OAUTH_CALLBACK_PORTS: Record = { radius: 1456, }; -const TERMS_TAIL = "用于自动化后台服务可能违反提供方的用户条款,账号有被限流或封禁的风险。"; +const TERMS_TAIL = "可能违反提供方的用户条款,账号可能被限流或封禁。"; /** 条款提示(研究文档 §5):订阅登录借用官方客户端身份 */ const TERMS_NOTES: Record = { - anthropic: `该登录借用 Claude Code 的客户端身份使用你的 Claude 订阅(请求会伪装成 Claude Code),${TERMS_TAIL}`, - "openai-codex": `该登录借用 Codex CLI 的客户端身份使用你的 ChatGPT 订阅,${TERMS_TAIL}`, - "github-copilot": `该登录借用 VS Code Copilot Chat 的客户端身份(请求带其伪装头)使用你的 Copilot 订阅,${TERMS_TAIL}`, - xai: `该登录借用官方客户端身份使用你的 SuperGrok / X Premium 订阅,${TERMS_TAIL}`, - "kimi-coding": `该登录借用 Kimi Code 官方客户端身份使用你的 Kimi 订阅,${TERMS_TAIL}`, - meta: `该登录借用官方客户端身份使用你的 Meta Muse 订阅,${TERMS_TAIL}`, + anthropic: `借用 Claude Code 的客户端身份使用 Claude 订阅,请求头模拟 Claude Code。${TERMS_TAIL}`, + "openai-codex": `借用 Codex CLI 的客户端身份使用 ChatGPT 订阅。${TERMS_TAIL}`, + "github-copilot": `借用 VS Code Copilot Chat 的客户端身份使用 Copilot 订阅,请求头模拟该客户端。${TERMS_TAIL}`, + xai: `借用官方客户端身份使用 SuperGrok / X Premium 订阅。${TERMS_TAIL}`, + "kimi-coding": `借用 Kimi Code 的客户端身份使用 Kimi 订阅。${TERMS_TAIL}`, + meta: `借用官方客户端身份使用 Meta Muse 订阅。${TERMS_TAIL}`, }; const env = (name: string, label: string, required: boolean, hint: string | null = null): PiEnvField => ({ @@ -115,10 +115,10 @@ const NO_BASE_OVERRIDE = new Set([ const NOTES: Record = { opencode: "OpenCode Zen:没有订阅登录,用 opencode.ai 控制台生成的 key(订阅体现在 key 上)", "opencode-go": "OpenCode Go:没有订阅登录,用 opencode.ai 控制台生成的 key(与 Zen 同一个 OPENCODE_API_KEY)", - openrouter: "OAuth 登录换得的是永久 API Key,不需要刷新", - radius: "pi 自家网关,模型表可动态刷新", - "amazon-bedrock": "API Key 填 Bedrock bearer token;或选“无”使用服务端 AWS 凭据链", - "google-vertex": "API Key 填 Google Cloud API key;或选“无”使用服务端 ADC / 服务账号", + openrouter: "登录换得永久 API Key,不需要刷新", + radius: "Pi 官方网关,模型列表动态刷新", + "amazon-bedrock": "API Key 填 Bedrock bearer token;或选“不需要凭证”使用服务端 AWS 凭证链", + "google-vertex": "API Key 填 Google Cloud API key;或选“不需要凭证”使用服务端 ADC / 服务账号", }; export function modelInfo(m: Model, source: PiModelInfo["source"] = "static"): PiModelInfo { diff --git a/packages/contracts/src/settings.ts b/packages/contracts/src/settings.ts index 4e84adc2..5a5cf2d7 100644 --- a/packages/contracts/src/settings.ts +++ b/packages/contracts/src/settings.ts @@ -406,7 +406,7 @@ export const SETTINGS_SECTIONS: readonly SettingsSectionDef[] = [ legacyTabs: [{ tab: "pi" }], anchors: [ { id: "upstream", label: "上游" }, - { id: "runtime", label: "上下文与子代理" }, + { id: "runtime", label: "上下文与子会话" }, { id: "budget", label: "预算" }, { id: "investigate", label: "自动调查" }, { id: "advanced", label: "高级" },