fix: handle slash commands instantly during long LLM processing
Split Run() into a dispatcher (main goroutine) and an LLM worker (background goroutine). The dispatcher always consumes messages and handles commands like /todo, /skills, /session immediately, even while the LLM worker is busy with a long tool-call chain. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
b43e961ff4
commit
dce19ee28c
1 changed files with 68 additions and 28 deletions
|
|
@ -156,25 +156,68 @@ func registerSharedTools(cfg *config.Config, msgBus *bus.MessageBus, registry *A
|
||||||
func (al *AgentLoop) Run(ctx context.Context) error {
|
func (al *AgentLoop) Run(ctx context.Context) error {
|
||||||
al.running.Store(true)
|
al.running.Store(true)
|
||||||
|
|
||||||
|
// LLM work is dispatched to a background worker so the main loop
|
||||||
|
// stays free to handle slash commands (/todo, /skills, …) instantly,
|
||||||
|
// even while a long tool-call chain is running.
|
||||||
|
llmQueue := make(chan bus.InboundMessage, 10)
|
||||||
|
workerDone := make(chan struct{})
|
||||||
|
go func() {
|
||||||
|
defer close(workerDone)
|
||||||
|
al.llmWorker(ctx, llmQueue)
|
||||||
|
}()
|
||||||
|
defer func() {
|
||||||
|
close(llmQueue)
|
||||||
|
<-workerDone
|
||||||
|
}()
|
||||||
|
|
||||||
for al.running.Load() {
|
for al.running.Load() {
|
||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
return nil
|
return nil
|
||||||
default:
|
default:
|
||||||
|
}
|
||||||
|
|
||||||
msg, ok := al.bus.ConsumeInbound(ctx)
|
msg, ok := al.bus.ConsumeInbound(ctx)
|
||||||
if !ok {
|
if !ok {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fast path: handle slash commands immediately without blocking the LLM worker.
|
||||||
|
if response, handled := al.handleCommand(ctx, msg); handled {
|
||||||
|
if response != "" {
|
||||||
|
al.bus.PublishOutbound(bus.OutboundMessage{
|
||||||
|
Channel: msg.Channel,
|
||||||
|
ChatID: msg.ChatID,
|
||||||
|
Content: response,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dispatch to LLM worker
|
||||||
|
select {
|
||||||
|
case llmQueue <- msg:
|
||||||
|
case <-ctx.Done():
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// llmWorker processes LLM messages sequentially in a background goroutine.
|
||||||
|
func (al *AgentLoop) llmWorker(ctx context.Context, queue <-chan bus.InboundMessage) {
|
||||||
|
for msg := range queue {
|
||||||
|
if ctx.Err() != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
response, err := al.processMessage(ctx, msg)
|
response, err := al.processMessage(ctx, msg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
response = fmt.Sprintf("Error processing message: %v", err)
|
response = fmt.Sprintf("Error processing message: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if response != "" {
|
if response != "" {
|
||||||
// Check if the message tool already sent a response during this round.
|
|
||||||
// If so, skip publishing to avoid duplicate messages to the user.
|
|
||||||
// Use default agent's tools to check (message tool is shared).
|
|
||||||
alreadySent := false
|
alreadySent := false
|
||||||
defaultAgent := al.registry.GetDefaultAgent()
|
defaultAgent := al.registry.GetDefaultAgent()
|
||||||
if defaultAgent != nil {
|
if defaultAgent != nil {
|
||||||
|
|
@ -194,9 +237,6 @@ func (al *AgentLoop) Run(ctx context.Context) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (al *AgentLoop) Stop() {
|
func (al *AgentLoop) Stop() {
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue