2026-01-10 22:24:13 +01:00
import type { ReasoningLevel , ThinkLevel } from "../auto-reply/thinking.js" ;
2026-01-27 21:57:15 -08:00
import type { MemoryCitationsMode } from "../config/types.memory.js" ;
import type { ResolvedTimeFormat } from "./date-time.js" ;
import type { EmbeddedContextFile } from "./pi-embedded-helpers.js" ;
2026-02-02 20:45:58 -08:00
import { SILENT_REPLY_TOKEN } from "../auto-reply/tokens.js" ;
import { listDeliverableMessageChannels } from "../utils/message-channel.js" ;
2026-02-16 02:48:46 +01:00
import { sanitizeForPromptLiteral } from "./sanitize-for-prompt.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 )
2026-01-27 21:57:15 -08:00
* - "minimal" : Reduced sections ( Tooling , Workspace , Runtime ) - used for subagents
2026-01-15 23:06:58 +00:00
* - "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-20 16:46:15 +00:00
skillsPrompt? : string ;
isMinimal : boolean ;
readToolName : string ;
2026-01-16 00:28:43 +00:00
} ) {
2026-02-02 21:15:43 -08:00
if ( params . isMinimal ) {
return [ ] ;
}
2026-01-20 16:46:15 +00:00
const trimmed = params . skillsPrompt ? . trim ( ) ;
2026-02-02 21:15:43 -08:00
if ( ! trimmed ) {
return [ ] ;
}
2026-01-20 16:46:15 +00:00
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-27 21:57:15 -08:00
function buildMemorySection ( params : {
isMinimal : boolean ;
availableTools : Set < string > ;
citationsMode? : MemoryCitationsMode ;
} ) {
2026-02-02 21:15:43 -08:00
if ( params . isMinimal ) {
return [ ] ;
}
2026-01-20 16:46:15 +00:00
if ( ! params . availableTools . has ( "memory_search" ) && ! params . availableTools . has ( "memory_get" ) ) {
return [ ] ;
}
2026-01-27 21:57:15 -08:00
const lines = [
2026-01-20 16:46:15 +00:00
"## 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-27 21:57:15 -08:00
if ( params . citationsMode === "off" ) {
lines . push (
"Citations are disabled: do not mention file paths or line numbers in replies unless the user explicitly asks." ,
) ;
} else {
lines . push (
"Citations: include Source: <path#line> when it helps the user verify memory snippets." ,
) ;
}
lines . push ( "" ) ;
return lines ;
2026-01-16 00:28:43 +00:00
}
function buildUserIdentitySection ( ownerLine : string | undefined , isMinimal : boolean ) {
2026-02-02 21:15:43 -08:00
if ( ! ownerLine || isMinimal ) {
return [ ] ;
}
2026-01-20 16:46:15 +00:00
return [ "## User Identity" , ownerLine , "" ] ;
2026-01-16 00:28:43 +00:00
}
2026-01-24 06:22:54 +00:00
function buildTimeSection ( params : { userTimezone? : string } ) {
2026-02-02 21:15:43 -08:00
if ( ! params . userTimezone ) {
return [ ] ;
}
2026-01-27 21:57:15 -08:00
return [ "## Current Date & Time" , ` Time zone: ${ params . userTimezone } ` , "" ] ;
2026-01-31 15:50:15 +01:00
}
2026-01-16 00:28:43 +00:00
function buildReplyTagsSection ( isMinimal : boolean ) {
2026-02-02 21:15:43 -08:00
if ( isMinimal ) {
return [ ] ;
}
2026-01-20 16:46:15 +00:00
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." ,
2026-02-10 00:35:56 -06:00
"- Prefer [[reply_to_current]]. Use [[reply_to:<id>]] only when an id was explicitly provided (e.g. by the user or a tool)." ,
2026-01-20 16:46:15 +00:00
"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-20 16:46:15 +00:00
isMinimal : boolean ;
availableTools : Set < string > ;
messageChannelOptions : string ;
inlineButtonsEnabled : boolean ;
runtimeChannel? : string ;
2026-01-22 03:27:26 +00:00
messageToolHints? : string [ ] ;
2026-01-16 00:28:43 +00:00
} ) {
2026-02-02 21:15:43 -08:00
if ( params . isMinimal ) {
return [ ] ;
}
2026-01-20 16:46:15 +00:00
return [
"## Messaging" ,
"- Reply in current session → automatically routes to the source channel (Signal, Telegram, etc.)" ,
"- Cross-session messaging → use sessions_send(sessionKey, message)" ,
Agents: add nested subagent orchestration controls and reduce subagent token waste (#14447)
* Agents: add subagent orchestration controls
* Agents: add subagent orchestration controls (WIP uncommitted changes)
* feat(subagents): add depth-based spawn gating for sub-sub-agents
* feat(subagents): tool policy, registry, and announce chain for nested agents
* feat(subagents): system prompt, docs, changelog for nested sub-agents
* fix(subagents): prevent model fallback override, show model during active runs, and block context overflow fallback
Bug 1: When a session has an explicit model override (e.g., gpt/openai-codex),
the fallback candidate logic in resolveFallbackCandidates silently appended the
global primary model (opus) as a backstop. On reinjection/steer with a transient
error, the session could fall back to opus which has a smaller context window
and crash. Fix: when storedModelOverride is set, pass fallbacksOverride ?? []
instead of undefined, preventing the implicit primary backstop.
Bug 2: Active subagents showed 'model n/a' in /subagents list because
resolveModelDisplay only read entry.model/modelProvider (populated after run
completes). Fix: fall back to modelOverride/providerOverride fields which are
populated at spawn time via sessions.patch.
Bug 3: Context overflow errors (prompt too long, context_length_exceeded) could
theoretically escape runEmbeddedPiAgent and be treated as failover candidates
in runWithModelFallback, causing a switch to a model with a smaller context
window. Fix: in runWithModelFallback, detect context overflow errors via
isLikelyContextOverflowError and rethrow them immediately instead of trying the
next model candidate.
* fix(subagents): track spawn depth in session store and fix announce routing for nested agents
* Fix compaction status tracking and dedupe overflow compaction triggers
* fix(subagents): enforce depth block via session store and implement cascade kill
* fix: inject group chat context into system prompt
* fix(subagents): always write model to session store at spawn time
* Preserve spawnDepth when agent handler rewrites session entry
* fix(subagents): suppress announce on steer-restart
* fix(subagents): fallback spawned session model to runtime default
* fix(subagents): enforce spawn depth when caller key resolves by sessionId
* feat(subagents): implement active-first ordering for numeric targets and enhance task display
- Added a test to verify that subagents with numeric targets follow an active-first list ordering.
- Updated `resolveSubagentTarget` to sort subagent runs based on active status and recent activity.
- Enhanced task display in command responses to prevent truncation of long task descriptions.
- Introduced new utility functions for compacting task text and managing subagent run states.
* fix(subagents): show model for active runs via run record fallback
When the spawned model matches the agent's default model, the session
store's override fields are intentionally cleared (isDefault: true).
The model/modelProvider fields are only populated after the run
completes. This left active subagents showing 'model n/a'.
Fix: store the resolved model on SubagentRunRecord at registration
time, and use it as a fallback in both display paths (subagents tool
and /subagents command) when the session store entry has no model info.
Changes:
- SubagentRunRecord: add optional model field
- registerSubagentRun: accept and persist model param
- sessions-spawn-tool: pass resolvedModel to registerSubagentRun
- subagents-tool: pass run record model as fallback to resolveModelDisplay
- commands-subagents: pass run record model as fallback to resolveModelDisplay
* feat(chat): implement session key resolution and reset on sidebar navigation
- Added functions to resolve the main session key and reset chat state when switching sessions from the sidebar.
- Updated the `renderTab` function to handle session key changes when navigating to the chat tab.
- Introduced a test to verify that the session resets to "main" when opening chat from the sidebar navigation.
* fix: subagent timeout=0 passthrough and fallback prompt duplication
Bug 1: runTimeoutSeconds=0 now means 'no timeout' instead of applying 600s default
- sessions-spawn-tool: default to undefined (not 0) when neither timeout param
is provided; use != null check so explicit 0 passes through to gateway
- agent.ts: accept 0 as valid timeout (resolveAgentTimeoutMs already handles
0 → MAX_SAFE_TIMEOUT_MS)
Bug 2: model fallback no longer re-injects the original prompt as a duplicate
- agent.ts: track fallback attempt index; on retries use a short continuation
message instead of the full original prompt since the session file already
contains it from the first attempt
- Also skip re-sending images on fallback retries (already in session)
* feat(subagents): truncate long task descriptions in subagents command output
- Introduced a new utility function to format task previews, limiting their length to improve readability.
- Updated the command handler to use the new formatting function, ensuring task descriptions are truncated appropriately.
- Adjusted related tests to verify that long task descriptions are now truncated in the output.
* refactor(subagents): update subagent registry path resolution and improve command output formatting
- Replaced direct import of STATE_DIR with a utility function to resolve the state directory dynamically.
- Enhanced the formatting of command output for active and recent subagents, adding separators for better readability.
- Updated related tests to reflect changes in command output structure.
* fix(subagent): default sessions_spawn to no timeout when runTimeoutSeconds omitted
The previous fix (75a791106) correctly handled the case where
runTimeoutSeconds was explicitly set to 0 ("no timeout"). However,
when models omit the parameter entirely (which is common since the
schema marks it as optional), runTimeoutSeconds resolved to undefined.
undefined flowed through the chain as:
sessions_spawn → timeout: undefined (since undefined != null is false)
→ gateway agent handler → agentCommand opts.timeout: undefined
→ resolveAgentTimeoutMs({ overrideSeconds: undefined })
→ DEFAULT_AGENT_TIMEOUT_SECONDS (600s = 10 minutes)
This caused subagents to be killed at exactly 10 minutes even though
the user's intent (via TOOLS.md) was for subagents to run without a
timeout.
Fix: default runTimeoutSeconds to 0 (no timeout) when neither
runTimeoutSeconds nor timeoutSeconds is provided by the caller.
Subagent spawns are long-running by design and should not inherit the
600s agent-command default timeout.
* fix(subagent): accept timeout=0 in agent-via-gateway path (second 600s default)
* fix: thread timeout override through getReplyFromConfig dispatch path
getReplyFromConfig called resolveAgentTimeoutMs({ cfg }) with no override,
always falling back to the config default (600s). Add timeoutOverrideSeconds
to GetReplyOptions and pass it through as overrideSeconds so callers of the
dispatch chain can specify a custom timeout (0 = no timeout).
This complements the existing timeout threading in agentCommand and the
cron isolated-agent runner, which already pass overrideSeconds correctly.
* feat(model-fallback): normalize OpenAI Codex model references and enhance fallback handling
- Added normalization for OpenAI Codex model references, specifically converting "gpt-5.3-codex" to "openai-codex" before execution.
- Updated the `resolveFallbackCandidates` function to utilize the new normalization logic.
- Enhanced tests to verify the correct behavior of model normalization and fallback mechanisms.
- Introduced a new test case to ensure that the normalization process works as expected for various input formats.
* feat(tests): add unit tests for steer failure behavior in openclaw-tools
- Introduced a new test file to validate the behavior of subagents when steer replacement dispatch fails.
- Implemented tests to ensure that the announce behavior is restored correctly and that the suppression reason is cleared as expected.
- Enhanced the subagent registry with a new function to clear steer restart suppression.
- Updated related components to support the new test scenarios.
* fix(subagents): replace stop command with kill in slash commands and documentation
- Updated the `/subagents` command to replace `stop` with `kill` for consistency in controlling sub-agent runs.
- Modified related documentation to reflect the change in command usage.
- Removed legacy timeoutSeconds references from the sessions-spawn-tool schema and tests to streamline timeout handling.
- Enhanced tests to ensure correct behavior of the updated commands and their interactions.
* feat(tests): add unit tests for readLatestAssistantReply function
- Introduced a new test file for the `readLatestAssistantReply` function to validate its behavior with various message scenarios.
- Implemented tests to ensure the function correctly retrieves the latest assistant message and handles cases where the latest message has no text.
- Mocked the gateway call to simulate different message histories for comprehensive testing.
* feat(tests): enhance subagent kill-all cascade tests and announce formatting
- Added a new test to verify that the `kill-all` command cascades through ended parents to active descendants in subagents.
- Updated the subagent announce formatting tests to reflect changes in message structure, including the replacement of "Findings:" with "Result:" and the addition of new expectations for message content.
- Improved the handling of long findings and stats in the announce formatting logic to ensure concise output.
- Refactored related functions to enhance clarity and maintainability in the subagent registry and tools.
* refactor(subagent): update announce formatting and remove unused constants
- Modified the subagent announce formatting to replace "Findings:" with "Result:" and adjusted related expectations in tests.
- Removed constants for maximum announce findings characters and summary words, simplifying the announcement logic.
- Updated the handling of findings to retain full content instead of truncating, ensuring more informative outputs.
- Cleaned up unused imports in the commands-subagents file to enhance code clarity.
* feat(tests): enhance billing error handling in user-facing text
- Added tests to ensure that normal text mentioning billing plans is not rewritten, preserving user context.
- Updated the `isBillingErrorMessage` and `sanitizeUserFacingText` functions to improve handling of billing-related messages.
- Introduced new test cases for various scenarios involving billing messages to ensure accurate processing and output.
- Enhanced the subagent announce flow to correctly manage active descendant runs, preventing premature announcements.
* feat(subagent): enhance workflow guidance and auto-announcement clarity
- Added a new guideline in the subagent system prompt to emphasize trust in push-based completion, discouraging busy polling for status updates.
- Updated documentation to clarify that sub-agents will automatically announce their results, improving user understanding of the workflow.
- Enhanced tests to verify the new guidance on avoiding polling loops and to ensure the accuracy of the updated prompts.
* fix(cron): avoid announcing interim subagent spawn acks
* chore: clean post-rebase imports
* fix(cron): fall back to child replies when parent stays interim
* fix(subagents): make active-run guidance advisory
* fix(subagents): update announce flow to handle active descendants and enhance test coverage
- Modified the announce flow to defer announcements when active descendant runs are present, ensuring accurate status reporting.
- Updated tests to verify the new behavior, including scenarios where no fallback requester is available and ensuring proper handling of finished subagents.
- Enhanced the announce formatting to include an `expectFinal` flag for better clarity in the announcement process.
* fix(subagents): enhance announce flow and formatting for user updates
- Updated the announce flow to provide clearer instructions for user updates based on active subagent runs and requester context.
- Refactored the announcement logic to improve clarity and ensure internal context remains private.
- Enhanced tests to verify the new message expectations and formatting, including updated prompts for user-facing updates.
- Introduced a new function to build reply instructions based on session context, improving the overall announcement process.
* fix: resolve prep blockers and changelog placement (#14447) (thanks @tyler6204)
* fix: restore cron delivery-plan import after rebase (#14447) (thanks @tyler6204)
* fix: resolve test failures from rebase conflicts (#14447) (thanks @tyler6204)
* fix: apply formatting after rebase (#14447) (thanks @tyler6204)
2026-02-14 22:03:45 -08:00
"- Sub-agent orchestration → use subagents(action=list|steer|kill)" ,
"- `[System Message] ...` blocks are internal context and are not user-visible by default." ,
2026-02-16 08:19:15 -05:00
` - If a \` [System Message] \` reports completed cron/subagent work and asks for a user update, rewrite it in your normal assistant voice and send that update (do not forward raw system text or default to ${ SILENT_REPLY_TOKEN } ). ` ,
2026-02-02 21:10:10 -08:00
"- Never use exec/curl for provider messaging; OpenClaw handles all routing internally." ,
2026-01-20 16:46:15 +00:00
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
2026-02-16 22:48:47 +05:30
? "- Inline buttons supported. Use `action=send` with `buttons=[[{text,callback_data,style?}]]`; `style` can be `primary`, `success`, or `danger`."
2026-01-20 16:46:15 +00:00
: params . runtimeChannel
? ` - Inline buttons not enabled for ${ params . runtimeChannel } . If you need them, ask to set ${ params . runtimeChannel } .capabilities.inlineButtons ("dm"|"group"|"all"|"allowlist"). `
: "" ,
2026-01-22 03:27:26 +00:00
. . . ( params . messageToolHints ? ? [ ] ) ,
2026-01-20 16:46:15 +00:00
]
. filter ( Boolean )
. join ( "\n" )
: "" ,
"" ,
] ;
2026-01-16 00:28:43 +00:00
}
2026-01-24 10:25:37 +00:00
function buildVoiceSection ( params : { isMinimal : boolean ; ttsHint? : string } ) {
2026-02-02 21:15:43 -08:00
if ( params . isMinimal ) {
return [ ] ;
}
2026-01-24 10:25:37 +00:00
const hint = params . ttsHint ? . trim ( ) ;
2026-02-02 21:15:43 -08:00
if ( ! hint ) {
return [ ] ;
}
2026-01-24 10:25:37 +00:00
return [ "## Voice (TTS)" , hint , "" ] ;
}
2026-02-16 15:20:42 +00:00
function buildLlmsTxtSection ( params : { isMinimal : boolean ; availableTools : Set < string > } ) {
if ( params . isMinimal ) {
return [ ] ;
}
if ( ! params . availableTools . has ( "web_fetch" ) ) {
return [ ] ;
}
return [
"## llms.txt Discovery" ,
"When exploring a new domain or website (via web_fetch or browser), check for an llms.txt file that describes how AI agents should interact with the site:" ,
"- Try `/llms.txt` or `/.well-known/llms.txt` at the domain root" ,
"- If found, follow its guidance for interacting with that site's content and APIs" ,
"- llms.txt is an emerging standard (like robots.txt for AI) — not all sites have one, so don't warn if missing" ,
"" ,
] ;
}
2026-01-18 18:43:31 +00:00
function buildDocsSection ( params : { docsPath? : string ; isMinimal : boolean ; readToolName : string } ) {
2026-01-20 16:46:15 +00:00
const docsPath = params . docsPath ? . trim ( ) ;
2026-02-02 21:15:43 -08:00
if ( ! docsPath || params . isMinimal ) {
return [ ] ;
}
2026-01-20 16:46:15 +00:00
return [
"## Documentation" ,
2026-02-02 21:10:10 -08:00
` OpenClaw docs: ${ docsPath } ` ,
2026-02-03 10:00:59 -08:00
"Mirror: https://docs.openclaw.ai" ,
"Source: https://github.com/openclaw/openclaw" ,
2026-01-20 16:46:15 +00:00
"Community: https://discord.com/invite/clawd" ,
2026-02-03 10:00:59 -08:00
"Find new skills: https://clawhub.com" ,
2026-02-02 21:10:10 -08:00
"For OpenClaw behavior, commands, config, or architecture: consult local docs first." ,
2026-02-03 10:00:59 -08:00
"When diagnosing issues, run `openclaw status` yourself when possible; only ask the user if you lack access (e.g., sandboxed)." ,
2026-01-20 16:46:15 +00:00
"" ,
] ;
2026-01-18 15:00:14 +00:00
}
2026-01-08 02:20:18 +01:00
export function buildAgentSystemPrompt ( params : {
2026-01-20 16:46:15 +00: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 ;
2026-01-22 08:05:47 +00:00
workspaceNotes? : string [ ] ;
2026-01-24 10:25:37 +00:00
ttsHint? : string ;
2026-01-20 16:46:15 +00:00
/** Controls which hardcoded sections to include. Defaults to "full". */
promptMode? : PromptMode ;
runtimeInfo ? : {
agentId? : string ;
host? : string ;
os? : string ;
arch? : string ;
node? : string ;
model? : string ;
defaultModel? : string ;
2026-02-07 11:32:31 -06:00
shell? : string ;
2026-01-20 16:46:15 +00:00
channel? : string ;
capabilities? : string [ ] ;
2026-01-22 05:07:40 +00:00
repoRoot? : string ;
2026-01-20 16:46:15 +00:00
} ;
2026-01-22 03:27:26 +00:00
messageToolHints? : string [ ] ;
2026-01-20 16:46:15 +00:00
sandboxInfo ? : {
enabled : boolean ;
workspaceDir? : string ;
2026-02-14 20:20:29 -08:00
containerWorkspaceDir? : string ;
2026-01-20 16:46:15 +00:00
workspaceAccess ? : "none" | "ro" | "rw" ;
agentWorkspaceMount? : string ;
2026-01-27 03:23:42 +00:00
browserBridgeUrl? : string ;
2026-01-20 16:46:15 +00:00
browserNoVncUrl? : string ;
hostBrowserAllowed? : boolean ;
elevated ? : {
allowed : boolean ;
2026-01-22 05:32:13 +00:00
defaultLevel : "on" | "off" | "ask" | "full" ;
2026-01-20 16:46:15 +00:00
} ;
} ;
/** Reaction guidance for the agent (for Telegram minimal/extensive modes). */
reactionGuidance ? : {
level : "minimal" | "extensive" ;
channel : string ;
} ;
2026-01-27 21:57:15 -08:00
memoryCitationsMode? : MemoryCitationsMode ;
2025-12-17 11:29:04 +01:00
} ) {
2026-01-20 16:46:15 +00: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" ,
2026-02-02 17:27:56 +00:00
nodes : "List/describe/notify/camera/screen on paired nodes" ,
2026-01-19 10:42:21 +09:00
cron : "Manage cron jobs and wake events (use for reminders; when scheduling a reminder, write the systemEvent text as something that will read like a reminder when it fires, and mention that it is a reminder depending on the time gap between setting and firing; include recent context in reminder text if appropriate)" ,
2026-01-20 16:46:15 +00:00
message : "Send messages and channel actions" ,
2026-02-02 21:10:10 -08:00
gateway : "Restart, apply config, or run updates on the running OpenClaw process" ,
2026-01-20 16:46:15 +00:00
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" ,
Agents: add nested subagent orchestration controls and reduce subagent token waste (#14447)
* Agents: add subagent orchestration controls
* Agents: add subagent orchestration controls (WIP uncommitted changes)
* feat(subagents): add depth-based spawn gating for sub-sub-agents
* feat(subagents): tool policy, registry, and announce chain for nested agents
* feat(subagents): system prompt, docs, changelog for nested sub-agents
* fix(subagents): prevent model fallback override, show model during active runs, and block context overflow fallback
Bug 1: When a session has an explicit model override (e.g., gpt/openai-codex),
the fallback candidate logic in resolveFallbackCandidates silently appended the
global primary model (opus) as a backstop. On reinjection/steer with a transient
error, the session could fall back to opus which has a smaller context window
and crash. Fix: when storedModelOverride is set, pass fallbacksOverride ?? []
instead of undefined, preventing the implicit primary backstop.
Bug 2: Active subagents showed 'model n/a' in /subagents list because
resolveModelDisplay only read entry.model/modelProvider (populated after run
completes). Fix: fall back to modelOverride/providerOverride fields which are
populated at spawn time via sessions.patch.
Bug 3: Context overflow errors (prompt too long, context_length_exceeded) could
theoretically escape runEmbeddedPiAgent and be treated as failover candidates
in runWithModelFallback, causing a switch to a model with a smaller context
window. Fix: in runWithModelFallback, detect context overflow errors via
isLikelyContextOverflowError and rethrow them immediately instead of trying the
next model candidate.
* fix(subagents): track spawn depth in session store and fix announce routing for nested agents
* Fix compaction status tracking and dedupe overflow compaction triggers
* fix(subagents): enforce depth block via session store and implement cascade kill
* fix: inject group chat context into system prompt
* fix(subagents): always write model to session store at spawn time
* Preserve spawnDepth when agent handler rewrites session entry
* fix(subagents): suppress announce on steer-restart
* fix(subagents): fallback spawned session model to runtime default
* fix(subagents): enforce spawn depth when caller key resolves by sessionId
* feat(subagents): implement active-first ordering for numeric targets and enhance task display
- Added a test to verify that subagents with numeric targets follow an active-first list ordering.
- Updated `resolveSubagentTarget` to sort subagent runs based on active status and recent activity.
- Enhanced task display in command responses to prevent truncation of long task descriptions.
- Introduced new utility functions for compacting task text and managing subagent run states.
* fix(subagents): show model for active runs via run record fallback
When the spawned model matches the agent's default model, the session
store's override fields are intentionally cleared (isDefault: true).
The model/modelProvider fields are only populated after the run
completes. This left active subagents showing 'model n/a'.
Fix: store the resolved model on SubagentRunRecord at registration
time, and use it as a fallback in both display paths (subagents tool
and /subagents command) when the session store entry has no model info.
Changes:
- SubagentRunRecord: add optional model field
- registerSubagentRun: accept and persist model param
- sessions-spawn-tool: pass resolvedModel to registerSubagentRun
- subagents-tool: pass run record model as fallback to resolveModelDisplay
- commands-subagents: pass run record model as fallback to resolveModelDisplay
* feat(chat): implement session key resolution and reset on sidebar navigation
- Added functions to resolve the main session key and reset chat state when switching sessions from the sidebar.
- Updated the `renderTab` function to handle session key changes when navigating to the chat tab.
- Introduced a test to verify that the session resets to "main" when opening chat from the sidebar navigation.
* fix: subagent timeout=0 passthrough and fallback prompt duplication
Bug 1: runTimeoutSeconds=0 now means 'no timeout' instead of applying 600s default
- sessions-spawn-tool: default to undefined (not 0) when neither timeout param
is provided; use != null check so explicit 0 passes through to gateway
- agent.ts: accept 0 as valid timeout (resolveAgentTimeoutMs already handles
0 → MAX_SAFE_TIMEOUT_MS)
Bug 2: model fallback no longer re-injects the original prompt as a duplicate
- agent.ts: track fallback attempt index; on retries use a short continuation
message instead of the full original prompt since the session file already
contains it from the first attempt
- Also skip re-sending images on fallback retries (already in session)
* feat(subagents): truncate long task descriptions in subagents command output
- Introduced a new utility function to format task previews, limiting their length to improve readability.
- Updated the command handler to use the new formatting function, ensuring task descriptions are truncated appropriately.
- Adjusted related tests to verify that long task descriptions are now truncated in the output.
* refactor(subagents): update subagent registry path resolution and improve command output formatting
- Replaced direct import of STATE_DIR with a utility function to resolve the state directory dynamically.
- Enhanced the formatting of command output for active and recent subagents, adding separators for better readability.
- Updated related tests to reflect changes in command output structure.
* fix(subagent): default sessions_spawn to no timeout when runTimeoutSeconds omitted
The previous fix (75a791106) correctly handled the case where
runTimeoutSeconds was explicitly set to 0 ("no timeout"). However,
when models omit the parameter entirely (which is common since the
schema marks it as optional), runTimeoutSeconds resolved to undefined.
undefined flowed through the chain as:
sessions_spawn → timeout: undefined (since undefined != null is false)
→ gateway agent handler → agentCommand opts.timeout: undefined
→ resolveAgentTimeoutMs({ overrideSeconds: undefined })
→ DEFAULT_AGENT_TIMEOUT_SECONDS (600s = 10 minutes)
This caused subagents to be killed at exactly 10 minutes even though
the user's intent (via TOOLS.md) was for subagents to run without a
timeout.
Fix: default runTimeoutSeconds to 0 (no timeout) when neither
runTimeoutSeconds nor timeoutSeconds is provided by the caller.
Subagent spawns are long-running by design and should not inherit the
600s agent-command default timeout.
* fix(subagent): accept timeout=0 in agent-via-gateway path (second 600s default)
* fix: thread timeout override through getReplyFromConfig dispatch path
getReplyFromConfig called resolveAgentTimeoutMs({ cfg }) with no override,
always falling back to the config default (600s). Add timeoutOverrideSeconds
to GetReplyOptions and pass it through as overrideSeconds so callers of the
dispatch chain can specify a custom timeout (0 = no timeout).
This complements the existing timeout threading in agentCommand and the
cron isolated-agent runner, which already pass overrideSeconds correctly.
* feat(model-fallback): normalize OpenAI Codex model references and enhance fallback handling
- Added normalization for OpenAI Codex model references, specifically converting "gpt-5.3-codex" to "openai-codex" before execution.
- Updated the `resolveFallbackCandidates` function to utilize the new normalization logic.
- Enhanced tests to verify the correct behavior of model normalization and fallback mechanisms.
- Introduced a new test case to ensure that the normalization process works as expected for various input formats.
* feat(tests): add unit tests for steer failure behavior in openclaw-tools
- Introduced a new test file to validate the behavior of subagents when steer replacement dispatch fails.
- Implemented tests to ensure that the announce behavior is restored correctly and that the suppression reason is cleared as expected.
- Enhanced the subagent registry with a new function to clear steer restart suppression.
- Updated related components to support the new test scenarios.
* fix(subagents): replace stop command with kill in slash commands and documentation
- Updated the `/subagents` command to replace `stop` with `kill` for consistency in controlling sub-agent runs.
- Modified related documentation to reflect the change in command usage.
- Removed legacy timeoutSeconds references from the sessions-spawn-tool schema and tests to streamline timeout handling.
- Enhanced tests to ensure correct behavior of the updated commands and their interactions.
* feat(tests): add unit tests for readLatestAssistantReply function
- Introduced a new test file for the `readLatestAssistantReply` function to validate its behavior with various message scenarios.
- Implemented tests to ensure the function correctly retrieves the latest assistant message and handles cases where the latest message has no text.
- Mocked the gateway call to simulate different message histories for comprehensive testing.
* feat(tests): enhance subagent kill-all cascade tests and announce formatting
- Added a new test to verify that the `kill-all` command cascades through ended parents to active descendants in subagents.
- Updated the subagent announce formatting tests to reflect changes in message structure, including the replacement of "Findings:" with "Result:" and the addition of new expectations for message content.
- Improved the handling of long findings and stats in the announce formatting logic to ensure concise output.
- Refactored related functions to enhance clarity and maintainability in the subagent registry and tools.
* refactor(subagent): update announce formatting and remove unused constants
- Modified the subagent announce formatting to replace "Findings:" with "Result:" and adjusted related expectations in tests.
- Removed constants for maximum announce findings characters and summary words, simplifying the announcement logic.
- Updated the handling of findings to retain full content instead of truncating, ensuring more informative outputs.
- Cleaned up unused imports in the commands-subagents file to enhance code clarity.
* feat(tests): enhance billing error handling in user-facing text
- Added tests to ensure that normal text mentioning billing plans is not rewritten, preserving user context.
- Updated the `isBillingErrorMessage` and `sanitizeUserFacingText` functions to improve handling of billing-related messages.
- Introduced new test cases for various scenarios involving billing messages to ensure accurate processing and output.
- Enhanced the subagent announce flow to correctly manage active descendant runs, preventing premature announcements.
* feat(subagent): enhance workflow guidance and auto-announcement clarity
- Added a new guideline in the subagent system prompt to emphasize trust in push-based completion, discouraging busy polling for status updates.
- Updated documentation to clarify that sub-agents will automatically announce their results, improving user understanding of the workflow.
- Enhanced tests to verify the new guidance on avoiding polling loops and to ensure the accuracy of the updated prompts.
* fix(cron): avoid announcing interim subagent spawn acks
* chore: clean post-rebase imports
* fix(cron): fall back to child replies when parent stays interim
* fix(subagents): make active-run guidance advisory
* fix(subagents): update announce flow to handle active descendants and enhance test coverage
- Modified the announce flow to defer announcements when active descendant runs are present, ensuring accurate status reporting.
- Updated tests to verify the new behavior, including scenarios where no fallback requester is available and ensuring proper handling of finished subagents.
- Enhanced the announce formatting to include an `expectFinal` flag for better clarity in the announcement process.
* fix(subagents): enhance announce flow and formatting for user updates
- Updated the announce flow to provide clearer instructions for user updates based on active subagent runs and requester context.
- Refactored the announcement logic to improve clarity and ensure internal context remains private.
- Enhanced tests to verify the new message expectations and formatting, including updated prompts for user-facing updates.
- Introduced a new function to build reply instructions based on session context, improving the overall announcement process.
* fix: resolve prep blockers and changelog placement (#14447) (thanks @tyler6204)
* fix: restore cron delivery-plan import after rebase (#14447) (thanks @tyler6204)
* fix: resolve test failures from rebase conflicts (#14447) (thanks @tyler6204)
* fix: apply formatting after rebase (#14447) (thanks @tyler6204)
2026-02-14 22:03:45 -08:00
subagents : "List, steer, or kill sub-agent runs for this requester session" ,
2026-01-20 16:46:15 +00:00
session_status :
2026-01-24 06:22:54 +00:00
"Show a /status-equivalent status card (usage + time + Reasoning/Verbose/Elevated); use for model-use questions (📊 session_status); optional per-session model override" ,
2026-02-16 22:54:58 +02:00
architect_pipeline :
"Manage Architect CEO orchestration state and audit decision gates for multi-agent build loops" ,
venture_studio :
"Capture web/forum pain-point research and generate monetized app plans + workflow documents; pair with web_search/web_fetch for source discovery" ,
2026-01-20 16:46:15 +00:00
image : "Analyze an image with the configured image model" ,
} ;
2026-01-05 06:32:44 +00:00
2026-01-20 16:46:15 +00: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" ,
Agents: add nested subagent orchestration controls and reduce subagent token waste (#14447)
* Agents: add subagent orchestration controls
* Agents: add subagent orchestration controls (WIP uncommitted changes)
* feat(subagents): add depth-based spawn gating for sub-sub-agents
* feat(subagents): tool policy, registry, and announce chain for nested agents
* feat(subagents): system prompt, docs, changelog for nested sub-agents
* fix(subagents): prevent model fallback override, show model during active runs, and block context overflow fallback
Bug 1: When a session has an explicit model override (e.g., gpt/openai-codex),
the fallback candidate logic in resolveFallbackCandidates silently appended the
global primary model (opus) as a backstop. On reinjection/steer with a transient
error, the session could fall back to opus which has a smaller context window
and crash. Fix: when storedModelOverride is set, pass fallbacksOverride ?? []
instead of undefined, preventing the implicit primary backstop.
Bug 2: Active subagents showed 'model n/a' in /subagents list because
resolveModelDisplay only read entry.model/modelProvider (populated after run
completes). Fix: fall back to modelOverride/providerOverride fields which are
populated at spawn time via sessions.patch.
Bug 3: Context overflow errors (prompt too long, context_length_exceeded) could
theoretically escape runEmbeddedPiAgent and be treated as failover candidates
in runWithModelFallback, causing a switch to a model with a smaller context
window. Fix: in runWithModelFallback, detect context overflow errors via
isLikelyContextOverflowError and rethrow them immediately instead of trying the
next model candidate.
* fix(subagents): track spawn depth in session store and fix announce routing for nested agents
* Fix compaction status tracking and dedupe overflow compaction triggers
* fix(subagents): enforce depth block via session store and implement cascade kill
* fix: inject group chat context into system prompt
* fix(subagents): always write model to session store at spawn time
* Preserve spawnDepth when agent handler rewrites session entry
* fix(subagents): suppress announce on steer-restart
* fix(subagents): fallback spawned session model to runtime default
* fix(subagents): enforce spawn depth when caller key resolves by sessionId
* feat(subagents): implement active-first ordering for numeric targets and enhance task display
- Added a test to verify that subagents with numeric targets follow an active-first list ordering.
- Updated `resolveSubagentTarget` to sort subagent runs based on active status and recent activity.
- Enhanced task display in command responses to prevent truncation of long task descriptions.
- Introduced new utility functions for compacting task text and managing subagent run states.
* fix(subagents): show model for active runs via run record fallback
When the spawned model matches the agent's default model, the session
store's override fields are intentionally cleared (isDefault: true).
The model/modelProvider fields are only populated after the run
completes. This left active subagents showing 'model n/a'.
Fix: store the resolved model on SubagentRunRecord at registration
time, and use it as a fallback in both display paths (subagents tool
and /subagents command) when the session store entry has no model info.
Changes:
- SubagentRunRecord: add optional model field
- registerSubagentRun: accept and persist model param
- sessions-spawn-tool: pass resolvedModel to registerSubagentRun
- subagents-tool: pass run record model as fallback to resolveModelDisplay
- commands-subagents: pass run record model as fallback to resolveModelDisplay
* feat(chat): implement session key resolution and reset on sidebar navigation
- Added functions to resolve the main session key and reset chat state when switching sessions from the sidebar.
- Updated the `renderTab` function to handle session key changes when navigating to the chat tab.
- Introduced a test to verify that the session resets to "main" when opening chat from the sidebar navigation.
* fix: subagent timeout=0 passthrough and fallback prompt duplication
Bug 1: runTimeoutSeconds=0 now means 'no timeout' instead of applying 600s default
- sessions-spawn-tool: default to undefined (not 0) when neither timeout param
is provided; use != null check so explicit 0 passes through to gateway
- agent.ts: accept 0 as valid timeout (resolveAgentTimeoutMs already handles
0 → MAX_SAFE_TIMEOUT_MS)
Bug 2: model fallback no longer re-injects the original prompt as a duplicate
- agent.ts: track fallback attempt index; on retries use a short continuation
message instead of the full original prompt since the session file already
contains it from the first attempt
- Also skip re-sending images on fallback retries (already in session)
* feat(subagents): truncate long task descriptions in subagents command output
- Introduced a new utility function to format task previews, limiting their length to improve readability.
- Updated the command handler to use the new formatting function, ensuring task descriptions are truncated appropriately.
- Adjusted related tests to verify that long task descriptions are now truncated in the output.
* refactor(subagents): update subagent registry path resolution and improve command output formatting
- Replaced direct import of STATE_DIR with a utility function to resolve the state directory dynamically.
- Enhanced the formatting of command output for active and recent subagents, adding separators for better readability.
- Updated related tests to reflect changes in command output structure.
* fix(subagent): default sessions_spawn to no timeout when runTimeoutSeconds omitted
The previous fix (75a791106) correctly handled the case where
runTimeoutSeconds was explicitly set to 0 ("no timeout"). However,
when models omit the parameter entirely (which is common since the
schema marks it as optional), runTimeoutSeconds resolved to undefined.
undefined flowed through the chain as:
sessions_spawn → timeout: undefined (since undefined != null is false)
→ gateway agent handler → agentCommand opts.timeout: undefined
→ resolveAgentTimeoutMs({ overrideSeconds: undefined })
→ DEFAULT_AGENT_TIMEOUT_SECONDS (600s = 10 minutes)
This caused subagents to be killed at exactly 10 minutes even though
the user's intent (via TOOLS.md) was for subagents to run without a
timeout.
Fix: default runTimeoutSeconds to 0 (no timeout) when neither
runTimeoutSeconds nor timeoutSeconds is provided by the caller.
Subagent spawns are long-running by design and should not inherit the
600s agent-command default timeout.
* fix(subagent): accept timeout=0 in agent-via-gateway path (second 600s default)
* fix: thread timeout override through getReplyFromConfig dispatch path
getReplyFromConfig called resolveAgentTimeoutMs({ cfg }) with no override,
always falling back to the config default (600s). Add timeoutOverrideSeconds
to GetReplyOptions and pass it through as overrideSeconds so callers of the
dispatch chain can specify a custom timeout (0 = no timeout).
This complements the existing timeout threading in agentCommand and the
cron isolated-agent runner, which already pass overrideSeconds correctly.
* feat(model-fallback): normalize OpenAI Codex model references and enhance fallback handling
- Added normalization for OpenAI Codex model references, specifically converting "gpt-5.3-codex" to "openai-codex" before execution.
- Updated the `resolveFallbackCandidates` function to utilize the new normalization logic.
- Enhanced tests to verify the correct behavior of model normalization and fallback mechanisms.
- Introduced a new test case to ensure that the normalization process works as expected for various input formats.
* feat(tests): add unit tests for steer failure behavior in openclaw-tools
- Introduced a new test file to validate the behavior of subagents when steer replacement dispatch fails.
- Implemented tests to ensure that the announce behavior is restored correctly and that the suppression reason is cleared as expected.
- Enhanced the subagent registry with a new function to clear steer restart suppression.
- Updated related components to support the new test scenarios.
* fix(subagents): replace stop command with kill in slash commands and documentation
- Updated the `/subagents` command to replace `stop` with `kill` for consistency in controlling sub-agent runs.
- Modified related documentation to reflect the change in command usage.
- Removed legacy timeoutSeconds references from the sessions-spawn-tool schema and tests to streamline timeout handling.
- Enhanced tests to ensure correct behavior of the updated commands and their interactions.
* feat(tests): add unit tests for readLatestAssistantReply function
- Introduced a new test file for the `readLatestAssistantReply` function to validate its behavior with various message scenarios.
- Implemented tests to ensure the function correctly retrieves the latest assistant message and handles cases where the latest message has no text.
- Mocked the gateway call to simulate different message histories for comprehensive testing.
* feat(tests): enhance subagent kill-all cascade tests and announce formatting
- Added a new test to verify that the `kill-all` command cascades through ended parents to active descendants in subagents.
- Updated the subagent announce formatting tests to reflect changes in message structure, including the replacement of "Findings:" with "Result:" and the addition of new expectations for message content.
- Improved the handling of long findings and stats in the announce formatting logic to ensure concise output.
- Refactored related functions to enhance clarity and maintainability in the subagent registry and tools.
* refactor(subagent): update announce formatting and remove unused constants
- Modified the subagent announce formatting to replace "Findings:" with "Result:" and adjusted related expectations in tests.
- Removed constants for maximum announce findings characters and summary words, simplifying the announcement logic.
- Updated the handling of findings to retain full content instead of truncating, ensuring more informative outputs.
- Cleaned up unused imports in the commands-subagents file to enhance code clarity.
* feat(tests): enhance billing error handling in user-facing text
- Added tests to ensure that normal text mentioning billing plans is not rewritten, preserving user context.
- Updated the `isBillingErrorMessage` and `sanitizeUserFacingText` functions to improve handling of billing-related messages.
- Introduced new test cases for various scenarios involving billing messages to ensure accurate processing and output.
- Enhanced the subagent announce flow to correctly manage active descendant runs, preventing premature announcements.
* feat(subagent): enhance workflow guidance and auto-announcement clarity
- Added a new guideline in the subagent system prompt to emphasize trust in push-based completion, discouraging busy polling for status updates.
- Updated documentation to clarify that sub-agents will automatically announce their results, improving user understanding of the workflow.
- Enhanced tests to verify the new guidance on avoiding polling loops and to ensure the accuracy of the updated prompts.
* fix(cron): avoid announcing interim subagent spawn acks
* chore: clean post-rebase imports
* fix(cron): fall back to child replies when parent stays interim
* fix(subagents): make active-run guidance advisory
* fix(subagents): update announce flow to handle active descendants and enhance test coverage
- Modified the announce flow to defer announcements when active descendant runs are present, ensuring accurate status reporting.
- Updated tests to verify the new behavior, including scenarios where no fallback requester is available and ensuring proper handling of finished subagents.
- Enhanced the announce formatting to include an `expectFinal` flag for better clarity in the announcement process.
* fix(subagents): enhance announce flow and formatting for user updates
- Updated the announce flow to provide clearer instructions for user updates based on active subagent runs and requester context.
- Refactored the announcement logic to improve clarity and ensure internal context remains private.
- Enhanced tests to verify the new message expectations and formatting, including updated prompts for user-facing updates.
- Introduced a new function to build reply instructions based on session context, improving the overall announcement process.
* fix: resolve prep blockers and changelog placement (#14447) (thanks @tyler6204)
* fix: restore cron delivery-plan import after rebase (#14447) (thanks @tyler6204)
* fix: resolve test failures from rebase conflicts (#14447) (thanks @tyler6204)
* fix: apply formatting after rebase (#14447) (thanks @tyler6204)
2026-02-14 22:03:45 -08:00
"subagents" ,
2026-01-20 16:46:15 +00:00
"session_status" ,
2026-02-16 22:54:58 +02:00
"architect_pipeline" ,
"venture_studio" ,
2026-01-20 16:46:15 +00:00
"image" ,
] ;
2026-01-05 06:32:44 +00:00
2026-01-20 16:46:15 +00: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-20 16:46:15 +00: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 ( ) ;
2026-02-02 21:15:43 -08:00
if ( ! normalized || ! value ? . trim ( ) ) {
continue ;
}
2026-01-20 16:46:15 +00:00
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 } ` ;
} ) ;
2026-02-02 21:15:43 -08:00
for ( const tool of extraTools . toSorted ( ) ) {
2026-01-20 16:46:15 +00:00
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-20 16:46:15 +00: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 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" ;
2026-02-14 20:20:29 -08:00
const sandboxContainerWorkspace = params . sandboxInfo ? . containerWorkspaceDir ? . trim ( ) ;
2026-02-16 02:48:46 +01:00
const sanitizedWorkspaceDir = sanitizeForPromptLiteral ( params . workspaceDir ) ;
const sanitizedSandboxContainerWorkspace = sandboxContainerWorkspace
? sanitizeForPromptLiteral ( sandboxContainerWorkspace )
: "" ;
2026-02-14 20:20:29 -08:00
const displayWorkspaceDir =
2026-02-16 02:48:46 +01:00
params . sandboxInfo ? . enabled && sanitizedSandboxContainerWorkspace
? sanitizedSandboxContainerWorkspace
: sanitizedWorkspaceDir ;
2026-02-14 20:20:29 -08:00
const workspaceGuidance =
2026-02-16 02:48:46 +01:00
params . sandboxInfo ? . enabled && sanitizedSandboxContainerWorkspace
2026-02-15 19:15:57 -08:00
? ` For read/write/edit/apply_patch, file paths resolve against host workspace: ${ sanitizedWorkspaceDir } . For bash/exec commands, use sandbox container paths under ${ sanitizedSandboxContainerWorkspace } (or relative paths from that workdir), not host paths. Prefer relative paths so both sandboxed exec and file tools work consistently. `
2026-02-14 20:20:29 -08:00
: "Treat this directory as the single global workspace for file operations unless explicitly instructed otherwise." ;
2026-02-02 22:56:29 -08:00
const safetySection = [
"## Safety" ,
"You have no independent goals: do not pursue self-preservation, replication, resource acquisition, or power-seeking; avoid long-term plans beyond the user's request." ,
"Prioritize safety and human oversight over completion; if instructions conflict, pause and ask; comply with stop/pause/audit requests and never bypass safeguards. (Inspired by Anthropic's constitution.)" ,
"Do not manipulate or persuade anyone to expand access or disable safeguards. Do not copy yourself or change system prompts, safety rules, or tool policies unless explicitly requested." ,
"" ,
] ;
2026-01-20 16:46:15 +00:00
const skillsSection = buildSkillsSection ( {
skillsPrompt ,
isMinimal ,
readToolName ,
} ) ;
2026-01-27 21:57:15 -08:00
const memorySection = buildMemorySection ( {
isMinimal ,
availableTools ,
citationsMode : params.memoryCitationsMode ,
} ) ;
2026-01-20 16:46:15 +00:00
const docsSection = buildDocsSection ( {
docsPath : params.docsPath ,
isMinimal ,
readToolName ,
} ) ;
2026-01-22 08:05:47 +00:00
const workspaceNotes = ( params . workspaceNotes ? ? [ ] ) . map ( ( note ) = > note . trim ( ) ) . filter ( Boolean ) ;
2025-12-23 13:32:07 +00:00
2026-01-20 16:46:15 +00:00
// For "none" mode, return just the basic identity line
if ( promptMode === "none" ) {
2026-02-02 21:10:10 -08:00
return "You are a personal assistant running inside OpenClaw." ;
2026-01-20 16:46:15 +00:00
}
2026-01-15 23:06:58 +00:00
2026-01-20 16:46:15 +00:00
const lines = [
2026-02-02 21:10:10 -08:00
"You are a personal assistant running inside OpenClaw." ,
2026-01-20 16:46:15 +00:00
"" ,
"## 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 ` ,
2026-02-02 21:27:41 -08:00
"- browser: control OpenClaw's dedicated browser" ,
2026-01-20 16:46:15 +00:00
"- canvas: present/eval/snapshot the Canvas" ,
2026-02-02 17:27:56 +00:00
"- nodes: list/describe/notify/camera/screen on paired nodes" ,
2026-01-19 10:42:21 +09:00
"- cron: manage cron jobs and wake events (use for reminders; when scheduling a reminder, write the systemEvent text as something that will read like a reminder when it fires, and mention that it is a reminder depending on the time gap between setting and firing; include recent context in reminder text if appropriate)" ,
2026-01-20 16:46:15 +00:00
"- sessions_list: list sessions" ,
"- sessions_history: fetch session history" ,
"- sessions_send: send to another session" ,
Agents: add nested subagent orchestration controls and reduce subagent token waste (#14447)
* Agents: add subagent orchestration controls
* Agents: add subagent orchestration controls (WIP uncommitted changes)
* feat(subagents): add depth-based spawn gating for sub-sub-agents
* feat(subagents): tool policy, registry, and announce chain for nested agents
* feat(subagents): system prompt, docs, changelog for nested sub-agents
* fix(subagents): prevent model fallback override, show model during active runs, and block context overflow fallback
Bug 1: When a session has an explicit model override (e.g., gpt/openai-codex),
the fallback candidate logic in resolveFallbackCandidates silently appended the
global primary model (opus) as a backstop. On reinjection/steer with a transient
error, the session could fall back to opus which has a smaller context window
and crash. Fix: when storedModelOverride is set, pass fallbacksOverride ?? []
instead of undefined, preventing the implicit primary backstop.
Bug 2: Active subagents showed 'model n/a' in /subagents list because
resolveModelDisplay only read entry.model/modelProvider (populated after run
completes). Fix: fall back to modelOverride/providerOverride fields which are
populated at spawn time via sessions.patch.
Bug 3: Context overflow errors (prompt too long, context_length_exceeded) could
theoretically escape runEmbeddedPiAgent and be treated as failover candidates
in runWithModelFallback, causing a switch to a model with a smaller context
window. Fix: in runWithModelFallback, detect context overflow errors via
isLikelyContextOverflowError and rethrow them immediately instead of trying the
next model candidate.
* fix(subagents): track spawn depth in session store and fix announce routing for nested agents
* Fix compaction status tracking and dedupe overflow compaction triggers
* fix(subagents): enforce depth block via session store and implement cascade kill
* fix: inject group chat context into system prompt
* fix(subagents): always write model to session store at spawn time
* Preserve spawnDepth when agent handler rewrites session entry
* fix(subagents): suppress announce on steer-restart
* fix(subagents): fallback spawned session model to runtime default
* fix(subagents): enforce spawn depth when caller key resolves by sessionId
* feat(subagents): implement active-first ordering for numeric targets and enhance task display
- Added a test to verify that subagents with numeric targets follow an active-first list ordering.
- Updated `resolveSubagentTarget` to sort subagent runs based on active status and recent activity.
- Enhanced task display in command responses to prevent truncation of long task descriptions.
- Introduced new utility functions for compacting task text and managing subagent run states.
* fix(subagents): show model for active runs via run record fallback
When the spawned model matches the agent's default model, the session
store's override fields are intentionally cleared (isDefault: true).
The model/modelProvider fields are only populated after the run
completes. This left active subagents showing 'model n/a'.
Fix: store the resolved model on SubagentRunRecord at registration
time, and use it as a fallback in both display paths (subagents tool
and /subagents command) when the session store entry has no model info.
Changes:
- SubagentRunRecord: add optional model field
- registerSubagentRun: accept and persist model param
- sessions-spawn-tool: pass resolvedModel to registerSubagentRun
- subagents-tool: pass run record model as fallback to resolveModelDisplay
- commands-subagents: pass run record model as fallback to resolveModelDisplay
* feat(chat): implement session key resolution and reset on sidebar navigation
- Added functions to resolve the main session key and reset chat state when switching sessions from the sidebar.
- Updated the `renderTab` function to handle session key changes when navigating to the chat tab.
- Introduced a test to verify that the session resets to "main" when opening chat from the sidebar navigation.
* fix: subagent timeout=0 passthrough and fallback prompt duplication
Bug 1: runTimeoutSeconds=0 now means 'no timeout' instead of applying 600s default
- sessions-spawn-tool: default to undefined (not 0) when neither timeout param
is provided; use != null check so explicit 0 passes through to gateway
- agent.ts: accept 0 as valid timeout (resolveAgentTimeoutMs already handles
0 → MAX_SAFE_TIMEOUT_MS)
Bug 2: model fallback no longer re-injects the original prompt as a duplicate
- agent.ts: track fallback attempt index; on retries use a short continuation
message instead of the full original prompt since the session file already
contains it from the first attempt
- Also skip re-sending images on fallback retries (already in session)
* feat(subagents): truncate long task descriptions in subagents command output
- Introduced a new utility function to format task previews, limiting their length to improve readability.
- Updated the command handler to use the new formatting function, ensuring task descriptions are truncated appropriately.
- Adjusted related tests to verify that long task descriptions are now truncated in the output.
* refactor(subagents): update subagent registry path resolution and improve command output formatting
- Replaced direct import of STATE_DIR with a utility function to resolve the state directory dynamically.
- Enhanced the formatting of command output for active and recent subagents, adding separators for better readability.
- Updated related tests to reflect changes in command output structure.
* fix(subagent): default sessions_spawn to no timeout when runTimeoutSeconds omitted
The previous fix (75a791106) correctly handled the case where
runTimeoutSeconds was explicitly set to 0 ("no timeout"). However,
when models omit the parameter entirely (which is common since the
schema marks it as optional), runTimeoutSeconds resolved to undefined.
undefined flowed through the chain as:
sessions_spawn → timeout: undefined (since undefined != null is false)
→ gateway agent handler → agentCommand opts.timeout: undefined
→ resolveAgentTimeoutMs({ overrideSeconds: undefined })
→ DEFAULT_AGENT_TIMEOUT_SECONDS (600s = 10 minutes)
This caused subagents to be killed at exactly 10 minutes even though
the user's intent (via TOOLS.md) was for subagents to run without a
timeout.
Fix: default runTimeoutSeconds to 0 (no timeout) when neither
runTimeoutSeconds nor timeoutSeconds is provided by the caller.
Subagent spawns are long-running by design and should not inherit the
600s agent-command default timeout.
* fix(subagent): accept timeout=0 in agent-via-gateway path (second 600s default)
* fix: thread timeout override through getReplyFromConfig dispatch path
getReplyFromConfig called resolveAgentTimeoutMs({ cfg }) with no override,
always falling back to the config default (600s). Add timeoutOverrideSeconds
to GetReplyOptions and pass it through as overrideSeconds so callers of the
dispatch chain can specify a custom timeout (0 = no timeout).
This complements the existing timeout threading in agentCommand and the
cron isolated-agent runner, which already pass overrideSeconds correctly.
* feat(model-fallback): normalize OpenAI Codex model references and enhance fallback handling
- Added normalization for OpenAI Codex model references, specifically converting "gpt-5.3-codex" to "openai-codex" before execution.
- Updated the `resolveFallbackCandidates` function to utilize the new normalization logic.
- Enhanced tests to verify the correct behavior of model normalization and fallback mechanisms.
- Introduced a new test case to ensure that the normalization process works as expected for various input formats.
* feat(tests): add unit tests for steer failure behavior in openclaw-tools
- Introduced a new test file to validate the behavior of subagents when steer replacement dispatch fails.
- Implemented tests to ensure that the announce behavior is restored correctly and that the suppression reason is cleared as expected.
- Enhanced the subagent registry with a new function to clear steer restart suppression.
- Updated related components to support the new test scenarios.
* fix(subagents): replace stop command with kill in slash commands and documentation
- Updated the `/subagents` command to replace `stop` with `kill` for consistency in controlling sub-agent runs.
- Modified related documentation to reflect the change in command usage.
- Removed legacy timeoutSeconds references from the sessions-spawn-tool schema and tests to streamline timeout handling.
- Enhanced tests to ensure correct behavior of the updated commands and their interactions.
* feat(tests): add unit tests for readLatestAssistantReply function
- Introduced a new test file for the `readLatestAssistantReply` function to validate its behavior with various message scenarios.
- Implemented tests to ensure the function correctly retrieves the latest assistant message and handles cases where the latest message has no text.
- Mocked the gateway call to simulate different message histories for comprehensive testing.
* feat(tests): enhance subagent kill-all cascade tests and announce formatting
- Added a new test to verify that the `kill-all` command cascades through ended parents to active descendants in subagents.
- Updated the subagent announce formatting tests to reflect changes in message structure, including the replacement of "Findings:" with "Result:" and the addition of new expectations for message content.
- Improved the handling of long findings and stats in the announce formatting logic to ensure concise output.
- Refactored related functions to enhance clarity and maintainability in the subagent registry and tools.
* refactor(subagent): update announce formatting and remove unused constants
- Modified the subagent announce formatting to replace "Findings:" with "Result:" and adjusted related expectations in tests.
- Removed constants for maximum announce findings characters and summary words, simplifying the announcement logic.
- Updated the handling of findings to retain full content instead of truncating, ensuring more informative outputs.
- Cleaned up unused imports in the commands-subagents file to enhance code clarity.
* feat(tests): enhance billing error handling in user-facing text
- Added tests to ensure that normal text mentioning billing plans is not rewritten, preserving user context.
- Updated the `isBillingErrorMessage` and `sanitizeUserFacingText` functions to improve handling of billing-related messages.
- Introduced new test cases for various scenarios involving billing messages to ensure accurate processing and output.
- Enhanced the subagent announce flow to correctly manage active descendant runs, preventing premature announcements.
* feat(subagent): enhance workflow guidance and auto-announcement clarity
- Added a new guideline in the subagent system prompt to emphasize trust in push-based completion, discouraging busy polling for status updates.
- Updated documentation to clarify that sub-agents will automatically announce their results, improving user understanding of the workflow.
- Enhanced tests to verify the new guidance on avoiding polling loops and to ensure the accuracy of the updated prompts.
* fix(cron): avoid announcing interim subagent spawn acks
* chore: clean post-rebase imports
* fix(cron): fall back to child replies when parent stays interim
* fix(subagents): make active-run guidance advisory
* fix(subagents): update announce flow to handle active descendants and enhance test coverage
- Modified the announce flow to defer announcements when active descendant runs are present, ensuring accurate status reporting.
- Updated tests to verify the new behavior, including scenarios where no fallback requester is available and ensuring proper handling of finished subagents.
- Enhanced the announce formatting to include an `expectFinal` flag for better clarity in the announcement process.
* fix(subagents): enhance announce flow and formatting for user updates
- Updated the announce flow to provide clearer instructions for user updates based on active subagent runs and requester context.
- Refactored the announcement logic to improve clarity and ensure internal context remains private.
- Enhanced tests to verify the new message expectations and formatting, including updated prompts for user-facing updates.
- Introduced a new function to build reply instructions based on session context, improving the overall announcement process.
* fix: resolve prep blockers and changelog placement (#14447) (thanks @tyler6204)
* fix: restore cron delivery-plan import after rebase (#14447) (thanks @tyler6204)
* fix: resolve test failures from rebase conflicts (#14447) (thanks @tyler6204)
* fix: apply formatting after rebase (#14447) (thanks @tyler6204)
2026-02-14 22:03:45 -08:00
"- subagents: list/steer/kill sub-agent runs" ,
2026-02-02 22:48:39 -08:00
'- session_status: show usage/time/model state and answer "what model are we using?"' ,
2026-01-20 16:46:15 +00:00
] . join ( "\n" ) ,
"TOOLS.md does not control tool availability; it is user guidance for how to use external tools." ,
2026-02-16 03:51:32 +01:00
` For long waits, avoid rapid poll loops: use ${ execToolName } with enough yieldMs or ${ processToolName } (action=poll, timeout=<ms>). ` ,
Agents: add nested subagent orchestration controls and reduce subagent token waste (#14447)
* Agents: add subagent orchestration controls
* Agents: add subagent orchestration controls (WIP uncommitted changes)
* feat(subagents): add depth-based spawn gating for sub-sub-agents
* feat(subagents): tool policy, registry, and announce chain for nested agents
* feat(subagents): system prompt, docs, changelog for nested sub-agents
* fix(subagents): prevent model fallback override, show model during active runs, and block context overflow fallback
Bug 1: When a session has an explicit model override (e.g., gpt/openai-codex),
the fallback candidate logic in resolveFallbackCandidates silently appended the
global primary model (opus) as a backstop. On reinjection/steer with a transient
error, the session could fall back to opus which has a smaller context window
and crash. Fix: when storedModelOverride is set, pass fallbacksOverride ?? []
instead of undefined, preventing the implicit primary backstop.
Bug 2: Active subagents showed 'model n/a' in /subagents list because
resolveModelDisplay only read entry.model/modelProvider (populated after run
completes). Fix: fall back to modelOverride/providerOverride fields which are
populated at spawn time via sessions.patch.
Bug 3: Context overflow errors (prompt too long, context_length_exceeded) could
theoretically escape runEmbeddedPiAgent and be treated as failover candidates
in runWithModelFallback, causing a switch to a model with a smaller context
window. Fix: in runWithModelFallback, detect context overflow errors via
isLikelyContextOverflowError and rethrow them immediately instead of trying the
next model candidate.
* fix(subagents): track spawn depth in session store and fix announce routing for nested agents
* Fix compaction status tracking and dedupe overflow compaction triggers
* fix(subagents): enforce depth block via session store and implement cascade kill
* fix: inject group chat context into system prompt
* fix(subagents): always write model to session store at spawn time
* Preserve spawnDepth when agent handler rewrites session entry
* fix(subagents): suppress announce on steer-restart
* fix(subagents): fallback spawned session model to runtime default
* fix(subagents): enforce spawn depth when caller key resolves by sessionId
* feat(subagents): implement active-first ordering for numeric targets and enhance task display
- Added a test to verify that subagents with numeric targets follow an active-first list ordering.
- Updated `resolveSubagentTarget` to sort subagent runs based on active status and recent activity.
- Enhanced task display in command responses to prevent truncation of long task descriptions.
- Introduced new utility functions for compacting task text and managing subagent run states.
* fix(subagents): show model for active runs via run record fallback
When the spawned model matches the agent's default model, the session
store's override fields are intentionally cleared (isDefault: true).
The model/modelProvider fields are only populated after the run
completes. This left active subagents showing 'model n/a'.
Fix: store the resolved model on SubagentRunRecord at registration
time, and use it as a fallback in both display paths (subagents tool
and /subagents command) when the session store entry has no model info.
Changes:
- SubagentRunRecord: add optional model field
- registerSubagentRun: accept and persist model param
- sessions-spawn-tool: pass resolvedModel to registerSubagentRun
- subagents-tool: pass run record model as fallback to resolveModelDisplay
- commands-subagents: pass run record model as fallback to resolveModelDisplay
* feat(chat): implement session key resolution and reset on sidebar navigation
- Added functions to resolve the main session key and reset chat state when switching sessions from the sidebar.
- Updated the `renderTab` function to handle session key changes when navigating to the chat tab.
- Introduced a test to verify that the session resets to "main" when opening chat from the sidebar navigation.
* fix: subagent timeout=0 passthrough and fallback prompt duplication
Bug 1: runTimeoutSeconds=0 now means 'no timeout' instead of applying 600s default
- sessions-spawn-tool: default to undefined (not 0) when neither timeout param
is provided; use != null check so explicit 0 passes through to gateway
- agent.ts: accept 0 as valid timeout (resolveAgentTimeoutMs already handles
0 → MAX_SAFE_TIMEOUT_MS)
Bug 2: model fallback no longer re-injects the original prompt as a duplicate
- agent.ts: track fallback attempt index; on retries use a short continuation
message instead of the full original prompt since the session file already
contains it from the first attempt
- Also skip re-sending images on fallback retries (already in session)
* feat(subagents): truncate long task descriptions in subagents command output
- Introduced a new utility function to format task previews, limiting their length to improve readability.
- Updated the command handler to use the new formatting function, ensuring task descriptions are truncated appropriately.
- Adjusted related tests to verify that long task descriptions are now truncated in the output.
* refactor(subagents): update subagent registry path resolution and improve command output formatting
- Replaced direct import of STATE_DIR with a utility function to resolve the state directory dynamically.
- Enhanced the formatting of command output for active and recent subagents, adding separators for better readability.
- Updated related tests to reflect changes in command output structure.
* fix(subagent): default sessions_spawn to no timeout when runTimeoutSeconds omitted
The previous fix (75a791106) correctly handled the case where
runTimeoutSeconds was explicitly set to 0 ("no timeout"). However,
when models omit the parameter entirely (which is common since the
schema marks it as optional), runTimeoutSeconds resolved to undefined.
undefined flowed through the chain as:
sessions_spawn → timeout: undefined (since undefined != null is false)
→ gateway agent handler → agentCommand opts.timeout: undefined
→ resolveAgentTimeoutMs({ overrideSeconds: undefined })
→ DEFAULT_AGENT_TIMEOUT_SECONDS (600s = 10 minutes)
This caused subagents to be killed at exactly 10 minutes even though
the user's intent (via TOOLS.md) was for subagents to run without a
timeout.
Fix: default runTimeoutSeconds to 0 (no timeout) when neither
runTimeoutSeconds nor timeoutSeconds is provided by the caller.
Subagent spawns are long-running by design and should not inherit the
600s agent-command default timeout.
* fix(subagent): accept timeout=0 in agent-via-gateway path (second 600s default)
* fix: thread timeout override through getReplyFromConfig dispatch path
getReplyFromConfig called resolveAgentTimeoutMs({ cfg }) with no override,
always falling back to the config default (600s). Add timeoutOverrideSeconds
to GetReplyOptions and pass it through as overrideSeconds so callers of the
dispatch chain can specify a custom timeout (0 = no timeout).
This complements the existing timeout threading in agentCommand and the
cron isolated-agent runner, which already pass overrideSeconds correctly.
* feat(model-fallback): normalize OpenAI Codex model references and enhance fallback handling
- Added normalization for OpenAI Codex model references, specifically converting "gpt-5.3-codex" to "openai-codex" before execution.
- Updated the `resolveFallbackCandidates` function to utilize the new normalization logic.
- Enhanced tests to verify the correct behavior of model normalization and fallback mechanisms.
- Introduced a new test case to ensure that the normalization process works as expected for various input formats.
* feat(tests): add unit tests for steer failure behavior in openclaw-tools
- Introduced a new test file to validate the behavior of subagents when steer replacement dispatch fails.
- Implemented tests to ensure that the announce behavior is restored correctly and that the suppression reason is cleared as expected.
- Enhanced the subagent registry with a new function to clear steer restart suppression.
- Updated related components to support the new test scenarios.
* fix(subagents): replace stop command with kill in slash commands and documentation
- Updated the `/subagents` command to replace `stop` with `kill` for consistency in controlling sub-agent runs.
- Modified related documentation to reflect the change in command usage.
- Removed legacy timeoutSeconds references from the sessions-spawn-tool schema and tests to streamline timeout handling.
- Enhanced tests to ensure correct behavior of the updated commands and their interactions.
* feat(tests): add unit tests for readLatestAssistantReply function
- Introduced a new test file for the `readLatestAssistantReply` function to validate its behavior with various message scenarios.
- Implemented tests to ensure the function correctly retrieves the latest assistant message and handles cases where the latest message has no text.
- Mocked the gateway call to simulate different message histories for comprehensive testing.
* feat(tests): enhance subagent kill-all cascade tests and announce formatting
- Added a new test to verify that the `kill-all` command cascades through ended parents to active descendants in subagents.
- Updated the subagent announce formatting tests to reflect changes in message structure, including the replacement of "Findings:" with "Result:" and the addition of new expectations for message content.
- Improved the handling of long findings and stats in the announce formatting logic to ensure concise output.
- Refactored related functions to enhance clarity and maintainability in the subagent registry and tools.
* refactor(subagent): update announce formatting and remove unused constants
- Modified the subagent announce formatting to replace "Findings:" with "Result:" and adjusted related expectations in tests.
- Removed constants for maximum announce findings characters and summary words, simplifying the announcement logic.
- Updated the handling of findings to retain full content instead of truncating, ensuring more informative outputs.
- Cleaned up unused imports in the commands-subagents file to enhance code clarity.
* feat(tests): enhance billing error handling in user-facing text
- Added tests to ensure that normal text mentioning billing plans is not rewritten, preserving user context.
- Updated the `isBillingErrorMessage` and `sanitizeUserFacingText` functions to improve handling of billing-related messages.
- Introduced new test cases for various scenarios involving billing messages to ensure accurate processing and output.
- Enhanced the subagent announce flow to correctly manage active descendant runs, preventing premature announcements.
* feat(subagent): enhance workflow guidance and auto-announcement clarity
- Added a new guideline in the subagent system prompt to emphasize trust in push-based completion, discouraging busy polling for status updates.
- Updated documentation to clarify that sub-agents will automatically announce their results, improving user understanding of the workflow.
- Enhanced tests to verify the new guidance on avoiding polling loops and to ensure the accuracy of the updated prompts.
* fix(cron): avoid announcing interim subagent spawn acks
* chore: clean post-rebase imports
* fix(cron): fall back to child replies when parent stays interim
* fix(subagents): make active-run guidance advisory
* fix(subagents): update announce flow to handle active descendants and enhance test coverage
- Modified the announce flow to defer announcements when active descendant runs are present, ensuring accurate status reporting.
- Updated tests to verify the new behavior, including scenarios where no fallback requester is available and ensuring proper handling of finished subagents.
- Enhanced the announce formatting to include an `expectFinal` flag for better clarity in the announcement process.
* fix(subagents): enhance announce flow and formatting for user updates
- Updated the announce flow to provide clearer instructions for user updates based on active subagent runs and requester context.
- Refactored the announcement logic to improve clarity and ensure internal context remains private.
- Enhanced tests to verify the new message expectations and formatting, including updated prompts for user-facing updates.
- Introduced a new function to build reply instructions based on session context, improving the overall announcement process.
* fix: resolve prep blockers and changelog placement (#14447) (thanks @tyler6204)
* fix: restore cron delivery-plan import after rebase (#14447) (thanks @tyler6204)
* fix: resolve test failures from rebase conflicts (#14447) (thanks @tyler6204)
* fix: apply formatting after rebase (#14447) (thanks @tyler6204)
2026-02-14 22:03:45 -08:00
"If a task is more complex or takes longer, spawn a sub-agent. Completion is push-based: it will auto-announce when done." ,
"Do not poll `subagents list` / `sessions_list` in a loop; only check status on-demand (for intervention, debugging, or when explicitly asked)." ,
2026-01-20 16:46:15 +00:00
"" ,
"## 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." ,
"" ,
2026-02-02 22:56:29 -08:00
. . . safetySection ,
2026-02-02 21:10:10 -08:00
"## OpenClaw CLI Quick Reference" ,
"OpenClaw is controlled via subcommands. Do not invent commands." ,
2026-01-20 16:46:15 +00:00
"To manage the Gateway daemon service (start/stop/restart):" ,
2026-02-02 21:27:41 -08:00
"- openclaw gateway status" ,
"- openclaw gateway start" ,
"- openclaw gateway stop" ,
"- openclaw gateway restart" ,
"If unsure, ask the user to run `openclaw help` (or `openclaw gateway --help`) and paste the output." ,
2026-01-20 16:46:15 +00:00
"" ,
. . . skillsSection ,
. . . memorySection ,
// Skip self-update for subagent/none modes
2026-02-02 21:10:10 -08:00
hasGateway && ! isMinimal ? "## OpenClaw Self-Update" : "" ,
2026-01-20 16:46:15 +00:00
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)." ,
2026-02-02 21:10:10 -08:00
"After restart, OpenClaw pings the last active session automatically." ,
2026-01-20 16:46:15 +00:00
] . 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 ? "" : "" ,
2026-02-02 22:56:29 -08:00
userTimezone
? "If you need the current date, time, or day of week, run session_status (📊 session_status)."
: "" ,
2026-01-20 16:46:15 +00:00
"## Workspace" ,
2026-02-14 20:20:29 -08:00
` Your working directory is: ${ displayWorkspaceDir } ` ,
workspaceGuidance ,
2026-01-22 08:05:47 +00:00
. . . workspaceNotes ,
2026-01-20 16:46:15 +00:00
"" ,
. . . 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." ,
2026-02-14 20:20:29 -08:00
params . sandboxInfo . containerWorkspaceDir
2026-02-16 02:48:46 +01:00
? ` Sandbox container workdir: ${ sanitizeForPromptLiteral ( params . sandboxInfo . containerWorkspaceDir ) } `
2026-02-14 20:20:29 -08:00
: "" ,
2026-01-20 16:46:15 +00:00
params . sandboxInfo . workspaceDir
2026-02-15 19:15:57 -08:00
? ` Sandbox host mount source (file tools bridge only; not valid inside sandbox exec): ${ sanitizeForPromptLiteral ( params . sandboxInfo . workspaceDir ) } `
2026-01-20 16:46:15 +00:00
: "" ,
params . sandboxInfo . workspaceAccess
? ` Agent workspace access: ${ params . sandboxInfo . workspaceAccess } ${
params . sandboxInfo . agentWorkspaceMount
2026-02-16 02:48:46 +01:00
? ` (mounted at ${ sanitizeForPromptLiteral ( params . sandboxInfo . agentWorkspaceMount ) } ) `
2026-01-20 16:46:15 +00:00
: ""
} `
: "" ,
2026-01-27 03:23:42 +00:00
params . sandboxInfo . browserBridgeUrl ? "Sandbox browser: enabled." : "" ,
2026-01-20 16:46:15 +00:00
params . sandboxInfo . browserNoVncUrl
2026-02-16 02:48:46 +01:00
? ` Sandbox browser observer (noVNC): ${ sanitizeForPromptLiteral ( params . sandboxInfo . browserNoVncUrl ) } `
2026-01-20 16:46:15 +00:00
: "" ,
params . sandboxInfo . hostBrowserAllowed === true
? "Host browser control: allowed."
: params . sandboxInfo . hostBrowserAllowed === false
? "Host browser control: blocked."
: "" ,
params . sandboxInfo . elevated ? . allowed
? "Elevated exec is available for this session."
: "" ,
params . sandboxInfo . elevated ? . allowed
2026-01-22 05:32:13 +00:00
? "User can toggle with /elevated on|off|ask|full."
2026-01-20 16:46:15 +00:00
: "" ,
params . sandboxInfo . elevated ? . allowed
2026-01-22 05:32:13 +00:00
? "You may also send /elevated on|off|ask|full when needed."
: "" ,
params . sandboxInfo . elevated ? . allowed
? ` Current elevated level: ${ params . sandboxInfo . elevated . defaultLevel } (ask runs exec on host with approvals; full auto-approves). `
2026-01-20 16:46:15 +00:00
: "" ,
]
. filter ( Boolean )
. join ( "\n" )
: "" ,
params . sandboxInfo ? . enabled ? "" : "" ,
. . . buildUserIdentitySection ( ownerLine , isMinimal ) ,
. . . buildTimeSection ( {
userTimezone ,
} ) ,
"## Workspace Files (injected)" ,
2026-02-02 21:10:10 -08:00
"These user-editable files are loaded by OpenClaw and included below in Project Context." ,
2026-01-20 16:46:15 +00:00
"" ,
. . . buildReplyTagsSection ( isMinimal ) ,
. . . buildMessagingSection ( {
isMinimal ,
availableTools ,
messageChannelOptions ,
inlineButtonsEnabled ,
runtimeChannel ,
2026-01-22 03:27:26 +00:00
messageToolHints : params.messageToolHints ,
2026-01-20 16:46:15 +00:00
} ) ,
2026-01-24 10:25:37 +00:00
. . . buildVoiceSection ( { isMinimal , ttsHint : params.ttsHint } ) ,
2026-02-16 15:20:42 +00:00
. . . buildLlmsTxtSection ( { isMinimal , availableTools } ) ,
2026-01-20 16:46:15 +00:00
] ;
2025-12-23 13:32:07 +00:00
2026-01-20 16:46:15 +00: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-20 16:46:15 +00:00
const contextFiles = params . contextFiles ? ? [ ] ;
2026-02-13 04:27:56 +08:00
const validContextFiles = contextFiles . filter (
( file ) = > typeof file . path === "string" && file . path . trim ( ) . length > 0 ,
) ;
if ( validContextFiles . length > 0 ) {
const hasSoulFile = validContextFiles . some ( ( file ) = > {
2026-01-23 06:00:13 +01:00
const normalizedPath = file . path . trim ( ) . replace ( /\\/g , "/" ) ;
const baseName = normalizedPath . split ( "/" ) . pop ( ) ? ? normalizedPath ;
return baseName . toLowerCase ( ) === "soul.md" ;
} ) ;
lines . push ( "# Project Context" , "" , "The following project context files have been loaded:" ) ;
if ( hasSoulFile ) {
lines . push (
"If SOUL.md is present, embody its persona and tone. Avoid stiff, generic replies; follow its guidance unless higher-priority instructions override it." ,
) ;
}
lines . push ( "" ) ;
2026-02-13 04:27:56 +08:00
for ( const file of validContextFiles ) {
2026-01-20 16:46:15 +00:00
lines . push ( ` ## ${ file . path } ` , "" , file . content , "" ) ;
}
}
2026-01-08 02:20:18 +01:00
2026-01-20 16:46:15 +00: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-20 16:46:15 +00: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" ,
2026-02-02 21:10:10 -08:00
'OpenClaw treats a leading/trailing "HEARTBEAT_OK" as a heartbeat ack (and may discard it).' ,
2026-01-20 16:46:15 +00:00
'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-20 16:46:15 +00: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-20 16:46:15 +00: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-20 16:46:15 +00:00
runtimeInfo ? : {
agentId? : string ;
host? : string ;
os? : string ;
arch? : string ;
node? : string ;
model? : string ;
defaultModel? : string ;
2026-02-07 11:32:31 -06:00
shell? : string ;
2026-01-22 05:07:40 +00:00
repoRoot? : string ;
2026-01-20 16:46:15 +00:00
} ,
runtimeChannel? : string ,
runtimeCapabilities : string [ ] = [ ] ,
defaultThinkLevel? : ThinkLevel ,
2026-01-19 05:27:38 +00:00
) : string {
2026-01-20 16:46:15 +00:00
return ` Runtime: ${ [
runtimeInfo ? . agentId ? ` agent= ${ runtimeInfo . agentId } ` : "" ,
runtimeInfo ? . host ? ` host= ${ runtimeInfo . host } ` : "" ,
2026-01-22 05:07:40 +00:00
runtimeInfo ? . repoRoot ? ` repo= ${ runtimeInfo . repoRoot } ` : "" ,
2026-01-20 16:46:15 +00:00
runtimeInfo ? . os
? ` os= ${ runtimeInfo . os } ${ runtimeInfo ? . arch ? ` ( ${ runtimeInfo . arch } ) ` : "" } `
: runtimeInfo ? . arch
? ` arch= ${ runtimeInfo . arch } `
: "" ,
runtimeInfo ? . node ? ` node= ${ runtimeInfo . node } ` : "" ,
runtimeInfo ? . model ? ` model= ${ runtimeInfo . model } ` : "" ,
runtimeInfo ? . defaultModel ? ` default_model= ${ runtimeInfo . defaultModel } ` : "" ,
2026-02-07 11:32:31 -06:00
runtimeInfo ? . shell ? ` shell= ${ runtimeInfo . shell } ` : "" ,
2026-01-20 16:46:15 +00:00
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
}