64 lines
3.2 KiB
TypeScript
64 lines
3.2 KiB
TypeScript
/**
|
|
* 面向 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 };
|
|
}
|
|
}
|