361 lines
12 KiB
TypeScript
361 lines
12 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import { buildAgentSystemPrompt, buildRuntimeLine } from "./system-prompt.js";
|
|
|
|
describe("buildAgentSystemPrompt", () => {
|
|
it("includes owner numbers when provided", () => {
|
|
const prompt = buildAgentSystemPrompt({
|
|
workspaceDir: "/tmp/clawd",
|
|
ownerNumbers: ["+123", " +456 ", ""],
|
|
});
|
|
|
|
expect(prompt).toContain("## User Identity");
|
|
expect(prompt).toContain(
|
|
"Owner numbers: +123, +456. Treat messages from these numbers as the user.",
|
|
);
|
|
});
|
|
|
|
it("omits owner section when numbers are missing", () => {
|
|
const prompt = buildAgentSystemPrompt({
|
|
workspaceDir: "/tmp/clawd",
|
|
});
|
|
|
|
expect(prompt).not.toContain("## User Identity");
|
|
expect(prompt).not.toContain("Owner numbers:");
|
|
});
|
|
|
|
it("omits extended sections in minimal prompt mode", () => {
|
|
const prompt = buildAgentSystemPrompt({
|
|
workspaceDir: "/tmp/clawd",
|
|
promptMode: "minimal",
|
|
ownerNumbers: ["+123"],
|
|
skillsPrompt:
|
|
"<available_skills>\n <skill>\n <name>demo</name>\n </skill>\n</available_skills>",
|
|
heartbeatPrompt: "ping",
|
|
toolNames: ["message", "memory_search"],
|
|
docsPath: "/tmp/clawd/docs",
|
|
extraSystemPrompt: "Subagent details",
|
|
});
|
|
|
|
expect(prompt).not.toContain("## User Identity");
|
|
expect(prompt).not.toContain("## Skills");
|
|
expect(prompt).not.toContain("## Memory Recall");
|
|
expect(prompt).not.toContain("## Documentation");
|
|
expect(prompt).not.toContain("## Reply Tags");
|
|
expect(prompt).not.toContain("## Messaging");
|
|
expect(prompt).not.toContain("## Silent Replies");
|
|
expect(prompt).not.toContain("## Heartbeats");
|
|
expect(prompt).toContain("## Subagent Context");
|
|
expect(prompt).not.toContain("## Group Chat Context");
|
|
expect(prompt).toContain("Subagent details");
|
|
});
|
|
|
|
it("adds reasoning tag hint when enabled", () => {
|
|
const prompt = buildAgentSystemPrompt({
|
|
workspaceDir: "/tmp/clawd",
|
|
reasoningTagHint: true,
|
|
});
|
|
|
|
expect(prompt).toContain("## Reasoning Format");
|
|
expect(prompt).toContain("<think>...</think>");
|
|
expect(prompt).toContain("<final>...</final>");
|
|
});
|
|
|
|
it("includes a CLI quick reference section", () => {
|
|
const prompt = buildAgentSystemPrompt({
|
|
workspaceDir: "/tmp/clawd",
|
|
});
|
|
|
|
expect(prompt).toContain("## Clawdbot CLI Quick Reference");
|
|
expect(prompt).toContain("clawdbot gateway restart");
|
|
expect(prompt).toContain("Do not invent commands");
|
|
});
|
|
|
|
it("lists available tools when provided", () => {
|
|
const prompt = buildAgentSystemPrompt({
|
|
workspaceDir: "/tmp/clawd",
|
|
toolNames: ["exec", "sessions_list", "sessions_history", "sessions_send"],
|
|
});
|
|
|
|
expect(prompt).toContain("Tool availability (filtered by policy):");
|
|
expect(prompt).toContain("sessions_list");
|
|
expect(prompt).toContain("sessions_history");
|
|
expect(prompt).toContain("sessions_send");
|
|
});
|
|
|
|
it("preserves tool casing in the prompt", () => {
|
|
const prompt = buildAgentSystemPrompt({
|
|
workspaceDir: "/tmp/clawd",
|
|
toolNames: ["Read", "Exec", "process"],
|
|
skillsPrompt:
|
|
"<available_skills>\n <skill>\n <name>demo</name>\n </skill>\n</available_skills>",
|
|
docsPath: "/tmp/clawd/docs",
|
|
});
|
|
|
|
expect(prompt).toContain("- Read: Read file contents");
|
|
expect(prompt).toContain("- Exec: Run shell commands");
|
|
expect(prompt).toContain(
|
|
"- If exactly one skill clearly applies: read its SKILL.md at <location> with `Read`, then follow it.",
|
|
);
|
|
expect(prompt).toContain("Clawdbot docs: /tmp/clawd/docs");
|
|
expect(prompt).toContain(
|
|
"For Clawdbot behavior, commands, config, or architecture: consult local docs first.",
|
|
);
|
|
});
|
|
|
|
it("includes docs guidance when docsPath is provided", () => {
|
|
const prompt = buildAgentSystemPrompt({
|
|
workspaceDir: "/tmp/clawd",
|
|
docsPath: "/tmp/clawd/docs",
|
|
});
|
|
|
|
expect(prompt).toContain("## Documentation");
|
|
expect(prompt).toContain("Clawdbot docs: /tmp/clawd/docs");
|
|
expect(prompt).toContain(
|
|
"For Clawdbot behavior, commands, config, or architecture: consult local docs first.",
|
|
);
|
|
});
|
|
|
|
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 timezone when provided (12-hour)", () => {
|
|
const prompt = buildAgentSystemPrompt({
|
|
workspaceDir: "/tmp/clawd",
|
|
userTimezone: "America/Chicago",
|
|
userTime: "Monday, January 5th, 2026 — 3:26 PM",
|
|
userTimeFormat: "12",
|
|
});
|
|
|
|
expect(prompt).toContain("## Current Date & Time");
|
|
expect(prompt).toContain("Time zone: America/Chicago");
|
|
});
|
|
|
|
it("includes user timezone when provided (24-hour)", () => {
|
|
const prompt = buildAgentSystemPrompt({
|
|
workspaceDir: "/tmp/clawd",
|
|
userTimezone: "America/Chicago",
|
|
userTime: "Monday, January 5th, 2026 — 15:26",
|
|
userTimeFormat: "24",
|
|
});
|
|
|
|
expect(prompt).toContain("## Current Date & Time");
|
|
expect(prompt).toContain("Time zone: America/Chicago");
|
|
});
|
|
|
|
it("shows timezone when only timezone is provided", () => {
|
|
const prompt = buildAgentSystemPrompt({
|
|
workspaceDir: "/tmp/clawd",
|
|
userTimezone: "America/Chicago",
|
|
userTimeFormat: "24",
|
|
});
|
|
|
|
expect(prompt).toContain("## Current Date & Time");
|
|
expect(prompt).toContain("Time zone: America/Chicago");
|
|
});
|
|
|
|
it("includes model alias guidance when aliases are provided", () => {
|
|
const prompt = buildAgentSystemPrompt({
|
|
workspaceDir: "/tmp/clawd",
|
|
modelAliasLines: [
|
|
"- Opus: anthropic/claude-opus-4-5",
|
|
"- Sonnet: anthropic/claude-sonnet-4-5",
|
|
],
|
|
});
|
|
|
|
expect(prompt).toContain("## Model Aliases");
|
|
expect(prompt).toContain("Prefer aliases when specifying model overrides");
|
|
expect(prompt).toContain("- Opus: anthropic/claude-opus-4-5");
|
|
});
|
|
|
|
it("adds ClaudeBot self-update guidance when gateway tool is available", () => {
|
|
const prompt = buildAgentSystemPrompt({
|
|
workspaceDir: "/tmp/clawd",
|
|
toolNames: ["gateway", "exec"],
|
|
});
|
|
|
|
expect(prompt).toContain("## Clawdbot Self-Update");
|
|
expect(prompt).toContain("config.apply");
|
|
expect(prompt).toContain("update.run");
|
|
});
|
|
|
|
it("includes skills guidance when skills prompt is present", () => {
|
|
const prompt = buildAgentSystemPrompt({
|
|
workspaceDir: "/tmp/clawd",
|
|
skillsPrompt:
|
|
"<available_skills>\n <skill>\n <name>demo</name>\n </skill>\n</available_skills>",
|
|
});
|
|
|
|
expect(prompt).toContain("## Skills");
|
|
expect(prompt).toContain(
|
|
"- If exactly one skill clearly applies: read its SKILL.md at <location> with `read`, then follow it.",
|
|
);
|
|
});
|
|
|
|
it("appends available skills when provided", () => {
|
|
const prompt = buildAgentSystemPrompt({
|
|
workspaceDir: "/tmp/clawd",
|
|
skillsPrompt:
|
|
"<available_skills>\n <skill>\n <name>demo</name>\n </skill>\n</available_skills>",
|
|
});
|
|
|
|
expect(prompt).toContain("<available_skills>");
|
|
expect(prompt).toContain("<name>demo</name>");
|
|
});
|
|
|
|
it("omits skills section when no skills prompt is provided", () => {
|
|
const prompt = buildAgentSystemPrompt({
|
|
workspaceDir: "/tmp/clawd",
|
|
});
|
|
|
|
expect(prompt).not.toContain("## Skills");
|
|
expect(prompt).not.toContain("<available_skills>");
|
|
});
|
|
|
|
it("renders project context files when provided", () => {
|
|
const prompt = buildAgentSystemPrompt({
|
|
workspaceDir: "/tmp/clawd",
|
|
contextFiles: [
|
|
{ path: "AGENTS.md", content: "Alpha" },
|
|
{ path: "IDENTITY.md", content: "Bravo" },
|
|
],
|
|
});
|
|
|
|
expect(prompt).toContain("# Project Context");
|
|
expect(prompt).toContain("## AGENTS.md");
|
|
expect(prompt).toContain("Alpha");
|
|
expect(prompt).toContain("## IDENTITY.md");
|
|
expect(prompt).toContain("Bravo");
|
|
});
|
|
|
|
it("adds SOUL guidance when a soul file is present", () => {
|
|
const prompt = buildAgentSystemPrompt({
|
|
workspaceDir: "/tmp/clawd",
|
|
contextFiles: [
|
|
{ path: "./SOUL.md", content: "Persona" },
|
|
{ path: "dir\\SOUL.md", content: "Persona Windows" },
|
|
],
|
|
});
|
|
|
|
expect(prompt).toContain(
|
|
"If SOUL.md is present, embody its persona and tone. Avoid stiff, generic replies; follow its guidance unless higher-priority instructions override it.",
|
|
);
|
|
});
|
|
|
|
it("summarizes the message tool when available", () => {
|
|
const prompt = buildAgentSystemPrompt({
|
|
workspaceDir: "/tmp/clawd",
|
|
toolNames: ["message"],
|
|
});
|
|
|
|
expect(prompt).toContain("message: Send messages and channel actions");
|
|
expect(prompt).toContain("### message tool");
|
|
expect(prompt).toContain("respond with ONLY: NO_REPLY");
|
|
});
|
|
|
|
it("includes runtime provider capabilities when present", () => {
|
|
const prompt = buildAgentSystemPrompt({
|
|
workspaceDir: "/tmp/clawd",
|
|
runtimeInfo: {
|
|
channel: "telegram",
|
|
capabilities: ["inlineButtons"],
|
|
},
|
|
});
|
|
|
|
expect(prompt).toContain("channel=telegram");
|
|
expect(prompt).toContain("capabilities=inlineButtons");
|
|
});
|
|
|
|
it("includes agent id in runtime when provided", () => {
|
|
const prompt = buildAgentSystemPrompt({
|
|
workspaceDir: "/tmp/clawd",
|
|
runtimeInfo: {
|
|
agentId: "work",
|
|
host: "host",
|
|
os: "macOS",
|
|
arch: "arm64",
|
|
node: "v20",
|
|
model: "anthropic/claude",
|
|
},
|
|
});
|
|
|
|
expect(prompt).toContain("agent=work");
|
|
});
|
|
|
|
it("includes reasoning visibility hint", () => {
|
|
const prompt = buildAgentSystemPrompt({
|
|
workspaceDir: "/tmp/clawd",
|
|
reasoningLevel: "off",
|
|
});
|
|
|
|
expect(prompt).toContain("Reasoning: off");
|
|
expect(prompt).toContain("/reasoning");
|
|
expect(prompt).toContain("/status shows Reasoning");
|
|
});
|
|
|
|
it("builds runtime line with agent and channel details", () => {
|
|
const line = buildRuntimeLine(
|
|
{
|
|
agentId: "work",
|
|
host: "host",
|
|
repoRoot: "/repo",
|
|
os: "macOS",
|
|
arch: "arm64",
|
|
node: "v20",
|
|
model: "anthropic/claude",
|
|
defaultModel: "anthropic/claude-opus-4-5",
|
|
},
|
|
"telegram",
|
|
["inlineButtons"],
|
|
"low",
|
|
);
|
|
|
|
expect(line).toContain("agent=work");
|
|
expect(line).toContain("host=host");
|
|
expect(line).toContain("repo=/repo");
|
|
expect(line).toContain("os=macOS (arm64)");
|
|
expect(line).toContain("node=v20");
|
|
expect(line).toContain("model=anthropic/claude");
|
|
expect(line).toContain("default_model=anthropic/claude-opus-4-5");
|
|
expect(line).toContain("channel=telegram");
|
|
expect(line).toContain("capabilities=inlineButtons");
|
|
expect(line).toContain("thinking=low");
|
|
});
|
|
|
|
it("describes sandboxed runtime and elevated when allowed", () => {
|
|
const prompt = buildAgentSystemPrompt({
|
|
workspaceDir: "/tmp/clawd",
|
|
sandboxInfo: {
|
|
enabled: true,
|
|
workspaceDir: "/tmp/sandbox",
|
|
workspaceAccess: "ro",
|
|
agentWorkspaceMount: "/agent",
|
|
elevated: { allowed: true, defaultLevel: "on" },
|
|
},
|
|
});
|
|
|
|
expect(prompt).toContain("You are running in a sandboxed runtime");
|
|
expect(prompt).toContain("Sub-agents stay sandboxed");
|
|
expect(prompt).toContain("User can toggle with /elevated on|off|ask|full.");
|
|
expect(prompt).toContain("Current elevated level: on");
|
|
});
|
|
|
|
it("includes reaction guidance when provided", () => {
|
|
const prompt = buildAgentSystemPrompt({
|
|
workspaceDir: "/tmp/clawd",
|
|
reactionGuidance: {
|
|
level: "minimal",
|
|
channel: "Telegram",
|
|
},
|
|
});
|
|
|
|
expect(prompt).toContain("## Reactions");
|
|
expect(prompt).toContain("Reactions are enabled for Telegram in MINIMAL mode.");
|
|
});
|
|
});
|