fix(agentic): add task aliases and session model normalization
Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
parent
425008f855
commit
9f9e42768d
6 changed files with 119 additions and 14 deletions
|
|
@ -123,16 +123,16 @@ func (s *PrepSubsystem) cmdSessionList(options core.Options) core.Result {
|
|||
return core.Result{Value: output, OK: true}
|
||||
}
|
||||
|
||||
// core-agent session start ax-follow-up --agent-type=codex
|
||||
// core-agent session start ax-follow-up --agent-type=claude:opus
|
||||
func (s *PrepSubsystem) cmdSessionStart(options core.Options) core.Result {
|
||||
planSlug := optionStringValue(options, "plan_slug", "plan", "_arg")
|
||||
agentType := optionStringValue(options, "agent_type", "agent")
|
||||
if planSlug == "" {
|
||||
core.Print(nil, "usage: core-agent session start <plan-slug> --agent-type=codex [--context='{\"repo\":\"go-io\"}']")
|
||||
core.Print(nil, "usage: core-agent session start <plan-slug> --agent-type=claude:opus [--context='{\"repo\":\"go-io\"}']")
|
||||
return core.Result{Value: core.E("agentic.cmdSessionStart", "plan_slug is required", nil), OK: false}
|
||||
}
|
||||
if agentType == "" {
|
||||
core.Print(nil, "usage: core-agent session start <plan-slug> --agent-type=codex [--context='{\"repo\":\"go-io\"}']")
|
||||
core.Print(nil, "usage: core-agent session start <plan-slug> --agent-type=claude:opus [--context='{\"repo\":\"go-io\"}']")
|
||||
return core.Result{Value: core.E("agentic.cmdSessionStart", "agent_type is required", nil), OK: false}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -123,6 +123,37 @@ func TestCommandsSession_CmdSessionStart_Good(t *testing.T) {
|
|||
assert.Equal(t, "opus", output.Session.AgentType)
|
||||
}
|
||||
|
||||
func TestCommandsSession_CmdSessionStart_Good_CanonicalAlias(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
require.Equal(t, "/v1/sessions", r.URL.Path)
|
||||
require.Equal(t, http.MethodPost, r.Method)
|
||||
|
||||
bodyResult := core.ReadAll(r.Body)
|
||||
require.True(t, bodyResult.OK)
|
||||
|
||||
var payload map[string]any
|
||||
parseResult := core.JSONUnmarshalString(bodyResult.Value.(string), &payload)
|
||||
require.True(t, parseResult.OK)
|
||||
assert.Equal(t, "opus", payload["agent_type"])
|
||||
|
||||
_, _ = w.Write([]byte(`{"data":{"session_id":"ses-start","plan_slug":"ax-follow-up","agent_type":"opus","status":"active"}}`))
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
subsystem := testPrepWithPlatformServer(t, server, "secret-token")
|
||||
result := subsystem.cmdSessionStart(core.NewOptions(
|
||||
core.Option{Key: "_arg", Value: "ax-follow-up"},
|
||||
core.Option{Key: "agent_type", Value: "claude:opus"},
|
||||
))
|
||||
require.True(t, result.OK)
|
||||
|
||||
output, ok := result.Value.(SessionOutput)
|
||||
require.True(t, ok)
|
||||
assert.Equal(t, "ses-start", output.Session.SessionID)
|
||||
assert.Equal(t, "ax-follow-up", output.Session.PlanSlug)
|
||||
assert.Equal(t, "opus", output.Session.AgentType)
|
||||
}
|
||||
|
||||
func TestCommandsSession_CmdSessionStart_Bad_MissingPlanSlug(t *testing.T) {
|
||||
subsystem := testPrepWithPlatformServer(t, nil, "secret-token")
|
||||
|
||||
|
|
@ -143,7 +174,7 @@ func TestCommandsSession_CmdSessionStart_Bad_InvalidAgentType(t *testing.T) {
|
|||
|
||||
assert.False(t, result.OK)
|
||||
require.Error(t, result.Value.(error))
|
||||
assert.Contains(t, result.Value.(error).Error(), "opus, sonnet, or haiku")
|
||||
assert.Contains(t, result.Value.(error).Error(), "claude:opus")
|
||||
}
|
||||
|
||||
func TestCommandsSession_CmdSessionStart_Ugly_InvalidResponse(t *testing.T) {
|
||||
|
|
|
|||
|
|
@ -11,12 +11,15 @@ func (s *PrepSubsystem) registerTaskCommands() {
|
|||
c.Command("task", core.Command{Description: "Manage plan tasks", Action: s.cmdTask})
|
||||
c.Command("agentic:task", core.Command{Description: "Manage plan tasks", Action: s.cmdTask})
|
||||
c.Command("task/create", core.Command{Description: "Create a task in a plan phase", Action: s.cmdTaskCreate})
|
||||
c.Command("agentic:task/create", core.Command{Description: "Create a task in a plan phase", Action: s.cmdTaskCreate})
|
||||
c.Command("task/update", core.Command{Description: "Update a plan task status, notes, priority, or category", Action: s.cmdTaskUpdate})
|
||||
c.Command("agentic:task/update", core.Command{Description: "Update a plan task status, notes, priority, or category", Action: s.cmdTaskUpdate})
|
||||
c.Command("task/toggle", core.Command{Description: "Toggle a plan task between pending and completed", Action: s.cmdTaskToggle})
|
||||
c.Command("agentic:task/toggle", core.Command{Description: "Toggle a plan task between pending and completed", Action: s.cmdTaskToggle})
|
||||
}
|
||||
|
||||
func (s *PrepSubsystem) cmdTask(options core.Options) core.Result {
|
||||
action := optionStringValue(options, "action")
|
||||
action := optionStringValue(options, "action", "_arg")
|
||||
switch action {
|
||||
case "create":
|
||||
return s.cmdTaskCreate(options)
|
||||
|
|
|
|||
|
|
@ -58,6 +58,9 @@ func TestCommands_TaskCommand_Good_SpecAliasRegistered(t *testing.T) {
|
|||
s.registerTaskCommands()
|
||||
|
||||
assert.Contains(t, c.Commands(), "agentic:task")
|
||||
assert.Contains(t, c.Commands(), "agentic:task/create")
|
||||
assert.Contains(t, c.Commands(), "agentic:task/update")
|
||||
assert.Contains(t, c.Commands(), "agentic:task/toggle")
|
||||
}
|
||||
|
||||
func TestCommands_TaskCommand_Good_Create(t *testing.T) {
|
||||
|
|
|
|||
|
|
@ -408,12 +408,13 @@ func (s *PrepSubsystem) sessionStart(ctx context.Context, _ *mcp.CallToolRequest
|
|||
if input.AgentType == "" {
|
||||
return nil, SessionOutput{}, core.E("sessionStart", "agent_type is required", nil)
|
||||
}
|
||||
if !validSessionAgentType(input.AgentType) {
|
||||
return nil, SessionOutput{}, core.E("sessionStart", "agent_type must be opus, sonnet, or haiku", nil)
|
||||
normalisedAgentType, ok := normaliseSessionAgentType(input.AgentType)
|
||||
if !ok {
|
||||
return nil, SessionOutput{}, core.E("sessionStart", "agent_type must be opus, sonnet, haiku, or claude:opus|claude:sonnet|claude:haiku", nil)
|
||||
}
|
||||
|
||||
body := map[string]any{
|
||||
"agent_type": input.AgentType,
|
||||
"agent_type": normalisedAgentType,
|
||||
}
|
||||
if input.PlanSlug != "" {
|
||||
body["plan_slug"] = input.PlanSlug
|
||||
|
|
@ -453,7 +454,11 @@ func (s *PrepSubsystem) sessionGet(ctx context.Context, _ *mcp.CallToolRequest,
|
|||
func (s *PrepSubsystem) sessionList(ctx context.Context, _ *mcp.CallToolRequest, input SessionListInput) (*mcp.CallToolResult, SessionListOutput, error) {
|
||||
path := "/v1/sessions"
|
||||
path = appendQueryParam(path, "plan_slug", input.PlanSlug)
|
||||
path = appendQueryParam(path, "agent_type", input.AgentType)
|
||||
if agentType, ok := normaliseSessionAgentType(input.AgentType); ok {
|
||||
path = appendQueryParam(path, "agent_type", agentType)
|
||||
} else {
|
||||
path = appendQueryParam(path, "agent_type", input.AgentType)
|
||||
}
|
||||
path = appendQueryParam(path, "status", input.Status)
|
||||
if input.Limit > 0 {
|
||||
path = appendQueryParam(path, "limit", core.Sprint(input.Limit))
|
||||
|
|
@ -478,7 +483,9 @@ func (s *PrepSubsystem) sessionContinue(ctx context.Context, _ *mcp.CallToolRequ
|
|||
}
|
||||
|
||||
body := map[string]any{}
|
||||
if input.AgentType != "" {
|
||||
if agentType, ok := normaliseSessionAgentType(input.AgentType); ok {
|
||||
body["agent_type"] = agentType
|
||||
} else if input.AgentType != "" {
|
||||
body["agent_type"] = input.AgentType
|
||||
}
|
||||
if len(input.WorkLog) > 0 {
|
||||
|
|
@ -1186,10 +1193,38 @@ func resultErrorValue(action string, result core.Result) error {
|
|||
}
|
||||
|
||||
func validSessionAgentType(agentType string) bool {
|
||||
switch core.Lower(core.Trim(agentType)) {
|
||||
_, ok := normaliseSessionAgentType(agentType)
|
||||
return ok
|
||||
}
|
||||
|
||||
func normaliseSessionAgentType(agentType string) (string, bool) {
|
||||
trimmed := core.Lower(core.Trim(agentType))
|
||||
if trimmed == "" {
|
||||
return "", false
|
||||
}
|
||||
|
||||
switch trimmed {
|
||||
case "claude":
|
||||
return "opus", true
|
||||
case "opus", "sonnet", "haiku":
|
||||
return true
|
||||
return trimmed, true
|
||||
}
|
||||
|
||||
parts := core.SplitN(trimmed, ":", 2)
|
||||
if len(parts) != 2 {
|
||||
return "", false
|
||||
}
|
||||
if parts[0] != "claude" {
|
||||
return "", false
|
||||
}
|
||||
switch core.Lower(core.Trim(agentType)) {
|
||||
case "claude:opus":
|
||||
return "opus", true
|
||||
case "claude:sonnet":
|
||||
return "sonnet", true
|
||||
case "claude:haiku":
|
||||
return "haiku", true
|
||||
default:
|
||||
return false
|
||||
return "", false
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -47,6 +47,39 @@ func TestSession_HandleSessionStart_Good(t *testing.T) {
|
|||
assert.Equal(t, "opus", output.Session.AgentType)
|
||||
}
|
||||
|
||||
func TestSession_HandleSessionStart_Good_CanonicalAlias(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
require.Equal(t, "/v1/sessions", r.URL.Path)
|
||||
require.Equal(t, http.MethodPost, r.Method)
|
||||
|
||||
bodyResult := core.ReadAll(r.Body)
|
||||
require.True(t, bodyResult.OK)
|
||||
|
||||
var payload map[string]any
|
||||
parseResult := core.JSONUnmarshalString(bodyResult.Value.(string), &payload)
|
||||
require.True(t, parseResult.OK)
|
||||
require.Equal(t, "opus", payload["agent_type"])
|
||||
require.Equal(t, "ax-follow-up", payload["plan_slug"])
|
||||
|
||||
_, _ = w.Write([]byte(`{"data":{"id":1,"session_id":"ses_abc123","plan_slug":"ax-follow-up","agent_type":"opus","status":"active","context_summary":{"repo":"core/go"}}}`))
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
subsystem := testPrepWithPlatformServer(t, server, "secret-token")
|
||||
result := subsystem.handleSessionStart(context.Background(), core.NewOptions(
|
||||
core.Option{Key: "agent_type", Value: "claude:opus"},
|
||||
core.Option{Key: "plan_slug", Value: "ax-follow-up"},
|
||||
core.Option{Key: "context", Value: `{"repo":"core/go"}`},
|
||||
))
|
||||
require.True(t, result.OK)
|
||||
|
||||
output, ok := result.Value.(SessionOutput)
|
||||
require.True(t, ok)
|
||||
assert.Equal(t, "ses_abc123", output.Session.SessionID)
|
||||
assert.Equal(t, "active", output.Session.Status)
|
||||
assert.Equal(t, "opus", output.Session.AgentType)
|
||||
}
|
||||
|
||||
func TestSession_HandleSessionStart_Bad(t *testing.T) {
|
||||
subsystem := testPrepWithPlatformServer(t, nil, "secret-token")
|
||||
|
||||
|
|
@ -61,7 +94,7 @@ func TestSession_HandleSessionStart_Bad_InvalidAgentType(t *testing.T) {
|
|||
core.Option{Key: "agent_type", Value: "codex"},
|
||||
))
|
||||
assert.False(t, result.OK)
|
||||
require.Contains(t, result.Value.(error).Error(), "opus, sonnet, or haiku")
|
||||
require.Contains(t, result.Value.(error).Error(), "claude:opus")
|
||||
}
|
||||
|
||||
func TestSession_HandleSessionStart_Ugly(t *testing.T) {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue