diff --git a/pkg/agentic/commands_session.go b/pkg/agentic/commands_session.go index 2ae736a..c41195f 100644 --- a/pkg/agentic/commands_session.go +++ b/pkg/agentic/commands_session.go @@ -8,6 +8,10 @@ import ( func (s *PrepSubsystem) registerSessionCommands() { c := s.Core() + c.Command("session/get", core.Command{Description: "Read a stored session by session ID", Action: s.cmdSessionGet}) + c.Command("agentic:session/get", core.Command{Description: "Read a stored session by session ID", Action: s.cmdSessionGet}) + c.Command("session/list", core.Command{Description: "List stored sessions with optional filters", Action: s.cmdSessionList}) + c.Command("agentic:session/list", core.Command{Description: "List stored sessions with optional filters", Action: s.cmdSessionList}) 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}) @@ -28,6 +32,97 @@ 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 get ses-abc123 +func (s *PrepSubsystem) cmdSessionGet(options core.Options) core.Result { + sessionID := optionStringValue(options, "session_id", "session-id", "id", "_arg") + if sessionID == "" { + core.Print(nil, "usage: core-agent session get ") + return core.Result{Value: core.E("agentic.cmdSessionGet", "session_id is required", nil), OK: false} + } + + result := s.handleSessionGet(s.commandContext(), core.NewOptions( + core.Option{Key: "session_id", Value: sessionID}, + )) + if !result.OK { + err := commandResultError("agentic.cmdSessionGet", 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.cmdSessionGet", "invalid session get 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, "status: %s", output.Session.Status) + core.Print(nil, "agent: %s", output.Session.AgentType) + if output.Session.PlanSlug != "" { + core.Print(nil, "plan: %s", output.Session.PlanSlug) + } + if output.Session.Summary != "" { + core.Print(nil, "summary: %s", output.Session.Summary) + } + if output.Session.CreatedAt != "" { + core.Print(nil, "started: %s", output.Session.CreatedAt) + } + if output.Session.UpdatedAt != "" { + core.Print(nil, "updated: %s", output.Session.UpdatedAt) + } + if output.Session.EndedAt != "" { + core.Print(nil, "ended: %s", output.Session.EndedAt) + } + if len(output.Session.ContextSummary) > 0 { + core.Print(nil, "context: %d item(s)", len(output.Session.ContextSummary)) + } + if len(output.Session.WorkLog) > 0 { + core.Print(nil, "work log: %d item(s)", len(output.Session.WorkLog)) + } + if len(output.Session.Artifacts) > 0 { + core.Print(nil, "artifacts: %d item(s)", len(output.Session.Artifacts)) + } + if len(output.Session.Handoff) > 0 { + core.Print(nil, "handoff: %d item(s)", len(output.Session.Handoff)) + } + + return core.Result{Value: output, OK: true} +} + +// core-agent session list --status=active --plan=ax-follow-up +func (s *PrepSubsystem) cmdSessionList(options core.Options) core.Result { + result := s.handleSessionList(s.commandContext(), core.NewOptions( + core.Option{Key: "plan_slug", Value: optionStringValue(options, "plan_slug", "plan")}, + core.Option{Key: "agent_type", Value: optionStringValue(options, "agent_type", "agent")}, + core.Option{Key: "status", Value: optionStringValue(options, "status")}, + core.Option{Key: "limit", Value: optionIntValue(options, "limit")}, + )) + if !result.OK { + err := commandResultError("agentic.cmdSessionList", result) + core.Print(nil, "error: %v", err) + return core.Result{Value: err, OK: false} + } + + output, ok := result.Value.(SessionListOutput) + if !ok { + err := core.E("agentic.cmdSessionList", "invalid session list output", nil) + core.Print(nil, "error: %v", err) + return core.Result{Value: err, OK: false} + } + + if output.Count == 0 { + core.Print(nil, "no sessions") + return core.Result{Value: output, OK: true} + } + + for _, session := range output.Sessions { + core.Print(nil, " %-10s %-10s %-24s %s", session.Status, session.AgentType, session.SessionID, sessionPlanSlug(session)) + } + core.Print(nil, "%d session(s)", output.Count) + return core.Result{Value: output, OK: true} +} + // 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") diff --git a/pkg/agentic/commands_session_test.go b/pkg/agentic/commands_session_test.go index c67ac71..610202f 100644 --- a/pkg/agentic/commands_session_test.go +++ b/pkg/agentic/commands_session_test.go @@ -19,6 +19,10 @@ func TestCommandsSession_RegisterSessionCommands_Good(t *testing.T) { s.registerSessionCommands() + assert.Contains(t, c.Commands(), "session/get") + assert.Contains(t, c.Commands(), "agentic:session/get") + assert.Contains(t, c.Commands(), "session/list") + assert.Contains(t, c.Commands(), "agentic:session/list") assert.Contains(t, c.Commands(), "session/handoff") assert.Contains(t, c.Commands(), "agentic:session/handoff") assert.Contains(t, c.Commands(), "session/start") @@ -39,6 +43,54 @@ func TestCommandsSession_RegisterSessionCommands_Good(t *testing.T) { assert.Contains(t, c.Commands(), "agentic:session/replay") } +func TestCommandsSession_CmdSessionGet_Good(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + require.Equal(t, "/v1/sessions/ses-get", r.URL.Path) + require.Equal(t, http.MethodGet, r.Method) + _, _ = w.Write([]byte(`{"data":{"session_id":"ses-get","plan_slug":"ax-follow-up","agent_type":"codex","status":"active","summary":"Working","created_at":"2026-03-31T12:00:00Z","updated_at":"2026-03-31T12:30:00Z","work_log":[{"type":"checkpoint","message":"started"}],"artifacts":[{"path":"pkg/agentic/session.go","action":"modified"}]}}`)) + })) + defer server.Close() + + subsystem := testPrepWithPlatformServer(t, server, "secret-token") + + output := captureStdout(t, func() { + result := subsystem.cmdSessionGet(core.NewOptions(core.Option{Key: "_arg", Value: "ses-get"})) + require.True(t, result.OK) + }) + + assert.Contains(t, output, "session: ses-get") + assert.Contains(t, output, "plan: ax-follow-up") + assert.Contains(t, output, "work log: 1 item(s)") + assert.Contains(t, output, "artifacts: 1 item(s)") +} + +func TestCommandsSession_CmdSessionList_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, "ax-follow-up", r.URL.Query().Get("plan_slug")) + require.Equal(t, "codex", r.URL.Query().Get("agent_type")) + require.Equal(t, "active", r.URL.Query().Get("status")) + require.Equal(t, "5", r.URL.Query().Get("limit")) + _, _ = w.Write([]byte(`{"data":[{"session_id":"ses-1","plan_slug":"ax-follow-up","agent_type":"codex","status":"active"},{"session_id":"ses-2","agent_type":"claude","status":"paused"}],"count":2}`)) + })) + defer server.Close() + + subsystem := testPrepWithPlatformServer(t, server, "secret-token") + + output := captureStdout(t, func() { + result := subsystem.cmdSessionList(core.NewOptions( + core.Option{Key: "plan_slug", Value: "ax-follow-up"}, + core.Option{Key: "agent_type", Value: "codex"}, + core.Option{Key: "status", Value: "active"}, + core.Option{Key: "limit", Value: 5}, + )) + require.True(t, result.OK) + }) + + assert.Contains(t, output, "ses-1") + assert.Contains(t, output, "2 session(s)") +} + 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) diff --git a/pkg/agentic/commands_test.go b/pkg/agentic/commands_test.go index a6e2ea1..e65eec4 100644 --- a/pkg/agentic/commands_test.go +++ b/pkg/agentic/commands_test.go @@ -1422,6 +1422,10 @@ func TestCommands_RegisterCommands_Good_AllRegistered(t *testing.T) { assert.Contains(t, cmds, "plan/delete") assert.Contains(t, cmds, "agentic:plan-cleanup") assert.Contains(t, cmds, "session/start") + assert.Contains(t, cmds, "session/get") + assert.Contains(t, cmds, "agentic:session/get") + assert.Contains(t, cmds, "session/list") + assert.Contains(t, cmds, "agentic:session/list") assert.Contains(t, cmds, "agentic:session/start") assert.Contains(t, cmds, "session/continue") assert.Contains(t, cmds, "agentic:session/continue")