Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,8 @@ jobs:
runs-on: blacksmith-8vcpu-ubuntu-2404
timeout-minutes: 25
env:
OPENCLAW_CONTRACT_SHA: 59fc573fb9e938a93b93522be6bf4d7bec0dbc6f
OPENCLAW_CONTRACT_REPOSITORY: openclaw/openclaw
OPENCLAW_CONTRACT_SHA: e79faff8aa755b201302edd286976a03f9ed79ea

steps:
- uses: actions/checkout@v7.0.1
Expand All @@ -59,7 +60,7 @@ jobs:
- name: Check out the pinned OpenClaw contract source
uses: actions/checkout@v7.0.1
with:
repository: openclaw/openclaw
repository: ${{ env.OPENCLAW_CONTRACT_REPOSITORY }}
ref: ${{ env.OPENCLAW_CONTRACT_SHA }}
path: .artifacts/openclaw-contract

Expand Down
2 changes: 1 addition & 1 deletion convex/packages.public.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9320,7 +9320,7 @@ describe("packages public queries", () => {
],
[
"storage:claw",
`---\nschemaVersion: 1\nagent:\n id: demo-claw\n name: Demo Claw\n description: ${longClawDescription}\nmetadata:\n openclaw.config: profiles/openclaw.yml\n---\n# Demo Claw\n`,
`---\nschemaVersion: 1\nagent:\n id: demo-claw\n name: Demo Claw\n description: ${longClawDescription}\nmetadata:\n openclaw.config: profiles/openclaw.yml\n---\nRun the demo workflow precisely.\n`,
],
["storage:profile", "schemaVersion: 1\nagent:\n tools:\n profile: coding\n"],
]);
Expand Down
17 changes: 11 additions & 6 deletions docs/claws.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,9 @@ points to its manifest:
}
```

`CLAW.md` starts with the grouped Claw manifest as YAML frontmatter. Its
non-empty Markdown body becomes the managed `SOUL.md` for the new agent, so do
not also declare an explicit `SOUL.md` bootstrap or supporting-file destination.
`CLAW.md` starts with the grouped Claw manifest as YAML frontmatter. A non-empty
Markdown body is the portable agent prompt; OpenClaw applies its exact UTF-8
contents as the managed `SOUL.md` for the new agent.

```markdown
---
Expand Down Expand Up @@ -60,8 +60,13 @@ Reviews and classifies incoming GitHub issues.
```

JSON manifests remain compatible. Set `openclaw.claw` to a package-relative
JSON file such as `openclaw.claw.json`. JSON has no implicit body file and may
declare an explicit `SOUL.md` source.
JSON file such as `openclaw.claw.json`; JSON has no Markdown body, so declare
`workspace.bootstrapFiles.SOUL.md` explicitly when it needs the equivalent
prompt.

Do not combine a non-empty `CLAW.md` body with an explicit workspace file whose
portable destination is `SOUL.md`. ClawHub rejects that ambiguous dual source.
Headings and task lists in the body are prompt text, not package-time commands.

Every `workspace.*.source` must name a file in the same package. Package names
and versions must match `package.json`, dependency versions must be exact, and
Expand Down Expand Up @@ -90,7 +95,7 @@ Publication rejects:
- a missing, invalid, or escaping `openclaw.claw` path;
- package identity or version mismatches;
- malformed `CLAW.md` frontmatter or manifest fields;
- an empty `CLAW.md` body or simultaneous explicit `SOUL.md` destination;
- a non-empty `CLAW.md` body combined with an explicit `SOUL.md` destination;
- missing workspace source files or portable path collisions;
- floating skill/plugin versions and resolved MCP credentials.

Expand Down
6 changes: 1 addition & 5 deletions fixtures/claws/hosted-e2e/package/CLAW.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,11 @@ agent:
name: Hosted E2E
metadata:
openclaw.config: profiles/openclaw.yml
workspace:
bootstrapFiles:
SOUL.md:
source: workspace/SOUL.md
packages: []
mcpServers: {}
cronJobs: []
---

# Hosted E2E

Fixture for the ClawHub feed-to-OpenClaw dry-run proof.
Use the published Claw package without mutating local state during proof.
3 changes: 0 additions & 3 deletions fixtures/claws/hosted-e2e/package/workspace/SOUL.md

This file was deleted.

2 changes: 1 addition & 1 deletion packages/clawhub/src/cli/commands/packages.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2601,7 +2601,7 @@ describe("package commands", () => {
"metadata:",
" openclaw.config: profiles/openclaw.yml",
"---",
"# GitHub Triage",
"Be precise.",
].join("\n"),
"utf8",
);
Expand Down
76 changes: 26 additions & 50 deletions packages/clawhub/src/schema/clawPackage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ export type ClawPackageValidationIssue = {
export type ValidatedClawPackage = {
manifestPath: string;
manifest: ClawManifest;
implicitWorkspaceFile?: { path: "SOUL.md"; text: string };
summary: ClawManifestSummary;
hasClawMarkdownBody: boolean;
};

const EXACT_VERSION_PATTERN =
Expand All @@ -28,7 +28,6 @@ const WINDOWS_RESERVED_PATH_SEGMENT = /^(?:con|prn|aux|nul|com[1-9]|lpt[1-9])(?:
const UNICODE_CONTROL_CHARACTER = /\p{Cc}/u;
const MAX_CLAW_MANIFEST_BYTES = 1024 * 1024;
const MAX_OPENCLAW_PROFILE_BYTES = 256 * 1024;
const OPENCLAW_TOOL_PROFILE_IDS = new Set(["minimal", "coding", "messaging", "full"]);
const StrictStringArraySchema = type("string[]");
const OpenClawProfileSchema = type({
"+": "reject",
Expand Down Expand Up @@ -258,11 +257,6 @@ function validateOpenClawProfile(
requireNonEmpty("agent.groupChat.mentionPatterns", parsed.agent.groupChat?.mentionPatterns);
if (parsed.agent.tools?.profile !== undefined && !isStrictNonEmpty(parsed.agent.tools.profile)) {
add("agent.tools.profile", "Must be non-empty without leading or trailing whitespace.");
} else if (
parsed.agent.tools?.profile !== undefined &&
!OPENCLAW_TOOL_PROFILE_IDS.has(parsed.agent.tools.profile)
) {
add("agent.tools.profile", "Must name a registered OpenClaw built-in profile.");
}
requireNonEmpty("agent.tools.allow", parsed.agent.tools?.allow);
requireNonEmpty("agent.tools.alsoAllow", parsed.agent.tools?.alsoAllow);
Expand Down Expand Up @@ -318,7 +312,10 @@ function validateOpenClawProfile(
return issues;
}

function parseManifestDocument(raw: string, manifestPath: string) {
function parseManifestDocument(
raw: string,
manifestPath: string,
): { value: unknown; clawMarkdownBody?: string } | { issues: ClawPackageValidationIssue[] } {
const filename = manifestPath.replaceAll("\\", "/").split("/").at(-1)?.toLowerCase();
if (filename === "claw.md") {
const markdown = raw.startsWith("\uFEFF") ? raw.slice(1) : raw;
Expand Down Expand Up @@ -376,7 +373,7 @@ function parseManifestDocument(raw: string, manifestPath: string) {
try {
return {
value: document.toJSON(),
markdownBody: markdown.slice(match[0].length),
clawMarkdownBody: markdown.slice(match[0].length),
};
} catch (error) {
return {
Expand Down Expand Up @@ -513,15 +510,7 @@ export function validateClawPackageContents(input: {
};
}
const parsed = parseManifestDocument(manifestFile.text, manifestPath);
if (parsed.issues) return { ok: false, issues: parsed.issues };
if (parsed.markdownBody !== undefined && parsed.markdownBody.trim().length === 0) {
return {
ok: false,
issues: [
issue("empty_claw_body", manifestPath, "CLAW.md must contain a non-empty Markdown body."),
],
};
}
if ("issues" in parsed) return { ok: false, issues: parsed.issues };
const validated = validateClawManifest(parsed.value);
if (!validated.ok) {
return {
Expand All @@ -531,23 +520,23 @@ export function validateClawPackageContents(input: {
),
};
}
if (
parsed.markdownBody !== undefined &&
(validated.manifest.workspace?.bootstrapFiles?.["SOUL.md"] !== undefined ||
(validated.manifest.workspace?.files ?? []).some(
(entry) => portablePathKey(entry.path) === portablePathKey("SOUL.md"),
))
) {
return {
ok: false,
issues: [
issue(
"duplicate_claw_body_destination",
"SOUL.md",
"The CLAW.md body is the managed SOUL.md source and cannot be combined with an explicit SOUL.md declaration.",
),
],
};
const hasClawMarkdownBody = (parsed.clawMarkdownBody?.trim().length ?? 0) > 0;
const implicitSoulPath = "SOUL.md";
const workspaceTargets = [
...Object.keys(validated.manifest.workspace?.bootstrapFiles ?? {}),
...(validated.manifest.workspace?.files ?? []).map((entry) => entry.path),
];
const hasImplicitSoulConflict =
workspaceTargets.some((path) => portablePathKey(path) === portablePathKey(implicitSoulPath)) ||
findClawPackagePathHierarchyCollision([implicitSoulPath, ...workspaceTargets]) !== null;
if (hasClawMarkdownBody && hasImplicitSoulConflict) {
issues.push(
issue(
"claw_body_soul_conflict",
"$.workspace",
"CLAW.md body content and an explicit SOUL.md workspace declaration cannot both be present.",
),
);
}

const openClawProfilePath = validated.manifest.metadata?.["openclaw.config"];
Expand Down Expand Up @@ -597,26 +586,13 @@ export function validateClawPackageContents(input: {
}
}
if (issues.length > 0) return { ok: false, issues };
const summary = summarizeClawManifest(validated.manifest);
const implicitWorkspaceFile =
parsed.markdownBody === undefined
? undefined
: { path: "SOUL.md" as const, text: parsed.markdownBody };
return {
ok: true,
value: {
manifestPath,
manifest: validated.manifest,
...(implicitWorkspaceFile ? { implicitWorkspaceFile } : {}),
summary: implicitWorkspaceFile
? {
...summary,
workspace: {
...summary.workspace,
bootstrapFiles: [...summary.workspace.bootstrapFiles, "SOUL.md"],
},
}
: summary,
summary: summarizeClawManifest(validated.manifest, { clawMarkdownBody: hasClawMarkdownBody }),
hasClawMarkdownBody,
},
};
}
9 changes: 7 additions & 2 deletions packages/clawhub/src/schema/claws.ts
Original file line number Diff line number Diff line change
Expand Up @@ -874,7 +874,10 @@ export function validateClawManifest(
return issues.length > 0 ? { ok: false, issues } : { ok: true, manifest: parsed };
}

export function summarizeClawManifest(manifest: ClawManifest): ClawManifestSummary {
export function summarizeClawManifest(
manifest: ClawManifest,
options: { clawMarkdownBody?: boolean } = {},
): ClawManifestSummary {
const packages = manifest.packages ?? [];
const agentName = truncateSummaryText(manifest.agent.name, CLAW_SUMMARY_AGENT_NAME_MAX_CHARS);
const agentDescription = truncateSummaryText(
Expand All @@ -890,7 +893,9 @@ export function summarizeClawManifest(manifest: ClawManifest): ClawManifestSumma
},
workspace: {
bootstrapFiles: CLAW_BOOTSTRAP_FILE_NAMES.filter(
(name) => manifest.workspace?.bootstrapFiles?.[name] !== undefined,
(name) =>
manifest.workspace?.bootstrapFiles?.[name] !== undefined ||
(name === "SOUL.md" && options.clawMarkdownBody === true),
),
fileCount: manifest.workspace?.files?.length ?? 0,
},
Expand Down
5 changes: 1 addition & 4 deletions packages/schema/dist/clawPackage.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,8 @@ export type ClawPackageValidationIssue = {
export type ValidatedClawPackage = {
manifestPath: string;
manifest: ClawManifest;
implicitWorkspaceFile?: {
path: "SOUL.md";
text: string;
};
summary: ClawManifestSummary;
hasClawMarkdownBody: boolean;
};
export declare function isSafeClawPackagePath(value: string): boolean;
export declare function findClawPackagePathHierarchyCollision(paths: readonly string[]): {
Expand Down
52 changes: 14 additions & 38 deletions packages/schema/dist/clawPackage.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion packages/schema/dist/clawPackage.js.map

Large diffs are not rendered by default.

4 changes: 3 additions & 1 deletion packages/schema/dist/claws.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -174,4 +174,6 @@ export declare function validateClawManifest(value: unknown): {
ok: false;
issues: ClawManifestValidationIssue[];
};
export declare function summarizeClawManifest(manifest: ClawManifest): ClawManifestSummary;
export declare function summarizeClawManifest(manifest: ClawManifest, options?: {
clawMarkdownBody?: boolean;
}): ClawManifestSummary;
5 changes: 3 additions & 2 deletions packages/schema/dist/claws.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion packages/schema/dist/claws.js.map

Large diffs are not rendered by default.

Loading
Loading