change v1

This commit is contained in:
lky-spec
2026-04-25 16:18:45 +08:00
parent 737f9bca95
commit c244e4cdf1
89 changed files with 17382 additions and 6198 deletions

View File

@@ -5,6 +5,8 @@ import (
"fmt"
"strings"
"time"
"github.com/google/uuid"
)
const (
@@ -30,22 +32,38 @@ const (
)
type ExecutionState struct {
SessionID string `json:"session_id"`
UserID int64 `json:"user_id"`
Goal string `json:"goal"`
Status string `json:"status"`
PlanID string `json:"plan_id"`
Steps []PlanStep `json:"steps,omitempty"`
CurrentStepID string `json:"current_step_id,omitempty"`
SessionID string `json:"session_id"`
UserID int64 `json:"user_id"`
Goal string `json:"goal"`
Status string `json:"status"`
PlanID string `json:"plan_id"`
Steps []PlanStep `json:"steps,omitempty"`
CurrentStepID string `json:"current_step_id,omitempty"`
CurrentReferences *CurrentReferences `json:"current_references,omitempty"`
DynamicSnapshots []Observation `json:"dynamic_snapshots,omitempty"`
ExecutionLog []Observation `json:"execution_log,omitempty"`
SummaryNotes []Observation `json:"summary_notes,omitempty"`
Waiting *WaitingState `json:"waiting,omitempty"`
Observations []Observation `json:"observations,omitempty"`
FinalAnswer string `json:"final_answer,omitempty"`
LastError string `json:"last_error,omitempty"`
UpdatedAt string `json:"updated_at"`
ReferenceHistory []ReferenceRecord `json:"reference_history,omitempty"`
DynamicSnapshots []Observation `json:"dynamic_snapshots,omitempty"`
ExecutionLog []Observation `json:"execution_log,omitempty"`
SummaryNotes []Observation `json:"summary_notes,omitempty"`
Waiting *WaitingState `json:"waiting,omitempty"`
Observations []Observation `json:"observations,omitempty"`
FinalAnswer string `json:"final_answer,omitempty"`
LastError string `json:"last_error,omitempty"`
UpdatedAt string `json:"updated_at"`
}
type SuspendedTask struct {
SnapshotID string `json:"snapshot_id,omitempty"`
IntentID string `json:"intent_id,omitempty"`
ParentIntentID string `json:"parent_intent_id,omitempty"`
Kind string `json:"kind,omitempty"`
ResumeHint string `json:"resume_hint,omitempty"`
ResumeOnSuccess bool `json:"resume_on_success,omitempty"`
ResumeTriggers []string `json:"resume_triggers,omitempty"`
SkillSession *skillSession `json:"skill_session,omitempty"`
WorkflowSession *WorkflowSession `json:"workflow_session,omitempty"`
ExecutionState *ExecutionState `json:"execution_state,omitempty"`
LocalHistory []chatMessage `json:"local_history,omitempty"`
SuspendedAt string `json:"suspended_at,omitempty"`
}
type PlanStep struct {
@@ -78,8 +96,18 @@ type WaitingState struct {
}
type EntityReference struct {
ID string `json:"id,omitempty"`
Name string `json:"name,omitempty"`
ID string `json:"id,omitempty"`
Name string `json:"name,omitempty"`
Source string `json:"source,omitempty"`
UpdatedAt string `json:"updated_at,omitempty"`
}
type ReferenceRecord struct {
Kind string `json:"kind,omitempty"`
ID string `json:"id,omitempty"`
Name string `json:"name,omitempty"`
Source string `json:"source,omitempty"`
CreatedAt string `json:"created_at,omitempty"`
}
type CurrentReferences struct {
@@ -89,6 +117,20 @@ type CurrentReferences struct {
Exchange *EntityReference `json:"exchange,omitempty"`
}
type SnapshotSummary struct {
SnapshotID string `json:"snapshot_id,omitempty"`
IntentID string `json:"intent_id,omitempty"`
ParentIntentID string `json:"parent_intent_id,omitempty"`
Kind string `json:"kind,omitempty"`
ResumeHint string `json:"resume_hint,omitempty"`
SuspendedAt string `json:"suspended_at,omitempty"`
}
type SnapshotManager struct {
agent *Agent
userID int64
}
type executionPlan struct {
Goal string `json:"goal"`
Steps []PlanStep `json:"steps"`
@@ -103,6 +145,82 @@ func ExecutionStateConfigKey(userID int64) string {
return fmt.Sprintf("agent_execution_state_%d", userID)
}
func taskStackConfigKey(userID int64) string {
return fmt.Sprintf("agent_task_stack_%d", userID)
}
func (a *Agent) SnapshotManager(userID int64) SnapshotManager {
return SnapshotManager{agent: a, userID: userID}
}
func (m SnapshotManager) Save(task SuspendedTask) {
if m.agent == nil {
return
}
m.agent.pushTaskStack(m.userID, task)
}
func (m SnapshotManager) Load() (SuspendedTask, bool) {
if m.agent == nil {
return SuspendedTask{}, false
}
return m.agent.popTaskStack(m.userID)
}
func (m SnapshotManager) Peek() (SuspendedTask, bool) {
if m.agent == nil {
return SuspendedTask{}, false
}
return m.agent.peekTaskStack(m.userID)
}
func (m SnapshotManager) List() []SnapshotSummary {
if m.agent == nil {
return nil
}
stack := m.agent.getTaskStack(m.userID)
out := make([]SnapshotSummary, 0, len(stack))
for _, item := range stack {
out = append(out, SnapshotSummary{
SnapshotID: strings.TrimSpace(item.SnapshotID),
IntentID: strings.TrimSpace(item.IntentID),
ParentIntentID: strings.TrimSpace(item.ParentIntentID),
Kind: strings.TrimSpace(item.Kind),
ResumeHint: strings.TrimSpace(item.ResumeHint),
SuspendedAt: strings.TrimSpace(item.SuspendedAt),
})
}
return out
}
func (m SnapshotManager) Stack() []SuspendedTask {
if m.agent == nil {
return nil
}
return m.agent.getTaskStack(m.userID)
}
func (m SnapshotManager) RemoveAt(index int) (SuspendedTask, bool) {
if m.agent == nil {
return SuspendedTask{}, false
}
stack := m.agent.getTaskStack(m.userID)
if index < 0 || index >= len(stack) {
return SuspendedTask{}, false
}
task := stack[index]
stack = append(stack[:index], stack[index+1:]...)
m.agent.saveTaskStack(m.userID, stack)
return task, true
}
func (m SnapshotManager) Clear() {
if m.agent == nil {
return
}
m.agent.clearTaskStack(m.userID)
}
func (a *Agent) getExecutionState(userID int64) ExecutionState {
if a.store == nil {
return ExecutionState{}
@@ -133,6 +251,9 @@ func (a *Agent) saveExecutionState(state ExecutionState) error {
if state.SessionID == "" {
return a.store.SetSystemConfig(ExecutionStateConfigKey(state.UserID), "")
}
if state.UserID != 0 && (state.CurrentReferences != nil || len(state.ReferenceHistory) > 0) {
a.saveReferenceMemory(state.UserID, state.CurrentReferences, state.ReferenceHistory)
}
data, err := json.Marshal(state)
if err != nil {
return err
@@ -149,6 +270,80 @@ func (a *Agent) clearExecutionState(userID int64) {
}
}
func (a *Agent) getTaskStack(userID int64) []SuspendedTask {
if a.store == nil {
return nil
}
raw, err := a.store.GetSystemConfig(taskStackConfigKey(userID))
if err != nil {
a.logger.Warn("failed to load task stack", "error", err, "user_id", userID)
return nil
}
raw = strings.TrimSpace(raw)
if raw == "" {
return nil
}
var stack []SuspendedTask
if err := json.Unmarshal([]byte(raw), &stack); err != nil {
a.logger.Warn("failed to parse task stack", "error", err, "user_id", userID)
return nil
}
return normalizeTaskStack(stack)
}
func (a *Agent) saveTaskStack(userID int64, stack []SuspendedTask) {
if a.store == nil {
return
}
stack = normalizeTaskStack(stack)
if len(stack) == 0 {
_ = a.store.SetSystemConfig(taskStackConfigKey(userID), "")
return
}
data, err := json.Marshal(stack)
if err != nil {
return
}
_ = a.store.SetSystemConfig(taskStackConfigKey(userID), string(data))
}
func (a *Agent) peekTaskStack(userID int64) (SuspendedTask, bool) {
stack := a.getTaskStack(userID)
if len(stack) == 0 {
return SuspendedTask{}, false
}
return stack[len(stack)-1], true
}
func (a *Agent) pushTaskStack(userID int64, task SuspendedTask) {
task = normalizeSuspendedTask(task)
if task.Kind == "" {
return
}
stack := a.getTaskStack(userID)
stack = append(stack, task)
stack = normalizeTaskStack(stack)
a.saveTaskStack(userID, stack)
}
func (a *Agent) popTaskStack(userID int64) (SuspendedTask, bool) {
stack := a.getTaskStack(userID)
if len(stack) == 0 {
return SuspendedTask{}, false
}
task := stack[len(stack)-1]
stack = stack[:len(stack)-1]
a.saveTaskStack(userID, stack)
return task, true
}
func (a *Agent) clearTaskStack(userID int64) {
if a.store == nil {
return
}
_ = a.store.SetSystemConfig(taskStackConfigKey(userID), "")
}
func newExecutionState(userID int64, goal string) ExecutionState {
now := time.Now().UTC().Format(time.RFC3339)
return normalizeExecutionState(ExecutionState{
@@ -168,6 +363,7 @@ func normalizeExecutionState(state ExecutionState) ExecutionState {
state.FinalAnswer = strings.TrimSpace(state.FinalAnswer)
state.LastError = strings.TrimSpace(state.LastError)
state.CurrentReferences = normalizeCurrentReferences(state.CurrentReferences)
state.ReferenceHistory = normalizeReferenceHistory(state.ReferenceHistory)
state.Waiting = normalizeWaitingState(state.Waiting)
if state.Status == "" && state.SessionID != "" {
state.Status = executionStatusPlanning
@@ -201,6 +397,88 @@ func normalizeExecutionState(state ExecutionState) ExecutionState {
return state
}
func normalizeSuspendedTask(task SuspendedTask) SuspendedTask {
task.SnapshotID = strings.TrimSpace(task.SnapshotID)
task.IntentID = strings.TrimSpace(task.IntentID)
task.ParentIntentID = strings.TrimSpace(task.ParentIntentID)
task.Kind = strings.TrimSpace(task.Kind)
task.ResumeHint = strings.TrimSpace(task.ResumeHint)
task.ResumeTriggers = cleanStringList(task.ResumeTriggers)
task.SuspendedAt = strings.TrimSpace(task.SuspendedAt)
if task.SkillSession != nil {
session := normalizeSkillSession(*task.SkillSession)
if session.Name == "" {
task.SkillSession = nil
} else {
task.SkillSession = &session
}
}
if task.WorkflowSession != nil {
session := normalizeWorkflowSession(*task.WorkflowSession)
if len(session.Tasks) == 0 {
task.WorkflowSession = nil
} else {
task.WorkflowSession = &session
}
}
if task.ExecutionState != nil {
state := normalizeExecutionState(*task.ExecutionState)
if strings.TrimSpace(state.SessionID) == "" {
task.ExecutionState = nil
} else {
task.ExecutionState = &state
}
}
if task.Kind == "" {
switch {
case task.SkillSession != nil:
task.Kind = "skill_session"
case task.WorkflowSession != nil:
task.Kind = "workflow_session"
case task.ExecutionState != nil:
task.Kind = "execution_state"
}
}
if task.Kind == "" {
return SuspendedTask{}
}
if task.SnapshotID == "" {
task.SnapshotID = "snap_" + uuid.NewString()
}
if task.IntentID == "" {
task.IntentID = "intent_" + uuid.NewString()
}
if task.SuspendedAt == "" {
task.SuspendedAt = time.Now().UTC().Format(time.RFC3339)
}
return task
}
func normalizeTaskStack(stack []SuspendedTask) []SuspendedTask {
if len(stack) == 0 {
return nil
}
now := time.Now().UTC()
out := make([]SuspendedTask, 0, len(stack))
for _, item := range stack {
item = normalizeSuspendedTask(item)
if item.Kind == "" {
continue
}
if t, err := time.Parse(time.RFC3339, item.SuspendedAt); err == nil && now.Sub(t) > 24*time.Hour {
continue
}
out = append(out, item)
}
if len(out) == 0 {
return nil
}
if len(out) > 5 {
out = out[len(out)-5:]
}
return out
}
func normalizeWaitingState(waiting *WaitingState) *WaitingState {
if waiting == nil {
return nil
@@ -224,9 +502,14 @@ func normalizeEntityReference(ref *EntityReference) *EntityReference {
}
ref.ID = strings.TrimSpace(ref.ID)
ref.Name = strings.TrimSpace(ref.Name)
ref.Source = strings.TrimSpace(ref.Source)
ref.UpdatedAt = strings.TrimSpace(ref.UpdatedAt)
if ref.ID == "" && ref.Name == "" {
return nil
}
if ref.UpdatedAt == "" {
ref.UpdatedAt = time.Now().UTC().Format(time.RFC3339)
}
return ref
}
@@ -244,6 +527,34 @@ func normalizeCurrentReferences(refs *CurrentReferences) *CurrentReferences {
return refs
}
func normalizeReferenceHistory(history []ReferenceRecord) []ReferenceRecord {
if len(history) == 0 {
return nil
}
out := make([]ReferenceRecord, 0, len(history))
for _, item := range history {
item.Kind = strings.TrimSpace(item.Kind)
item.ID = strings.TrimSpace(item.ID)
item.Name = strings.TrimSpace(item.Name)
item.Source = strings.TrimSpace(item.Source)
item.CreatedAt = strings.TrimSpace(item.CreatedAt)
if item.Kind == "" || (item.ID == "" && item.Name == "") {
continue
}
if item.CreatedAt == "" {
item.CreatedAt = time.Now().UTC().Format(time.RFC3339)
}
out = append(out, item)
}
if len(out) == 0 {
return nil
}
if len(out) > 12 {
out = out[len(out)-12:]
}
return out
}
func normalizeObservationList(values []Observation) []Observation {
if len(values) == 0 {
return nil
@@ -332,8 +643,8 @@ func buildObservationContext(state ExecutionState) map[string]any {
state = normalizeExecutionState(state)
return map[string]any{
"current_references": state.CurrentReferences,
"dynamic_snapshots": state.DynamicSnapshots,
"execution_log": state.ExecutionLog,
"summary_notes": state.SummaryNotes,
"dynamic_snapshots": state.DynamicSnapshots,
"execution_log": state.ExecutionLog,
"summary_notes": state.SummaryNotes,
}
}