openclaw / src /claws /application-schema.test.ts
SaylorTwift's picture
SaylorTwift HF Staff
Add files using upload-large-folder tool
d197cf3 verified
Raw History Blame Contribute Delete
13 kB
import { mkdir, realpath, writeFile } from "node:fs/promises";
import { join } from "node:path";
import { afterEach, describe, expect, it } from "vitest";
import { useAutoCleanupTempDirTracker } from "../../test/helpers/temp-dir.js";
import { clawProfileExtensionPackages } from "./application-plan.js";
import { buildClawAddPlan, type ClawAddPlanContext } from "./lifecycle.js";
import { parseClawManifest, parseClawOpenClawProfile } from "./schema.js";
import type { ClawManifest, ClawSourceIdentity } from "./types.js";
const tempDirs = useAutoCleanupTempDirTracker(afterEach);
function requireManifest(value: unknown): ClawManifest {
const result = parseClawManifest(value);
if (!result.ok) {
throw new Error(JSON.stringify(result.diagnostics));
}
return result.manifest;
}
async function createPlanSource(): Promise<{ source: ClawSourceIdentity; workspace: string }> {
const root = tempDirs.make("openclaw-claw-application-plan-");
await mkdir(join(root, "workspace", "schemas"), { recursive: true });
await writeFile(join(root, "workspace", "schemas", "market.json"), "{}\n", "utf8");
return {
source: {
kind: "package",
name: "@acme/market-analyst",
version: "1.0.0",
packageRoot: root,
manifestPath: join(root, "CLAW.md"),
integrityKind: "development-snapshot",
integrity: "sha256:test",
byteLength: 0,
},
workspace: join(await realpath(root), "new-workspace"),
};
}
const extension = {
id: "market-data",
kind: "plugin",
format: "claude",
source: "clawhub",
ref: "@acme/market-data",
version: "2.0.1",
} as const;
describe("Claw application schema v1", () => {
it("accepts strict native extension assertions without a schema bump", () => {
expect(
parseClawOpenClawProfile({
schemaVersion: 1,
agent: { tools: { profile: "coding", allow: ["read"] } },
extensions: [extension],
}),
).toMatchObject({
ok: true,
profile: { schemaVersion: 1, extensions: [{ id: "market-data", format: "claude" }] },
});
});
it("rejects duplicate extensions and unknown formats", () => {
expect(
parseClawOpenClawProfile({ schemaVersion: 1, agent: {}, extensions: [extension, extension] })
.ok,
).toBe(false);
expect(
parseClawOpenClawProfile({
schemaVersion: 1,
agent: {},
extensions: [{ ...extension, format: "future" }],
}).ok,
).toBe(false);
});
});
describe("Claw application planning v1", () => {
it("discloses model and delegation effects with nonblocking local availability notices", async () => {
const { source, workspace } = await createPlanSource();
const agent = {
model: { primary: "acme/primary", fallbacks: ["acme/missing"] },
subagents: { allowAgents: ["researcher", "writer"], delegationMode: "prefer" as const },
};
const params = {
manifest: requireManifest({ schemaVersion: 1, agent: { id: "analyst" } }),
openClawProfile: { schemaVersion: 1 as const, agent },
source,
};
const context: ClawAddPlanContext = {
workspace,
existingAgentIds: ["researcher"],
config: {
models: {
providers: {
acme: {
baseUrl: "https://models.example.test",
models: [
{
id: "primary",
name: "Primary",
reasoning: false,
input: ["text"],
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
maxTokens: 4096,
},
],
},
},
},
},
};
const plan = await buildClawAddPlan({ ...params, context });
expect(plan.agent.config).toMatchObject(agent);
expect(plan.capabilityChanges).toContainEqual(
expect.objectContaining({ kind: "agent", effect: agent }),
);
expect(plan.blockers).toEqual([]);
expect(plan.readiness).toEqual({ ready: true, requirements: [] });
expect(plan.diagnostics).toMatchObject([
{
level: "warning",
phase: "plan",
code: "delegation_target_unresolved",
path: "$.profiles.openclaw.agent.subagents.allowAgents[1]",
},
{
level: "warning",
phase: "plan",
code: "model_not_in_catalog",
path: "$.profiles.openclaw.agent.model.fallbacks[0]",
},
]);
const unavailable = await buildClawAddPlan({ ...params, context: { workspace } });
expect(unavailable.diagnostics).toContainEqual(
expect.objectContaining({
code: "model_not_in_catalog",
path: "$.profiles.openclaw.agent.model.primary",
}),
);
expect(unavailable.blockers).toEqual([]);
expect(unavailable.planIntegrity).not.toBe(plan.planIntegrity);
});
it("projects profile extensions onto canonical plugin package identities", () => {
expect(
clawProfileExtensionPackages({ schemaVersion: 1, agent: {}, extensions: [extension] }),
).toEqual([
{
kind: "plugin",
source: "clawhub",
ref: "@acme/market-data",
version: "2.0.1",
},
]);
expect(clawProfileExtensionPackages(undefined)).toEqual([]);
});
it("plans a canonical extension and an ordinary managed schema asset", async () => {
const { source, workspace } = await createPlanSource();
const manifest = requireManifest({
schemaVersion: 1,
agent: { id: "market-analyst" },
workspace: {
files: [{ source: "workspace/schemas/market.json", path: "schemas/market.json" }],
},
});
const plan = await buildClawAddPlan({
manifest,
openClawProfile: { schemaVersion: 1, agent: {}, extensions: [extension] },
source,
context: {
workspace,
packagePreflight: async () => ({
ok: true,
action: "install",
integrity: `sha256:${"b".repeat(64)}`,
installId: "market-data",
detectedFormat: "claude",
mapped: ["commands", "skills"],
unavailable: ["agents"],
adapterIdentity: "openclaw/test",
}),
},
});
expect(plan.extensions).toEqual([
expect.objectContaining({
id: "market-data",
detectedFormat: "claude",
mapped: ["commands", "skills"],
unavailable: ["agents"],
requirementState: "missing-installable",
blocked: false,
}),
]);
expect(plan.actions).toContainEqual(
expect.objectContaining({
kind: "package",
id: "plugin:@acme/market-data",
blocked: false,
details: expect.objectContaining({
extension: expect.objectContaining({
id: "market-data",
adapterIdentity: "openclaw/test",
}),
}),
}),
);
expect(plan.actions).toContainEqual(
expect.objectContaining({ kind: "workspaceFile", id: "schemas/market.json" }),
);
});
it("reports a reused extension with remaining local setup as setup-required", async () => {
const { source, workspace } = await createPlanSource();
const prerequisite = {
kind: "plugin-setup" as const,
plugin: "market-data",
provider: "market-data",
envVars: ["MARKET_DATA_TOKEN"],
authMethods: [],
};
const plan = await buildClawAddPlan({
manifest: requireManifest({ schemaVersion: 1, agent: { id: "market-analyst" } }),
openClawProfile: { schemaVersion: 1, agent: {}, extensions: [extension] },
source,
context: {
workspace,
packagePreflight: async () => ({
ok: true,
action: "reuse",
integrity: `sha256:${"b".repeat(64)}`,
installId: "market-data",
detectedFormat: "claude",
mapped: ["skills"],
unavailable: [],
adapterIdentity: "openclaw/test",
requirements: [prerequisite],
}),
},
});
expect(plan.extensions?.[0]).toMatchObject({
requirementState: "setup-required",
ownerAction: "reuse",
});
expect(plan.actions).toContainEqual(
expect.objectContaining({
kind: "package",
action: "reuse",
details: expect.objectContaining({ requirementState: "setup-required" }),
}),
);
expect(plan.readiness).toEqual({ ready: false, requirements: [prerequisite] });
});
it("blocks incomplete adapter provenance", async () => {
const { source, workspace } = await createPlanSource();
const plan = await buildClawAddPlan({
manifest: requireManifest({ schemaVersion: 1, agent: { id: "market-analyst" } }),
openClawProfile: { schemaVersion: 1, agent: {}, extensions: [extension] },
source,
context: {
workspace,
packagePreflight: async () => ({
ok: true,
action: "install",
integrity: `sha256:${"b".repeat(64)}`,
installId: "market-data",
detectedFormat: "claude",
mapped: ["skills"],
unavailable: [],
}),
},
});
expect(plan.blockers).toContainEqual(
expect.objectContaining({ code: "extension_provenance_incomplete" }),
);
expect(plan.actions).toContainEqual(
expect.objectContaining({ id: "plugin:@acme/market-data", blocked: true }),
);
});
it("blocks duplicate portable and profile package declarations", async () => {
const { source, workspace } = await createPlanSource();
const manifest = requireManifest({
schemaVersion: 1,
agent: { id: "market-analyst" },
packages: [
{
kind: "plugin",
source: "clawhub",
ref: "@acme/market-data",
version: "2.0.1",
},
],
});
const plan = await buildClawAddPlan({
manifest,
openClawProfile: { schemaVersion: 1, agent: {}, extensions: [extension] },
source,
context: {
workspace,
packagePreflight: async () => ({
ok: true,
action: "install",
integrity: `sha256:${"b".repeat(64)}`,
installId: "market-data",
detectedFormat: "claude",
mapped: ["skills"],
unavailable: [],
adapterIdentity: "openclaw/test",
}),
},
});
expect(plan.blockers).toContainEqual(
expect.objectContaining({ code: "extension_package_collision" }),
);
expect(plan.actions.filter((action) => action.id === "plugin:@acme/market-data")).toHaveLength(
1,
);
});
it("blocks a declared format that differs from canonical detection", async () => {
const { source, workspace } = await createPlanSource();
const plan = await buildClawAddPlan({
manifest: requireManifest({ schemaVersion: 1, agent: { id: "market-analyst" } }),
openClawProfile: {
schemaVersion: 1,
agent: {},
extensions: [{ ...extension, format: "codex" }],
},
source,
context: {
workspace,
packagePreflight: async () => ({
ok: true,
action: "install",
integrity: `sha256:${"b".repeat(64)}`,
installId: "market-data",
detectedFormat: "claude",
mapped: ["skills"],
unavailable: [],
adapterIdentity: "openclaw/test",
}),
},
});
expect(plan.extensions?.[0]?.blocked).toBe(true);
expect(plan.blockers).toContainEqual(
expect.objectContaining({
code: "extension_format_mismatch",
path: "$.profiles.openclaw.extensions[0].format",
}),
);
});
});
describe("parseClawOpenClawProfile model and delegation", () => {
it.each([
{
model: { primary: "acme/model", fallbacks: ["acme/team/fallback"] },
subagents: { allowAgents: ["researcher", "writer_2"], delegationMode: "prefer" },
},
{
model: { primary: "acme/model", fallbacks: [] },
subagents: { allowAgents: [], delegationMode: "suggest" },
},
{ model: { primary: "acme/model" }, subagents: {} },
])("preserves declared selections: %j", (agent) => {
expect(parseClawOpenClawProfile({ schemaVersion: 1, agent })).toMatchObject({
ok: true,
profile: { agent },
});
});
it.each([
{ model: {} },
{ model: "acme/model" },
{ model: { primary: "model" } },
{ model: { primary: "/model" } },
{ model: { primary: "acme/" } },
{ model: { primary: "acme/has space" } },
{ model: { primary: "acme/model", fallbacks: [""] } },
{ model: { primary: "acme/model", fallbacks: ["invalid"] } },
{ model: { primary: "acme/model", extra: true } },
{ subagents: { delegationMode: "required" } },
{ subagents: { allowAgents: ["Invalid"] } },
{ subagents: { allowAgents: ["*"] } },
{ subagents: { allowAgents: [""] } },
{ subagents: { extra: true } },
])("rejects invalid profile selections: %j", (agent) => {
expect(parseClawOpenClawProfile({ schemaVersion: 1, agent }).ok).toBe(false);
});
});