Details (original message): Add catalog-driven orchestration: intent catalog injection, deterministic plan validation, and system-function steps.
143 lines
5.5 KiB
Go
143 lines
5.5 KiB
Go
package squad
|
|
|
|
import (
|
|
"crypto/rand"
|
|
"encoding/hex"
|
|
"time"
|
|
)
|
|
|
|
// HitlMode HITL 策略模式。
|
|
type HitlMode string
|
|
|
|
const (
|
|
// HitlInteractive 默认:挂起并等待人工回应。
|
|
HitlInteractive HitlMode = "interactive"
|
|
// HitlAuto 无值守:阻塞缺口按角色 fallback 假设兜底(未声明则步骤失败);
|
|
// 高风险决策(计划确认/权限升级)不静默猜测,仍转为失败并留痕。
|
|
HitlAuto HitlMode = "auto"
|
|
// HitlDisabled 不挂起:所有本应挂起的点直接按失败处理。
|
|
HitlDisabled HitlMode = "disabled"
|
|
)
|
|
|
|
// HitlPolicy HITL 策略(全局默认 → 角色覆盖 → Goal 覆盖,窄者胜)。
|
|
type HitlPolicy struct {
|
|
Mode HitlMode `json:"mode,omitempty"`
|
|
// TimeoutAfter 非零时,pending 请求超过该时长未回应自动过期;
|
|
// 过期动作由 ExpireAction 决定(fail / cancel)。
|
|
TimeoutAfter time.Duration `json:"timeout_after,omitempty"`
|
|
// ExpireAction 过期动作:fail(默认,运行失败留痕)/ cancel(运行取消)。
|
|
ExpireAction string `json:"expire_action,omitempty"`
|
|
}
|
|
|
|
func (p HitlPolicy) mode() HitlMode {
|
|
if p.Mode == "" {
|
|
return HitlInteractive
|
|
}
|
|
return p.Mode
|
|
}
|
|
|
|
// HumanRequestKind 人工请求类型。
|
|
type HumanRequestKind string
|
|
|
|
const (
|
|
HitlClarification HumanRequestKind = "clarification" // 规划歧义澄清
|
|
HitlConfirmation HumanRequestKind = "confirmation" // 计划/高风险步骤确认
|
|
HitlGap HumanRequestKind = "gap" // 阻塞缺口
|
|
HitlAdjustment HumanRequestKind = "adjustment" // 动态调整审批
|
|
HitlPermission HumanRequestKind = "permission" // 权限升级审批
|
|
HitlQualityReview HumanRequestKind = "quality_review" // 质检超限人工裁决
|
|
HitlEscalation HumanRequestKind = "escalation" // leader/成员升级问人(委派不了/需要拍板)
|
|
)
|
|
|
|
// Severity 人工请求紧急度(inbox 语义:action_required 需要人做决定,
|
|
// attention 建议关注;用于通知分层与降噪)。
|
|
const (
|
|
SeverityActionRequired = "action_required"
|
|
SeverityAttention = "attention"
|
|
)
|
|
|
|
// WaitCondition 挂起请求的自动唤醒条件(等事件而非只等人)。
|
|
// 条件满足时引擎合成一条自动决策续跑——唤醒报告的是事实,不是业务完成。
|
|
type WaitCondition struct {
|
|
Type string `json:"type"` // run_done | field_set
|
|
RunID string `json:"run_id,omitempty"` // run_done:等待该运行到达终态
|
|
Field string `json:"field,omitempty"` // field_set:等待黑板出现该字段
|
|
}
|
|
|
|
// MaxWakeupFires 单个挂起请求自动唤醒次数上限(防失控护栏)。
|
|
const MaxWakeupFires = 5
|
|
|
|
// HumanRequest 一次结构化人工请求(可持久化、可恢复、按 ID 幂等回应)。
|
|
type HumanRequest struct {
|
|
ID string `json:"id"`
|
|
RunID string `json:"run_id"`
|
|
StepID string `json:"step_id,omitempty"`
|
|
Kind HumanRequestKind `json:"kind"`
|
|
Severity string `json:"severity,omitempty"` // action_required(默认)/ attention
|
|
Question string `json:"question"`
|
|
Options []HumanOption `json:"options,omitempty"`
|
|
Context string `json:"context,omitempty"` // 脱敏摘要(计划/缺口/质检报告)
|
|
// WaitCondition 非空时,条件满足可自动唤醒(人工回应仍然优先)。
|
|
WaitCondition *WaitCondition `json:"wait_condition,omitempty"`
|
|
// WakeupFires 已自动唤醒次数(护栏:达到 MaxWakeupFires 后过期)。
|
|
WakeupFires int `json:"wakeup_fires,omitempty"`
|
|
CreatedAt time.Time `json:"created_at"`
|
|
ExpiresAt *time.Time `json:"expires_at,omitempty"`
|
|
Status string `json:"status"` // pending, answered, expired, cancelled
|
|
AnsweredAt *time.Time `json:"answered_at,omitempty"`
|
|
}
|
|
|
|
// HumanOption 一个可选项;Value 为回应时提交的 choice。
|
|
type HumanOption struct {
|
|
Value string `json:"value"`
|
|
Label string `json:"label"`
|
|
}
|
|
|
|
// RespondDecision 对一次 HumanRequest 的人工决策。
|
|
type RespondDecision struct {
|
|
RequestID string `json:"request_id"`
|
|
// Answer 自由文本回答(clarification/gap)。
|
|
Answer string `json:"answer,omitempty"`
|
|
// Choice 选项值(confirmation/adjustment/permission/quality_review 常用):
|
|
// approve / reject / retry / skip / abort 等,或 HumanOption.Value。
|
|
Choice string `json:"choice,omitempty"`
|
|
// EditPlan 确认计划时的修改指令(附加在 Answer 上传递给引擎)。
|
|
EditPlan string `json:"edit_plan,omitempty"`
|
|
}
|
|
|
|
// newRequestID 生成请求 ID。
|
|
func newRequestID() string {
|
|
b := make([]byte, 8)
|
|
_, _ = rand.Read(b)
|
|
return "hr_" + hex.EncodeToString(b)
|
|
}
|
|
|
|
// hitlGate 引擎内部:一次挂起的完整上下文(落盘 pending.json)。
|
|
type hitlGate struct {
|
|
Request HumanRequest `json:"request"`
|
|
Plan *SquadPlan `json:"plan,omitempty"` // confirmation 时携带候选计划
|
|
Steps []StepSpec `json:"steps,omitempty"` // adjustment:建议追加的步骤
|
|
SkipIDs []string `json:"skip_ids,omitempty"` // adjustment:建议跳过的步骤
|
|
PrevStatus RunStatus `json:"prev_status"` // 回应后恢复到的状态
|
|
}
|
|
|
|
// mergeHitlPolicy 按全局 → 角色 → Goal 顺序取窄者(后设置者覆盖)。
|
|
func mergeHitlPolicy(base HitlPolicy, overrides ...*HitlPolicy) HitlPolicy {
|
|
out := base
|
|
for _, ov := range overrides {
|
|
if ov == nil {
|
|
continue
|
|
}
|
|
if ov.Mode != "" {
|
|
out.Mode = ov.Mode
|
|
}
|
|
if ov.TimeoutAfter != 0 {
|
|
out.TimeoutAfter = ov.TimeoutAfter
|
|
}
|
|
if ov.ExpireAction != "" {
|
|
out.ExpireAction = ov.ExpireAction
|
|
}
|
|
}
|
|
return out
|
|
}
|