fix(agent): suppress plan nudge and SkipPlaceholder for system messages

When a subagent completes, processSystemMessage triggers runAgentLoop
with SendResponse: true. This caused three problems:

1. The plan-execution nudge fired, forcing the LLM into unnecessary
   iterations when it was correctly waiting for user input
2. The response was sent without SkipPlaceholder, consuming the
   Telegram "Thinking..." placeholder and corrupting status messages
3. Tool call results from the nudged iterations leaked into the chat

Add SystemMessage flag to processOptions. When set:
- Plan continuation nudge is suppressed in runLLMIteration
- Outbound response uses SkipPlaceholder: true

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
dj-oyu 2026-02-25 16:46:19 +09:00
parent aabdcb7325
commit 681ad10c2e

View file

@ -115,6 +115,7 @@ type processOptions struct {
NoHistory bool // If true, don't load session history (for heartbeat) NoHistory bool // If true, don't load session history (for heartbeat)
TaskID string // Unique task ID for background task status tracking TaskID string // Unique task ID for background task status tracking
Background bool // If true, this is a background task (cron/heartbeat) — enables live task notifications Background bool // If true, this is a background task (cron/heartbeat) — enables live task notifications
SystemMessage bool // If true, this is a system/subagent message — suppress plan nudge, use SkipPlaceholder
} }
const defaultResponse = "I've completed processing but have no response to give. Increase `max_tool_iterations` in config.json." const defaultResponse = "I've completed processing but have no response to give. Increase `max_tool_iterations` in config.json."
@ -816,6 +817,7 @@ func (al *AgentLoop) processSystemMessage(ctx context.Context, msg bus.InboundMe
DefaultResponse: "Background task completed.", DefaultResponse: "Background task completed.",
EnableSummary: false, EnableSummary: false,
SendResponse: true, SendResponse: true,
SystemMessage: true,
}) })
} }
@ -1225,9 +1227,10 @@ func (al *AgentLoop) runAgentLoop(ctx context.Context, agent *AgentInstance, opt
// 8. Optional: send response via bus // 8. Optional: send response via bus
if opts.SendResponse { if opts.SendResponse {
_ = al.bus.PublishOutbound(ctx, bus.OutboundMessage{ _ = al.bus.PublishOutbound(ctx, bus.OutboundMessage{
Channel: opts.Channel, Channel: opts.Channel,
ChatID: opts.ChatID, ChatID: opts.ChatID,
Content: finalContent, Content: finalContent,
SkipPlaceholder: opts.SystemMessage,
}) })
} }
@ -2282,7 +2285,7 @@ func (al *AgentLoop) runLLMIteration(
curUnchecked = strings.Count(agent.ContextBuilder.ReadMemory(), "- [ ]") curUnchecked = strings.Count(agent.ContextBuilder.ReadMemory(), "- [ ]")
} }
if curUnchecked > 0 && !planMarkNudged && if curUnchecked > 0 && !planMarkNudged &&
planSnapshot == "executing" { planSnapshot == "executing" && !opts.SystemMessage {
planMarkNudged = true planMarkNudged = true
messages = append(messages, providers.Message{ messages = append(messages, providers.Message{
Role: "assistant", Role: "assistant",