Why: Q1 WP5 - platform's SQLite becomes an implementation of the squad Store port so run state and orchestration state live in one database, and platform's in-process execution goes through the Executor port instead of bypassing it (X6). Evidence: all five modules go vet clean and go test ok, including engine-on-SQLite (state in DB, no run-level JSON files, second engine recovers from the same DB), SQLiteStore round trip, and the full squad compatibility net. Impact: runState/hitlGate exported so the port is implementable; Options.Store wires into engineDeps; leader persistence and checkpoints route through the port; PrepareWorkspace no longer writes run-level JSON (st.save owns it); platform RunInProcess takes squad.Executor (callers updated); serve mounts the engine on platform's RunStore - single orchestration.db for both. Refs: Documents proposal 2026-10-07-agent-stack-n1q1i1-plans.md Q1 WP5
136 lines
4.0 KiB
Go
136 lines
4.0 KiB
Go
package cli
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"flag"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"time"
|
|
|
|
platform "agent-squad/adapters/sqlite"
|
|
"agent-squad/squad"
|
|
)
|
|
|
|
func cmdPlatform(args []string) int {
|
|
if len(args) < 2 {
|
|
fmt.Fprintln(os.Stderr, "用法: agent-squad", args[0], "create|import|assign|claim|fire ...")
|
|
return 2
|
|
}
|
|
fs := flag.NewFlagSet(args[0], flag.ContinueOnError)
|
|
data := fs.String("data", dataDir(), "数据目录")
|
|
workspace := fs.String("workspace", "", "workspace id")
|
|
name := fs.String("name", "", "名称")
|
|
if err := fs.Parse(args[2:]); err != nil {
|
|
return 2
|
|
}
|
|
st, err := platform.Open(filepath.Join(*data, "orchestration.db"))
|
|
if err != nil {
|
|
fmt.Fprintln(os.Stderr, err)
|
|
return 1
|
|
}
|
|
defer st.Close()
|
|
switch args[0] + " " + args[1] {
|
|
case "agent create":
|
|
a, err := st.CreateAgent(platform.Agent{WorkspaceID: *workspace, Name: *name})
|
|
return printPlatform(a, err)
|
|
case "skill import":
|
|
if fs.NArg() < 1 {
|
|
fmt.Fprintln(os.Stderr, "skill import 需要目录或 zip 路径")
|
|
return 2
|
|
}
|
|
sk, err := st.ImportSkill(*workspace, fs.Arg(0), "rename")
|
|
return printPlatform(sk, err)
|
|
case "squad create":
|
|
if fs.NArg() < 1 {
|
|
fmt.Fprintln(os.Stderr, "squad create 需要 leader agent id")
|
|
return 2
|
|
}
|
|
sq, err := st.CreateSquad(platform.Squad{WorkspaceID: *workspace, Name: *name, LeaderAgentID: fs.Arg(0)}, nil)
|
|
return printPlatform(sq, err)
|
|
case "issue create":
|
|
issue, err := st.CreateIssue(platform.Issue{WorkspaceID: *workspace, Title: *name, Status: "todo"})
|
|
return printPlatform(issue, err)
|
|
case "issue assign":
|
|
if fs.NArg() < 3 {
|
|
fmt.Fprintln(os.Stderr, "issue assign <issue> <agent|squad> <id>")
|
|
return 2
|
|
}
|
|
err := st.AssignIssue(fs.Arg(0), fs.Arg(1), fs.Arg(2), true)
|
|
return printPlatform(map[string]bool{"ok": err == nil}, err)
|
|
case "autopilot create":
|
|
if fs.NArg() < 2 {
|
|
fmt.Fprintln(os.Stderr, "autopilot create <agent-id> <create_issue|run_only>")
|
|
return 2
|
|
}
|
|
a, err := st.CreateAutopilot(platform.Autopilot{
|
|
WorkspaceID: *workspace, Title: *name, Runbook: *name,
|
|
AssigneeKind: "agent", AssigneeID: fs.Arg(0), ExecutionMode: fs.Arg(1),
|
|
})
|
|
return printPlatform(a, err)
|
|
case "runtime probe":
|
|
rts, err := st.RegisterProbed(*workspace)
|
|
return printPlatform(rts, err)
|
|
case "daemon claim":
|
|
if fs.NArg() < 1 {
|
|
fmt.Fprintln(os.Stderr, "daemon claim <runtime-id>")
|
|
return 2
|
|
}
|
|
task, ok, err := st.Claim(fs.Arg(0), time.Now())
|
|
return printPlatform(map[string]any{"ok": ok, "task": task}, err)
|
|
case "chat create":
|
|
c, err := st.CreateChat(platform.ChatSession{WorkspaceID: *workspace, OwnerID: *name})
|
|
return printPlatform(c, err)
|
|
case "project create":
|
|
p, err := st.CreateProject(platform.Project{WorkspaceID: *workspace, Name: *name})
|
|
return printPlatform(p, err)
|
|
case "daemon run-native":
|
|
if fs.NArg() < 2 {
|
|
fmt.Fprintln(os.Stderr, "daemon run-native <task-id> <workdir>")
|
|
return 2
|
|
}
|
|
task, err := st.TaskByToken(fs.Arg(0))
|
|
if err != nil {
|
|
var idErr error
|
|
task, idErr = taskByID(st, fs.Arg(0))
|
|
if idErr != nil {
|
|
fmt.Fprintln(os.Stderr, err)
|
|
return 1
|
|
}
|
|
}
|
|
text, err := st.RunInProcess(context.Background(), task, fs.Arg(1), scriptedExecutor{})
|
|
return printPlatform(map[string]string{"text": text}, err)
|
|
default:
|
|
fmt.Fprintln(os.Stderr, "未知编排命令", args[0], args[1])
|
|
return 2
|
|
}
|
|
}
|
|
|
|
func taskByID(st *platform.Store, id string) (platform.Task, error) {
|
|
return st.TaskByToken(id)
|
|
}
|
|
|
|
// scriptedExecutor 是 daemon run-native 冒烟路径的脚本执行器(X6 后经端口注入)。
|
|
type scriptedExecutor struct{}
|
|
|
|
func (scriptedExecutor) Name() string { return "scripted" }
|
|
|
|
func (scriptedExecutor) Execute(ctx context.Context, req squad.ExecRequest) (*squad.ExecResult, error) {
|
|
return &squad.ExecResult{
|
|
Success: true, Logs: "ok",
|
|
Patches: map[string][]byte{"output.md": []byte("ok")},
|
|
}, nil
|
|
}
|
|
|
|
func printPlatform(v any, err error) int {
|
|
if err != nil {
|
|
fmt.Fprintln(os.Stderr, err)
|
|
return 1
|
|
}
|
|
enc := json.NewEncoder(os.Stdout)
|
|
enc.SetIndent("", " ")
|
|
_ = enc.Encode(v)
|
|
return 0
|
|
}
|