feat: refine subagents + add chat.inject
Co-authored-by: Tyler Yust <tyler6204@users.noreply.github.com>
This commit is contained in:
@@ -4,6 +4,14 @@ import { listDeliverableMessageChannels } from "../utils/message-channel.js";
|
||||
import type { ResolvedTimeFormat } from "./date-time.js";
|
||||
import type { EmbeddedContextFile } from "./pi-embedded-helpers.js";
|
||||
|
||||
/**
|
||||
* Controls which hardcoded sections are included in the system prompt.
|
||||
* - "full": All sections (default, for main agent)
|
||||
* - "minimal": Reduced sections (Tooling, Workspace, Runtime) - used for subagents
|
||||
* - "none": Just basic identity line, no sections
|
||||
*/
|
||||
export type PromptMode = "full" | "minimal" | "none";
|
||||
|
||||
export function buildAgentSystemPrompt(params: {
|
||||
workspaceDir: string;
|
||||
defaultThinkLevel?: ThinkLevel;
|
||||
@@ -20,6 +28,8 @@ export function buildAgentSystemPrompt(params: {
|
||||
contextFiles?: EmbeddedContextFile[];
|
||||
skillsPrompt?: string;
|
||||
heartbeatPrompt?: string;
|
||||
/** Controls which hardcoded sections to include. Defaults to "full". */
|
||||
promptMode?: PromptMode;
|
||||
runtimeInfo?: {
|
||||
host?: string;
|
||||
os?: string;
|
||||
@@ -179,17 +189,22 @@ export function buildAgentSystemPrompt(params: {
|
||||
const runtimeCapabilitiesLower = new Set(runtimeCapabilities.map((cap) => cap.toLowerCase()));
|
||||
const inlineButtonsEnabled = runtimeCapabilitiesLower.has("inlinebuttons");
|
||||
const messageChannelOptions = listDeliverableMessageChannels().join("|");
|
||||
const promptMode = params.promptMode ?? "full";
|
||||
const isMinimal = promptMode === "minimal" || promptMode === "none";
|
||||
const skillsLines = skillsPrompt ? [skillsPrompt, ""] : [];
|
||||
const skillsSection = skillsPrompt
|
||||
? [
|
||||
"## Skills",
|
||||
`Skills provide task-specific instructions. Use \`${readToolName}\` to load the SKILL.md at the location listed for that skill.`,
|
||||
...skillsLines,
|
||||
"",
|
||||
]
|
||||
: [];
|
||||
// Skip skills section for subagent/none modes
|
||||
const skillsSection =
|
||||
skillsPrompt && !isMinimal
|
||||
? [
|
||||
"## Skills",
|
||||
`Skills provide task-specific instructions. Use \`${readToolName}\` to load the SKILL.md at the location listed for that skill.`,
|
||||
...skillsLines,
|
||||
"",
|
||||
]
|
||||
: [];
|
||||
// Skip memory section for subagent/none modes
|
||||
const memorySection =
|
||||
availableTools.has("memory_search") || availableTools.has("memory_get")
|
||||
!isMinimal && (availableTools.has("memory_search") || availableTools.has("memory_get"))
|
||||
? [
|
||||
"## Memory Recall",
|
||||
"Before answering anything about prior work, decisions, dates, people, preferences, or todos: run memory_search on MEMORY.md + memory/*.md; then use memory_get to pull only the needed lines. If low confidence after search, say you checked.",
|
||||
@@ -197,6 +212,11 @@ export function buildAgentSystemPrompt(params: {
|
||||
]
|
||||
: [];
|
||||
|
||||
// For "none" mode, return just the basic identity line
|
||||
if (promptMode === "none") {
|
||||
return "You are a personal assistant running inside Clawdbot.";
|
||||
}
|
||||
|
||||
const lines = [
|
||||
"You are a personal assistant running inside Clawdbot.",
|
||||
"",
|
||||
@@ -235,8 +255,9 @@ export function buildAgentSystemPrompt(params: {
|
||||
"",
|
||||
...skillsSection,
|
||||
...memorySection,
|
||||
hasGateway ? "## Clawdbot Self-Update" : "",
|
||||
hasGateway
|
||||
// Skip self-update for subagent/none modes
|
||||
hasGateway && !isMinimal ? "## Clawdbot Self-Update" : "",
|
||||
hasGateway && !isMinimal
|
||||
? [
|
||||
"Get Updates (self-update) is ONLY allowed when the user explicitly asks for it.",
|
||||
"Do not run config.apply or update.run unless the user explicitly requests an update or config change; if it's not explicit, ask first.",
|
||||
@@ -244,16 +265,19 @@ export function buildAgentSystemPrompt(params: {
|
||||
"After restart, Clawdbot pings the last active session automatically.",
|
||||
].join("\n")
|
||||
: "",
|
||||
hasGateway ? "" : "",
|
||||
hasGateway && !isMinimal ? "" : "",
|
||||
"",
|
||||
params.modelAliasLines && params.modelAliasLines.length > 0 ? "## Model Aliases" : "",
|
||||
params.modelAliasLines && params.modelAliasLines.length > 0
|
||||
// Skip model aliases for subagent/none modes
|
||||
params.modelAliasLines && params.modelAliasLines.length > 0 && !isMinimal
|
||||
? "## Model Aliases"
|
||||
: "",
|
||||
params.modelAliasLines && params.modelAliasLines.length > 0 && !isMinimal
|
||||
? "Prefer aliases when specifying model overrides; full provider/model is also accepted."
|
||||
: "",
|
||||
params.modelAliasLines && params.modelAliasLines.length > 0
|
||||
params.modelAliasLines && params.modelAliasLines.length > 0 && !isMinimal
|
||||
? params.modelAliasLines.join("\n")
|
||||
: "",
|
||||
params.modelAliasLines && params.modelAliasLines.length > 0 ? "" : "",
|
||||
params.modelAliasLines && params.modelAliasLines.length > 0 && !isMinimal ? "" : "",
|
||||
"## Workspace",
|
||||
`Your working directory is: ${params.workspaceDir}`,
|
||||
"Treat this directory as the single global workspace for file operations unless explicitly instructed otherwise.",
|
||||
@@ -311,9 +335,10 @@ export function buildAgentSystemPrompt(params: {
|
||||
.join("\n")
|
||||
: "",
|
||||
params.sandboxInfo?.enabled ? "" : "",
|
||||
ownerLine ? "## User Identity" : "",
|
||||
ownerLine ?? "",
|
||||
ownerLine ? "" : "",
|
||||
// Skip user identity for subagent/none modes
|
||||
ownerLine && !isMinimal ? "## User Identity" : "",
|
||||
ownerLine && !isMinimal ? ownerLine : "",
|
||||
ownerLine && !isMinimal ? "" : "",
|
||||
...(userTimezone || userTime
|
||||
? [
|
||||
"## Current Date & Time",
|
||||
@@ -329,38 +354,50 @@ export function buildAgentSystemPrompt(params: {
|
||||
"## Workspace Files (injected)",
|
||||
"These user-editable files are loaded by Clawdbot and included below in Project Context.",
|
||||
"",
|
||||
"## Reply Tags",
|
||||
"To request a native reply/quote on supported surfaces, include one tag in your reply:",
|
||||
"- [[reply_to_current]] replies to the triggering message.",
|
||||
"- [[reply_to:<id>]] replies to a specific message id when you have it.",
|
||||
"Whitespace inside the tag is allowed (e.g. [[ reply_to_current ]] / [[ reply_to: 123 ]]).",
|
||||
"Tags are stripped before sending; support depends on the current channel config.",
|
||||
"",
|
||||
"## Messaging",
|
||||
"- Reply in current session → automatically routes to the source channel (Signal, Telegram, etc.)",
|
||||
"- Cross-session messaging → use sessions_send(sessionKey, message)",
|
||||
"- Never use exec/curl for provider messaging; Clawdbot handles all routing internally.",
|
||||
availableTools.has("message")
|
||||
? [
|
||||
// Skip reply tags for subagent/none modes
|
||||
...(isMinimal
|
||||
? []
|
||||
: [
|
||||
"## Reply Tags",
|
||||
"To request a native reply/quote on supported surfaces, include one tag in your reply:",
|
||||
"- [[reply_to_current]] replies to the triggering message.",
|
||||
"- [[reply_to:<id>]] replies to a specific message id when you have it.",
|
||||
"Whitespace inside the tag is allowed (e.g. [[ reply_to_current ]] / [[ reply_to: 123 ]]).",
|
||||
"Tags are stripped before sending; support depends on the current channel config.",
|
||||
"",
|
||||
"### message tool",
|
||||
"- Use `message` for proactive sends + channel actions (polls, reactions, etc.).",
|
||||
"- For `action=send`, include `to` and `message`.",
|
||||
`- If multiple channels are configured, pass \`channel\` (${messageChannelOptions}).`,
|
||||
inlineButtonsEnabled
|
||||
? "- Inline buttons supported. Use `action=send` with `buttons=[[{text,callback_data}]]` (callback_data routes back as a user message)."
|
||||
: runtimeChannel
|
||||
? `- Inline buttons not enabled for ${runtimeChannel}. If you need them, ask to add "inlineButtons" to ${runtimeChannel}.capabilities or ${runtimeChannel}.accounts.<id>.capabilities.`
|
||||
: "",
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join("\n")
|
||||
: "",
|
||||
"",
|
||||
]),
|
||||
// Skip messaging section for subagent/none modes
|
||||
...(isMinimal
|
||||
? []
|
||||
: [
|
||||
"## Messaging",
|
||||
"- Reply in current session → automatically routes to the source channel (Signal, Telegram, etc.)",
|
||||
"- Cross-session messaging → use sessions_send(sessionKey, message)",
|
||||
"- Never use exec/curl for provider messaging; Clawdbot handles all routing internally.",
|
||||
availableTools.has("message")
|
||||
? [
|
||||
"",
|
||||
"### message tool",
|
||||
"- Use `message` for proactive sends + channel actions (polls, reactions, etc.).",
|
||||
"- For `action=send`, include `to` and `message`.",
|
||||
`- If multiple channels are configured, pass \`channel\` (${messageChannelOptions}).`,
|
||||
inlineButtonsEnabled
|
||||
? "- Inline buttons supported. Use `action=send` with `buttons=[[{text,callback_data}]]` (callback_data routes back as a user message)."
|
||||
: runtimeChannel
|
||||
? `- Inline buttons not enabled for ${runtimeChannel}. If you need them, ask to add "inlineButtons" to ${runtimeChannel}.capabilities or ${runtimeChannel}.accounts.<id>.capabilities.`
|
||||
: "",
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join("\n")
|
||||
: "",
|
||||
"",
|
||||
]),
|
||||
];
|
||||
|
||||
if (extraSystemPrompt) {
|
||||
lines.push("## Group Chat Context", extraSystemPrompt, "");
|
||||
// Use "Subagent Context" header for minimal mode (subagents), otherwise "Group Chat Context"
|
||||
const contextHeader = promptMode === "minimal" ? "## Subagent Context" : "## Group Chat Context";
|
||||
lines.push(contextHeader, extraSystemPrompt, "");
|
||||
}
|
||||
if (params.reactionGuidance) {
|
||||
const { level, channel } = params.reactionGuidance;
|
||||
@@ -402,26 +439,38 @@ export function buildAgentSystemPrompt(params: {
|
||||
}
|
||||
}
|
||||
|
||||
// Skip silent replies for subagent/none modes
|
||||
if (!isMinimal) {
|
||||
lines.push(
|
||||
"## Silent Replies",
|
||||
`When you have nothing to say, respond with ONLY: ${SILENT_REPLY_TOKEN}`,
|
||||
"",
|
||||
"⚠️ Rules:",
|
||||
"- It must be your ENTIRE message — nothing else",
|
||||
`- Never append it to an actual response (never include "${SILENT_REPLY_TOKEN}" in real replies)`,
|
||||
"- Never wrap it in markdown or code blocks",
|
||||
"",
|
||||
`❌ Wrong: "Here's help... ${SILENT_REPLY_TOKEN}"`,
|
||||
`❌ Wrong: "${SILENT_REPLY_TOKEN}"`,
|
||||
`✅ Right: ${SILENT_REPLY_TOKEN}`,
|
||||
"",
|
||||
);
|
||||
}
|
||||
|
||||
// Skip heartbeats for subagent/none modes
|
||||
if (!isMinimal) {
|
||||
lines.push(
|
||||
"## Heartbeats",
|
||||
heartbeatPromptLine,
|
||||
"If you receive a heartbeat poll (a user message matching the heartbeat prompt above), and there is nothing that needs attention, reply exactly:",
|
||||
"HEARTBEAT_OK",
|
||||
'Clawdbot treats a leading/trailing "HEARTBEAT_OK" as a heartbeat ack (and may discard it).',
|
||||
'If something needs attention, do NOT include "HEARTBEAT_OK"; reply with the alert text instead.',
|
||||
"",
|
||||
);
|
||||
}
|
||||
|
||||
lines.push(
|
||||
"## Silent Replies",
|
||||
`When you have nothing to say, respond with ONLY: ${SILENT_REPLY_TOKEN}`,
|
||||
"",
|
||||
"⚠️ Rules:",
|
||||
"- It must be your ENTIRE message — nothing else",
|
||||
`- Never append it to an actual response (never include "${SILENT_REPLY_TOKEN}" in real replies)`,
|
||||
"- Never wrap it in markdown or code blocks",
|
||||
"",
|
||||
`❌ Wrong: "Here's help... ${SILENT_REPLY_TOKEN}"`,
|
||||
`❌ Wrong: "${SILENT_REPLY_TOKEN}"`,
|
||||
`✅ Right: ${SILENT_REPLY_TOKEN}`,
|
||||
"",
|
||||
"## Heartbeats",
|
||||
heartbeatPromptLine,
|
||||
"If you receive a heartbeat poll (a user message matching the heartbeat prompt above), and there is nothing that needs attention, reply exactly:",
|
||||
"HEARTBEAT_OK",
|
||||
'Clawdbot treats a leading/trailing "HEARTBEAT_OK" as a heartbeat ack (and may discard it).',
|
||||
'If something needs attention, do NOT include "HEARTBEAT_OK"; reply with the alert text instead.',
|
||||
"",
|
||||
"## Runtime",
|
||||
`Runtime: ${[
|
||||
runtimeInfo?.host ? `host=${runtimeInfo.host}` : "",
|
||||
|
||||
Reference in New Issue
Block a user