Skip to content

Commit a63983e

Browse files
author
upstream_merger
committed
feat(rpc): add decision point RPC operations
Add RPC protocol and handlers for decision point operations: - OpDecisionCreate, OpDecisionGet, OpDecisionResolve, OpDecisionList - Request/response types for all operations - Handler implementations in server_issues_epics.go - Routing registration in server_routing_validation_diagnostics.go This enables the daemon to handle decision point CRUD operations for human-in-the-loop gates in agent workflows. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> Executed-By: gastown/crew/upstream_merger Rig: gastown Role: crew
1 parent 27c79e4 commit a63983e

2 files changed

Lines changed: 255 additions & 0 deletions

File tree

internal/rpc/server_issues_epics.go

Lines changed: 246 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2280,3 +2280,249 @@ func (s *Server) handleGateWait(req *Request) Response {
22802280
Data: data,
22812281
}
22822282
}
2283+
2284+
// Decision point handlers
2285+
2286+
// handleDecisionCreate creates a new decision point for an issue
2287+
func (s *Server) handleDecisionCreate(req *Request) Response {
2288+
var args DecisionCreateArgs
2289+
if err := json.Unmarshal(req.Args, &args); err != nil {
2290+
return Response{
2291+
Success: false,
2292+
Error: fmt.Sprintf("invalid decision create args: %v", err),
2293+
}
2294+
}
2295+
2296+
store := s.storage
2297+
if store == nil {
2298+
return Response{
2299+
Success: false,
2300+
Error: "storage not available",
2301+
}
2302+
}
2303+
2304+
ctx := s.reqCtx(req)
2305+
2306+
// Verify issue exists
2307+
issue, err := store.GetIssue(ctx, args.IssueID)
2308+
if err != nil {
2309+
return Response{
2310+
Success: false,
2311+
Error: fmt.Sprintf("failed to get issue: %v", err),
2312+
}
2313+
}
2314+
if issue == nil {
2315+
return Response{
2316+
Success: false,
2317+
Error: fmt.Sprintf("issue %s not found", args.IssueID),
2318+
}
2319+
}
2320+
2321+
// Serialize options to JSON
2322+
optionsJSON, err := json.Marshal(args.Options)
2323+
if err != nil {
2324+
return Response{
2325+
Success: false,
2326+
Error: fmt.Sprintf("failed to serialize options: %v", err),
2327+
}
2328+
}
2329+
2330+
// Set defaults
2331+
maxIterations := args.MaxIterations
2332+
if maxIterations == 0 {
2333+
maxIterations = 3
2334+
}
2335+
2336+
dp := &types.DecisionPoint{
2337+
IssueID: args.IssueID,
2338+
Prompt: args.Prompt,
2339+
Options: string(optionsJSON),
2340+
DefaultOption: args.DefaultOption,
2341+
MaxIterations: maxIterations,
2342+
Iteration: 1,
2343+
RequestedBy: args.RequestedBy,
2344+
CreatedAt: time.Now(),
2345+
}
2346+
2347+
if err := store.CreateDecisionPoint(ctx, dp); err != nil {
2348+
return Response{
2349+
Success: false,
2350+
Error: fmt.Sprintf("failed to create decision point: %v", err),
2351+
}
2352+
}
2353+
2354+
result := &DecisionResponse{
2355+
Decision: dp,
2356+
Issue: issue,
2357+
}
2358+
data, _ := json.Marshal(result)
2359+
return Response{
2360+
Success: true,
2361+
Data: data,
2362+
}
2363+
}
2364+
2365+
// handleDecisionGet retrieves a decision point for an issue
2366+
func (s *Server) handleDecisionGet(req *Request) Response {
2367+
var args DecisionGetArgs
2368+
if err := json.Unmarshal(req.Args, &args); err != nil {
2369+
return Response{
2370+
Success: false,
2371+
Error: fmt.Sprintf("invalid decision get args: %v", err),
2372+
}
2373+
}
2374+
2375+
store := s.storage
2376+
if store == nil {
2377+
return Response{
2378+
Success: false,
2379+
Error: "storage not available",
2380+
}
2381+
}
2382+
2383+
ctx := s.reqCtx(req)
2384+
2385+
dp, err := store.GetDecisionPoint(ctx, args.IssueID)
2386+
if err != nil {
2387+
return Response{
2388+
Success: false,
2389+
Error: fmt.Sprintf("failed to get decision point: %v", err),
2390+
}
2391+
}
2392+
if dp == nil {
2393+
return Response{
2394+
Success: false,
2395+
Error: fmt.Sprintf("no decision point for issue %s", args.IssueID),
2396+
}
2397+
}
2398+
2399+
// Get associated issue for context
2400+
issue, _ := store.GetIssue(ctx, args.IssueID)
2401+
2402+
result := &DecisionResponse{
2403+
Decision: dp,
2404+
Issue: issue,
2405+
}
2406+
data, _ := json.Marshal(result)
2407+
return Response{
2408+
Success: true,
2409+
Data: data,
2410+
}
2411+
}
2412+
2413+
// handleDecisionResolve resolves a decision point
2414+
func (s *Server) handleDecisionResolve(req *Request) Response {
2415+
var args DecisionResolveArgs
2416+
if err := json.Unmarshal(req.Args, &args); err != nil {
2417+
return Response{
2418+
Success: false,
2419+
Error: fmt.Sprintf("invalid decision resolve args: %v", err),
2420+
}
2421+
}
2422+
2423+
store := s.storage
2424+
if store == nil {
2425+
return Response{
2426+
Success: false,
2427+
Error: "storage not available",
2428+
}
2429+
}
2430+
2431+
ctx := s.reqCtx(req)
2432+
2433+
// Get existing decision point
2434+
dp, err := store.GetDecisionPoint(ctx, args.IssueID)
2435+
if err != nil {
2436+
return Response{
2437+
Success: false,
2438+
Error: fmt.Sprintf("failed to get decision point: %v", err),
2439+
}
2440+
}
2441+
if dp == nil {
2442+
return Response{
2443+
Success: false,
2444+
Error: fmt.Sprintf("no decision point for issue %s", args.IssueID),
2445+
}
2446+
}
2447+
2448+
// Update decision point with response
2449+
now := time.Now()
2450+
dp.SelectedOption = args.SelectedOption
2451+
dp.ResponseText = args.ResponseText
2452+
dp.RespondedAt = &now
2453+
dp.RespondedBy = args.RespondedBy
2454+
if args.Guidance != "" {
2455+
dp.Guidance = args.Guidance
2456+
}
2457+
2458+
if err := store.UpdateDecisionPoint(ctx, dp); err != nil {
2459+
return Response{
2460+
Success: false,
2461+
Error: fmt.Sprintf("failed to update decision point: %v", err),
2462+
}
2463+
}
2464+
2465+
// Get associated issue for context
2466+
issue, _ := store.GetIssue(ctx, args.IssueID)
2467+
2468+
result := &DecisionResponse{
2469+
Decision: dp,
2470+
Issue: issue,
2471+
}
2472+
data, _ := json.Marshal(result)
2473+
return Response{
2474+
Success: true,
2475+
Data: data,
2476+
}
2477+
}
2478+
2479+
// handleDecisionList returns pending decision points
2480+
func (s *Server) handleDecisionList(req *Request) Response {
2481+
var args DecisionListArgs
2482+
if req.Args != nil {
2483+
if err := json.Unmarshal(req.Args, &args); err != nil {
2484+
return Response{
2485+
Success: false,
2486+
Error: fmt.Sprintf("invalid decision list args: %v", err),
2487+
}
2488+
}
2489+
}
2490+
2491+
store := s.storage
2492+
if store == nil {
2493+
return Response{
2494+
Success: false,
2495+
Error: "storage not available",
2496+
}
2497+
}
2498+
2499+
ctx := s.reqCtx(req)
2500+
2501+
decisions, err := store.ListPendingDecisions(ctx)
2502+
if err != nil {
2503+
return Response{
2504+
Success: false,
2505+
Error: fmt.Sprintf("failed to list decisions: %v", err),
2506+
}
2507+
}
2508+
2509+
// Build response with associated issues
2510+
responses := make([]*DecisionResponse, 0, len(decisions))
2511+
for _, dp := range decisions {
2512+
issue, _ := store.GetIssue(ctx, dp.IssueID)
2513+
responses = append(responses, &DecisionResponse{
2514+
Decision: dp,
2515+
Issue: issue,
2516+
})
2517+
}
2518+
2519+
result := &DecisionListResponse{
2520+
Decisions: responses,
2521+
Count: len(responses),
2522+
}
2523+
data, _ := json.Marshal(result)
2524+
return Response{
2525+
Success: true,
2526+
Data: data,
2527+
}
2528+
}

internal/rpc/server_routing_validation_diagnostics.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,15 @@ func (s *Server) handleRequest(req *Request) Response {
253253
resp = s.handleGateClose(req)
254254
case OpGateWait:
255255
resp = s.handleGateWait(req)
256+
// Decision point operations
257+
case OpDecisionCreate:
258+
resp = s.handleDecisionCreate(req)
259+
case OpDecisionGet:
260+
resp = s.handleDecisionGet(req)
261+
case OpDecisionResolve:
262+
resp = s.handleDecisionResolve(req)
263+
case OpDecisionList:
264+
resp = s.handleDecisionList(req)
256265
default:
257266
s.metrics.RecordError(req.Operation)
258267
return Response{

0 commit comments

Comments
 (0)