feat: add graceful shutdown
This commit is contained in:
@@ -21,7 +21,6 @@ vendor/
|
|||||||
|
|
||||||
# IDE and editor files
|
# IDE and editor files
|
||||||
.idea/
|
.idea/
|
||||||
.vscode/
|
|
||||||
*.swp
|
*.swp
|
||||||
*.swo
|
*.swo
|
||||||
*~
|
*~
|
||||||
|
|||||||
Vendored
+22
@@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"name": "Debug Kandev Backend",
|
||||||
|
"type": "go",
|
||||||
|
"request": "launch",
|
||||||
|
"mode": "debug",
|
||||||
|
"program": "${workspaceFolder}/apps/backend/cmd/kandev",
|
||||||
|
"cwd": "${workspaceFolder}/apps/backend",
|
||||||
|
"env": {
|
||||||
|
"PATH": "${workspaceFolder}/apps/backend/bin:${env:PATH}"
|
||||||
|
},
|
||||||
|
"preLaunchTask": "build-backend",
|
||||||
|
"console": "integratedTerminal",
|
||||||
|
"stopOnEntry": false,
|
||||||
|
"showLog": true,
|
||||||
|
"trace": "verbose",
|
||||||
|
"logOutput": "debugger,dap",
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
Vendored
+26
@@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"version": "2.0.0",
|
||||||
|
"tasks": [
|
||||||
|
{
|
||||||
|
"label": "build-backend",
|
||||||
|
"type": "shell",
|
||||||
|
"command": "make",
|
||||||
|
"args": ["build-backend"],
|
||||||
|
"options": {
|
||||||
|
"cwd": "${workspaceFolder}"
|
||||||
|
},
|
||||||
|
"problemMatcher": ["$go"],
|
||||||
|
"presentation": {
|
||||||
|
"echo": true,
|
||||||
|
"reveal": "always",
|
||||||
|
"focus": false,
|
||||||
|
"panel": "shared",
|
||||||
|
"showReuseMessage": false
|
||||||
|
},
|
||||||
|
"group": {
|
||||||
|
"kind": "build",
|
||||||
|
"isDefault": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -806,6 +806,13 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if lifecycleMgr != nil {
|
if lifecycleMgr != nil {
|
||||||
|
log.Info("Stopping agents gracefully...")
|
||||||
|
stopCtx, stopCancel := context.WithTimeout(context.Background(), 20*time.Second)
|
||||||
|
if err := lifecycleMgr.StopAllAgents(stopCtx); err != nil {
|
||||||
|
log.Error("Graceful agent stop error", zap.Error(err))
|
||||||
|
}
|
||||||
|
stopCancel()
|
||||||
|
|
||||||
if err := lifecycleMgr.Stop(); err != nil {
|
if err := lifecycleMgr.Stop(); err != nil {
|
||||||
log.Error("Lifecycle manager stop error", zap.Error(err))
|
log.Error("Lifecycle manager stop error", zap.Error(err))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -161,4 +161,3 @@ func (c *Controller) ListAgentTypes(ctx context.Context, req dto.ListAgentTypesR
|
|||||||
|
|
||||||
return resp, nil
|
return resp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ package lifecycle
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@@ -375,6 +376,32 @@ func (m *Manager) Stop() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// StopAllAgents attempts a graceful shutdown of all active agents.
|
||||||
|
func (m *Manager) StopAllAgents(ctx context.Context) error {
|
||||||
|
instanceIDs := make([]string, 0)
|
||||||
|
m.mu.RLock()
|
||||||
|
for instanceID := range m.instances {
|
||||||
|
instanceIDs = append(instanceIDs, instanceID)
|
||||||
|
}
|
||||||
|
m.mu.RUnlock()
|
||||||
|
|
||||||
|
if len(instanceIDs) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var errs []error
|
||||||
|
for _, instanceID := range instanceIDs {
|
||||||
|
if err := m.StopAgent(ctx, instanceID, false); err != nil {
|
||||||
|
errs = append(errs, err)
|
||||||
|
m.logger.Warn("failed to stop agent during shutdown",
|
||||||
|
zap.String("instance_id", instanceID),
|
||||||
|
zap.Error(err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return errors.Join(errs...)
|
||||||
|
}
|
||||||
|
|
||||||
// Launch launches a new agent for a task
|
// Launch launches a new agent for a task
|
||||||
func (m *Manager) Launch(ctx context.Context, req *LaunchRequest) (*AgentInstance, error) {
|
func (m *Manager) Launch(ctx context.Context, req *LaunchRequest) (*AgentInstance, error) {
|
||||||
m.logger.Info("launching agent",
|
m.logger.Info("launching agent",
|
||||||
@@ -596,6 +623,9 @@ func (m *Manager) launchStandalone(ctx context.Context, instanceID string, req *
|
|||||||
agentCommand = agentCommand + " " + agentConfig.ModelFlag + " " + profileInfo.Model
|
agentCommand = agentCommand + " " + agentConfig.ModelFlag + " " + profileInfo.Model
|
||||||
}
|
}
|
||||||
if req.ACPSessionID != "" && profileInfo != nil && profileInfo.AgentName == "auggie" {
|
if req.ACPSessionID != "" && profileInfo != nil && profileInfo.AgentName == "auggie" {
|
||||||
|
m.logger.Info("adding auggie resume flag to command (standalone)",
|
||||||
|
zap.String("task_id", req.TaskID),
|
||||||
|
zap.String("session_id", req.ACPSessionID))
|
||||||
agentCommand = agentCommand + " --resume " + req.ACPSessionID
|
agentCommand = agentCommand + " --resume " + req.ACPSessionID
|
||||||
}
|
}
|
||||||
// If empty, agentctl will use its default (auggie --acp)
|
// If empty, agentctl will use its default (auggie --acp)
|
||||||
@@ -835,6 +865,10 @@ func (m *Manager) initializeACPSession(ctx context.Context, instance *AgentInsta
|
|||||||
}
|
}
|
||||||
|
|
||||||
instance.ACPSessionID = sessionID
|
instance.ACPSessionID = sessionID
|
||||||
|
m.logger.Info("ACP session ready",
|
||||||
|
zap.String("instance_id", instance.ID),
|
||||||
|
zap.String("session_id", sessionID),
|
||||||
|
zap.String("agent_name", agentName))
|
||||||
m.publishACPSessionCreated(instance, sessionID)
|
m.publishACPSessionCreated(instance, sessionID)
|
||||||
|
|
||||||
// 3. Set up updates stream to receive session notifications
|
// 3. Set up updates stream to receive session notifications
|
||||||
@@ -1594,6 +1628,9 @@ func (m *Manager) buildContainerConfig(instanceID string, req *LaunchRequest, ag
|
|||||||
cmd = append(cmd, agentConfig.ModelFlag, profileInfo.Model)
|
cmd = append(cmd, agentConfig.ModelFlag, profileInfo.Model)
|
||||||
}
|
}
|
||||||
if req.ACPSessionID != "" && profileInfo != nil && profileInfo.AgentName == "auggie" {
|
if req.ACPSessionID != "" && profileInfo != nil && profileInfo.AgentName == "auggie" {
|
||||||
|
m.logger.Info("adding auggie resume flag to command",
|
||||||
|
zap.String("task_id", req.TaskID),
|
||||||
|
zap.String("session_id", req.ACPSessionID))
|
||||||
cmd = append(cmd, "--resume", req.ACPSessionID)
|
cmd = append(cmd, "--resume", req.ACPSessionID)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1631,7 +1668,7 @@ func (m *Manager) expandMountTemplate(source, workspacePath, taskID string) stri
|
|||||||
}
|
}
|
||||||
augmentSessionsDir := filepath.Join(homeDir, ".augment", "sessions")
|
augmentSessionsDir := filepath.Join(homeDir, ".augment", "sessions")
|
||||||
// Ensure the directory exists
|
// Ensure the directory exists
|
||||||
_ = os.MkdirAll(augmentSessionsDir, 0755)
|
_ = os.MkdirAll(augmentSessionsDir, 0o755)
|
||||||
result = strings.ReplaceAll(result, "{augment_sessions}", augmentSessionsDir)
|
result = strings.ReplaceAll(result, "{augment_sessions}", augmentSessionsDir)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2059,6 +2096,12 @@ func (m *Manager) publishACPSessionCreated(instance *AgentInstance, sessionID st
|
|||||||
zap.String("event_type", events.AgentACPSessionCreated),
|
zap.String("event_type", events.AgentACPSessionCreated),
|
||||||
zap.String("instance_id", instance.ID),
|
zap.String("instance_id", instance.ID),
|
||||||
zap.Error(err))
|
zap.Error(err))
|
||||||
|
} else {
|
||||||
|
m.logger.Info("published ACP session event",
|
||||||
|
zap.String("event_type", events.AgentACPSessionCreated),
|
||||||
|
zap.String("task_id", instance.TaskID),
|
||||||
|
zap.String("agent_instance_id", instance.ID),
|
||||||
|
zap.String("acp_session_id", sessionID))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user