fix(agent-session): preserve handed-off sessions

Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
Virgil 2026-04-01 21:53:19 +00:00
parent bbb651797a
commit 1f333fc53d
5 changed files with 40 additions and 7 deletions

View file

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

View file

@ -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',

View file

@ -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;
}

View file

@ -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();
}

View file

@ -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([