feat: production-grade hardening v1.1.0
- fix: Compile() defensive map copy, prevents post-compile Graph mutation - fix: correct 'origin'→'destination' in edge validation error message - feat: export ErrNotPaused/ErrAlreadyFinished for errors.Is() support - feat: WithPreNodeHook/WithPostNodeHook now support chaining (multiple hooks) - feat: Compile detects conflicting outgoing edge types on same node - fix: FileCheckpointer atomic write (tmp+rename, crash-safe) - fix: FileCheckpointer threadID path traversal validation - refactor: remove redundant continue in run() loop - docs: add doc.go package-level documentation - test: +7 test cases covering error paths, edge conflicts, hook chaining
This commit is contained in:
1 parent
235f66c9ba
commit
de457f670a
8 files changed
+352
-33
No files matched your search
+20
-12
@@ -76,7 +76,15 @@ func (e *Engine[S]) WithMaxSteps(n int) *Engine[S] {
|
||||
// 钩子函数接收到的 state 是节点执行【前】的状态快照,请勿修改(只读语义)。
|
||||
// 钩子中的 panic 会向上传播,建议在内部做好 recover。
|
||||
func (e *Engine[S]) WithPreNodeHook(fn HookFn[S]) *Engine[S] {
|
||||
e.preNodeHook = fn
|
||||
if e.preNodeHook == nil {
|
||||
e.preNodeHook = fn
|
||||
} else {
|
||||
prev := e.preNodeHook
|
||||
e.preNodeHook = func(ctx context.Context, name string, s S) {
|
||||
prev(ctx, name, s)
|
||||
fn(ctx, name, s)
|
||||
}
|
||||
}
|
||||
return e
|
||||
}
|
||||
|
||||
@@ -89,7 +97,15 @@ func (e *Engine[S]) WithPreNodeHook(fn HookFn[S]) *Engine[S] {
|
||||
//
|
||||
// 钩子函数接收到的 state 是节点执行【后】更新过的状态,同为只读语义。
|
||||
func (e *Engine[S]) WithPostNodeHook(fn HookFn[S]) *Engine[S] {
|
||||
e.postNodeHook = fn
|
||||
if e.postNodeHook == nil {
|
||||
e.postNodeHook = fn
|
||||
} else {
|
||||
prev := e.postNodeHook
|
||||
e.postNodeHook = func(ctx context.Context, name string, s S) {
|
||||
prev(ctx, name, s)
|
||||
fn(ctx, name, s)
|
||||
}
|
||||
}
|
||||
return e
|
||||
}
|
||||
|
||||
@@ -117,10 +133,10 @@ func (e *Engine[S]) Start(ctx context.Context, startNode string, initialState S)
|
||||
// 但会附加所有已注册的 Engine 增强选项(Hooks、MaxSteps、StateCloner)。
|
||||
func (e *Engine[S]) Resume(ctx context.Context, thread *Thread[S], modifiedState S) (*Thread[S], error) {
|
||||
if !thread.IsPaused {
|
||||
return nil, errNotPaused
|
||||
return nil, ErrNotPaused
|
||||
}
|
||||
if thread.IsFinished {
|
||||
return nil, errAlreadyFinished
|
||||
return nil, ErrAlreadyFinished
|
||||
}
|
||||
|
||||
thread.State = modifiedState
|
||||
@@ -129,12 +145,4 @@ func (e *Engine[S]) Resume(ctx context.Context, thread *Thread[S], modifiedState
|
||||
return e.graph.run(ctx, thread, e.opts())
|
||||
}
|
||||
|
||||
// 复用与 CompiledGraph.Resume 中相同的哨兵错误,避免魔法字符串重复
|
||||
var (
|
||||
errNotPaused = errStr("cannot resume: thread is not paused")
|
||||
errAlreadyFinished = errStr("cannot resume: thread is already finished")
|
||||
)
|
||||
|
||||
type errStr string
|
||||
|
||||
func (e errStr) Error() string { return string(e) }
|
||||
Reference in new issue
Block a user