feat(handoff): route crew lifecycle requests to deacon
Crew workers now use deacon for lifecycle management instead of requiring manual session termination. When a crew worker runs 'gt handoff', it sends a lifecycle request to the deacon which handles session kill/restart like it does for Mayor and Witness. Changes: - Route crew manager to deacon/ instead of "human" - Add getCrewIdentity() to extract <rig>-crew-<name> from session - Include full crew identity in LIFECYCLE subject for daemon parsing - Remove special case that skipped lifecycle flow for crew Also fixes pre-existing test failures in daemon/lifecycle_test.go where BeadsMessage field names were out of sync with the struct. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -30,8 +30,8 @@ func TestParseLifecycleRequest_Cycle(t *testing.T) {
|
||||
|
||||
for _, tc := range tests {
|
||||
msg := &BeadsMessage{
|
||||
Title: tc.title,
|
||||
Sender: "test-sender",
|
||||
Subject: tc.title,
|
||||
From: "test-sender",
|
||||
}
|
||||
result := d.parseLifecycleRequest(msg)
|
||||
if result == nil {
|
||||
@@ -64,8 +64,8 @@ func TestParseLifecycleRequest_PrefixMatchesCycle(t *testing.T) {
|
||||
|
||||
for _, tc := range tests {
|
||||
msg := &BeadsMessage{
|
||||
Title: tc.title,
|
||||
Sender: "test-sender",
|
||||
Subject: tc.title,
|
||||
From: "test-sender",
|
||||
}
|
||||
result := d.parseLifecycleRequest(msg)
|
||||
if result == nil {
|
||||
@@ -91,8 +91,8 @@ func TestParseLifecycleRequest_NotLifecycle(t *testing.T) {
|
||||
|
||||
for _, title := range tests {
|
||||
msg := &BeadsMessage{
|
||||
Title: title,
|
||||
Sender: "test-sender",
|
||||
Subject: title,
|
||||
From: "test-sender",
|
||||
}
|
||||
result := d.parseLifecycleRequest(msg)
|
||||
if result != nil {
|
||||
@@ -116,8 +116,8 @@ func TestParseLifecycleRequest_ExtractsFrom(t *testing.T) {
|
||||
|
||||
for _, tc := range tests {
|
||||
msg := &BeadsMessage{
|
||||
Title: tc.title,
|
||||
Sender: tc.sender,
|
||||
Subject: tc.title,
|
||||
From: tc.sender,
|
||||
}
|
||||
result := d.parseLifecycleRequest(msg)
|
||||
if result == nil {
|
||||
@@ -135,8 +135,8 @@ func TestParseLifecycleRequest_FallsBackToSender(t *testing.T) {
|
||||
|
||||
// When the title doesn't contain a parseable "from", use sender
|
||||
msg := &BeadsMessage{
|
||||
Title: "LIFECYCLE: requesting cycle", // no role before "requesting"
|
||||
Sender: "fallback-sender",
|
||||
Subject: "LIFECYCLE: requesting cycle", // no role before "requesting"
|
||||
From: "fallback-sender",
|
||||
}
|
||||
result := d.parseLifecycleRequest(msg)
|
||||
if result == nil {
|
||||
@@ -200,23 +200,23 @@ func TestIdentityToSession_Unknown(t *testing.T) {
|
||||
|
||||
func TestBeadsMessage_Serialization(t *testing.T) {
|
||||
msg := BeadsMessage{
|
||||
ID: "msg-123",
|
||||
Title: "Test Message",
|
||||
Description: "A test message body",
|
||||
Sender: "test-sender",
|
||||
Assignee: "test-assignee",
|
||||
Priority: 1,
|
||||
Status: "open",
|
||||
ID: "msg-123",
|
||||
Subject: "Test Message",
|
||||
Body: "A test message body",
|
||||
From: "test-sender",
|
||||
To: "test-recipient",
|
||||
Priority: "high",
|
||||
Type: "message",
|
||||
}
|
||||
|
||||
// Verify all fields are accessible
|
||||
if msg.ID != "msg-123" {
|
||||
t.Errorf("ID mismatch")
|
||||
}
|
||||
if msg.Title != "Test Message" {
|
||||
t.Errorf("Title mismatch")
|
||||
if msg.Subject != "Test Message" {
|
||||
t.Errorf("Subject mismatch")
|
||||
}
|
||||
if msg.Status != "open" {
|
||||
t.Errorf("Status mismatch")
|
||||
if msg.From != "test-sender" {
|
||||
t.Errorf("From mismatch")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user