feat: port NOFXi agent module onto latest dev base (#1485)

* feat: integrate NOFXi agent into dev

* Enhance NOFXi agent workflow and diagnostics
This commit is contained in:
lky-spec
2026-04-21 23:47:55 +08:00
committed by GitHub
parent 1ba50bdedf
commit 3ca95b294d
88 changed files with 22630 additions and 1143 deletions

59
safe/go.go Normal file
View File

@@ -0,0 +1,59 @@
// Package safe provides panic-recovery wrappers for goroutines.
// A panic in any bare goroutine tears down the entire process.
// Use safe.Go instead of `go func()` in long-running or critical paths.
package safe
import (
"fmt"
"nofx/logger"
"runtime/debug"
)
// Go launches fn in a new goroutine with automatic panic recovery.
// If fn panics, the panic is logged (with stack trace) but the process
// continues running. An optional onPanic callback receives the recovered value.
func Go(fn func(), onPanic ...func(recovered interface{})) {
go func() {
defer func() {
if r := recover(); r != nil {
stack := string(debug.Stack())
logger.Errorf("🔥 goroutine panic recovered: %v\n%s", r, stack)
for _, cb := range onPanic {
func() {
defer func() {
if r2 := recover(); r2 != nil {
logger.Errorf("🔥 onPanic callback itself panicked: %v", r2)
}
}()
cb(r)
}()
}
}
}()
fn()
}()
}
// GoNamed is like Go but tags the log line with a human-readable name.
func GoNamed(name string, fn func(), onPanic ...func(recovered interface{})) {
Go(func() {
fn()
}, append([]func(interface{}){
func(r interface{}) {
logger.Errorf("🔥 [%s] goroutine panicked: %v", name, r)
},
}, onPanic...)...)
}
// Must converts a panic into an error. Useful inside goroutines where you
// want to handle panics as errors in the caller's recovery flow.
func Must(fn func()) (err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("panic: %v\n%s", r, debug.Stack())
}
}()
fn()
return nil
}

29
safe/io.go Normal file
View File

@@ -0,0 +1,29 @@
// Package safe provides safe I/O helpers.
package safe
import (
"fmt"
"io"
)
// MaxResponseBody is the default maximum size for HTTP response bodies (10MB).
const MaxResponseBody = 10 * 1024 * 1024
// ReadAllLimited reads all bytes from r up to maxBytes.
// If maxBytes <= 0, it defaults to MaxResponseBody (10MB).
// Returns an error if the response exceeds the limit.
func ReadAllLimited(r io.Reader, maxBytes ...int64) ([]byte, error) {
limit := int64(MaxResponseBody)
if len(maxBytes) > 0 && maxBytes[0] > 0 {
limit = maxBytes[0]
}
lr := io.LimitReader(r, limit+1)
data, err := io.ReadAll(lr)
if err != nil {
return nil, err
}
if int64(len(data)) > limit {
return nil, fmt.Errorf("response body exceeds %d bytes limit", limit)
}
return data, nil
}