style: format agent workspace and prompts
This commit is contained in:
@@ -140,6 +140,9 @@ workspace lives).
|
|||||||
|
|
||||||
### 1) Initialize the repo
|
### 1) Initialize the repo
|
||||||
|
|
||||||
|
If git is installed, brand-new workspaces are initialized automatically. If this
|
||||||
|
workspace is not already a repo, run:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd ~/clawd
|
cd ~/clawd
|
||||||
git init
|
git init
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ Clawd reads operating instructions and “memory” from its workspace directory
|
|||||||
|
|
||||||
By default, Clawdbot uses `~/clawd` as the agent workspace, and will create it (plus starter `AGENTS.md`, `SOUL.md`, `TOOLS.md`, `IDENTITY.md`, `USER.md`) automatically on setup/first agent run. `BOOTSTRAP.md` is only created when the workspace is brand new (it should not come back after you delete it).
|
By default, Clawdbot uses `~/clawd` as the agent workspace, and will create it (plus starter `AGENTS.md`, `SOUL.md`, `TOOLS.md`, `IDENTITY.md`, `USER.md`) automatically on setup/first agent run. `BOOTSTRAP.md` is only created when the workspace is brand new (it should not come back after you delete it).
|
||||||
|
|
||||||
Tip: treat this folder like Clawd’s “memory” and make it a git repo (ideally private) so your `AGENTS.md` + memory files are backed up.
|
Tip: treat this folder like Clawd’s “memory” and make it a git repo (ideally private) so your `AGENTS.md` + memory files are backed up. If git is installed, brand-new workspaces are auto-initialized.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
clawdbot setup
|
clawdbot setup
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ import {
|
|||||||
loadWorkspaceSkillEntries,
|
loadWorkspaceSkillEntries,
|
||||||
resolveSkillsPromptForRun,
|
resolveSkillsPromptForRun,
|
||||||
} from "../../skills.js";
|
} from "../../skills.js";
|
||||||
|
import { DEFAULT_BOOTSTRAP_FILENAME } from "../../workspace.js";
|
||||||
import { buildSystemPromptReport } from "../../system-prompt-report.js";
|
import { buildSystemPromptReport } from "../../system-prompt-report.js";
|
||||||
import { resolveDefaultModelForAgent } from "../../model-selection.js";
|
import { resolveDefaultModelForAgent } from "../../model-selection.js";
|
||||||
|
|
||||||
@@ -184,6 +185,11 @@ export async function runEmbeddedAttempt(
|
|||||||
sessionId: params.sessionId,
|
sessionId: params.sessionId,
|
||||||
warn: makeBootstrapWarn({ sessionLabel, warn: (message) => log.warn(message) }),
|
warn: makeBootstrapWarn({ sessionLabel, warn: (message) => log.warn(message) }),
|
||||||
});
|
});
|
||||||
|
const workspaceNotes = hookAdjustedBootstrapFiles.some(
|
||||||
|
(file) => file.name === DEFAULT_BOOTSTRAP_FILENAME && !file.missing,
|
||||||
|
)
|
||||||
|
? ["Reminder: commit your changes in this workspace after edits."]
|
||||||
|
: undefined;
|
||||||
|
|
||||||
const agentDir = params.agentDir ?? resolveClawdbotAgentDir();
|
const agentDir = params.agentDir ?? resolveClawdbotAgentDir();
|
||||||
|
|
||||||
@@ -314,6 +320,7 @@ export async function runEmbeddedAttempt(
|
|||||||
: undefined,
|
: undefined,
|
||||||
skillsPrompt,
|
skillsPrompt,
|
||||||
docsPath: docsPath ?? undefined,
|
docsPath: docsPath ?? undefined,
|
||||||
|
workspaceNotes,
|
||||||
reactionGuidance,
|
reactionGuidance,
|
||||||
promptMode,
|
promptMode,
|
||||||
runtimeInfo,
|
runtimeInfo,
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ export function buildEmbeddedSystemPrompt(params: {
|
|||||||
level: "minimal" | "extensive";
|
level: "minimal" | "extensive";
|
||||||
channel: string;
|
channel: string;
|
||||||
};
|
};
|
||||||
|
workspaceNotes?: string[];
|
||||||
/** Controls which hardcoded sections to include. Defaults to "full". */
|
/** Controls which hardcoded sections to include. Defaults to "full". */
|
||||||
promptMode?: PromptMode;
|
promptMode?: PromptMode;
|
||||||
runtimeInfo: {
|
runtimeInfo: {
|
||||||
@@ -54,6 +55,7 @@ export function buildEmbeddedSystemPrompt(params: {
|
|||||||
heartbeatPrompt: params.heartbeatPrompt,
|
heartbeatPrompt: params.heartbeatPrompt,
|
||||||
skillsPrompt: params.skillsPrompt,
|
skillsPrompt: params.skillsPrompt,
|
||||||
docsPath: params.docsPath,
|
docsPath: params.docsPath,
|
||||||
|
workspaceNotes: params.workspaceNotes,
|
||||||
reactionGuidance: params.reactionGuidance,
|
reactionGuidance: params.reactionGuidance,
|
||||||
promptMode: params.promptMode,
|
promptMode: params.promptMode,
|
||||||
runtimeInfo: params.runtimeInfo,
|
runtimeInfo: params.runtimeInfo,
|
||||||
|
|||||||
@@ -115,6 +115,15 @@ describe("buildAgentSystemPrompt", () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("includes workspace notes when provided", () => {
|
||||||
|
const prompt = buildAgentSystemPrompt({
|
||||||
|
workspaceDir: "/tmp/clawd",
|
||||||
|
workspaceNotes: ["Reminder: commit your changes in this workspace after edits."],
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(prompt).toContain("Reminder: commit your changes in this workspace after edits.");
|
||||||
|
});
|
||||||
|
|
||||||
it("includes user time when provided (12-hour)", () => {
|
it("includes user time when provided (12-hour)", () => {
|
||||||
const prompt = buildAgentSystemPrompt({
|
const prompt = buildAgentSystemPrompt({
|
||||||
workspaceDir: "/tmp/clawd",
|
workspaceDir: "/tmp/clawd",
|
||||||
|
|||||||
@@ -148,6 +148,7 @@ export function buildAgentSystemPrompt(params: {
|
|||||||
skillsPrompt?: string;
|
skillsPrompt?: string;
|
||||||
heartbeatPrompt?: string;
|
heartbeatPrompt?: string;
|
||||||
docsPath?: string;
|
docsPath?: string;
|
||||||
|
workspaceNotes?: string[];
|
||||||
/** Controls which hardcoded sections to include. Defaults to "full". */
|
/** Controls which hardcoded sections to include. Defaults to "full". */
|
||||||
promptMode?: PromptMode;
|
promptMode?: PromptMode;
|
||||||
runtimeInfo?: {
|
runtimeInfo?: {
|
||||||
@@ -327,6 +328,7 @@ export function buildAgentSystemPrompt(params: {
|
|||||||
isMinimal,
|
isMinimal,
|
||||||
readToolName,
|
readToolName,
|
||||||
});
|
});
|
||||||
|
const workspaceNotes = (params.workspaceNotes ?? []).map((note) => note.trim()).filter(Boolean);
|
||||||
|
|
||||||
// For "none" mode, return just the basic identity line
|
// For "none" mode, return just the basic identity line
|
||||||
if (promptMode === "none") {
|
if (promptMode === "none") {
|
||||||
@@ -403,6 +405,7 @@ export function buildAgentSystemPrompt(params: {
|
|||||||
"## Workspace",
|
"## Workspace",
|
||||||
`Your working directory is: ${params.workspaceDir}`,
|
`Your working directory is: ${params.workspaceDir}`,
|
||||||
"Treat this directory as the single global workspace for file operations unless explicitly instructed otherwise.",
|
"Treat this directory as the single global workspace for file operations unless explicitly instructed otherwise.",
|
||||||
|
...workspaceNotes,
|
||||||
"",
|
"",
|
||||||
...docsSection,
|
...docsSection,
|
||||||
params.sandboxInfo?.enabled ? "## Sandbox" : "",
|
params.sandboxInfo?.enabled ? "## Sandbox" : "",
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import fs from "node:fs/promises";
|
|||||||
import os from "node:os";
|
import os from "node:os";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import { describe, expect, it } from "vitest";
|
import { describe, expect, it } from "vitest";
|
||||||
|
import { runCommandWithTimeout } from "../process/exec.js";
|
||||||
import type { WorkspaceBootstrapFile } from "./workspace.js";
|
import type { WorkspaceBootstrapFile } from "./workspace.js";
|
||||||
import {
|
import {
|
||||||
DEFAULT_AGENTS_FILENAME,
|
DEFAULT_AGENTS_FILENAME,
|
||||||
@@ -40,6 +41,34 @@ describe("ensureAgentWorkspace", () => {
|
|||||||
await expect(fs.stat(bootstrap)).resolves.toBeDefined();
|
await expect(fs.stat(bootstrap)).resolves.toBeDefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("initializes a git repo for brand-new workspaces when git is available", async () => {
|
||||||
|
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdbot-ws-"));
|
||||||
|
const nested = path.join(dir, "nested");
|
||||||
|
const gitAvailable = await runCommandWithTimeout(["git", "--version"], { timeoutMs: 2_000 })
|
||||||
|
.then((res) => res.code === 0)
|
||||||
|
.catch(() => false);
|
||||||
|
if (!gitAvailable) return;
|
||||||
|
|
||||||
|
await ensureAgentWorkspace({
|
||||||
|
dir: nested,
|
||||||
|
ensureBootstrapFiles: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
await expect(fs.stat(path.join(nested, ".git"))).resolves.toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not initialize git when workspace already exists", async () => {
|
||||||
|
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdbot-ws-"));
|
||||||
|
await fs.writeFile(path.join(dir, "AGENTS.md"), "custom", "utf-8");
|
||||||
|
|
||||||
|
await ensureAgentWorkspace({
|
||||||
|
dir,
|
||||||
|
ensureBootstrapFiles: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
await expect(fs.stat(path.join(dir, ".git"))).rejects.toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
it("does not overwrite existing AGENTS.md", async () => {
|
it("does not overwrite existing AGENTS.md", async () => {
|
||||||
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdbot-ws-"));
|
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdbot-ws-"));
|
||||||
const agentsPath = path.join(dir, "AGENTS.md");
|
const agentsPath = path.join(dir, "AGENTS.md");
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import path from "node:path";
|
|||||||
import { fileURLToPath } from "node:url";
|
import { fileURLToPath } from "node:url";
|
||||||
|
|
||||||
import { isSubagentSessionKey } from "../routing/session-key.js";
|
import { isSubagentSessionKey } from "../routing/session-key.js";
|
||||||
|
import { runCommandWithTimeout } from "../process/exec.js";
|
||||||
import { resolveUserPath } from "../utils.js";
|
import { resolveUserPath } from "../utils.js";
|
||||||
|
|
||||||
export function resolveDefaultAgentWorkspaceDir(
|
export function resolveDefaultAgentWorkspaceDir(
|
||||||
@@ -81,6 +82,35 @@ async function writeFileIfMissing(filePath: string, content: string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function hasGitRepo(dir: string): Promise<boolean> {
|
||||||
|
try {
|
||||||
|
await fs.stat(path.join(dir, ".git"));
|
||||||
|
return true;
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function isGitAvailable(): Promise<boolean> {
|
||||||
|
try {
|
||||||
|
const result = await runCommandWithTimeout(["git", "--version"], { timeoutMs: 2_000 });
|
||||||
|
return result.code === 0;
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function ensureGitRepo(dir: string, isBrandNewWorkspace: boolean) {
|
||||||
|
if (!isBrandNewWorkspace) return;
|
||||||
|
if (await hasGitRepo(dir)) return;
|
||||||
|
if (!(await isGitAvailable())) return;
|
||||||
|
try {
|
||||||
|
await runCommandWithTimeout(["git", "init"], { cwd: dir, timeoutMs: 10_000 });
|
||||||
|
} catch {
|
||||||
|
// Ignore git init failures; workspace creation should still succeed.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export async function ensureAgentWorkspace(params?: {
|
export async function ensureAgentWorkspace(params?: {
|
||||||
dir?: string;
|
dir?: string;
|
||||||
ensureBootstrapFiles?: boolean;
|
ensureBootstrapFiles?: boolean;
|
||||||
@@ -140,6 +170,7 @@ export async function ensureAgentWorkspace(params?: {
|
|||||||
if (isBrandNewWorkspace) {
|
if (isBrandNewWorkspace) {
|
||||||
await writeFileIfMissing(bootstrapPath, bootstrapTemplate);
|
await writeFileIfMissing(bootstrapPath, bootstrapTemplate);
|
||||||
}
|
}
|
||||||
|
await ensureGitRepo(dir, isBrandNewWorkspace);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
dir,
|
dir,
|
||||||
|
|||||||
Reference in New Issue
Block a user