Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
0227ad7
fix(cli): use provider-neutral onboarding copy
yimoj Aug 12, 2026
8637936
test(cli): exercise resumed provider copy
yimoj Aug 12, 2026
84b8d56
Merge branch 'main' into fix/8853-onboarding-workspace-copy
cv Aug 12, 2026
a19e816
merge: refresh PR branch from main
apurvvkumaria Aug 12, 2026
c821cfb
Merge branch 'main' into fix/8853-onboarding-workspace-copy
cv Aug 12, 2026
bd4f0c5
Merge branch 'main' into fix/8853-onboarding-workspace-copy
cv Aug 12, 2026
c7b0094
docs(workspace): list seeded OpenClaw files
apurvvkumaria Aug 12, 2026
1011969
test(docs): enforce workspace inventory split
apurvvkumaria Aug 12, 2026
2619dfe
Merge branch 'main' into fix/8853-onboarding-workspace-copy
prekshivyas Aug 13, 2026
b0edfbc
docs(workspaces): correct named workspace seeding
apurvvkumaria Aug 13, 2026
1cf46d3
Merge remote-tracking branch 'origin/main' into fix/8853-onboarding-w…
prekshivyas Aug 13, 2026
95d866d
Merge branch 'main' into fix/8853-onboarding-workspace-copy
prekshivyas Aug 13, 2026
af22c7d
fix(ci): restore static lint baseline
prekshivyas Aug 13, 2026
968a875
fix(ci): track managed image test size
prekshivyas Aug 13, 2026
3365b6e
test(ci): split managed image retention coverage
prekshivyas Aug 13, 2026
0feda92
test(ci): extract managed image workflow types
prekshivyas Aug 13, 2026
690b8ff
test(ci): allow loaded shields setup
prekshivyas Aug 13, 2026
bba5781
fix(ci): refresh libssh2 staging source
prekshivyas Aug 13, 2026
3df3d36
merge: resolve conflicts with main
github-actions[bot] Aug 13, 2026
3aafc7f
merge: resolve conflicts with main
github-actions[bot] Aug 13, 2026
aa10692
chore: remove unrelated CI changes
cv Aug 13, 2026
4f353eb
chore: finish PR scope cleanup
cv Aug 13, 2026
d469718
fix(ci): refresh libssh2 staging source
cv Aug 13, 2026
cdd1cab
docs(workspace): define template seeding condition
cv Aug 13, 2026
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: 5 additions & 0 deletions ci/source-shape-test-budget.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,11 @@
"test": "matches the bundled local-inference host-gateway ports (#5744)",
"category": "compatibility"
},
{
"file": "test/agent-variant-docs.test.ts",
"test": "keeps the OpenClaw workspace inventory aligned with seeded runtime templates",
"category": "compatibility"
},
{
"file": "test/code-change-considerations.test.ts",
"test": "keeps the stage-neutral questions in one concise owner",
Expand Down
15 changes: 10 additions & 5 deletions docs/manage-sandboxes/workspace-files.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,10 @@ These files live at `/sandbox/.openclaw/workspace/` and are collectively called
| `USER.md` | Stores information about the human the agent assists. |
| `IDENTITY.md` | Short identity card with name, language, emoji, and creature type. |
| `AGENTS.md` | Behavioral rules, memory conventions, safety guidelines, and session workflow. |
| `MEMORY.md` | Curated long-term memory distilled from daily notes. |
| `memory/` | Directory of daily note files (`YYYY-MM-DD.md`) for session continuity. |
| `TOOLS.md` | Records workspace-specific tool guidance and operational notes. |
| `HEARTBEAT.md` | Defines recurring heartbeat checks when heartbeat processing is enabled. |
| `MEMORY.md` | Curated long-term memory distilled from daily notes. OpenClaw creates this file when it first stores long-term memory. |
| `memory/` | Directory of daily note files (`YYYY-MM-DD.md`) for session continuity. OpenClaw creates this directory when it first stores a daily note. |

## Where They Live

Expand All @@ -93,11 +95,13 @@ All workspace files reside inside the sandbox filesystem:
```text
/sandbox/.openclaw/workspace/
├── AGENTS.md
├── HEARTBEAT.md
├── IDENTITY.md
├── MEMORY.md
├── MEMORY.md # created on first long-term memory use
├── SOUL.md
├── TOOLS.md
├── USER.md
└── memory/
└── memory/ # created on first daily memory use
├── 2026-03-18.md
└── 2026-03-19.md
```
Expand All @@ -123,7 +127,8 @@ For example, a Teams-integrated deployment can use a shared `main` agent plus pe
└── workspace-ops/ # named agent "ops"
```

Each per-agent workspace contains the same Markdown file structure as the default workspace: `SOUL.md`, `USER.md`, `IDENTITY.md`, `AGENTS.md`, `MEMORY.md`, and `memory/`.
Each per-agent workspace uses the same seeded Markdown file structure as the default workspace: `AGENTS.md`, `SOUL.md`, `IDENTITY.md`, `USER.md`, `TOOLS.md`, and `HEARTBEAT.md`.
OpenClaw creates `MEMORY.md` and `memory/` separately in each workspace when that agent first uses long-term or daily memory.
Files are per-agent.
Changes in `workspace-main/AGENTS.md` are not visible to `workspace-support/`.

Expand Down
2 changes: 1 addition & 1 deletion src/lib/onboard/machine/definition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export const ONBOARD_MACHINE_STATE_DEFINITIONS = [
state: "provider_selection",
terminal: false,
stepName: "provider_selection",
progress: { number: 3, total: 8, title: "Configuring inference (NIM)" },
progress: { number: 3, total: 8, title: "Configuring inference provider" },
},
{
state: "inference",
Expand Down
20 changes: 18 additions & 2 deletions src/lib/onboard/machine/progress.test.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

import { describe, expect, it } from "vitest";
import { describe, expect, it, vi } from "vitest";

import { step } from "../prompt-helpers";
import { skippedStepMessage } from "../skipped-step-message";
import { ONBOARD_MACHINE_STATE_DEFINITIONS } from "./definition";
import { getOnboardProgressStep, ONBOARD_PROGRESS_STEPS } from "./progress";

vi.mock("../prompt-helpers", () => ({ step: vi.fn() }));

describe("onboard progress metadata", () => {
it("derives state-backed progress labels from machine definitions", () => {
for (const definition of ONBOARD_MACHINE_STATE_DEFINITIONS) {
Expand All @@ -18,7 +22,7 @@ describe("onboard progress metadata", () => {
expect(ONBOARD_PROGRESS_STEPS).toEqual({
preflight: { number: 1, total: 8, title: "Preflight checks" },
gateway: { number: 2, total: 8, title: "Starting OpenShell gateway" },
provider_selection: { number: 3, total: 8, title: "Configuring inference (NIM)" },
provider_selection: { number: 3, total: 8, title: "Configuring inference provider" },
inference: { number: 4, total: 8, title: "Setting up inference provider" },
messaging: { number: 5, total: 8, title: "Messaging channels" },
sandbox: { number: 6, total: 8, title: "Creating sandbox" },
Expand All @@ -35,4 +39,16 @@ describe("onboard progress metadata", () => {
});
expect(getOnboardProgressStep("not-a-step")).toBeNull();
});

it("renders provider-neutral copy when resume skips Ollama selection (#8853)", () => {
const log = vi.spyOn(console, "log").mockImplementation(() => {});

skippedStepMessage("provider_selection", "ollama-local / qwen3.5:9b");

expect(step).toHaveBeenCalledWith(3, 8, "Configuring inference provider");
expect(step).not.toHaveBeenCalledWith(3, 8, expect.stringContaining("NIM"));
expect(log).toHaveBeenCalledWith(
" [resume] Skipping provider_selection (ollama-local / qwen3.5:9b)",
);
});
});
85 changes: 85 additions & 0 deletions test/agent-variant-docs.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,91 @@ Use \`$$nemoclaw\` for the current variant.
`;

describe("agent variant docs", () => {
// source-shape-contract: compatibility -- OpenClaw workspace documentation must list every runtime-seeded template so generated variants cannot omit operator-visible files.
it("keeps the OpenClaw workspace inventory aligned with seeded runtime templates", () => {
const workspaceFiles = readFileSync(
new URL("../docs/manage-sandboxes/workspace-files.mdx", import.meta.url),
"utf8",
);
const startScript = readFileSync(
new URL("../scripts/nemoclaw-start.sh", import.meta.url),
"utf8",
);
const seedFunction = startScript.slice(
startScript.indexOf("seed_default_workspace_templates()"),
);
const seededFiles = seedFunction
.match(/for file in ([^;]+); do/)?.[1]
.trim()
.split(/\s+/);

expect(seededFiles).toEqual([
"AGENTS.md",
"SOUL.md",
"IDENTITY.md",
"USER.md",
"TOOLS.md",
"HEARTBEAT.md",
]);
const expectedSeededFiles = seededFiles ?? [];
const deferredFiles = ["MEMORY.md", "memory/"];

const rendered = renderAgentVariantPage(workspaceFiles, "openclaw", {
sourcePath: "/repo/docs/manage-sandboxes/workspace-files.mdx",
});
const fileReference = rendered.match(
/## File Reference\n\n([\s\S]*?)\n\n## Where They Live/,
)?.[1];
const tableFiles = Array.from(
fileReference?.matchAll(/^\| `([^`]+)` \|/gm) ?? [],
(match) => match[1],
);
expect(tableFiles.filter((file) => !deferredFiles.includes(file)).sort()).toEqual(
[...expectedSeededFiles].sort(),
);
expect(tableFiles.filter((file) => deferredFiles.includes(file)).sort()).toEqual(
[...deferredFiles].sort(),
);

const workspaceTree = rendered.match(
/```text\n\/sandbox\/\.openclaw\/workspace\/\n([\s\S]*?)\n```/,
)?.[1];
const treeFiles = Array.from(
workspaceTree?.matchAll(/^[├└]── ([^\s]+)/gm) ?? [],
(match) => match[1],
);
for (const file of expectedSeededFiles) {
expect(tableFiles).toContain(file);
expect(treeFiles).toContain(file);
}
expect(treeFiles.filter((file) => !deferredFiles.includes(file)).sort()).toEqual(
[...expectedSeededFiles].sort(),
);
expect(treeFiles.filter((file) => deferredFiles.includes(file)).sort()).toEqual(
[...deferredFiles].sort(),
);

const multiAgentSection = rendered.match(
/## Multi-Agent Deployments\n\n([\s\S]*?)\n\n## Persistence Behavior/,
)?.[1];
const seededSummary = multiAgentSection?.match(
/same seeded Markdown file structure[^:]*: ([^\n]+)\n/,
)?.[1];
const deferredSummary = multiAgentSection?.match(
/OpenClaw creates ([^\n]+) separately in each workspace/,
)?.[1];
const summarySeededFiles = Array.from(
seededSummary?.matchAll(/`([^`]+)`/g) ?? [],
(match) => match[1],
);
const summaryDeferredFiles = Array.from(
deferredSummary?.matchAll(/`([^`]+)`/g) ?? [],
(match) => match[1],
);
expect(summarySeededFiles.sort()).toEqual([...expectedSeededFiles].sort());
expect(summaryDeferredFiles.sort()).toEqual([...deferredFiles].sort());
});

it("renders OpenClaw placeholder code and content", () => {
const rendered = renderAgentVariantPage(source, "openclaw");

Expand Down
Loading