From 1ae9d360cc9bb68bb19212085440a970fa42db07 Mon Sep 17 00:00:00 2001 From: pikastech Date: Fri, 24 Jul 2026 08:43:55 +0200 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=94=AF=E6=8C=81=E8=B4=A6=E5=8F=B7?= =?UTF-8?q?=E4=BC=98=E5=85=88=E7=BA=A7=E5=BC=82=E6=9E=84=E6=89=B9=E9=87=8F?= =?UTF-8?q?=E5=BA=94=E7=94=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .agents/skills/unidesk-sub2api/SKILL.md | 12 +++++++ .../runtime-options.ts | 32 ++++++++++++++++--- .../runtime-remote-script-tail.ts | 7 +++- .../platform-infra-sub2api-codex/runtime.ts | 6 ++-- 4 files changed, 50 insertions(+), 7 deletions(-) diff --git a/.agents/skills/unidesk-sub2api/SKILL.md b/.agents/skills/unidesk-sub2api/SKILL.md index ef1e3329..29bc67e9 100644 --- a/.agents/skills/unidesk-sub2api/SKILL.md +++ b/.agents/skills/unidesk-sub2api/SKILL.md @@ -36,6 +36,7 @@ bun scripts/cli.ts platform-infra sub2api upstream-groups status --target PK01 bun scripts/cli.ts platform-infra sub2api upstream-groups smoke --target PK01 cd /root/apistate bun scripts/apistate-cli.ts --config config/sub2rank.yaml scores rank --calls 1000 +bun scripts/apistate-cli.ts --config config/sub2rank.yaml scores priority-plan bun scripts/apistate-cli.ts --config config/sub2rank.yaml users impact --start --end --affected-only ``` @@ -97,6 +98,17 @@ bun scripts/apistate-cli.ts --config config/sub2rank.yaml users impact --start < 同一份评级; - 评级后的 priority 变更仍走 `runtime apply --kind priority` 的 dry-run 和 confirm,不直接写 PostgreSQL。 + - 优先级调优默认读取 YAML 中的 `monitor.recentCallLimit`: + - `sub2api.scorePolicy` 拥有评分权重和质量曲线; + - `sub2api.priorityPlan` 拥有候选范围、置信度、可用性和分数到 priority + 的连续映射参数; + - `scores priority-plan` 自动生成完整 review plan 和异构 + `priorities` 对象; + - review 后使用一次 + `runtime apply --kind priority --priorities-json ''` dry-run; + - 确认时对同一对象增加 `--confirm`,在一个远端作业内完成整批写入和逐账号 + 回读; + - 禁止按账号逐条 apply,或把分级标签硬编码为 priority。 - 用户要求按时间窗统计使用用户、客户可见故障或补偿名单时,使用 ApiState 的 PostgreSQL 用户影响入口: - `users impact --start --end ` 默认列出全部非管理员活动用户; diff --git a/scripts/src/platform-infra-sub2api-codex/runtime-options.ts b/scripts/src/platform-infra-sub2api-codex/runtime-options.ts index 910d58e0..33561260 100644 --- a/scripts/src/platform-infra-sub2api-codex/runtime-options.ts +++ b/scripts/src/platform-infra-sub2api-codex/runtime-options.ts @@ -16,6 +16,7 @@ export interface RuntimeOptions { template: string | null; kind: "temp-unschedulable" | "priority" | "account-name" | "capacity" | "groups" | "model-mapping"; priority: number | null; + priorities: Record | null; capacity: number | null; groupIds: number[]; name: string | null; @@ -49,6 +50,7 @@ export function parseRuntimeOptions(args: string[]): RuntimeOptions { let template: string | null = null; let kind: RuntimeOptions["kind"] = "temp-unschedulable"; let priority: number | null = null; + let priorities: Record | null = null; let capacity: number | null = null; let groupIds: number[] = []; let name: string | null = null; @@ -98,6 +100,8 @@ export function parseRuntimeOptions(args: string[]): RuntimeOptions { else if (arg.startsWith("--kind=")) kind = parseRuntimeKind(arg.slice("--kind=".length)); else if (arg === "--priority") priority = parsePriority(readValue("--priority")); else if (arg.startsWith("--priority=")) priority = parsePriority(arg.slice("--priority=".length)); + else if (arg === "--priorities-json") priorities = parsePriorities(readValue("--priorities-json")); + else if (arg.startsWith("--priorities-json=")) priorities = parsePriorities(arg.slice("--priorities-json=".length)); else if (arg === "--capacity") capacity = parseCapacity(readValue("--capacity")); else if (arg.startsWith("--capacity=")) capacity = parseCapacity(arg.slice("--capacity=".length)); else if (arg === "--groups") groupIds = parseGroupIds(readValue("--groups")); @@ -134,7 +138,7 @@ export function parseRuntimeOptions(args: string[]): RuntimeOptions { if (account !== null && accounts.length > 0) throw new Error("use only one of --account or --accounts"); if (selectConfiguredPolicy && (account !== null || accounts.length > 0)) throw new Error("--select-configured-policy cannot be combined with --account or --accounts"); if ((action === "get" || action === "infrastructure") && !account) throw new Error(`runtime ${action} requires --account `); - if ((action === "apply" || action === "delete") && account === null && accounts.length === 0 && !selectConfiguredPolicy) { + if ((action === "apply" || action === "delete") && account === null && accounts.length === 0 && !selectConfiguredPolicy && priorities === null) { throw new Error(`runtime ${action} requires --account, --accounts, or --select-configured-policy`); } if (accounts.length > 0 && action !== "apply" && action !== "delete") throw new Error(`runtime ${action} does not accept --accounts`); @@ -156,9 +160,11 @@ export function parseRuntimeOptions(args: string[]): RuntimeOptions { if (!selectConfiguredPolicy && excludeAccounts.length > 0) throw new Error("--exclude-accounts requires --select-configured-policy"); if (action === "apply" && kind === "temp-unschedulable" && !template) throw new Error("runtime apply --kind temp-unschedulable requires --template "); if (kind === "priority" && action !== "apply" && action !== "create") throw new Error("runtime --kind priority only supports apply or create"); - if (kind === "priority" && priority === null) throw new Error("runtime apply --kind priority requires --priority <1-1000>"); + if (kind === "priority" && priority === null && priorities === null) throw new Error("runtime apply --kind priority requires --priority <1-1000> or --priorities-json "); + if (kind === "priority" && priority !== null && priorities !== null) throw new Error("use only one of --priority or --priorities-json"); + if (priorities !== null && (account !== null || accounts.length > 0 || selectConfiguredPolicy)) throw new Error("--priorities-json provides its own account ID selection"); if (kind === "priority" && template !== null) throw new Error("runtime apply --kind priority does not accept --template"); - if (kind !== "priority" && priority !== null && action !== "create") throw new Error("--priority requires --kind priority"); + if (kind !== "priority" && (priority !== null || priorities !== null) && action !== "create") throw new Error("--priority and --priorities-json require --kind priority"); if (kind === "capacity" && action !== "apply" && action !== "create") throw new Error("runtime --kind capacity only supports apply or create"); if (kind === "capacity" && capacity === null) throw new Error("runtime apply --kind capacity requires --capacity <1-100000>"); if (kind === "capacity" && template !== null) throw new Error("runtime apply --kind capacity does not accept --template"); @@ -187,7 +193,7 @@ export function parseRuntimeOptions(args: string[]): RuntimeOptions { if (!/^\d+[mhd]$/u.test(since)) throw new Error("--since must use m, h, or d"); if (["list", "get", "errors", "events", "infrastructure"].includes(action) && confirm) throw new Error(`runtime ${action} does not accept --confirm`); if ([full, raw, json].filter(Boolean).length > 1) throw new Error("use only one of --json, --full, or --raw"); - return { action, account, accounts, selectConfiguredPolicy, accountType, excludeAccounts, group, allGroups, platform, pageToken, template, kind, priority, capacity, groupIds, name, baseUrl, proxyId, modelMappings, confirm, full, raw, json, recent, since, tail, targetId }; + return { action, account, accounts, selectConfiguredPolicy, accountType, excludeAccounts, group, allGroups, platform, pageToken, template, kind, priority, priorities, capacity, groupIds, name, baseUrl, proxyId, modelMappings, confirm, full, raw, json, recent, since, tail, targetId }; } function parseBaseUrl(value: string): string { @@ -263,6 +269,24 @@ function parsePriority(value: string): number { return parsed; } +function parsePriorities(value: string): Record { + let parsed: unknown; + try { + parsed = JSON.parse(value); + } catch { + throw new Error("--priorities-json must be a JSON object"); + } + if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error("--priorities-json must be a JSON object"); + const entries = Object.entries(parsed); + if (entries.length === 0 || entries.length > 100) throw new Error("--priorities-json requires 1 to 100 account priorities"); + const result: Record = {}; + for (const [accountId, priority] of entries) { + if (!/^[1-9]\d*$/u.test(accountId)) throw new Error("--priorities-json keys must be positive numeric account IDs"); + result[accountId] = parsePriority(String(priority)); + } + return result; +} + function parseRuntimeTail(value: string): number { const parsed = Number(value); if (!Number.isInteger(parsed) || parsed < 100 || parsed > 200_000) throw new Error("--tail must be an integer from 100 to 200000"); diff --git a/scripts/src/platform-infra-sub2api-codex/runtime-remote-script-tail.ts b/scripts/src/platform-infra-sub2api-codex/runtime-remote-script-tail.ts index 39d1535a..4870e43d 100644 --- a/scripts/src/platform-infra-sub2api-codex/runtime-remote-script-tail.ts +++ b/scripts/src/platform-infra-sub2api-codex/runtime-remote-script-tail.ts @@ -1183,7 +1183,12 @@ def mutation_plan(detail, action, include_rules): } if PAYLOAD.get("kind") == "priority": before_priority = int(detail.get("priority") or 0) - desired_priority = int(PAYLOAD.get("priority")) + priorities = PAYLOAD.get("priorities") + desired_priority = int( + priorities.get(str(detail.get("id"))) + if isinstance(priorities, dict) + else PAYLOAD.get("priority") + ) return { "accountName": detail.get("name"), "accountId": detail.get("id"), diff --git a/scripts/src/platform-infra-sub2api-codex/runtime.ts b/scripts/src/platform-infra-sub2api-codex/runtime.ts index 5660e4d2..1aa947a1 100644 --- a/scripts/src/platform-infra-sub2api-codex/runtime.ts +++ b/scripts/src/platform-infra-sub2api-codex/runtime.ts @@ -24,7 +24,7 @@ export async function codexPoolRuntime(config: UniDeskConfig, args: string[]): P const payload = { action: options.action, account: options.account, - accounts: options.accounts, + accounts: options.priorities === null ? options.accounts : Object.keys(options.priorities), selectConfiguredPolicy: options.selectConfiguredPolicy, accountType: options.accountType, excludeAccounts: options.excludeAccounts, @@ -34,6 +34,7 @@ export async function codexPoolRuntime(config: UniDeskConfig, args: string[]): P pageToken: options.pageToken, kind: options.kind, priority: options.priority, + priorities: options.priorities, capacity: options.capacity, groupIds: options.groupIds, name: options.name, @@ -101,6 +102,7 @@ export async function codexPoolRuntime(config: UniDeskConfig, args: string[]): P template: options.template, kind: options.kind, priority: options.priority, + priorities: options.priorities, capacity: options.capacity, groupIds: options.groupIds, name: options.name, @@ -198,7 +200,7 @@ function renderRuntimeHelp(args: string[]): RenderedCliResult { "", "Kinds:", " temp-unschedulable Requires --template ", - " priority Requires --priority <1-1000>; lower is higher", + " priority Requires --priority <1-1000> or heterogeneous --priorities-json ; lower is higher", " capacity Requires --capacity <1-100000>; account concurrency guard", " account-name Requires one --account and --name ", " groups Requires one --account and --groups ; replaces all memberships",