2026-08-21 15:58:46 -07:00
|
|
|
package action
|
|
|
|
|
|
2026-08-21 18:07:12 -07:00
|
|
|
import (
|
|
|
|
|
"context"
|
|
|
|
|
"fmt"
|
|
|
|
|
|
|
|
|
|
"github.com/rockliang/poimen/workflows/action/llm"
|
|
|
|
|
"github.com/rockliang/poimen/workflows/prompts"
|
|
|
|
|
"github.com/rockliang/poimen/workflows/statemachine"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// PlanningInput is input to PlanningActivity.
|
|
|
|
|
type PlanningInput struct {
|
2026-08-21 21:51:41 -07:00
|
|
|
Config statemachine.OrchestratorConfig
|
|
|
|
|
BoardState string // JSON or markdown of task board
|
|
|
|
|
RepoPath string // Path to target repository
|
|
|
|
|
Milestone string // e.g., "T0"
|
|
|
|
|
TaskResults []statemachine.TaskUnitOutput // Results from completed tasks
|
2026-08-21 18:07:12 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TaskDispatch represents a dispatched task.
|
|
|
|
|
type TaskDispatch struct {
|
|
|
|
|
TaskID string
|
|
|
|
|
PromptSpec statemachine.PromptSpec
|
|
|
|
|
BaseTimeout *int64 // optional override in milliseconds
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// PlanningOutput is the output of PlanningActivity.
|
|
|
|
|
type PlanningOutput struct {
|
2026-08-21 21:51:41 -07:00
|
|
|
TasksToDispatch []string // Task IDs to dispatch in this cycle
|
|
|
|
|
CompletedBranches []string // Branches to squash merge (when milestone complete)
|
|
|
|
|
SubmilestoneComplete bool // Whether the milestone is complete
|
2026-08-21 18:07:12 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// PlanningActivity calls the Planner LLM to decide which tasks to dispatch.
|
|
|
|
|
func PlanningActivity(ctx context.Context, in PlanningInput) (PlanningOutput, error) {
|
|
|
|
|
// Get LLM client
|
|
|
|
|
client, err := llm.NewClient()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return PlanningOutput{}, fmt.Errorf("failed to create LLM client: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Get planner spec
|
|
|
|
|
plannerSpec, exists := in.Config.RolePrompts["planner"]
|
|
|
|
|
if !exists {
|
|
|
|
|
return PlanningOutput{}, fmt.Errorf("planner role prompt not configured")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Render template
|
|
|
|
|
var templateContent string
|
|
|
|
|
if plannerSpec.RawTemplate != "" {
|
|
|
|
|
templateContent = plannerSpec.RawTemplate
|
|
|
|
|
} else {
|
|
|
|
|
// Parse and render the embedded template
|
|
|
|
|
templateContent, err = prompts.Render(plannerSpec.TemplateRef, map[string]any{
|
|
|
|
|
"SystemPrompt": in.Config.SystemPrompt,
|
|
|
|
|
"BoardState": in.BoardState,
|
|
|
|
|
"Milestone": in.Milestone,
|
|
|
|
|
"Config": in.Config,
|
|
|
|
|
})
|
|
|
|
|
if err != nil {
|
|
|
|
|
return PlanningOutput{}, fmt.Errorf("failed to render planner template: %w", err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Call LLM
|
|
|
|
|
messages := []llm.MessageParam{
|
|
|
|
|
{
|
|
|
|
|
Role: "user",
|
|
|
|
|
Content: templateContent,
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
response, err := client.CreateMessage(ctx, llm.MessageInput{
|
|
|
|
|
Model: plannerSpec.Model,
|
|
|
|
|
SystemPrompt: in.Config.SystemPrompt,
|
|
|
|
|
Messages: messages,
|
|
|
|
|
})
|
|
|
|
|
if err != nil {
|
|
|
|
|
return PlanningOutput{}, fmt.Errorf("planner LLM call failed: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// For now, return empty dispatch (will be parsed from LLM response in full implementation)
|
|
|
|
|
// This is a stub that allows the test to verify the activity is called
|
|
|
|
|
_ = response
|
|
|
|
|
return PlanningOutput{
|
2026-08-21 21:51:41 -07:00
|
|
|
TasksToDispatch: []string{},
|
|
|
|
|
CompletedBranches: []string{},
|
2026-08-21 18:07:12 -07:00
|
|
|
SubmilestoneComplete: false,
|
|
|
|
|
}, nil
|
|
|
|
|
}
|