feat(sources,organize): site agent capabilities and organize.minSizeBytes setting (W10-pre2)
- site.list / site.health_check / site.refresh_login / site.user_data capabilities over the W9-A site center services; outputs whitelisted (no secret refs, cookies, passwords, login names); execute kinds need automation grant. IDs use snake_case (CapabilityId regex); registry count 17 -> 21. - settings.organize.minSizeBytes (default 50 MiB); file-manager identify / organize and planImport take request > setting > port default. - tests and runbook notes (sites, files). Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
c08d8ce62a
commit
81bf010e3e
18 files changed
+563
-15
No files matched your search
@@ -8,7 +8,20 @@ import { ExternalEntityType, ExternalIdentity } from "./identity.ts";
|
||||
import { ServerMapping } from "./ops.ts";
|
||||
import { OrganizationAction, OrganizationRecord } from "./organization.ts";
|
||||
import { AcquisitionSlot, PolicyRevision } from "./policy.ts";
|
||||
import { SourceRelease } from "./sources.ts";
|
||||
import {
|
||||
SiteAccountError,
|
||||
SiteAccountOverall,
|
||||
SiteAccountStatus,
|
||||
SiteAccountUserData,
|
||||
SiteAdapter,
|
||||
SiteAuthKind,
|
||||
SiteHealth,
|
||||
SiteHealthResult,
|
||||
SiteLastHealth,
|
||||
SiteLoginResult,
|
||||
SiteUserDataPoint,
|
||||
SourceRelease,
|
||||
} from "./sources.ts";
|
||||
|
||||
// 首批能力的输入/输出 schema;名字与 CAPABILITY_CATALOG 中的 inputSchema/outputSchema 一致。
|
||||
// 写能力的输入只接受计划/对象 ID,不接受模型自拟的幂等键或任意路径。
|
||||
@@ -236,6 +249,73 @@ export const AgentAwaitInput = z.object({
|
||||
});
|
||||
export const AgentAwaitOutput = z.object({ results: z.array(SubagentResult), allDone: z.boolean() });
|
||||
|
||||
// ── 站点能力(W10-pre2):输出只给状态与统计,不含凭证、Cookie、secret 引用、登录名与站点 config ──
|
||||
|
||||
/** 账号摘要:凭证只给"有没有" */
|
||||
export const SiteAccountBrief = z.object({
|
||||
id: Id,
|
||||
label: z.string(),
|
||||
authKind: SiteAuthKind,
|
||||
enabled: z.boolean(),
|
||||
status: SiteAccountStatus,
|
||||
lastLoginAt: z.string().nullable(),
|
||||
lastVerifiedAt: z.string().nullable(),
|
||||
lastError: SiteAccountError.nullable(),
|
||||
userDataAt: z.string().nullable(),
|
||||
hasSession: z.boolean(),
|
||||
hasPasskey: z.boolean(),
|
||||
});
|
||||
export const SiteBrief = z.object({
|
||||
id: Id,
|
||||
key: z.string(),
|
||||
name: z.string(),
|
||||
adapter: SiteAdapter,
|
||||
baseUrl: z.string(),
|
||||
enabled: z.boolean(),
|
||||
healthStatus: SiteHealth,
|
||||
lastHealth: SiteLastHealth.nullable(),
|
||||
lastHealthAt: z.string().nullable(),
|
||||
accountStatus: SiteAccountOverall,
|
||||
accounts: z.array(SiteAccountBrief),
|
||||
});
|
||||
export const SiteListInput = z.object({
|
||||
/** 缺省只列启用的站点 */
|
||||
includeDisabled: z.boolean().default(false),
|
||||
keys: z.array(z.string().min(1)).max(100).optional(),
|
||||
});
|
||||
export const SiteListOutput = z.object({ items: z.array(SiteBrief) });
|
||||
|
||||
export const SiteHealthCheckInput = z.object({ siteId: Id });
|
||||
export const SiteHealthCheckOutput = SiteHealthResult;
|
||||
|
||||
export const SiteLoginRefreshInput = z.object({
|
||||
siteId: Id,
|
||||
/** 缺省:该站第一个启用的非 passkey 账号 */
|
||||
accountId: Id.optional(),
|
||||
});
|
||||
/** 同步执行(≤ 30 s)的登录结果 */
|
||||
export const SiteLoginRefreshOutput = SiteLoginResult;
|
||||
|
||||
export const SiteUserDataInput = z.object({
|
||||
siteId: Id,
|
||||
accountId: Id.optional(),
|
||||
days: z.number().int().min(1).max(400).default(30),
|
||||
});
|
||||
export const SiteUserDataOutput = z.object({
|
||||
siteId: Id,
|
||||
/** 各账号最近一次成功的用户数据 */
|
||||
accounts: z.array(
|
||||
z.object({
|
||||
accountId: Id,
|
||||
label: z.string(),
|
||||
status: SiteAccountStatus,
|
||||
userData: SiteAccountUserData.nullable(),
|
||||
userDataAt: z.string().nullable(),
|
||||
}),
|
||||
),
|
||||
history: z.array(SiteUserDataPoint),
|
||||
});
|
||||
|
||||
/** 能力 ID → 输入/输出 schema(server 注册处理器、agent 生成工具定义时共用) */
|
||||
export const CAPABILITY_IO = {
|
||||
"catalog.lookup": { input: CatalogLookupInput, output: CatalogLookupOutput },
|
||||
@@ -255,6 +335,10 @@ export const CAPABILITY_IO = {
|
||||
"inventory.query": { input: InventoryQueryInput, output: InventoryQueryOutput },
|
||||
"agent.spawn": { input: AgentSpawnInput, output: AgentSpawnOutput },
|
||||
"agent.await": { input: AgentAwaitInput, output: AgentAwaitOutput },
|
||||
"site.list": { input: SiteListInput, output: SiteListOutput },
|
||||
"site.health_check": { input: SiteHealthCheckInput, output: SiteHealthCheckOutput },
|
||||
"site.refresh_login": { input: SiteLoginRefreshInput, output: SiteLoginRefreshOutput },
|
||||
"site.user_data": { input: SiteUserDataInput, output: SiteUserDataOutput },
|
||||
} as const satisfies Record<KnownCapabilityId, { input: z.ZodType; output: z.ZodType }>;
|
||||
|
||||
type CapabilityIo = typeof CAPABILITY_IO;
|
||||
@@ -309,4 +393,12 @@ export type AgentSpawnOutput = z.infer<typeof AgentSpawnOutput>;
|
||||
export type AgentAwaitInput = z.infer<typeof AgentAwaitInput>;
|
||||
export type AgentAwaitOutput = z.infer<typeof AgentAwaitOutput>;
|
||||
export type SubagentResult = z.infer<typeof SubagentResult>;
|
||||
export type SiteListInput = z.infer<typeof SiteListInput>;
|
||||
export type SiteListOutput = z.infer<typeof SiteListOutput>;
|
||||
export type SiteHealthCheckInput = z.infer<typeof SiteHealthCheckInput>;
|
||||
export type SiteHealthCheckOutput = z.infer<typeof SiteHealthCheckOutput>;
|
||||
export type SiteLoginRefreshInput = z.infer<typeof SiteLoginRefreshInput>;
|
||||
export type SiteLoginRefreshOutput = z.infer<typeof SiteLoginRefreshOutput>;
|
||||
export type SiteUserDataInput = z.infer<typeof SiteUserDataInput>;
|
||||
export type SiteUserDataOutput = z.infer<typeof SiteUserDataOutput>;
|
||||
export type SubagentBudget = z.infer<typeof SubagentBudget>;
|
||||
@@ -68,6 +68,10 @@ export const CAPABILITY_IDS = [
|
||||
"inventory.query",
|
||||
"agent.spawn",
|
||||
"agent.await",
|
||||
"site.list",
|
||||
"site.health_check",
|
||||
"site.refresh_login",
|
||||
"site.user_data",
|
||||
] as const;
|
||||
export const KnownCapabilityId = z.enum(CAPABILITY_IDS);
|
||||
export type KnownCapabilityId = z.infer<typeof KnownCapabilityId>;
|
||||
@@ -297,6 +301,53 @@ export const CAPABILITY_CATALOG: Record<KnownCapabilityId, CapabilityDescriptor>
|
||||
true,
|
||||
false,
|
||||
),
|
||||
// W10-pre2 站点能力(W9-A 站点中心的 Agent 入口);输出一律不含凭证、Cookie 与 secret 引用
|
||||
"site.list": d(
|
||||
"site.list",
|
||||
"列出站点",
|
||||
"列出已登记站点的健康、登录状态与账号摘要(不含凭证)",
|
||||
"read",
|
||||
"SiteList",
|
||||
"read",
|
||||
"low",
|
||||
true,
|
||||
false,
|
||||
),
|
||||
// 只读站点首页并写回健康列,重跑无害:声明 replay safe
|
||||
"site.health_check": d(
|
||||
"site.health_check",
|
||||
"检测站点",
|
||||
"检测站点首页连通与登录态,写回站点健康状态",
|
||||
"execute",
|
||||
"SiteHealthCheck",
|
||||
"admin",
|
||||
"low",
|
||||
true,
|
||||
false,
|
||||
"safe",
|
||||
),
|
||||
"site.refresh_login": d(
|
||||
"site.refresh_login",
|
||||
"刷新站点登录",
|
||||
"对站点账号重新登录或校验 Cookie / API Key,成功后拉取用户数据;需持续授权或审批",
|
||||
"execute",
|
||||
"SiteLoginRefresh",
|
||||
"admin",
|
||||
"medium",
|
||||
true,
|
||||
false,
|
||||
),
|
||||
"site.user_data": d(
|
||||
"site.user_data",
|
||||
"查站点用户数据",
|
||||
"读取站点账号的最新用户数据(上传、下载、分享率、魔力、做种)与按天历史",
|
||||
"read",
|
||||
"SiteUserData",
|
||||
"read",
|
||||
"low",
|
||||
true,
|
||||
false,
|
||||
),
|
||||
};
|
||||
|
||||
function d(
|
||||
@@ -309,6 +360,8 @@ function d(
|
||||
riskLevel: RiskLevel,
|
||||
idempotent: boolean,
|
||||
supportsPreview: boolean,
|
||||
/** 缺省按类别推导;仅对幂等且副作用可重跑的能力显式给 safe */
|
||||
replay?: CapabilityReplay,
|
||||
): CapabilityDescriptor {
|
||||
return {
|
||||
id,
|
||||
@@ -323,6 +376,6 @@ function d(
|
||||
idempotent,
|
||||
supportsPreview,
|
||||
// 有副作用的类别一律不可重放
|
||||
replay: kind === "read" || kind === "preview" ? "safe" : "never",
|
||||
replay: replay ?? (kind === "read" || kind === "preview" ? "safe" : "never"),
|
||||
};
|
||||
}
|
||||
@@ -37,6 +37,8 @@ export const OrganizeSettings = z.object({
|
||||
defaultMode: OrganizeMode,
|
||||
/** 跨卷 Move 采用 Copy→核验→删源;EXDEV 不静默降级 */
|
||||
allowCrossVolumeMove: z.boolean(),
|
||||
/** 整理 / 文件管理识别时视频文件的最小体积(字节),低于它的视为样片跳过;请求(向导)里给的值优先 */
|
||||
minSizeBytes: z.number().int().nonnegative(),
|
||||
});
|
||||
export type OrganizeSettings = z.infer<typeof OrganizeSettings>;
|
||||
|
||||
@@ -315,7 +317,7 @@ export type SettingsKey = z.infer<typeof SettingsKey>;
|
||||
export const DEFAULT_SETTINGS: GlobalSettings = {
|
||||
automation: { enabled: true, readOnlyReconcile: false },
|
||||
cache: { hardThresholdPercent: 90, resumePercent: 85, checkIntervalSec: 60, cleanupTargetPercent: 80 },
|
||||
organize: { defaultMode: "copy", allowCrossVolumeMove: true },
|
||||
organize: { defaultMode: "copy", allowCrossVolumeMove: true, minSizeBytes: 50 * 1024 * 1024 },
|
||||
retention: { seedRetentionDays: 7, logRetentionDays: 180 },
|
||||
pi: {
|
||||
defaultUpstreamId: null,
|
||||
@@ -548,6 +550,7 @@ export const SETTINGS_SECTIONS: readonly SettingsSectionDef[] = [
|
||||
fields: [
|
||||
"organize.defaultMode",
|
||||
"organize.allowCrossVolumeMove",
|
||||
"organize.minSizeBytes",
|
||||
"naming.movie",
|
||||
"naming.tv",
|
||||
"naming.variety",
|
||||
|
||||
@@ -201,7 +201,9 @@ describe("关键合同行为", () => {
|
||||
expect(spec).toMatchObject(meta);
|
||||
expect(spec.inputSchema).toBe(C.CAPABILITY_IO[id].input);
|
||||
expect(spec.outputSchema).toBe(C.CAPABILITY_IO[id].output);
|
||||
if (spec.kind !== "read" && spec.kind !== "preview") expect(spec.replay, id).toBe("never");
|
||||
// 例外:site.health_check 只读首页并写回健康列,幂等可重跑
|
||||
if (spec.kind !== "read" && spec.kind !== "preview" && id !== "site.health_check")
|
||||
expect(spec.replay, id).toBe("never");
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Reference in new issue
Block a user