mirror of
https://github.com/tiennm99/goclaw.git
synced 2026-10-03 05:20:01 +00:00
feat(infra): tracing recovery, browser cleanup, CLI fixes, UI workspace split (#709)
- Tracing: recover stale running traces/spans on startup (PG + SQLite) - Browser: Chrome orphan cleanup via launcher PID, timeouts, Leakless - Claude CLI: WaitDelay 5s + context-cancel early exit - Agent loop: safety-net defer to finalize orphan root traces - UI: split workspace sharing into separate Memory and KG toggles - Minor: for-range idiom, min() builtin
This commit is contained in:
1 parent
7d7b716074
commit
41e6c8f5cc
15 files changed
+208
-45
No files matched your search
+51
-10
@@ -15,6 +15,7 @@ import (
|
||||
type Manager struct {
|
||||
mu sync.Mutex
|
||||
browser *rod.Browser
|
||||
launcher *launcher.Launcher // retained for PID-based cleanup on crash
|
||||
refs *RefStore
|
||||
pages map[string]*rod.Page // targetID → page
|
||||
console map[string][]ConsoleMessage // targetID → console messages
|
||||
@@ -107,12 +108,7 @@ func (m *Manager) Start(ctx context.Context) error {
|
||||
}
|
||||
// Connection dead — clean up and reconnect
|
||||
m.logger.Info("browser connection lost, reconnecting")
|
||||
m.closeTenantContextsLocked()
|
||||
m.browser = nil
|
||||
m.pages = make(map[string]*rod.Page)
|
||||
m.console = make(map[string][]ConsoleMessage)
|
||||
m.pageTenants = make(map[string]string)
|
||||
m.refs = NewRefStore()
|
||||
m.cleanupDeadBrowserLocked()
|
||||
}
|
||||
|
||||
var controlURL string
|
||||
@@ -126,23 +122,45 @@ func (m *Manager) Start(ctx context.Context) error {
|
||||
controlURL = u
|
||||
m.logger.Info("connecting to remote Chrome", "cdp", controlURL, "remote", m.remoteURL)
|
||||
} else {
|
||||
// Local Chrome — launch via rod launcher
|
||||
// Local Chrome — launch via rod launcher with stability flags
|
||||
launchCtx, launchCancel := context.WithTimeout(ctx, 30*time.Second)
|
||||
defer launchCancel()
|
||||
|
||||
l := launcher.New().
|
||||
Context(launchCtx).
|
||||
Leakless(true).
|
||||
Headless(m.headless).
|
||||
Set("disable-gpu").
|
||||
Set("no-first-run").
|
||||
Set("no-default-browser-check")
|
||||
Set("no-default-browser-check").
|
||||
Set("disable-dev-shm-usage").
|
||||
Set("disable-software-rasterizer").
|
||||
Set("disable-extensions").
|
||||
Set("disable-background-networking").
|
||||
Set("disable-renderer-backgrounding").
|
||||
Set("disable-background-timer-throttling").
|
||||
Set("disable-backgrounding-occluded-windows")
|
||||
|
||||
u, err := l.Launch()
|
||||
if err != nil {
|
||||
return fmt.Errorf("launch Chrome: %w", err)
|
||||
}
|
||||
controlURL = u
|
||||
m.logger.Info("Chrome launched", "cdp", controlURL, "headless", m.headless)
|
||||
m.launcher = l
|
||||
m.logger.Info("Chrome launched", "cdp", controlURL, "headless", m.headless, "pid", l.PID())
|
||||
}
|
||||
|
||||
b := rod.New().ControlURL(controlURL)
|
||||
connectCtx, connectCancel := context.WithTimeout(ctx, 15*time.Second)
|
||||
defer connectCancel()
|
||||
|
||||
b := rod.New().Context(connectCtx).ControlURL(controlURL)
|
||||
if err := b.Connect(); err != nil {
|
||||
// If local launch succeeded but connect failed, kill the orphan process
|
||||
if m.launcher != nil {
|
||||
m.launcher.Kill()
|
||||
m.launcher.Cleanup()
|
||||
m.launcher = nil
|
||||
}
|
||||
return fmt.Errorf("connect to Chrome: %w", err)
|
||||
}
|
||||
|
||||
@@ -182,6 +200,12 @@ func (m *Manager) Stop(ctx context.Context) error {
|
||||
if m.remoteURL == "" {
|
||||
// Local Chrome — close the browser process
|
||||
err = m.browser.Close()
|
||||
// Force-kill via launcher if retained
|
||||
if m.launcher != nil {
|
||||
m.launcher.Kill()
|
||||
m.launcher.Cleanup()
|
||||
m.launcher = nil
|
||||
}
|
||||
}
|
||||
// Remote Chrome — just drop the connection; sidecar stays alive
|
||||
|
||||
@@ -203,6 +227,23 @@ func (m *Manager) closeTenantContextsLocked() {
|
||||
m.tenantCtxs = make(map[string]*rod.Browser)
|
||||
}
|
||||
|
||||
// cleanupDeadBrowserLocked resets all state and kills any orphan Chrome process.
|
||||
// Must be called with mu held.
|
||||
func (m *Manager) cleanupDeadBrowserLocked() {
|
||||
m.closeTenantContextsLocked()
|
||||
if m.launcher != nil {
|
||||
m.launcher.Kill()
|
||||
m.launcher.Cleanup()
|
||||
m.launcher = nil
|
||||
}
|
||||
m.browser = nil
|
||||
m.pages = make(map[string]*rod.Page)
|
||||
m.console = make(map[string][]ConsoleMessage)
|
||||
m.pageTenants = make(map[string]string)
|
||||
m.pageLastUsed = make(map[string]time.Time)
|
||||
m.refs = NewRefStore()
|
||||
}
|
||||
|
||||
// MasterTenantID is the well-known master tenant UUID string.
|
||||
// Pages opened without a tenant context or by the master tenant use the main browser directly.
|
||||
const MasterTenantID = "0193a5b0-7000-7000-8000-000000000001"
|
||||
|
||||
Reference in new issue
Block a user