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:
rootandClaude Opus 5.5 committed 2026-09-28 16:02:30 +08:00
1 parent c08d8ce62a
commit 81bf010e3e
18 files changed
+563 -15

No files matched your search

+93 -1
View File
@@ -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>;
+54 -1
View File
@@ -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"),
};
}
+4 -1
View File
@@ -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",
+3 -1
View File
@@ -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");
}
});