/** * 面向 Agent 的客户端能力清单生成器。 * * 根据当前公开的 Surface 和 Capability 计算带 revision 的 Inventory;只有可见能力变化时 * 才更新,避免重试或内部状态抖动错误扩大 Agent 的可调用范围。 */ import type { CapabilityDefinition } from "@/runtime/capabilities/capability-registry"; import type { SurfaceRegistrySnapshot } from "@/runtime/surfaces/surface-registry"; import type { CoreAppRecordInput } from "@/runtime/app-management/app-registry"; export type ClientInventory = Readonly<{ revision: string; standard_components: readonly Readonly<{ id: "notice" | "choice" | "confirm" | "input"; version: "1" }>[]; applications: readonly Readonly<{ id: string; version: string; surfaces: readonly Readonly<{ id: string; events: readonly string[] }>[]; tools?: readonly Readonly<{ name: string; handling: string }>[]; }>[]; capabilities: readonly CapabilityDefinition[]; }>; /** * Builds the only inventory the Agent may receive. It is an observation of * host-local enablement and policy, not an install operation nor a grant of * authority. The model deliberately has no access to bundle IDs, code, local * paths, enabled timestamps, user content, cookies, or device identifiers. */ export class ClientInventoryPublisher { private revision = 0; private previousFingerprint = ""; private current: ClientInventory | undefined; public sync(surfaces: SurfaceRegistrySnapshot, capabilities: readonly CapabilityDefinition[], apps: readonly CoreAppRecordInput[] = []): { changed: boolean; inventory: ClientInventory } { const surfaceApplications = surfaces.enabled .map(enabled => ({ id: enabled.app_id, version: enabled.version, surfaces: [{ id: "task-dashboard" as const, events: ["cancel"] as ["cancel"] }] })) const manifestApplications = apps.filter(app => app.enabled).map(app => ({ id: app.app_scope, version: app.manifest.version, surfaces: [], tools: app.manifest.tools.map(tool => ({ name: tool.name, handling: tool.handling })), })); const applications = [...surfaceApplications, ...manifestApplications] .sort((left, right) => `${left.id}@${left.version}`.localeCompare(`${right.id}@${right.version}`)); const allowedCapabilities = [...capabilities] .sort((left, right) => left.name.localeCompare(right.name)) .map(definition => ({ ...definition, input_schema: { ...definition.input_schema, required: [...definition.input_schema.required], properties: { ...definition.input_schema.properties } } })); const fingerprint = JSON.stringify({ applications, capabilities: allowedCapabilities }); if (this.current && fingerprint === this.previousFingerprint) return { changed: false, inventory: this.current }; this.revision += 1; this.previousFingerprint = fingerprint; this.current = { revision: `catalog-${this.revision}`, standard_components: [ { id: "notice", version: "1" }, { id: "choice", version: "1" }, { id: "confirm", version: "1" }, { id: "input", version: "1" }, ], applications, capabilities: allowedCapabilities, }; return { changed: true, inventory: this.current }; } }