Files
beads/internal/rpc/protocol.go
Steve Yegge 3ec517cc1b Fix daemon/direct mode inconsistency in graph commands (bd-fu83)
Commands relate, unrelate, duplicate, and supersede now properly
use RPC Update when daemonClient is available, instead of always
calling store.UpdateIssue() directly and bypassing the daemon.

Added RelatesTo, DuplicateOf, and SupersededBy fields to UpdateArgs
in the RPC protocol, and updated server_issues_epics.go to handle them.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-18 01:34:14 -08:00

400 lines
16 KiB
Go

package rpc
import (
"encoding/json"
)
// Operation constants for all bd commands
const (
OpPing = "ping"
OpStatus = "status"
OpHealth = "health"
OpMetrics = "metrics"
OpCreate = "create"
OpUpdate = "update"
OpClose = "close"
OpList = "list"
OpCount = "count"
OpShow = "show"
OpReady = "ready"
OpStale = "stale"
OpStats = "stats"
OpDepAdd = "dep_add"
OpDepRemove = "dep_remove"
OpDepTree = "dep_tree"
OpLabelAdd = "label_add"
OpLabelRemove = "label_remove"
OpCommentList = "comment_list"
OpCommentAdd = "comment_add"
OpBatch = "batch"
OpResolveID = "resolve_id"
OpCompact = "compact"
OpCompactStats = "compact_stats"
OpExport = "export"
OpImport = "import"
OpEpicStatus = "epic_status"
OpGetMutations = "get_mutations"
OpShutdown = "shutdown"
OpDelete = "delete"
)
// Request represents an RPC request from client to daemon
type Request struct {
Operation string `json:"operation"`
Args json.RawMessage `json:"args"`
Actor string `json:"actor,omitempty"`
RequestID string `json:"request_id,omitempty"`
Cwd string `json:"cwd,omitempty"` // Working directory for database discovery
ClientVersion string `json:"client_version,omitempty"` // Client version for compatibility checks
ExpectedDB string `json:"expected_db,omitempty"` // Expected database path for validation (absolute)
}
// Response represents an RPC response from daemon to client
type Response struct {
Success bool `json:"success"`
Data json.RawMessage `json:"data,omitempty"`
Error string `json:"error,omitempty"`
}
// CreateArgs represents arguments for the create operation
type CreateArgs struct {
ID string `json:"id,omitempty"`
Parent string `json:"parent,omitempty"` // Parent ID for hierarchical issues
Title string `json:"title"`
Description string `json:"description,omitempty"`
IssueType string `json:"issue_type"`
Priority int `json:"priority"`
Design string `json:"design,omitempty"`
AcceptanceCriteria string `json:"acceptance_criteria,omitempty"`
Assignee string `json:"assignee,omitempty"`
ExternalRef string `json:"external_ref,omitempty"` // Link to external issue trackers
EstimatedMinutes *int `json:"estimated_minutes,omitempty"` // Time estimate in minutes
Labels []string `json:"labels,omitempty"`
Dependencies []string `json:"dependencies,omitempty"`
// Messaging fields (bd-kwro)
Sender string `json:"sender,omitempty"` // Who sent this (for messages)
Ephemeral bool `json:"ephemeral,omitempty"` // Can be bulk-deleted when closed
RepliesTo string `json:"replies_to,omitempty"` // Issue ID for conversation threading
}
// UpdateArgs represents arguments for the update operation
type UpdateArgs struct {
ID string `json:"id"`
Title *string `json:"title,omitempty"`
Description *string `json:"description,omitempty"`
Status *string `json:"status,omitempty"`
Priority *int `json:"priority,omitempty"`
Design *string `json:"design,omitempty"`
AcceptanceCriteria *string `json:"acceptance_criteria,omitempty"`
Notes *string `json:"notes,omitempty"`
Assignee *string `json:"assignee,omitempty"`
ExternalRef *string `json:"external_ref,omitempty"` // Link to external issue trackers
EstimatedMinutes *int `json:"estimated_minutes,omitempty"` // Time estimate in minutes
IssueType *string `json:"issue_type,omitempty"` // Issue type (bug|feature|task|epic|chore)
AddLabels []string `json:"add_labels,omitempty"`
RemoveLabels []string `json:"remove_labels,omitempty"`
SetLabels []string `json:"set_labels,omitempty"`
// Messaging fields (bd-kwro)
Sender *string `json:"sender,omitempty"` // Who sent this (for messages)
Ephemeral *bool `json:"ephemeral,omitempty"` // Can be bulk-deleted when closed
RepliesTo *string `json:"replies_to,omitempty"` // Issue ID for conversation threading
// Graph link fields (bd-fu83)
RelatesTo *string `json:"relates_to,omitempty"` // JSON array of related issue IDs
DuplicateOf *string `json:"duplicate_of,omitempty"` // Canonical issue ID if duplicate
SupersededBy *string `json:"superseded_by,omitempty"` // Replacement issue ID if obsolete
}
// CloseArgs represents arguments for the close operation
type CloseArgs struct {
ID string `json:"id"`
Reason string `json:"reason,omitempty"`
}
// DeleteArgs represents arguments for the delete operation
type DeleteArgs struct {
IDs []string `json:"ids"` // Issue IDs to delete
Force bool `json:"force,omitempty"` // Force deletion without confirmation
DryRun bool `json:"dry_run,omitempty"` // Preview mode
Cascade bool `json:"cascade,omitempty"` // Recursively delete dependents
Reason string `json:"reason,omitempty"` // Reason for deletion
}
// ListArgs represents arguments for the list operation
type ListArgs struct {
Query string `json:"query,omitempty"`
Status string `json:"status,omitempty"`
Priority *int `json:"priority,omitempty"`
IssueType string `json:"issue_type,omitempty"`
Assignee string `json:"assignee,omitempty"`
Label string `json:"label,omitempty"` // Deprecated: use Labels
Labels []string `json:"labels,omitempty"` // AND semantics
LabelsAny []string `json:"labels_any,omitempty"` // OR semantics
IDs []string `json:"ids,omitempty"` // Filter by specific issue IDs
Limit int `json:"limit,omitempty"`
// Pattern matching
TitleContains string `json:"title_contains,omitempty"`
DescriptionContains string `json:"description_contains,omitempty"`
NotesContains string `json:"notes_contains,omitempty"`
// Date ranges (ISO 8601 format)
CreatedAfter string `json:"created_after,omitempty"`
CreatedBefore string `json:"created_before,omitempty"`
UpdatedAfter string `json:"updated_after,omitempty"`
UpdatedBefore string `json:"updated_before,omitempty"`
ClosedAfter string `json:"closed_after,omitempty"`
ClosedBefore string `json:"closed_before,omitempty"`
// Empty/null checks
EmptyDescription bool `json:"empty_description,omitempty"`
NoAssignee bool `json:"no_assignee,omitempty"`
NoLabels bool `json:"no_labels,omitempty"`
// Priority range
PriorityMin *int `json:"priority_min,omitempty"`
PriorityMax *int `json:"priority_max,omitempty"`
}
// CountArgs represents arguments for the count operation
type CountArgs struct {
// Supports all the same filters as ListArgs
Query string `json:"query,omitempty"`
Status string `json:"status,omitempty"`
Priority *int `json:"priority,omitempty"`
IssueType string `json:"issue_type,omitempty"`
Assignee string `json:"assignee,omitempty"`
Labels []string `json:"labels,omitempty"`
LabelsAny []string `json:"labels_any,omitempty"`
IDs []string `json:"ids,omitempty"`
// Pattern matching
TitleContains string `json:"title_contains,omitempty"`
DescriptionContains string `json:"description_contains,omitempty"`
NotesContains string `json:"notes_contains,omitempty"`
// Date ranges
CreatedAfter string `json:"created_after,omitempty"`
CreatedBefore string `json:"created_before,omitempty"`
UpdatedAfter string `json:"updated_after,omitempty"`
UpdatedBefore string `json:"updated_before,omitempty"`
ClosedAfter string `json:"closed_after,omitempty"`
ClosedBefore string `json:"closed_before,omitempty"`
// Empty/null checks
EmptyDescription bool `json:"empty_description,omitempty"`
NoAssignee bool `json:"no_assignee,omitempty"`
NoLabels bool `json:"no_labels,omitempty"`
// Priority range
PriorityMin *int `json:"priority_min,omitempty"`
PriorityMax *int `json:"priority_max,omitempty"`
// Grouping option (only one can be specified)
GroupBy string `json:"group_by,omitempty"` // "status", "priority", "type", "assignee", "label"
}
// ShowArgs represents arguments for the show operation
type ShowArgs struct {
ID string `json:"id"`
}
// ResolveIDArgs represents arguments for the resolve_id operation
type ResolveIDArgs struct {
ID string `json:"id"`
}
// ReadyArgs represents arguments for the ready operation
type ReadyArgs struct {
Assignee string `json:"assignee,omitempty"`
Unassigned bool `json:"unassigned,omitempty"`
Priority *int `json:"priority,omitempty"`
Limit int `json:"limit,omitempty"`
SortPolicy string `json:"sort_policy,omitempty"`
Labels []string `json:"labels,omitempty"`
LabelsAny []string `json:"labels_any,omitempty"`
}
// StaleArgs represents arguments for the stale command
type StaleArgs struct {
Days int `json:"days,omitempty"`
Status string `json:"status,omitempty"`
Limit int `json:"limit,omitempty"`
}
// DepAddArgs represents arguments for adding a dependency
type DepAddArgs struct {
FromID string `json:"from_id"`
ToID string `json:"to_id"`
DepType string `json:"dep_type"`
}
// DepRemoveArgs represents arguments for removing a dependency
type DepRemoveArgs struct {
FromID string `json:"from_id"`
ToID string `json:"to_id"`
DepType string `json:"dep_type,omitempty"`
}
// DepTreeArgs represents arguments for the dep tree operation
type DepTreeArgs struct {
ID string `json:"id"`
MaxDepth int `json:"max_depth,omitempty"`
}
// LabelAddArgs represents arguments for adding a label
type LabelAddArgs struct {
ID string `json:"id"`
Label string `json:"label"`
}
// LabelRemoveArgs represents arguments for removing a label
type LabelRemoveArgs struct {
ID string `json:"id"`
Label string `json:"label"`
}
// CommentListArgs represents arguments for listing comments on an issue
type CommentListArgs struct {
ID string `json:"id"`
}
// CommentAddArgs represents arguments for adding a comment to an issue
type CommentAddArgs struct {
ID string `json:"id"`
Author string `json:"author"`
Text string `json:"text"`
}
// EpicStatusArgs represents arguments for the epic status operation
type EpicStatusArgs struct {
EligibleOnly bool `json:"eligible_only,omitempty"`
}
// PingResponse is the response for a ping operation
type PingResponse struct {
Message string `json:"message"`
Version string `json:"version"`
}
// StatusResponse represents the daemon status metadata
type StatusResponse struct {
Version string `json:"version"` // Server/daemon version
WorkspacePath string `json:"workspace_path"` // Absolute path to workspace root
DatabasePath string `json:"database_path"` // Absolute path to database file
SocketPath string `json:"socket_path"` // Path to Unix socket
PID int `json:"pid"` // Process ID
UptimeSeconds float64 `json:"uptime_seconds"` // Time since daemon started
LastActivityTime string `json:"last_activity_time"` // ISO 8601 timestamp of last request
ExclusiveLockActive bool `json:"exclusive_lock_active"` // Whether an exclusive lock is held
ExclusiveLockHolder string `json:"exclusive_lock_holder,omitempty"` // Lock holder name if active
// Daemon configuration
AutoCommit bool `json:"auto_commit"` // Whether auto-commit is enabled
AutoPush bool `json:"auto_push"` // Whether auto-push is enabled
LocalMode bool `json:"local_mode"` // Whether running in local-only mode (no git)
SyncInterval string `json:"sync_interval"` // Sync interval (e.g., "5s")
DaemonMode string `json:"daemon_mode"` // Sync mode: "poll" or "events"
}
// HealthResponse is the response for a health check operation
type HealthResponse struct {
Status string `json:"status"` // "healthy", "degraded", "unhealthy"
Version string `json:"version"` // Server/daemon version
ClientVersion string `json:"client_version,omitempty"` // Client version from request
Compatible bool `json:"compatible"` // Whether versions are compatible
Uptime float64 `json:"uptime_seconds"`
DBResponseTime float64 `json:"db_response_ms"`
ActiveConns int32 `json:"active_connections"`
MaxConns int `json:"max_connections"`
MemoryAllocMB uint64 `json:"memory_alloc_mb"`
Error string `json:"error,omitempty"`
}
// BatchArgs represents arguments for batch operations
type BatchArgs struct {
Operations []BatchOperation `json:"operations"`
}
// BatchOperation represents a single operation in a batch
type BatchOperation struct {
Operation string `json:"operation"`
Args json.RawMessage `json:"args"`
}
// BatchResponse contains the results of a batch operation
type BatchResponse struct {
Results []BatchResult `json:"results"`
}
// BatchResult represents the result of a single operation in a batch
type BatchResult struct {
Success bool `json:"success"`
Data json.RawMessage `json:"data,omitempty"`
Error string `json:"error,omitempty"`
}
// CompactArgs represents arguments for the compact operation
type CompactArgs struct {
IssueID string `json:"issue_id,omitempty"` // Empty for --all
Tier int `json:"tier"` // 1 or 2
DryRun bool `json:"dry_run"`
Force bool `json:"force"`
All bool `json:"all"`
APIKey string `json:"api_key,omitempty"`
Workers int `json:"workers,omitempty"`
BatchSize int `json:"batch_size,omitempty"`
}
// CompactStatsArgs represents arguments for compact stats operation
type CompactStatsArgs struct {
Tier int `json:"tier,omitempty"`
}
// CompactResponse represents the response from a compact operation
type CompactResponse struct {
Success bool `json:"success"`
IssueID string `json:"issue_id,omitempty"`
Results []CompactResult `json:"results,omitempty"` // For batch operations
Stats *CompactStatsData `json:"stats,omitempty"` // For stats operation
OriginalSize int `json:"original_size,omitempty"`
CompactedSize int `json:"compacted_size,omitempty"`
Reduction string `json:"reduction,omitempty"`
Duration string `json:"duration,omitempty"`
DryRun bool `json:"dry_run,omitempty"`
}
// CompactResult represents the result of compacting a single issue
type CompactResult struct {
IssueID string `json:"issue_id"`
Success bool `json:"success"`
Error string `json:"error,omitempty"`
OriginalSize int `json:"original_size,omitempty"`
CompactedSize int `json:"compacted_size,omitempty"`
Reduction string `json:"reduction,omitempty"`
}
// CompactStatsData represents compaction statistics
type CompactStatsData struct {
Tier1Candidates int `json:"tier1_candidates"`
Tier2Candidates int `json:"tier2_candidates"`
TotalClosed int `json:"total_closed"`
Tier1MinAge string `json:"tier1_min_age"`
Tier2MinAge string `json:"tier2_min_age"`
EstimatedSavings string `json:"estimated_savings,omitempty"`
}
// ExportArgs represents arguments for the export operation
type ExportArgs struct {
JSONLPath string `json:"jsonl_path"` // Path to export JSONL file
}
// ImportArgs represents arguments for the import operation
type ImportArgs struct {
JSONLPath string `json:"jsonl_path"` // Path to import JSONL file
}
// GetMutationsArgs represents arguments for retrieving recent mutations
type GetMutationsArgs struct {
Since int64 `json:"since"` // Unix timestamp in milliseconds (0 for all recent)
}