Changeset 0.29.0 (#195)
This commit is contained in:
@@ -7,7 +7,6 @@ import (
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"git.gobha.me/xcaliber/chat-switchboard/database"
|
||||
"git.gobha.me/xcaliber/chat-switchboard/models"
|
||||
"git.gobha.me/xcaliber/chat-switchboard/store"
|
||||
"git.gobha.me/xcaliber/chat-switchboard/webhook"
|
||||
@@ -79,18 +78,13 @@ func (t *workflowAdvanceTool) Execute(ctx context.Context, execCtx ExecutionCont
|
||||
}
|
||||
|
||||
// Read current workflow state
|
||||
var workflowID string
|
||||
var currentStage int
|
||||
var status string
|
||||
var wfID *string
|
||||
err := database.DB.QueryRowContext(ctx, database.Q(`
|
||||
SELECT workflow_id, COALESCE(current_stage, 0), COALESCE(workflow_status, 'active')
|
||||
FROM channels WHERE id = $1 AND type = 'workflow'
|
||||
`), channelID).Scan(&wfID, ¤tStage, &status)
|
||||
if err != nil || wfID == nil {
|
||||
ws, err := t.stores.Channels.GetWorkflowStatus(ctx, channelID)
|
||||
if err != nil || ws == nil || ws.WorkflowID == nil {
|
||||
return "", fmt.Errorf("not a workflow channel")
|
||||
}
|
||||
workflowID = *wfID
|
||||
workflowID := *ws.WorkflowID
|
||||
currentStage := ws.CurrentStage
|
||||
status := ws.Status
|
||||
|
||||
if status != "active" {
|
||||
return "", fmt.Errorf("workflow is %s, cannot advance", status)
|
||||
@@ -103,17 +97,12 @@ func (t *workflowAdvanceTool) Execute(ctx context.Context, execCtx ExecutionCont
|
||||
}
|
||||
|
||||
// Merge collected data into stage_data
|
||||
mergedData := MergeWorkflowStageData(ctx, channelID, args.Data)
|
||||
mergedData := MergeWorkflowStageData(ctx, t.stores.Channels, channelID, args.Data)
|
||||
nextStage := currentStage + 1
|
||||
|
||||
if nextStage >= len(stages) {
|
||||
// Workflow complete
|
||||
_, err = database.DB.ExecContext(ctx, database.Q(`
|
||||
UPDATE channels
|
||||
SET current_stage = $1, workflow_status = 'completed',
|
||||
stage_data = $2, last_activity_at = $3, ai_mode = 'off'
|
||||
WHERE id = $4
|
||||
`), nextStage, mergedData, time.Now().UTC(), channelID)
|
||||
err = t.stores.Channels.CompleteWorkflow(ctx, channelID, nextStage, json.RawMessage(mergedData))
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to complete workflow: %w", err)
|
||||
}
|
||||
@@ -133,11 +122,7 @@ func (t *workflowAdvanceTool) Execute(ctx context.Context, execCtx ExecutionCont
|
||||
}
|
||||
|
||||
// Advance to next stage
|
||||
_, err = database.DB.ExecContext(ctx, database.Q(`
|
||||
UPDATE channels
|
||||
SET current_stage = $1, stage_data = $2, last_activity_at = $3
|
||||
WHERE id = $4
|
||||
`), nextStage, mergedData, time.Now().UTC(), channelID)
|
||||
err = t.stores.Channels.AdvanceWorkflowStage(ctx, channelID, nextStage, json.RawMessage(mergedData))
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to advance: %w", err)
|
||||
}
|
||||
@@ -148,7 +133,7 @@ func (t *workflowAdvanceTool) Execute(ctx context.Context, execCtx ExecutionCont
|
||||
// Create assignment if next stage has an assignment team
|
||||
nextStageDef := stages[nextStage]
|
||||
if nextStageDef.AssignmentTeamID != nil {
|
||||
CreateWorkflowAssignment(ctx, channelID, nextStage, *nextStageDef.AssignmentTeamID)
|
||||
CreateWorkflowAssignment(ctx, t.stores, channelID, nextStage, *nextStageDef.AssignmentTeamID)
|
||||
}
|
||||
|
||||
result, _ := json.Marshal(map[string]interface{}{
|
||||
@@ -163,11 +148,9 @@ func (t *workflowAdvanceTool) Execute(ctx context.Context, execCtx ExecutionCont
|
||||
// ── Shared helpers (used by both tool and handler) ─
|
||||
|
||||
// MergeWorkflowStageData reads current stage_data, merges new data in Go, returns JSON string.
|
||||
func MergeWorkflowStageData(ctx context.Context, channelID string, newData json.RawMessage) string {
|
||||
var existing []byte
|
||||
_ = database.DB.QueryRowContext(ctx, database.Q(`
|
||||
SELECT COALESCE(stage_data, '{}') FROM channels WHERE id = $1
|
||||
`), channelID).Scan(&existing)
|
||||
// v0.29.0: channelStore parameter replaces raw database.DB access.
|
||||
func MergeWorkflowStageData(ctx context.Context, channelStore store.ChannelStore, channelID string, newData json.RawMessage) string {
|
||||
existing, _ := channelStore.GetStageData(ctx, channelID)
|
||||
|
||||
if len(newData) == 0 || string(newData) == "null" {
|
||||
if len(existing) == 0 {
|
||||
@@ -204,10 +187,11 @@ func CreateWorkflowStageNote(ctx context.Context, stores store.Stores, channelID
|
||||
content := string(data)
|
||||
|
||||
// Find workflow channel owner for the note's user_id
|
||||
var userID string
|
||||
_ = database.DB.QueryRowContext(ctx, database.Q(`
|
||||
SELECT user_id FROM channels WHERE id = $1
|
||||
`), channelID).Scan(&userID)
|
||||
ch, err := stores.Channels.GetByID(ctx, channelID)
|
||||
if err != nil || ch == nil {
|
||||
return
|
||||
}
|
||||
userID := ch.UserID
|
||||
if userID == "" {
|
||||
return
|
||||
}
|
||||
@@ -226,28 +210,18 @@ func CreateWorkflowStageNote(ctx context.Context, stores store.Stores, channelID
|
||||
|
||||
// CreateWorkflowAssignment creates an unassigned workflow assignment entry.
|
||||
// Returns the assignment ID (for round-robin auto-assignment).
|
||||
func CreateWorkflowAssignment(ctx context.Context, channelID string, stage int, teamID string) string {
|
||||
id := store.NewID()
|
||||
if database.IsSQLite() {
|
||||
_, err := database.DB.ExecContext(ctx, `
|
||||
INSERT INTO workflow_assignments (id, channel_id, stage, team_id)
|
||||
VALUES (?, ?, ?, ?)
|
||||
`, id, channelID, stage, teamID)
|
||||
if err != nil {
|
||||
log.Printf("⚠️ Failed to create workflow assignment: %v", err)
|
||||
return ""
|
||||
}
|
||||
return id
|
||||
// v0.29.0: accepts stores parameter, delegates to WorkflowStore.
|
||||
func CreateWorkflowAssignment(ctx context.Context, stores store.Stores, channelID string, stage int, teamID string) string {
|
||||
a := &store.WorkflowAssignment{
|
||||
ChannelID: channelID,
|
||||
Stage: stage,
|
||||
TeamID: teamID,
|
||||
}
|
||||
_, err := database.DB.ExecContext(ctx, `
|
||||
INSERT INTO workflow_assignments (id, channel_id, stage, team_id)
|
||||
VALUES ($1, $2, $3, $4)
|
||||
`, id, channelID, stage, teamID)
|
||||
if err != nil {
|
||||
if err := stores.Workflows.CreateAssignment(ctx, a); err != nil {
|
||||
log.Printf("⚠️ Failed to create workflow assignment: %v", err)
|
||||
return ""
|
||||
}
|
||||
return id
|
||||
return a.ID
|
||||
}
|
||||
|
||||
// ── Workflow Completion Triggers (v0.27.3) ──
|
||||
@@ -307,10 +281,11 @@ func TriggerWorkflowOnComplete(ctx context.Context, stores store.Stores, workflo
|
||||
targetData = sourceData
|
||||
}
|
||||
|
||||
var ownerID string
|
||||
_ = database.DB.QueryRowContext(ctx, database.Q(
|
||||
`SELECT user_id FROM channels WHERE id = $1`,
|
||||
), channelID).Scan(&ownerID)
|
||||
ch2, err2 := stores.Channels.GetByID(ctx, channelID)
|
||||
if err2 != nil || ch2 == nil {
|
||||
return
|
||||
}
|
||||
ownerID := ch2.UserID
|
||||
if ownerID == "" {
|
||||
return
|
||||
}
|
||||
@@ -338,17 +313,12 @@ func TriggerWorkflowOnComplete(ctx context.Context, stores store.Stores, workflo
|
||||
}
|
||||
|
||||
initialData, _ := json.Marshal(targetData)
|
||||
_, err = database.DB.ExecContext(ctx, database.Q(`
|
||||
UPDATE channels
|
||||
SET workflow_id = $1, workflow_version = $2, current_stage = 0,
|
||||
stage_data = $3, workflow_status = 'active',
|
||||
last_activity_at = $4, ai_mode = 'auto'
|
||||
WHERE id = $5
|
||||
`), target.ID, ver.VersionNumber, string(initialData), time.Now().UTC(), ch.ID)
|
||||
err = stores.Channels.SetWorkflowInstance(ctx, ch.ID, target.ID, ver.VersionNumber, initialData, "active")
|
||||
if err != nil {
|
||||
log.Printf("[workflow] on_complete: failed to set workflow columns: %v", err)
|
||||
return
|
||||
}
|
||||
_ = stores.Channels.Update(ctx, ch.ID, map[string]interface{}{"ai_mode": "auto"})
|
||||
|
||||
// Bind first stage persona
|
||||
if len(stages) > 0 && stages[0].PersonaID != nil {
|
||||
|
||||
Reference in New Issue
Block a user