feat(agentic): expose session start and continue commands
Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
parent
6aebdc07b6
commit
318cff805d
3 changed files with 199 additions and 0 deletions
|
|
@ -8,6 +8,10 @@ import (
|
|||
|
||||
func (s *PrepSubsystem) registerSessionCommands() {
|
||||
c := s.Core()
|
||||
c.Command("session/start", core.Command{Description: "Start a stored session for a plan", Action: s.cmdSessionStart})
|
||||
c.Command("agentic:session/start", core.Command{Description: "Start a stored session for a plan", Action: s.cmdSessionStart})
|
||||
c.Command("session/continue", core.Command{Description: "Continue a stored session from saved context", Action: s.cmdSessionContinue})
|
||||
c.Command("agentic:session/continue", core.Command{Description: "Continue a stored session from saved context", Action: s.cmdSessionContinue})
|
||||
c.Command("session/handoff", core.Command{Description: "Pause a stored session with handoff context", Action: s.cmdSessionHandoff})
|
||||
c.Command("agentic:session/handoff", core.Command{Description: "Pause a stored session with handoff context", Action: s.cmdSessionHandoff})
|
||||
c.Command("session/end", core.Command{Description: "End a stored session with status, summary, and handoff notes", Action: s.cmdSessionEnd})
|
||||
|
|
@ -24,6 +28,81 @@ func (s *PrepSubsystem) registerSessionCommands() {
|
|||
c.Command("agentic:session/replay", core.Command{Description: "Build replay context for a stored session", Action: s.cmdSessionReplay})
|
||||
}
|
||||
|
||||
// core-agent session start ax-follow-up --agent-type=codex
|
||||
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\"}']")
|
||||
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\"}']")
|
||||
return core.Result{Value: core.E("agentic.cmdSessionStart", "agent_type is required", nil), OK: false}
|
||||
}
|
||||
|
||||
result := s.handleSessionStart(s.commandContext(), core.NewOptions(
|
||||
core.Option{Key: "plan_slug", Value: planSlug},
|
||||
core.Option{Key: "agent_type", Value: agentType},
|
||||
core.Option{Key: "context", Value: optionAnyMapValue(options, "context")},
|
||||
))
|
||||
if !result.OK {
|
||||
err := commandResultError("agentic.cmdSessionStart", result)
|
||||
core.Print(nil, "error: %v", err)
|
||||
return core.Result{Value: err, OK: false}
|
||||
}
|
||||
|
||||
output, ok := result.Value.(SessionOutput)
|
||||
if !ok {
|
||||
err := core.E("agentic.cmdSessionStart", "invalid session start output", nil)
|
||||
core.Print(nil, "error: %v", err)
|
||||
return core.Result{Value: err, OK: false}
|
||||
}
|
||||
|
||||
core.Print(nil, "session: %s", output.Session.SessionID)
|
||||
core.Print(nil, "plan: %s", output.Session.PlanSlug)
|
||||
core.Print(nil, "agent: %s", output.Session.AgentType)
|
||||
core.Print(nil, "status: %s", output.Session.Status)
|
||||
return core.Result{Value: output, OK: true}
|
||||
}
|
||||
|
||||
// core-agent session continue ses-abc123 --agent-type=codex
|
||||
func (s *PrepSubsystem) cmdSessionContinue(options core.Options) core.Result {
|
||||
sessionID := optionStringValue(options, "session_id", "session-id", "id", "_arg")
|
||||
agentType := optionStringValue(options, "agent_type", "agent")
|
||||
if sessionID == "" {
|
||||
core.Print(nil, "usage: core-agent session continue <session-id> [--agent-type=codex] [--work-log='[{\"type\":\"checkpoint\",\"message\":\"...\"}]'] [--context='{\"repo\":\"go-io\"}']")
|
||||
return core.Result{Value: core.E("agentic.cmdSessionContinue", "session_id is required", nil), OK: false}
|
||||
}
|
||||
|
||||
result := s.handleSessionContinue(s.commandContext(), core.NewOptions(
|
||||
core.Option{Key: "session_id", Value: sessionID},
|
||||
core.Option{Key: "agent_type", Value: agentType},
|
||||
core.Option{Key: "work_log", Value: optionAnyMapSliceValue(options, "work_log")},
|
||||
core.Option{Key: "context", Value: optionAnyMapValue(options, "context")},
|
||||
))
|
||||
if !result.OK {
|
||||
err := commandResultError("agentic.cmdSessionContinue", result)
|
||||
core.Print(nil, "error: %v", err)
|
||||
return core.Result{Value: err, OK: false}
|
||||
}
|
||||
|
||||
output, ok := result.Value.(SessionOutput)
|
||||
if !ok {
|
||||
err := core.E("agentic.cmdSessionContinue", "invalid session continue output", nil)
|
||||
core.Print(nil, "error: %v", err)
|
||||
return core.Result{Value: err, OK: false}
|
||||
}
|
||||
|
||||
core.Print(nil, "session: %s", output.Session.SessionID)
|
||||
core.Print(nil, "agent: %s", output.Session.AgentType)
|
||||
core.Print(nil, "status: %s", output.Session.Status)
|
||||
if len(output.Session.WorkLog) > 0 {
|
||||
core.Print(nil, "work log: %d item(s)", len(output.Session.WorkLog))
|
||||
}
|
||||
return core.Result{Value: output, OK: true}
|
||||
}
|
||||
|
||||
// core-agent session handoff ses-abc123 --summary="Ready for review" --next-steps="Run the verifier"
|
||||
func (s *PrepSubsystem) cmdSessionHandoff(options core.Options) core.Result {
|
||||
sessionID := optionStringValue(options, "session_id", "session-id", "id", "_arg")
|
||||
|
|
|
|||
|
|
@ -21,6 +21,10 @@ func TestCommandsSession_RegisterSessionCommands_Good(t *testing.T) {
|
|||
|
||||
assert.Contains(t, c.Commands(), "session/handoff")
|
||||
assert.Contains(t, c.Commands(), "agentic:session/handoff")
|
||||
assert.Contains(t, c.Commands(), "session/start")
|
||||
assert.Contains(t, c.Commands(), "agentic:session/start")
|
||||
assert.Contains(t, c.Commands(), "session/continue")
|
||||
assert.Contains(t, c.Commands(), "agentic:session/continue")
|
||||
assert.Contains(t, c.Commands(), "session/end")
|
||||
assert.Contains(t, c.Commands(), "agentic:session/end")
|
||||
assert.Contains(t, c.Commands(), "session/complete")
|
||||
|
|
@ -35,6 +39,118 @@ func TestCommandsSession_RegisterSessionCommands_Good(t *testing.T) {
|
|||
assert.Contains(t, c.Commands(), "agentic:session/replay")
|
||||
}
|
||||
|
||||
func TestCommandsSession_CmdSessionStart_Good(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, "codex", payload["agent_type"])
|
||||
assert.Equal(t, "ax-follow-up", payload["plan_slug"])
|
||||
|
||||
_, _ = w.Write([]byte(`{"data":{"session_id":"ses-start","plan_slug":"ax-follow-up","agent_type":"codex","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: "codex"},
|
||||
))
|
||||
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, "codex", output.Session.AgentType)
|
||||
}
|
||||
|
||||
func TestCommandsSession_CmdSessionStart_Bad_MissingPlanSlug(t *testing.T) {
|
||||
subsystem := testPrepWithPlatformServer(t, nil, "secret-token")
|
||||
|
||||
result := subsystem.cmdSessionStart(core.NewOptions(core.Option{Key: "agent_type", Value: "codex"}))
|
||||
|
||||
assert.False(t, result.OK)
|
||||
require.Error(t, result.Value.(error))
|
||||
assert.Contains(t, result.Value.(error).Error(), "plan_slug is required")
|
||||
}
|
||||
|
||||
func TestCommandsSession_CmdSessionStart_Ugly_InvalidResponse(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
_, _ = w.Write([]byte(`{"data":`))
|
||||
}))
|
||||
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: "codex"},
|
||||
))
|
||||
assert.False(t, result.OK)
|
||||
}
|
||||
|
||||
func TestCommandsSession_CmdSessionContinue_Good(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
require.Equal(t, "/v1/sessions/ses-continue/continue", 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, "codex", payload["agent_type"])
|
||||
|
||||
_, _ = w.Write([]byte(`{"data":{"session_id":"ses-continue","agent_type":"codex","status":"active","work_log":[{"type":"checkpoint","message":"continue"}]}}`))
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
subsystem := testPrepWithPlatformServer(t, server, "secret-token")
|
||||
result := subsystem.cmdSessionContinue(core.NewOptions(
|
||||
core.Option{Key: "_arg", Value: "ses-continue"},
|
||||
core.Option{Key: "agent_type", Value: "codex"},
|
||||
core.Option{Key: "work_log", Value: []map[string]any{{"type": "checkpoint", "message": "continue"}}},
|
||||
))
|
||||
require.True(t, result.OK)
|
||||
|
||||
output, ok := result.Value.(SessionOutput)
|
||||
require.True(t, ok)
|
||||
assert.Equal(t, "ses-continue", output.Session.SessionID)
|
||||
assert.Equal(t, "codex", output.Session.AgentType)
|
||||
require.Len(t, output.Session.WorkLog, 1)
|
||||
}
|
||||
|
||||
func TestCommandsSession_CmdSessionContinue_Bad_MissingSessionID(t *testing.T) {
|
||||
subsystem := testPrepWithPlatformServer(t, nil, "secret-token")
|
||||
|
||||
result := subsystem.cmdSessionContinue(core.NewOptions(core.Option{Key: "agent_type", Value: "codex"}))
|
||||
|
||||
assert.False(t, result.OK)
|
||||
require.Error(t, result.Value.(error))
|
||||
assert.Contains(t, result.Value.(error).Error(), "session_id is required")
|
||||
}
|
||||
|
||||
func TestCommandsSession_CmdSessionContinue_Ugly_InvalidResponse(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
_, _ = w.Write([]byte(`{"data":`))
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
subsystem := testPrepWithPlatformServer(t, server, "secret-token")
|
||||
result := subsystem.cmdSessionContinue(core.NewOptions(
|
||||
core.Option{Key: "_arg", Value: "ses-continue"},
|
||||
core.Option{Key: "agent_type", Value: "codex"},
|
||||
))
|
||||
assert.False(t, result.OK)
|
||||
}
|
||||
|
||||
func TestCommandsSession_CmdSessionHandoff_Good(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
t.Setenv("CORE_WORKSPACE", dir)
|
||||
|
|
|
|||
|
|
@ -1421,6 +1421,10 @@ func TestCommands_RegisterCommands_Good_AllRegistered(t *testing.T) {
|
|||
assert.Contains(t, cmds, "plan/archive")
|
||||
assert.Contains(t, cmds, "plan/delete")
|
||||
assert.Contains(t, cmds, "agentic:plan-cleanup")
|
||||
assert.Contains(t, cmds, "session/start")
|
||||
assert.Contains(t, cmds, "agentic:session/start")
|
||||
assert.Contains(t, cmds, "session/continue")
|
||||
assert.Contains(t, cmds, "agentic:session/continue")
|
||||
assert.Contains(t, cmds, "session/end")
|
||||
assert.Contains(t, cmds, "agentic:session/end")
|
||||
assert.Contains(t, cmds, "pr-manage")
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue