feat(agentic): add session end CLI command
Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
parent
43e2a14b82
commit
d910814067
4 changed files with 118 additions and 0 deletions
|
|
@ -10,6 +10,8 @@ func (s *PrepSubsystem) registerSessionCommands() {
|
|||
c := s.Core()
|
||||
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})
|
||||
c.Command("agentic:session/end", core.Command{Description: "End a stored session with status, summary, and handoff notes", Action: s.cmdSessionEnd})
|
||||
c.Command("session/resume", core.Command{Description: "Resume a paused or handed-off session from local cache", Action: s.cmdSessionResume})
|
||||
c.Command("agentic:session/resume", core.Command{Description: "Resume a paused or handed-off session from local cache", Action: s.cmdSessionResume})
|
||||
c.Command("session/replay", core.Command{Description: "Build replay context for a stored session", Action: s.cmdSessionReplay})
|
||||
|
|
@ -60,6 +62,51 @@ func (s *PrepSubsystem) cmdSessionHandoff(options core.Options) core.Result {
|
|||
return core.Result{Value: output, OK: true}
|
||||
}
|
||||
|
||||
// core-agent session end ses-abc123 --summary="Ready for review" --status=completed
|
||||
func (s *PrepSubsystem) cmdSessionEnd(options core.Options) core.Result {
|
||||
sessionID := optionStringValue(options, "session_id", "session-id", "id", "_arg")
|
||||
summary := optionStringValue(options, "summary")
|
||||
status := optionStringValue(options, "status")
|
||||
if status == "" {
|
||||
status = "completed"
|
||||
}
|
||||
if sessionID == "" {
|
||||
core.Print(nil, "usage: core-agent session end <session-id> --summary=\"Ready for review\" [--status=completed] [--handoff-notes=\"...\"]")
|
||||
return core.Result{Value: core.E("agentic.cmdSessionEnd", "session_id is required", nil), OK: false}
|
||||
}
|
||||
if summary == "" {
|
||||
core.Print(nil, "usage: core-agent session end <session-id> --summary=\"Ready for review\" [--status=completed] [--handoff-notes=\"...\"]")
|
||||
return core.Result{Value: core.E("agentic.cmdSessionEnd", "summary is required", nil), OK: false}
|
||||
}
|
||||
|
||||
result := s.handleSessionEnd(s.commandContext(), core.NewOptions(
|
||||
core.Option{Key: "session_id", Value: sessionID},
|
||||
core.Option{Key: "status", Value: status},
|
||||
core.Option{Key: "summary", Value: summary},
|
||||
core.Option{Key: "handoff_notes", Value: optionAnyMapValue(options, "handoff_notes", "handoff-notes", "handoff")},
|
||||
))
|
||||
if !result.OK {
|
||||
err := commandResultError("agentic.cmdSessionEnd", 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.cmdSessionEnd", "invalid session end 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, "summary: %s", output.Session.Summary)
|
||||
if len(output.Session.Handoff) > 0 {
|
||||
core.Print(nil, "handoff: %d item(s)", len(output.Session.Handoff))
|
||||
}
|
||||
return core.Result{Value: output, OK: true}
|
||||
}
|
||||
|
||||
func (s *PrepSubsystem) cmdSessionResume(options core.Options) core.Result {
|
||||
sessionID := optionStringValue(options, "session_id", "session-id", "id", "_arg")
|
||||
if sessionID == "" {
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@
|
|||
package agentic
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
|
|
@ -19,6 +21,8 @@ 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/end")
|
||||
assert.Contains(t, c.Commands(), "agentic:session/end")
|
||||
assert.Contains(t, c.Commands(), "session/resume")
|
||||
assert.Contains(t, c.Commands(), "agentic:session/resume")
|
||||
assert.Contains(t, c.Commands(), "session/replay")
|
||||
|
|
@ -92,6 +96,69 @@ func TestCommandsSession_CmdSessionHandoff_Ugly_CorruptedCacheFallsBackToRemoteE
|
|||
assert.Contains(t, result.Value.(error).Error(), "no platform API key configured")
|
||||
}
|
||||
|
||||
func TestCommandsSession_CmdSessionEnd_Good(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
require.Equal(t, "/v1/sessions/ses-end/end", 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, "completed", payload["status"])
|
||||
require.Equal(t, "Ready for review", payload["summary"])
|
||||
|
||||
handoffNotes, ok := payload["handoff_notes"].(map[string]any)
|
||||
require.True(t, ok)
|
||||
assert.Equal(t, "Ready for review", handoffNotes["summary"])
|
||||
assert.Equal(t, []any{"Run the verifier"}, handoffNotes["next_steps"])
|
||||
|
||||
_, _ = w.Write([]byte(`{"data":{"session_id":"ses-end","agent_type":"codex","status":"completed","summary":"Ready for review","handoff":{"summary":"Ready for review","next_steps":["Run the verifier"]},"ended_at":"2026-03-31T12:00:00Z"}}`))
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
subsystem := testPrepWithPlatformServer(t, server, "secret-token")
|
||||
result := subsystem.cmdSessionEnd(core.NewOptions(
|
||||
core.Option{Key: "session_id", Value: "ses-end"},
|
||||
core.Option{Key: "summary", Value: "Ready for review"},
|
||||
core.Option{Key: "handoff_notes", Value: `{"summary":"Ready for review","next_steps":["Run the verifier"]}`},
|
||||
))
|
||||
require.True(t, result.OK)
|
||||
|
||||
output, ok := result.Value.(SessionOutput)
|
||||
require.True(t, ok)
|
||||
assert.Equal(t, "completed", output.Session.Status)
|
||||
assert.Equal(t, "Ready for review", output.Session.Summary)
|
||||
require.NotNil(t, output.Session.Handoff)
|
||||
assert.Equal(t, "Ready for review", output.Session.Handoff["summary"])
|
||||
}
|
||||
|
||||
func TestCommandsSession_CmdSessionEnd_Bad_MissingSummary(t *testing.T) {
|
||||
subsystem := testPrepWithPlatformServer(t, nil, "secret-token")
|
||||
result := subsystem.cmdSessionEnd(core.NewOptions(
|
||||
core.Option{Key: "session_id", Value: "ses-end"},
|
||||
))
|
||||
assert.False(t, result.OK)
|
||||
require.Error(t, result.Value.(error))
|
||||
assert.Contains(t, result.Value.(error).Error(), "summary is required")
|
||||
}
|
||||
|
||||
func TestCommandsSession_CmdSessionEnd_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.cmdSessionEnd(core.NewOptions(
|
||||
core.Option{Key: "session_id", Value: "ses-end"},
|
||||
core.Option{Key: "summary", Value: "Ready for review"},
|
||||
))
|
||||
assert.False(t, result.OK)
|
||||
}
|
||||
|
||||
func TestCommandsSession_CmdSessionResume_Good(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
t.Setenv("CORE_WORKSPACE", dir)
|
||||
|
|
|
|||
|
|
@ -1389,6 +1389,8 @@ 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/end")
|
||||
assert.Contains(t, cmds, "agentic:session/end")
|
||||
assert.Contains(t, cmds, "pr-manage")
|
||||
assert.Contains(t, cmds, "agentic:pr-manage")
|
||||
assert.Contains(t, cmds, "review-queue")
|
||||
|
|
|
|||
|
|
@ -688,6 +688,8 @@ func TestPrep_OnStartup_Good_RegistersGenerateCommand(t *testing.T) {
|
|||
assert.Contains(t, c.Commands(), "lang/list")
|
||||
assert.Contains(t, c.Commands(), "plan-cleanup")
|
||||
assert.Contains(t, c.Commands(), "plan/from-issue")
|
||||
assert.Contains(t, c.Commands(), "session/end")
|
||||
assert.Contains(t, c.Commands(), "agentic:session/end")
|
||||
assert.Contains(t, c.Commands(), "session/resume")
|
||||
assert.Contains(t, c.Commands(), "session/replay")
|
||||
assert.Contains(t, c.Commands(), "review-queue")
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue