Files
goclaw/cmd/gateway_consumer_process.go
T
viettranx 1ac08155b0 feat(trace): reliable stop/abort with ctx-aware streams and 2-phase router
Makes the Stop button on the traces page actually stop running traces.
Seven-phase implementation across provider HTTP, agent router, trace
persistence, WS events, tool exec, i18n, and integration tests.

- Provider HTTP+SSE ctx-aware: close socket on cancel via CtxBody wrapper
- Router 2-phase abort: CAS state machine, 3s grace, force-mark fallback
- Trace retry: 3 inline retries + 10-max retry queue, stale recovery 10min
- trace.status WS event: real-time UI updates (invalidates query on receive)
- Tool exec: process-group kill (SIGTERM→3s→SIGKILL), Rod page ctx watch
- i18n: 6 abort toast variants in en/vi/zh
- Integration: 9 scenarios, -race clean

Fixes tenant-ctx loss in forceMarkTraceAborted and retry worker broadcast
(caught by code-reviewer: C1/C2). Stale threshold intentionally 10min
because start_time-based; last_span_at migration is a follow-up.
2026-04-14 18:28:31 +07:00

51 lines
1.7 KiB
Go

package cmd
import (
"context"
"fmt"
"strings"
"github.com/nextlevelbuilder/goclaw/internal/agent"
"github.com/nextlevelbuilder/goclaw/internal/config"
"github.com/nextlevelbuilder/goclaw/internal/scheduler"
)
// makeSchedulerRunFunc creates the RunFunc for the scheduler.
// It extracts the agentID from the session key and routes to the correct agent loop.
func makeSchedulerRunFunc(agents *agent.Router, cfg *config.Config) scheduler.RunFunc {
return func(ctx context.Context, req agent.RunRequest) (*agent.RunResult, error) {
// Extract agentID from session key.
// Supported formats:
// agent:{agentId}:{rest}
// delegate:{sourceUUID8}:{targetAgentKey}:{delegationId} (legacy, kept for existing sessions)
agentID := cfg.ResolveDefaultAgentID()
if parts := strings.SplitN(req.SessionKey, ":", 4); len(parts) >= 2 {
switch parts[0] {
case "agent":
agentID = parts[1]
case "delegate":
if len(parts) >= 3 {
agentID = parts[2]
}
}
}
loop, err := agents.Get(ctx, agentID)
if err != nil {
return nil, fmt.Errorf("agent %s not found: %w", agentID, err)
}
// Register run with the agent router so IsSessionBusy() and AbortRunsForSession()
// work correctly for inbound channel runs (Telegram DM intent classifier, /stop, etc.).
// The ctx from the scheduler is already cancellable; we create a child so the router's
// cancel func is independent from the scheduler's cancel func. Calling cancel twice is safe.
runCtx, cancel := context.WithCancel(ctx)
injectCh := agents.RegisterRun(runCtx, req.RunID, req.SessionKey, agentID, cancel)
defer agents.UnregisterRun(req.RunID)
defer cancel()
req.InjectCh = injectCh
return loop.Run(runCtx, req)
}
}