From 1f333fc53d5f7de2cae825586ca39a71ae1444c8 Mon Sep 17 00:00:00 2001 From: Virgil Date: Wed, 1 Apr 2026 21:53:19 +0000 Subject: [PATCH] fix(agent-session): preserve handed-off sessions Co-Authored-By: Virgil --- php/Actions/Session/ListSessions.php | 2 +- php/Mcp/Tools/Agent/Session/SessionList.php | 2 +- php/Models/AgentSession.php | 8 ++++-- php/Services/AgentSessionService.php | 4 +-- php/tests/Feature/AgentSessionTest.php | 31 +++++++++++++++++++++ 5 files changed, 40 insertions(+), 7 deletions(-) diff --git a/php/Actions/Session/ListSessions.php b/php/Actions/Session/ListSessions.php index 3987919..0ab361b 100644 --- a/php/Actions/Session/ListSessions.php +++ b/php/Actions/Session/ListSessions.php @@ -37,7 +37,7 @@ class ListSessions public function handle(int $workspaceId, ?string $status = null, ?string $planSlug = null, ?int $limit = null): Collection { if ($status !== null) { - $valid = ['active', 'paused', 'completed', 'failed']; + $valid = ['active', 'paused', 'completed', 'failed', 'handed_off']; if (! in_array($status, $valid, true)) { throw new \InvalidArgumentException( sprintf('status must be one of: %s', implode(', ', $valid)) diff --git a/php/Mcp/Tools/Agent/Session/SessionList.php b/php/Mcp/Tools/Agent/Session/SessionList.php index 551147c..4bad613 100644 --- a/php/Mcp/Tools/Agent/Session/SessionList.php +++ b/php/Mcp/Tools/Agent/Session/SessionList.php @@ -34,7 +34,7 @@ class SessionList extends AgentTool 'status' => [ 'type' => 'string', 'description' => 'Filter by status', - 'enum' => ['active', 'paused', 'completed', 'failed'], + 'enum' => ['active', 'paused', 'completed', 'failed', 'handed_off'], ], 'plan_slug' => [ 'type' => 'string', diff --git a/php/Models/AgentSession.php b/php/Models/AgentSession.php index cf9cfce..bd657de 100644 --- a/php/Models/AgentSession.php +++ b/php/Models/AgentSession.php @@ -84,6 +84,8 @@ class AgentSession extends Model public const STATUS_FAILED = 'failed'; + public const STATUS_HANDED_OFF = 'handed_off'; + // Agent types public const AGENT_OPUS = 'opus'; @@ -151,7 +153,7 @@ class AgentSession extends Model public function isEnded(): bool { - return in_array($this->status, [self::STATUS_COMPLETED, self::STATUS_FAILED]); + return in_array($this->status, [self::STATUS_COMPLETED, self::STATUS_FAILED, self::STATUS_HANDED_OFF], true); } // Actions @@ -243,9 +245,9 @@ class AgentSession extends Model */ public function end(string $status, ?string $summary = null): self { - $validStatuses = [self::STATUS_COMPLETED, self::STATUS_FAILED]; + $validStatuses = [self::STATUS_COMPLETED, self::STATUS_FAILED, self::STATUS_HANDED_OFF]; - if (! in_array($status, $validStatuses)) { + if (! in_array($status, $validStatuses, true)) { $status = self::STATUS_COMPLETED; } diff --git a/php/Services/AgentSessionService.php b/php/Services/AgentSessionService.php index 8f86295..721e6c6 100644 --- a/php/Services/AgentSessionService.php +++ b/php/Services/AgentSessionService.php @@ -74,8 +74,8 @@ class AgentSessionService return null; } - // Only resume if paused or was handed off - if ($session->status === AgentSession::STATUS_PAUSED) { + // Resume paused or handed-off sessions back into the active state. + if (in_array($session->status, [AgentSession::STATUS_PAUSED, AgentSession::STATUS_HANDED_OFF], true)) { $session->resume(); } diff --git a/php/tests/Feature/AgentSessionTest.php b/php/tests/Feature/AgentSessionTest.php index 56c1552..2307c54 100644 --- a/php/tests/Feature/AgentSessionTest.php +++ b/php/tests/Feature/AgentSessionTest.php @@ -6,6 +6,7 @@ namespace Core\Mod\Agentic\Tests\Feature; use Core\Mod\Agentic\Models\AgentPlan; use Core\Mod\Agentic\Models\AgentSession; +use Core\Mod\Agentic\Services\AgentSessionService; use Core\Tenant\Models\Workspace; use Illuminate\Foundation\Testing\RefreshDatabase; use Tests\TestCase; @@ -90,6 +91,21 @@ class AgentSessionTest extends TestCase $this->assertNotNull($fresh->last_active_at); } + public function test_it_can_resume_a_handed_off_session(): void + { + $session = AgentSession::factory()->create([ + 'workspace_id' => $this->workspace->id, + 'status' => AgentSession::STATUS_HANDED_OFF, + 'last_active_at' => now()->subHour(), + ]); + + $resumed = app(AgentSessionService::class)->resume($session->session_id); + + $this->assertNotNull($resumed); + $this->assertTrue($resumed->isActive()); + $this->assertGreaterThan($session->last_active_at, $resumed->fresh()->last_active_at); + } + public function test_it_can_be_completed_with_summary(): void { $session = AgentSession::factory()->active()->create([ @@ -118,6 +134,21 @@ class AgentSessionTest extends TestCase $this->assertNotNull($fresh->ended_at); } + public function test_it_can_be_marked_as_handed_off(): void + { + $session = AgentSession::factory()->active()->create([ + 'workspace_id' => $this->workspace->id, + ]); + + $session->end(AgentSession::STATUS_HANDED_OFF, 'Handed off to another agent'); + + $fresh = $session->fresh(); + $this->assertEquals(AgentSession::STATUS_HANDED_OFF, $fresh->status); + $this->assertEquals('Handed off to another agent', $fresh->final_summary); + $this->assertNotNull($fresh->ended_at); + $this->assertTrue($fresh->isEnded()); + } + public function test_it_logs_actions(): void { $session = AgentSession::factory()->active()->create([