Files
agent-radar 2ed3415fb7
Publish Claude Code Binaries / Stage and publish binaries (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-arm64 (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build linux-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build windows-amd64 (push) Canceled after 0s
Sync Tutti Agent Skills / sync (push) Canceled after 0s
Windows Agent Adapters / Test Windows agent adapters (push) Canceled after 0s
Windows Daemon Adapters / Test Windows daemon adapters (push) Canceled after 0s
Publish Tutti App Runtime / Publish runtime catalog (push) Canceled after 0s
源码快照 @ 2026-09-01 (source-only, 剔除非源码素材)
2026-09-01 20:17:27 +08:00

201 lines
6.8 KiB
Go

package tuttimodeplan
import (
"bytes"
"encoding/json"
"fmt"
"io"
"strings"
workspaceissues "github.com/tutti-os/tutti/packages/workspace/issues"
executionbiz "github.com/tutti-os/tutti/services/tuttid/biz/tuttimodeexecution"
cliservice "github.com/tutti-os/tutti/services/tuttid/service/cli"
)
type mutationOperationInput struct {
Kind string `json:"kind"`
TaskID string `json:"taskId"`
Task *mutationTaskInput `json:"task"`
}
type mutationTaskInput struct {
TaskID string `json:"taskId"`
Title *string `json:"title"`
Content *string `json:"content"`
Priority *string `json:"priority"`
DueAtUnixMS *int64 `json:"dueAtUnixMs"`
AgentTargetID *string `json:"agentTargetId"`
ModelPlanID *string `json:"modelPlanId"`
Model *string `json:"model"`
PermissionModeID *string `json:"permissionModeId"`
ReasoningEffort *string `json:"reasoningEffort"`
ExecutionDirectory *string `json:"executionDirectory"`
DependencyTaskIDs *[]string `json:"dependencyTaskIds"`
Parallelizable *bool `json:"parallelizable"`
AutoAccept *bool `json:"autoAccept"`
}
func parseMutationOperationsJSON(value string) ([]executionbiz.MutationOperation, error) {
var parsed []mutationOperationInput
decoder := json.NewDecoder(bytes.NewBufferString(value))
decoder.DisallowUnknownFields()
if err := decoder.Decode(&parsed); err != nil {
return nil, invalidMutationInput(
`operations-json must be one non-empty JSON array using "kind", "taskId", ` +
`and a "task" object with "taskId": ` +
err.Error(),
)
}
if len(parsed) == 0 {
return nil, invalidMutationInput("operations-json must not be empty")
}
if err := decoder.Decode(&struct{}{}); err != io.EOF {
return nil, invalidMutationInput("operations-json must contain exactly one JSON value")
}
operations := make([]executionbiz.MutationOperation, 0, len(parsed))
for _, input := range parsed {
operation, err := mutationOperation(input)
if err != nil {
return nil, err
}
operations = append(operations, operation)
}
return operations, nil
}
func mutationOperation(input mutationOperationInput) (executionbiz.MutationOperation, error) {
operation := executionbiz.MutationOperation{
Kind: executionbiz.MutationOperationKind(strings.TrimSpace(input.Kind)),
TaskID: strings.TrimSpace(input.TaskID),
}
if input.Task != nil {
if input.Task.Priority != nil {
priority := strings.ToLower(strings.TrimSpace(*input.Task.Priority))
if priority != string(workspaceissues.PriorityHigh) &&
priority != string(workspaceissues.PriorityMedium) &&
priority != string(workspaceissues.PriorityLow) {
return executionbiz.MutationOperation{}, invalidMutationInput(
"task.priority must be high, medium, or low",
)
}
}
if input.Task.DueAtUnixMS != nil && *input.Task.DueAtUnixMS < 0 {
return executionbiz.MutationOperation{}, invalidMutationInput(
"task.dueAtUnixMs must be zero or greater",
)
}
operation.Task, operation.TaskFields = input.Task.domainTask()
}
switch operation.Kind {
case executionbiz.MutationOperationAdd:
if input.Task == nil || operation.Task.TaskID == "" ||
strings.TrimSpace(operation.Task.Title) == "" {
return executionbiz.MutationOperation{}, invalidMutationInput(
"add requires task.taskId, task.title, and a complete schedulable task",
)
}
if strings.TrimSpace(operation.Task.AgentTargetID) == "" {
return executionbiz.MutationOperation{}, cliservice.InvalidInputReasonError(
string(executionbiz.RejectionMissingAgentTarget),
"add task "+operation.Task.TaskID+
" is missing agentTargetId. Hint: choose a target from `tutti agent list --json`",
nil,
)
}
case executionbiz.MutationOperationUpdate:
if operation.TaskID == "" || input.Task == nil || !operation.TaskFields.Any() {
return executionbiz.MutationOperation{}, invalidMutationInput(
"update requires taskId and at least one explicitly present task field",
)
}
case executionbiz.MutationOperationRework:
if operation.TaskID == "" || input.Task == nil || operation.Task.TaskID == "" {
return executionbiz.MutationOperation{}, invalidMutationInput(
"rework requires taskId plus task.taskId for the replacement",
)
}
case executionbiz.MutationOperationSupersede:
if operation.TaskID == "" || input.Task != nil {
return executionbiz.MutationOperation{}, invalidMutationInput(
"supersede requires taskId and does not accept task",
)
}
default:
return executionbiz.MutationOperation{}, invalidMutationInput(
"entries must use kind add, update, rework, or supersede; op and replacement are not supported",
)
}
return operation, nil
}
func (input mutationTaskInput) domainTask() (
workspaceissues.Task,
executionbiz.MutationTaskFields,
) {
task := workspaceissues.Task{TaskID: strings.TrimSpace(input.TaskID)}
fields := executionbiz.MutationTaskFields{}
if input.Title != nil {
task.Title = *input.Title
fields.Title = true
}
if input.Content != nil {
task.Content = *input.Content
fields.Content = true
}
if input.Priority != nil {
task.Priority = workspaceissues.Priority(strings.TrimSpace(*input.Priority))
fields.Priority = true
}
if input.DueAtUnixMS != nil {
task.DueAtUnixMS = *input.DueAtUnixMS
fields.DueAtUnixMS = true
}
if input.AgentTargetID != nil {
task.AgentTargetID = strings.TrimSpace(*input.AgentTargetID)
fields.AgentTargetID = true
}
if input.ModelPlanID != nil {
task.ModelPlanID = strings.TrimSpace(*input.ModelPlanID)
fields.ModelPlanID = true
}
if input.Model != nil {
task.Model = strings.TrimSpace(*input.Model)
fields.Model = true
}
if input.PermissionModeID != nil {
task.PermissionModeID = strings.TrimSpace(*input.PermissionModeID)
fields.PermissionModeID = true
}
if input.ReasoningEffort != nil {
task.ReasoningEffort = strings.TrimSpace(*input.ReasoningEffort)
fields.ReasoningEffort = true
}
if input.ExecutionDirectory != nil {
task.ExecutionDirectory = strings.TrimSpace(*input.ExecutionDirectory)
fields.ExecutionDirectory = true
}
if input.DependencyTaskIDs != nil {
task.DependencyTaskIDs = append([]string(nil), (*input.DependencyTaskIDs)...)
fields.DependencyTaskIDs = true
}
if input.Parallelizable != nil {
task.Parallelizable = *input.Parallelizable
fields.Parallelizable = true
}
if input.AutoAccept != nil {
task.AutoAccept = *input.AutoAccept
fields.AutoAccept = true
}
return task, fields
}
func invalidMutationInput(message string) error {
return cliservice.InvalidInputReasonError(
string(executionbiz.RejectionInvalidMutation),
strings.TrimSpace(message)+
". Hint: run `tutti plan issue mutate --help` and use the documented schema",
fmt.Errorf("%w: invalid operations-json", cliservice.ErrInvalidInput),
)
}