fix(agent): add TargetRef nil guards and ensureHistory for robustness

- Add nil checks for session.TargetRef in all four execute*Action
  handlers (Trader/Exchange/Model/Strategy) to prevent panic on
  corrupted sessions; bulk-delete and query actions are excluded
- Add ensureHistory() helper and call it in runPlannedAgentWithContextMode
  to prevent nil panic when history is not initialized

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
shinchan-zhai
2026-05-11 16:43:36 +08:00
parent 94844b7139
commit ca8bed4a58
3 changed files with 47 additions and 0 deletions

View File

@@ -80,6 +80,12 @@ func New(tm *manager.TraderManager, st *store.Store, cfg *Config, logger *slog.L
func (a *Agent) SetAIClient(c mcp.AIClient) { a.aiClient = c }
func (a *Agent) ensureHistory() {
if a.history == nil {
a.history = newChatHistory(100)
}
}
func (a *Agent) log() *slog.Logger {
if a != nil && a.logger != nil {
return a.logger

View File

@@ -2551,6 +2551,7 @@ func (a *Agent) runPlannedAgentWithContextMode(ctx context.Context, storeUserID
answer, _, err := a.driveActiveSession(ctx, storeUserID, userID, lang, text, session, onEvent)
return answer, err
}
a.ensureHistory()
a.history.Add(userID, "user", text)
if onEvent != nil {
onEvent(StreamEventPlanning, a.planningStatusText(lang))

View File

@@ -1479,6 +1479,12 @@ func (a *Agent) executeTraderManagementAction(storeUserID string, userID int64,
}
return formatReadFastPathResponse(lang, "list_traders", a.toolListTraders(storeUserID))
case "start", "stop", "delete":
if session.TargetRef == nil && !(session.Action == "delete" && fieldValue(session, "bulk_scope") == "all") {
if lang == "zh" {
return "请先指定要操作的交易员。"
}
return "Please specify which trader to operate on."
}
if fieldValue(session, skillDAGStepField) == "" {
setSkillDAGStep(&session, "await_confirmation")
}
@@ -1894,6 +1900,17 @@ func (a *Agent) executeBulkTraderDelete(storeUserID string, userID int64, lang,
}
func (a *Agent) executeExchangeManagementAction(storeUserID string, userID int64, lang, text string, session skillSession) string {
switch session.Action {
case "query", "query_list", "create":
// These actions don't need a target — fall through.
default:
if session.TargetRef == nil {
if lang == "zh" {
return "请先指定要操作的交易所配置。"
}
return "Please specify which exchange config to operate on."
}
}
switch session.Action {
case "query_detail":
if detail, ok := a.describeExchange(storeUserID, lang, session.TargetRef); ok {
@@ -2069,6 +2086,17 @@ func (a *Agent) executeExchangeManagementAction(storeUserID string, userID int64
}
func (a *Agent) executeModelManagementAction(storeUserID string, userID int64, lang, text string, session skillSession) string {
switch session.Action {
case "query", "query_list", "create":
// These actions don't need a target — fall through.
default:
if session.TargetRef == nil {
if lang == "zh" {
return "请先指定要操作的模型。"
}
return "Please specify which model to operate on."
}
}
switch session.Action {
case "query_detail":
if detail, ok := a.describeModel(storeUserID, lang, session.TargetRef); ok {
@@ -2228,6 +2256,18 @@ func (a *Agent) executeModelManagementAction(storeUserID string, userID int64, l
}
func (a *Agent) executeStrategyManagementAction(storeUserID string, userID int64, lang, text string, session skillSession) string {
switch session.Action {
case "query", "query_list", "create":
// These actions don't need a target — fall through.
default:
isBulkDelete := session.Action == "delete" && fieldValue(session, "bulk_scope") == "all"
if session.TargetRef == nil && !isBulkDelete {
if lang == "zh" {
return "请先指定要操作的策略。"
}
return "Please specify which strategy to operate on."
}
}
switch session.Action {
case "query", "query_list":
return formatReadFastPathResponse(lang, "get_strategies", a.toolGetStrategies(storeUserID))