feat(agentic): expose session start and continue commands

Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
Virgil 2026-04-02 00:05:51 +00:00
parent 6aebdc07b6
commit 318cff805d
3 changed files with 199 additions and 0 deletions

View file

@ -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")

View file

@ -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)

View file

@ -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")