diff --git a/cmd/dev.go b/cmd/dev.go index ed6ef7a6..82e47f95 100644 --- a/cmd/dev.go +++ b/cmd/dev.go @@ -2,6 +2,7 @@ package cmd import ( "context" + "errors" "fmt" "io" "os" @@ -123,6 +124,10 @@ Examples: tuiLoggerErr := dev.NewTUILogger(logLevel, ui, dev.StdErr) if err := server.Connect(ui, tuiLogger, tuiLoggerErr); err != nil { + if errors.Is(err, context.Canceled) { + ui.Close(true) + return + } log.Error("failed to start live dev connection: %s", err) ui.Close(true) return diff --git a/internal/bundler/shim.go b/internal/bundler/shim.go index 6eb4c0a0..206a0472 100644 --- a/internal/bundler/shim.go +++ b/internal/bundler/shim.go @@ -69,39 +69,43 @@ function getSourceMap(filename) { } const frameRegex = /(.+)\((.+):(\d+):(\d+)\)$/; Error.prepareStackTrace = function (err, stack) { - const _stack = __prepareStackTrace(err, stack); - const tok = _stack.split('\n'); - const lines = []; - for (const t of tok) { - if (t.includes('.agentuity/') && frameRegex.test(t)) { - const parts = frameRegex.exec(t); - if (parts.length === 5) { - const filename = parts[2]; - const sm = getSourceMap(filename+'.map'); - if (sm) { - const lineno = parts[3]; - const colno = parts[4]; - const pos = sm.originalPositionFor({ - line: +lineno, - column: +colno, - }) - if (pos && pos.source) { - const startIndex = filename.indexOf('.agentuity/'); - const offset = filename.includes('../node_modules/') ? 11 : 0; - const basedir = filename.substring(0, startIndex + offset); - const sourceOffset = pos.source.indexOf('src/'); - const source = pos.source.substring(sourceOffset); - const newfile = __agentuity_join(basedir, source); - const newline = parts[1] + '(' + newfile + ':' + pos.line + ':' + pos.column + ')'; - lines.push(newline); - continue; + try { + const _stack = __prepareStackTrace(err, stack); + const tok = _stack.split('\n'); + const lines = []; + for (const t of tok) { + if (t.includes('.agentuity/') && frameRegex.test(t)) { + const parts = frameRegex.exec(t); + if (parts.length === 5) { + const filename = parts[2]; + const sm = getSourceMap(filename+'.map'); + if (sm) { + const lineno = parts[3]; + const colno = parts[4]; + const pos = sm.originalPositionFor({ + line: +lineno, + column: +colno, + }) + if (pos && pos.source) { + const startIndex = filename.indexOf('.agentuity/'); + const offset = filename.includes('../node_modules/') ? 11 : 0; + const basedir = filename.substring(0, startIndex + offset); + const sourceOffset = pos.source.indexOf('src/'); + const source = pos.source.substring(sourceOffset); + const newfile = __agentuity_join(basedir, source); + const newline = parts[1] + '(' + newfile + ':' + pos.line + ':' + pos.column + ')'; + lines.push(newline); + continue; + } } } } + lines.push(t); } - lines.push(t); + return lines.join('\n'); + } catch (e) { + return stack; } - return lines.join('\n'); }; })(); ` diff --git a/internal/dev/server.go b/internal/dev/server.go index dd147199..7a8a8efc 100644 --- a/internal/dev/server.go +++ b/internal/dev/server.go @@ -56,7 +56,7 @@ type Server struct { apiclient *util.APIClient publicUrl string port int - connected chan string + connected chan error pendingLogger logger.Logger expiresAt *time.Time tlsCertificate *tls.Certificate @@ -161,6 +161,8 @@ func (s *Server) reconnect() { func (s *Server) connect(initial bool) { var gerr error + s.logger.Trace("connecting to devmode server") + // hold a connection lock to prevent multiple go routines from trying to reconnect // before the previous connect goroutine has finished s.connectionLock.Lock() @@ -168,7 +170,7 @@ func (s *Server) connect(initial bool) { defer func() { if initial && gerr != nil { - s.connected <- gerr.Error() + s.connected <- gerr } s.logger.Debug("connection closed") select { @@ -201,12 +203,19 @@ func (s *Server) connect(initial bool) { } }() + s.logger.Trace("refreshing connection metadata") + refreshStart := time.Now() if err := s.refreshConnection(); err != nil { - s.logger.Error("failed to refresh connection: %s", err) + if !initial { + s.logger.Error("failed to refresh connection: %s", err) + } + // initial will bubble this up gerr = err return } + s.logger.Trace("refreshed connection metadata in %v", time.Since(refreshStart)) + var tlsConfig tls.Config tlsConfig.Certificates = []tls.Certificate{*s.tlsCertificate} tlsConfig.NextProtos = []string{"h2"} @@ -221,6 +230,8 @@ func (s *Server) connect(initial bool) { hostname = fmt.Sprintf("%s:443", hostname) } + s.logger.Trace("dialing devmode server: %s", hostname) + dialStart := time.Now() conn, err := tls.Dial("tcp", hostname, &tlsConfig) if err != nil { gerr = err @@ -228,9 +239,10 @@ func (s *Server) connect(initial bool) { return } s.conn = conn + s.logger.Trace("dialed devmode server in %v", time.Since(dialStart)) if initial { - s.connected <- "" + s.connected <- nil } // if we successfully connect, reset our connection failures @@ -557,18 +569,15 @@ func (s *Server) HealthCheck(devModeUrl string) error { } func (s *Server) Connect(ui *DevModeUI, tuiLogger logger.Logger, tuiLoggerErr logger.Logger) error { - s.logger = tuiLogger - if pl, ok := tuiLoggerErr.(*PendingLogger); ok { - pl.drain(ui, tuiLoggerErr) - } if pl, ok := s.logger.(*PendingLogger); ok { - pl.drain(ui, s.logger) + pl.drain(ui, tuiLogger) } + s.logger = tuiLogger s.pendingLogger = s.logger - msg := <-s.connected + err := <-s.connected close(s.connected) - if msg != "" { - return fmt.Errorf("%s", msg) + if err != nil { + return err } return nil } @@ -590,11 +599,12 @@ func (s *Server) monitor() { } func New(args ServerArgs) (*Server, error) { - id := cstr.NewHash(args.OrgId, args.UserId) + id := cstr.NewHash(args.Project.Project.ProjectId, args.UserId) tracer := otel.Tracer("@agentuity/cli", trace.WithInstrumentationAttributes( attribute.String("id", id), attribute.String("@agentuity/orgId", args.OrgId), attribute.String("@agentuity/userId", args.UserId), + attribute.String("@agentuity/projectId", args.Project.Project.ProjectId), attribute.Bool("@agentuity/devmode", true), attribute.String("name", "@agentuity/cli"), attribute.String("version", args.Version), @@ -618,9 +628,9 @@ func New(args ServerArgs) (*Server, error) { tracer: tracer, version: args.Version, port: args.Port, - apiclient: util.NewAPIClient(context.Background(), pendingLogger, args.APIURL, args.APIKey), + apiclient: util.NewAPIClient(ctx, pendingLogger, args.APIURL, args.APIKey), pendingLogger: pendingLogger, - connected: make(chan string, 1), + connected: make(chan error, 1), } go server.connect(true) diff --git a/internal/util/api.go b/internal/util/api.go index 45bfcb0c..8b8279f9 100644 --- a/internal/util/api.go +++ b/internal/util/api.go @@ -175,6 +175,9 @@ func (c *APIClient) Do(method, pathParam string, payload interface{}, response i continue } if err != nil { + if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) { + return err + } return NewAPIError(u.String(), method, 0, "", fmt.Errorf("error sending request: %w", err), traceID) } break