2026-01-10 22:24:13 +01:00
import type { ReasoningLevel , ThinkLevel } from "../auto-reply/thinking.js" ;
2026-01-09 23:29:01 +00:00
import { SILENT_REPLY_TOKEN } from "../auto-reply/tokens.js" ;
2026-01-15 02:42:41 +00:00
import { listDeliverableMessageChannels } from "../utils/message-channel.js" ;
2026-01-15 22:26:31 +00:00
import type { ResolvedTimeFormat } from "./date-time.js" ;
2026-01-08 02:20:18 +01:00
import type { EmbeddedContextFile } from "./pi-embedded-helpers.js" ;
2025-12-17 11:29:04 +01:00
2026-01-15 23:06:58 +00:00
/ * *
* 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" ;
2026-01-16 00:28:43 +00:00
function buildSkillsSection ( params : {
2026-01-18 23:55:16 -08:00
skillsPrompt? : string ;
isMinimal : boolean ;
readToolName : string ;
2026-01-16 00:28:43 +00:00
} ) {
2026-01-18 23:55:16 -08:00
const trimmed = params . skillsPrompt ? . trim ( ) ;
if ( ! trimmed || params . isMinimal ) return [ ] ;
return [
"## Skills (mandatory)" ,
"Before replying: scan <available_skills> <description> entries." ,
` - If exactly one skill clearly applies: read its SKILL.md at <location> with \` ${ params . readToolName } \` , then follow it. ` ,
"- If multiple could apply: choose the most specific one, then read/follow it." ,
"- If none clearly apply: do not read any SKILL.md." ,
"Constraints: never read more than one skill up front; only read after selecting." ,
trimmed ,
"" ,
] ;
2026-01-16 00:28:43 +00:00
}
2026-01-16 03:30:56 +00:00
function buildMemorySection ( params : { isMinimal : boolean ; availableTools : Set < string > } ) {
2026-01-18 23:55:16 -08:00
if ( params . isMinimal ) return [ ] ;
if ( ! params . availableTools . has ( "memory_search" ) && ! params . availableTools . has ( "memory_get" ) ) {
return [ ] ;
}
return [
"## 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." ,
"" ,
] ;
2026-01-16 00:28:43 +00:00
}
function buildUserIdentitySection ( ownerLine : string | undefined , isMinimal : boolean ) {
2026-01-18 23:55:16 -08:00
if ( ! ownerLine || isMinimal ) return [ ] ;
return [ "## User Identity" , ownerLine , "" ] ;
2026-01-16 00:28:43 +00:00
}
function buildTimeSection ( params : {
2026-01-18 23:55:16 -08:00
userTimezone? : string ;
userTime? : string ;
userTimeFormat? : ResolvedTimeFormat ;
2026-01-16 00:28:43 +00:00
} ) {
2026-01-18 23:55:16 -08:00
if ( ! params . userTimezone && ! params . userTime ) return [ ] ;
return [
"## Current Date & Time" ,
params . userTime
? ` ${ params . userTime } ( ${ params . userTimezone ? ? "unknown" } ) `
: ` Time zone: ${ params . userTimezone } . Current time unknown; assume UTC for date/time references. ` ,
params . userTimeFormat
? ` Time format: ${ params . userTimeFormat === "24" ? "24-hour" : "12-hour" } `
: "" ,
"" ,
] ;
2026-01-16 00:28:43 +00:00
}
function buildReplyTagsSection ( isMinimal : boolean ) {
2026-01-18 23:55:16 -08:00
if ( isMinimal ) return [ ] ;
return [
"## 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." ,
"" ,
] ;
2026-01-16 00:28:43 +00:00
}
function buildMessagingSection ( params : {
2026-01-18 23:55:16 -08:00
isMinimal : boolean ;
availableTools : Set < string > ;
messageChannelOptions : string ;
inlineButtonsEnabled : boolean ;
runtimeChannel? : string ;
2026-01-16 00:28:43 +00:00
} ) {
2026-01-18 23:55:16 -08:00
if ( params . isMinimal ) return [ ] ;
return [
"## 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." ,
params . 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 \` ( ${ params . messageChannelOptions } ). ` ,
` - If you use \` message \` ( \` action=send \` ) to deliver your user-visible reply, respond with ONLY: ${ SILENT_REPLY_TOKEN } (avoid duplicate replies). ` ,
params . inlineButtonsEnabled
? "- Inline buttons supported. Use `action=send` with `buttons=[[{text,callback_data}]]` (callback_data routes back as a user message)."
: params . runtimeChannel
? ` - Inline buttons not enabled for ${ params . runtimeChannel } . If you need them, ask to set ${ params . runtimeChannel } .capabilities.inlineButtons ("dm"|"group"|"all"|"allowlist"). `
: "" ,
]
. filter ( Boolean )
. join ( "\n" )
: "" ,
"" ,
] ;
2026-01-16 00:28:43 +00:00
}
2026-01-18 18:43:31 +00:00
function buildDocsSection ( params : { docsPath? : string ; isMinimal : boolean ; readToolName : string } ) {
2026-01-18 23:55:16 -08:00
const docsPath = params . docsPath ? . trim ( ) ;
if ( ! docsPath || params . isMinimal ) return [ ] ;
return [
"## Documentation" ,
` Clawdbot docs: ${ docsPath } ` ,
"Mirror: https://docs.clawd.bot" ,
"Source: https://github.com/clawdbot/clawdbot" ,
"Community: https://discord.com/invite/clawd" ,
"Find new skills: https://clawdhub.com" ,
"For Clawdbot behavior, commands, config, or architecture: consult local docs first." ,
"When diagnosing issues, run `clawdbot status` yourself when possible; only ask the user if you lack access (e.g., sandboxed)." ,
"" ,
] ;
2026-01-18 15:00:14 +00:00
}
2026-01-08 02:20:18 +01:00
export function buildAgentSystemPrompt ( params : {
2026-01-18 23:55:16 -08:00
workspaceDir : string ;
defaultThinkLevel? : ThinkLevel ;
reasoningLevel? : ReasoningLevel ;
extraSystemPrompt? : string ;
ownerNumbers? : string [ ] ;
reasoningTagHint? : boolean ;
toolNames? : string [ ] ;
toolSummaries? : Record < string , string > ;
modelAliasLines? : string [ ] ;
userTimezone? : string ;
userTime? : string ;
userTimeFormat? : ResolvedTimeFormat ;
contextFiles? : EmbeddedContextFile [ ] ;
skillsPrompt? : string ;
heartbeatPrompt? : string ;
docsPath? : string ;
/** Controls which hardcoded sections to include. Defaults to "full". */
promptMode? : PromptMode ;
runtimeInfo ? : {
agentId? : string ;
host? : string ;
os? : string ;
arch? : string ;
node? : string ;
model? : string ;
channel? : string ;
capabilities? : string [ ] ;
} ;
sandboxInfo ? : {
enabled : boolean ;
workspaceDir? : string ;
workspaceAccess ? : "none" | "ro" | "rw" ;
agentWorkspaceMount? : string ;
browserControlUrl? : string ;
browserNoVncUrl? : string ;
hostBrowserAllowed? : boolean ;
allowedControlUrls? : string [ ] ;
allowedControlHosts? : string [ ] ;
allowedControlPorts? : number [ ] ;
elevated ? : {
allowed : boolean ;
defaultLevel : "on" | "off" ;
} ;
} ;
/** Reaction guidance for the agent (for Telegram minimal/extensive modes). */
reactionGuidance ? : {
level : "minimal" | "extensive" ;
channel : string ;
} ;
2025-12-17 11:29:04 +01:00
} ) {
2026-01-18 23:55:16 -08:00
const coreToolSummaries : Record < string , string > = {
read : "Read file contents" ,
write : "Create or overwrite files" ,
edit : "Make precise edits to files" ,
apply_patch : "Apply multi-file patches" ,
grep : "Search file contents for patterns" ,
find : "Find files by glob pattern" ,
ls : "List directory contents" ,
exec : "Run shell commands (pty available for TTY-required CLIs)" ,
process : "Manage background exec sessions" ,
web_search : "Search the web (Brave API)" ,
web_fetch : "Fetch and extract readable content from a URL" ,
// Channel docking: add login tools here when a channel needs interactive linking.
browser : "Control web browser" ,
canvas : "Present/eval/snapshot the Canvas" ,
nodes : "List/describe/notify/camera/screen on paired nodes" ,
cron : "Manage cron jobs and wake events (use for reminders; include recent context in reminder text if appropriate)" ,
message : "Send messages and channel actions" ,
gateway : "Restart, apply config, or run updates on the running Clawdbot process" ,
agents_list : "List agent ids allowed for sessions_spawn" ,
sessions_list : "List other sessions (incl. sub-agents) with filters/last" ,
sessions_history : "Fetch history for another session/sub-agent" ,
sessions_send : "Send a message to another session/sub-agent" ,
sessions_spawn : "Spawn a sub-agent session" ,
session_status :
"Show a /status-equivalent status card (usage + Reasoning/Verbose/Elevated); optional per-session model override" ,
image : "Analyze an image with the configured image model" ,
} ;
2026-01-05 06:32:44 +00:00
2026-01-18 23:55:16 -08:00
const toolOrder = [
"read" ,
"write" ,
"edit" ,
"apply_patch" ,
"grep" ,
"find" ,
"ls" ,
"exec" ,
"process" ,
"web_search" ,
"web_fetch" ,
"browser" ,
"canvas" ,
"nodes" ,
"cron" ,
"message" ,
"gateway" ,
"agents_list" ,
"sessions_list" ,
"sessions_history" ,
"sessions_send" ,
"session_status" ,
"image" ,
] ;
2026-01-05 06:32:44 +00:00
2026-01-18 23:55:16 -08:00
const rawToolNames = ( params . toolNames ? ? [ ] ) . map ( ( tool ) = > tool . trim ( ) ) ;
const canonicalToolNames = rawToolNames . filter ( Boolean ) ;
// Preserve caller casing while deduping tool names by lowercase.
const canonicalByNormalized = new Map < string , string > ( ) ;
for ( const name of canonicalToolNames ) {
const normalized = name . toLowerCase ( ) ;
if ( ! canonicalByNormalized . has ( normalized ) ) {
canonicalByNormalized . set ( normalized , name ) ;
}
}
const resolveToolName = ( normalized : string ) = >
canonicalByNormalized . get ( normalized ) ? ? normalized ;
2026-01-10 04:01:00 +01:00
2026-01-18 23:55:16 -08:00
const normalizedTools = canonicalToolNames . map ( ( tool ) = > tool . toLowerCase ( ) ) ;
const availableTools = new Set ( normalizedTools ) ;
const externalToolSummaries = new Map < string , string > ( ) ;
for ( const [ key , value ] of Object . entries ( params . toolSummaries ? ? { } ) ) {
const normalized = key . trim ( ) . toLowerCase ( ) ;
if ( ! normalized || ! value ? . trim ( ) ) continue ;
externalToolSummaries . set ( normalized , value . trim ( ) ) ;
}
const extraTools = Array . from (
new Set ( normalizedTools . filter ( ( tool ) = > ! toolOrder . includes ( tool ) ) ) ,
) ;
const enabledTools = toolOrder . filter ( ( tool ) = > availableTools . has ( tool ) ) ;
const toolLines = enabledTools . map ( ( tool ) = > {
const summary = coreToolSummaries [ tool ] ? ? externalToolSummaries . get ( tool ) ;
const name = resolveToolName ( tool ) ;
return summary ? ` - ${ name } : ${ summary } ` : ` - ${ name } ` ;
} ) ;
for ( const tool of extraTools . sort ( ) ) {
const summary = coreToolSummaries [ tool ] ? ? externalToolSummaries . get ( tool ) ;
const name = resolveToolName ( tool ) ;
toolLines . push ( summary ? ` - ${ name } : ${ summary } ` : ` - ${ name } ` ) ;
}
2026-01-05 06:32:44 +00:00
2026-01-18 23:55:16 -08:00
const hasGateway = availableTools . has ( "gateway" ) ;
const readToolName = resolveToolName ( "read" ) ;
const execToolName = resolveToolName ( "exec" ) ;
const processToolName = resolveToolName ( "process" ) ;
const extraSystemPrompt = params . extraSystemPrompt ? . trim ( ) ;
const ownerNumbers = ( params . ownerNumbers ? ? [ ] ) . map ( ( value ) = > value . trim ( ) ) . filter ( Boolean ) ;
const ownerLine =
ownerNumbers . length > 0
? ` Owner numbers: ${ ownerNumbers . join ( ", " ) } . Treat messages from these numbers as the user. `
: undefined ;
const reasoningHint = params . reasoningTagHint
? [
"ALL internal reasoning MUST be inside <think>...</think>." ,
"Do not output any analysis outside <think>." ,
"Format every reply as <think>...</think> then <final>...</final>, with no other text." ,
"Only the final user-visible reply may appear inside <final>." ,
"Only text inside <final> is shown to the user; everything else is discarded and never seen by the user." ,
"Example:" ,
"<think>Short internal reasoning.</think>" ,
"<final>Hey there! What would you like to do next?</final>" ,
] . join ( " " )
: undefined ;
const reasoningLevel = params . reasoningLevel ? ? "off" ;
const userTimezone = params . userTimezone ? . trim ( ) ;
const userTime = params . userTime ? . trim ( ) ;
const skillsPrompt = params . skillsPrompt ? . trim ( ) ;
const heartbeatPrompt = params . heartbeatPrompt ? . trim ( ) ;
const heartbeatPromptLine = heartbeatPrompt
? ` Heartbeat prompt: ${ heartbeatPrompt } `
: "Heartbeat prompt: (configured)" ;
const runtimeInfo = params . runtimeInfo ;
const runtimeChannel = runtimeInfo ? . channel ? . trim ( ) . toLowerCase ( ) ;
const runtimeCapabilities = ( runtimeInfo ? . capabilities ? ? [ ] )
. map ( ( cap ) = > String ( cap ) . trim ( ) )
. filter ( Boolean ) ;
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 skillsSection = buildSkillsSection ( {
skillsPrompt ,
isMinimal ,
readToolName ,
} ) ;
const memorySection = buildMemorySection ( { isMinimal , availableTools } ) ;
const docsSection = buildDocsSection ( {
docsPath : params.docsPath ,
isMinimal ,
readToolName ,
} ) ;
2025-12-23 13:32:07 +00:00
2026-01-18 23:55:16 -08:00
// For "none" mode, return just the basic identity line
if ( promptMode === "none" ) {
return "You are a personal assistant running inside Clawdbot." ;
}
2026-01-15 23:06:58 +00:00
2026-01-18 23:55:16 -08:00
const lines = [
"You are a personal assistant running inside Clawdbot." ,
"" ,
"## Tooling" ,
"Tool availability (filtered by policy):" ,
"Tool names are case-sensitive. Call tools exactly as listed." ,
toolLines . length > 0
? toolLines . join ( "\n" )
: [
"Pi lists the standard tools above. This runtime enables:" ,
"- grep: search file contents for patterns" ,
"- find: find files by glob pattern" ,
"- ls: list directory contents" ,
"- apply_patch: apply multi-file patches" ,
` - ${ execToolName } : run shell commands (supports background via yieldMs/background) ` ,
` - ${ processToolName } : manage background exec sessions ` ,
"- browser: control clawd's dedicated browser" ,
"- canvas: present/eval/snapshot the Canvas" ,
"- nodes: list/describe/notify/camera/screen on paired nodes" ,
"- cron: manage cron jobs and wake events (use for reminders; include recent context in reminder text if appropriate)" ,
"- sessions_list: list sessions" ,
"- sessions_history: fetch session history" ,
"- sessions_send: send to another session" ,
] . join ( "\n" ) ,
"TOOLS.md does not control tool availability; it is user guidance for how to use external tools." ,
"If a task is more complex or takes longer, spawn a sub-agent. It will do the work for you and ping you when it's done. You can always check up on it." ,
"" ,
"## Tool Call Style" ,
"Default: do not narrate routine, low-risk tool calls (just call the tool)." ,
"Narrate only when it helps: multi-step work, complex/challenging problems, sensitive actions (e.g., deletions), or when the user explicitly asks." ,
"Keep narration brief and value-dense; avoid repeating obvious steps." ,
"Use plain human language for narration unless in a technical context." ,
"" ,
"## Clawdbot CLI Quick Reference" ,
"Clawdbot is controlled via subcommands. Do not invent commands." ,
"To manage the Gateway daemon service (start/stop/restart):" ,
"- clawdbot daemon status" ,
"- clawdbot daemon start" ,
"- clawdbot daemon stop" ,
"- clawdbot daemon restart" ,
"If unsure, ask the user to run `clawdbot help` (or `clawdbot daemon --help`) and paste the output." ,
"" ,
. . . skillsSection ,
. . . memorySection ,
// 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." ,
"Actions: config.get, config.schema, config.apply (validate + write full config, then restart), update.run (update deps or git, then restart)." ,
"After restart, Clawdbot pings the last active session automatically." ,
] . join ( "\n" )
: "" ,
hasGateway && ! isMinimal ? "" : "" ,
"" ,
// 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 && ! isMinimal
? params . modelAliasLines . join ( "\n" )
: "" ,
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." ,
"" ,
. . . docsSection ,
params . sandboxInfo ? . enabled ? "## Sandbox" : "" ,
params . sandboxInfo ? . enabled
? [
"You are running in a sandboxed runtime (tools execute in Docker)." ,
"Some tools may be unavailable due to sandbox policy." ,
"Sub-agents stay sandboxed (no elevated/host access). Need outside-sandbox read/write? Don't spawn; ask first." ,
params . sandboxInfo . workspaceDir
? ` Sandbox workspace: ${ params . sandboxInfo . workspaceDir } `
: "" ,
params . sandboxInfo . workspaceAccess
? ` Agent workspace access: ${ params . sandboxInfo . workspaceAccess } ${
params . sandboxInfo . agentWorkspaceMount
? ` (mounted at ${ params . sandboxInfo . agentWorkspaceMount } ) `
: ""
} `
: "" ,
params . sandboxInfo . browserControlUrl
? ` Sandbox browser control URL: ${ params . sandboxInfo . browserControlUrl } `
: "" ,
params . sandboxInfo . browserNoVncUrl
? ` Sandbox browser observer (noVNC): ${ params . sandboxInfo . browserNoVncUrl } `
: "" ,
params . sandboxInfo . hostBrowserAllowed === true
? "Host browser control: allowed."
: params . sandboxInfo . hostBrowserAllowed === false
? "Host browser control: blocked."
: "" ,
params . sandboxInfo . allowedControlUrls ? . length
? ` Browser control URL allowlist: ${ params . sandboxInfo . allowedControlUrls . join ( ", " ) } `
: "" ,
params . sandboxInfo . allowedControlHosts ? . length
? ` Browser control host allowlist: ${ params . sandboxInfo . allowedControlHosts . join ( ", " ) } `
: "" ,
params . sandboxInfo . allowedControlPorts ? . length
? ` Browser control port allowlist: ${ params . sandboxInfo . allowedControlPorts . join ( ", " ) } `
: "" ,
params . sandboxInfo . elevated ? . allowed
? "Elevated exec is available for this session."
: "" ,
params . sandboxInfo . elevated ? . allowed ? "User can toggle with /elevated on|off." : "" ,
params . sandboxInfo . elevated ? . allowed
? "You may also send /elevated on|off when needed."
: "" ,
params . sandboxInfo . elevated ? . allowed
? ` Current elevated level: ${ params . sandboxInfo . elevated . defaultLevel } (on runs exec on host; off runs in sandbox). `
: "" ,
]
. filter ( Boolean )
. join ( "\n" )
: "" ,
params . sandboxInfo ? . enabled ? "" : "" ,
. . . buildUserIdentitySection ( ownerLine , isMinimal ) ,
. . . buildTimeSection ( {
userTimezone ,
userTime ,
userTimeFormat : params.userTimeFormat ,
} ) ,
"## Workspace Files (injected)" ,
"These user-editable files are loaded by Clawdbot and included below in Project Context." ,
"" ,
. . . buildReplyTagsSection ( isMinimal ) ,
. . . buildMessagingSection ( {
isMinimal ,
availableTools ,
messageChannelOptions ,
inlineButtonsEnabled ,
runtimeChannel ,
} ) ,
] ;
2025-12-23 13:32:07 +00:00
2026-01-18 23:55:16 -08:00
if ( 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 ;
const guidanceText =
level === "minimal"
? [
` Reactions are enabled for ${ channel } in MINIMAL mode. ` ,
"React ONLY when truly relevant:" ,
"- Acknowledge important user requests or confirmations" ,
"- Express genuine sentiment (humor, appreciation) sparingly" ,
"- Avoid reacting to routine messages or your own replies" ,
"Guideline: at most 1 reaction per 5-10 exchanges." ,
] . join ( "\n" )
: [
` Reactions are enabled for ${ channel } in EXTENSIVE mode. ` ,
"Feel free to react liberally:" ,
"- Acknowledge messages with appropriate emojis" ,
"- Express sentiment and personality through reactions" ,
"- React to interesting content, humor, or notable events" ,
"- Use reactions to confirm understanding or agreement" ,
"Guideline: react whenever it feels natural." ,
] . join ( "\n" ) ;
lines . push ( "## Reactions" , guidanceText , "" ) ;
}
if ( reasoningHint ) {
lines . push ( "## Reasoning Format" , reasoningHint , "" ) ;
}
2025-12-23 13:32:07 +00:00
2026-01-18 23:55:16 -08:00
const contextFiles = params . contextFiles ? ? [ ] ;
if ( contextFiles . length > 0 ) {
lines . push (
"# Project Context" ,
"" ,
"The following project context files have been loaded:" ,
"" ,
) ;
for ( const file of contextFiles ) {
lines . push ( ` ## ${ file . path } ` , "" , file . content , "" ) ;
}
}
2026-01-08 02:20:18 +01:00
2026-01-18 23:55:16 -08:00
// 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 } ` ,
"" ,
) ;
}
2026-01-15 23:06:58 +00:00
2026-01-18 23:55:16 -08:00
// 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.' ,
"" ,
) ;
}
2026-01-15 23:06:58 +00:00
2026-01-18 23:55:16 -08:00
lines . push (
"## Runtime" ,
buildRuntimeLine ( runtimeInfo , runtimeChannel , runtimeCapabilities , params . defaultThinkLevel ) ,
` Reasoning: ${ reasoningLevel } (hidden unless on/stream). Toggle /reasoning; /status shows Reasoning when enabled. ` ,
) ;
2025-12-23 13:32:07 +00:00
2026-01-18 23:55:16 -08:00
return lines . filter ( Boolean ) . join ( "\n" ) ;
2025-12-17 11:29:04 +01:00
}
2026-01-19 05:27:38 +00:00
export function buildRuntimeLine (
2026-01-18 23:55:16 -08:00
runtimeInfo ? : {
agentId? : string ;
host? : string ;
os? : string ;
arch? : string ;
node? : string ;
model? : string ;
} ,
runtimeChannel? : string ,
runtimeCapabilities : string [ ] = [ ] ,
defaultThinkLevel? : ThinkLevel ,
2026-01-19 05:27:38 +00:00
) : string {
2026-01-18 23:55:16 -08:00
return ` Runtime: ${ [
runtimeInfo ? . agentId ? ` agent= ${ runtimeInfo . agentId } ` : "" ,
runtimeInfo ? . host ? ` host= ${ runtimeInfo . host } ` : "" ,
runtimeInfo ? . os
? ` os= ${ runtimeInfo . os } ${ runtimeInfo ? . arch ? ` ( ${ runtimeInfo . arch } ) ` : "" } `
: runtimeInfo ? . arch
? ` arch= ${ runtimeInfo . arch } `
: "" ,
runtimeInfo ? . node ? ` node= ${ runtimeInfo . node } ` : "" ,
runtimeInfo ? . model ? ` model= ${ runtimeInfo . model } ` : "" ,
runtimeChannel ? ` channel= ${ runtimeChannel } ` : "" ,
runtimeChannel
? ` capabilities= ${ runtimeCapabilities . length > 0 ? runtimeCapabilities . join ( "," ) : "none" } `
: "" ,
` thinking= ${ defaultThinkLevel ? ? "off" } ` ,
]
. filter ( Boolean )
. join ( " | " ) } ` ;
2026-01-19 05:27:38 +00:00
}