feat: capture last system prompt sent to LLM for Mini App display

GetSystemPrompt() now returns the exact system prompt that was last sent
to the LLM provider (including summary, channel info, and background
preamble), instead of rebuilding from current state which may differ.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
dj-oyu 2026-02-24 17:16:59 +09:00
parent 7c4e57cf18
commit 8f68847d55

View file

@ -90,6 +90,7 @@ type AgentLoop struct {
sessionLocks sync.Map // sessionKey → *sessionSemaphore sessionLocks sync.Map // sessionKey → *sessionSemaphore
activeTasks sync.Map // sessionKey → *activeTask activeTasks sync.Map // sessionKey → *activeTask
sessions *SessionTracker sessions *SessionTracker
lastSystemPrompt atomic.Value // string — last system prompt sent to LLM
OnStateChange func() // called on plan/session/skills mutations OnStateChange func() // called on plan/session/skills mutations
OnUserMessage func() // called when a real user message is processed OnUserMessage func() // called when a real user message is processed
} }
@ -810,6 +811,9 @@ func (al *AgentLoop) runAgentLoop(ctx context.Context, agent *AgentInstance, opt
// 1. Update tool contexts // 1. Update tool contexts
al.updateToolContexts(agent, opts.Channel, opts.ChatID) al.updateToolContexts(agent, opts.Channel, opts.ChatID)
// 1a. Set session-specific working directory for bootstrap file lookup
agent.ContextBuilder.SetWorkDir(agent.EffectiveWorkspace(opts.SessionKey))
// 1b. Inject peer session awareness into system prompt // 1b. Inject peer session awareness into system prompt
projectPath := agent.ContextBuilder.GetPlanWorkDir() projectPath := agent.ContextBuilder.GetPlanWorkDir()
if projectPath == "" { if projectPath == "" {
@ -904,6 +908,11 @@ func (al *AgentLoop) runAgentLoop(ctx context.Context, agent *AgentInstance, opt
al.stats.RecordPrompt() al.stats.RecordPrompt()
} }
// Capture the finalized system prompt for Mini App inspection
if len(messages) > 0 {
al.lastSystemPrompt.Store(messages[0].Content)
}
// 5. Run LLM iteration loop // 5. Run LLM iteration loop
finalContent, iteration, err := al.runLLMIteration(ctx, agent, messages, opts, task, preStatus) finalContent, iteration, err := al.runLLMIteration(ctx, agent, messages, opts, task, preStatus)
if err != nil { if err != nil {
@ -2450,6 +2459,33 @@ func (al *AgentLoop) GetSessionStats() *stats.Stats {
return &s return &s
} }
// GetContextInfo returns the bootstrap file resolution and directory context for the default agent.
func (al *AgentLoop) GetContextInfo() (workDir, planWorkDir, workspace string, bootstrap []BootstrapFileInfo) {
agent := al.registry.GetDefaultAgent()
if agent == nil {
return "", "", "", nil
}
workspace = agent.Workspace
planWorkDir = agent.ContextBuilder.GetPlanWorkDir()
workDir = agent.ContextBuilder.workDir
bootstrap = agent.ContextBuilder.ResolveBootstrapPaths()
return
}
// GetSystemPrompt returns the system prompt last sent to the LLM.
// Falls back to building from current state if no LLM call has occurred yet.
func (al *AgentLoop) GetSystemPrompt() string {
if v := al.lastSystemPrompt.Load(); v != nil {
return v.(string)
}
// Fallback: no LLM call yet — build from current state
agent := al.registry.GetDefaultAgent()
if agent == nil {
return ""
}
return agent.ContextBuilder.BuildSystemPrompt()
}
// formatMessagesForLog formats messages for logging // formatMessagesForLog formats messages for logging
func formatMessagesForLog(messages []providers.Message) string { func formatMessagesForLog(messages []providers.Message) string {
if len(messages) == 0 { if len(messages) == 0 {