Changeset 0.27.3 (#170)

This commit is contained in:
2026-03-11 09:12:57 +00:00
parent dcb915555e
commit a46ec63464
14 changed files with 561 additions and 110 deletions

View File

@@ -7,8 +7,9 @@ import (
"github.com/gin-gonic/gin"
"git.gobha.me/xcaliber/chat-switchboard/models"
"git.gobha.me/xcaliber/chat-switchboard/taskutil"
"git.gobha.me/xcaliber/chat-switchboard/store"
"git.gobha.me/xcaliber/chat-switchboard/taskutil"
"git.gobha.me/xcaliber/chat-switchboard/webhook"
)
// TaskHandler manages task CRUD and manual execution.
@@ -129,6 +130,11 @@ func (h *TaskHandler) Create(c *gin.Context) {
t.IsActive = true
// v0.27.3: Generate webhook secret if webhook URL is provided
if t.WebhookURL != "" && t.WebhookSecret == "" {
t.WebhookSecret = webhook.GenerateSecret()
}
if err := h.stores.Tasks.Create(ctx, &t); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to create task: " + err.Error()})
return

View File

@@ -355,103 +355,8 @@ func (h *WorkflowInstanceHandler) emitWorkflowEvent(label, channelID string, dat
// triggerOnComplete checks if the workflow has an on_complete chain config
// and starts the target workflow if so.
func (h *WorkflowInstanceHandler) triggerOnComplete(ctx context.Context, workflowID, channelID, mergedData string) {
if h.stores.Workflows == nil {
return
}
wf, err := h.stores.Workflows.GetByID(ctx, workflowID)
if err != nil || wf == nil || len(wf.OnComplete) == 0 || string(wf.OnComplete) == "null" {
return
}
var chain struct {
Action string `json:"action"`
TargetSlug string `json:"target_slug"`
DataMap map[string]string `json:"data_map"` // source_key → target_key
}
if err := json.Unmarshal(wf.OnComplete, &chain); err != nil || chain.Action != "start_workflow" || chain.TargetSlug == "" {
return
}
// Look up target workflow
target, err := h.stores.Workflows.GetBySlug(ctx, wf.TeamID, chain.TargetSlug)
if err != nil || target == nil || !target.IsActive {
log.Printf("[workflow] on_complete: target workflow '%s' not found or inactive", chain.TargetSlug)
return
}
// Map stage_data from completed workflow to initial stage_data for target
var sourceData map[string]any
if err := json.Unmarshal([]byte(mergedData), &sourceData); err != nil {
sourceData = map[string]any{}
}
targetData := map[string]any{}
if len(chain.DataMap) > 0 {
for srcKey, tgtKey := range chain.DataMap {
if v, ok := sourceData[srcKey]; ok {
targetData[tgtKey] = v
}
}
} else {
// No mapping = pass all data through
targetData = sourceData
}
// Get the channel owner to start the chained workflow as
var ownerID string
_ = database.DB.QueryRowContext(ctx, database.Q(`
SELECT user_id FROM channels WHERE id = $1
`), channelID).Scan(&ownerID)
if ownerID == "" {
return
}
// Create a new workflow channel for the target
ver, err := h.stores.Workflows.GetLatestVersion(ctx, target.ID)
if err != nil {
log.Printf("[workflow] on_complete: target '%s' has no published version", chain.TargetSlug)
return
}
stages, err := h.stores.Workflows.ListStages(ctx, target.ID)
if err != nil || len(stages) == 0 {
return
}
ch := &models.Channel{
UserID: ownerID,
Title: target.Name + " (chained)",
Description: target.Description,
Type: "workflow",
TeamID: target.TeamID,
}
if err := h.stores.Channels.Create(ctx, ch); err != nil {
log.Printf("[workflow] on_complete: failed to create chained channel: %v", err)
return
}
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)
if err != nil {
log.Printf("[workflow] on_complete: failed to set workflow columns: %v", err)
return
}
_ = h.stores.Channels.AddParticipant(ctx, &models.ChannelParticipant{
ChannelID: ch.ID, ParticipantType: "user", ParticipantID: ownerID, Role: "owner",
})
if stages[0].PersonaID != nil {
_ = h.stores.Channels.AddParticipant(ctx, &models.ChannelParticipant{
ChannelID: ch.ID, ParticipantType: "persona", ParticipantID: *stages[0].PersonaID, Role: "member",
})
}
log.Printf("[workflow] on_complete: chained '%s' → '%s' (channel %s → %s)",
wf.Slug, target.Slug, channelID, ch.ID)
// v0.27.3: Delegate to shared implementation (also handles webhooks)
go tools.TriggerWorkflowOnComplete(ctx, h.stores, workflowID, channelID, mergedData)
}
// tryRoundRobin checks if the stage has auto_assign:"round_robin" in