From 43dc4871130760e380c0e476742c6049b8cb3182 Mon Sep 17 00:00:00 2001 From: claude Date: Thu, 6 Aug 2026 01:30:41 +0400 Subject: [PATCH] ipc: one helper for the list reads on the wire (V-575) Fourteen table entries carried the same four lines: call the CoreAPI method, return early on error, swap a nil slice for an empty one so the wire says [] and not null. withParamsSlice holds that once and each entry is now the call it makes. Three id-only request types were the same struct under three names, so the routine transitions use the idReq that was already declared and unused. The revert reply was a map literal on one side and an anonymous struct on the other; revertResp names it. Both are wire-identical. --- internal/ipc/api.go | 15 ++-- internal/ipc/client.go | 12 ++-- internal/ipc/server.go | 159 ++++++++++++----------------------------- 3 files changed, 55 insertions(+), 131 deletions(-) diff --git a/internal/ipc/api.go b/internal/ipc/api.go index 8128700..113c825 100644 --- a/internal/ipc/api.go +++ b/internal/ipc/api.go @@ -594,7 +594,7 @@ type setTaskFieldsReq struct { BlockedOn string `json:"blocked_on,omitempty"` } -// idReq — methods keyed by a single id. +// idReq — methods keyed by a single id, which is every routine transition. type idReq struct { ID int64 `json:"id"` } @@ -652,6 +652,11 @@ type calendarEventsReq struct { type revertReq struct { Key string `json:"key"` } + +// revertResp — the id of the voiding fact the revert wrote. +type revertResp struct { + NewID int64 `json:"new_id"` +} type writeNoteReq struct { Ts time.Time `json:"ts"` Text string `json:"text"` @@ -780,14 +785,6 @@ type listProposedRoutinesResp struct { Routines []ProposedRoutine `json:"routines"` } -type dismissProposedRoutineReq struct { - ID int64 `json:"id"` -} - -type acceptProposedRoutineReq struct { - ID int64 `json:"id"` -} - // IntakeEvent — one entry of the unified intake journal on the wire. Mirrors // event.Event field for field; the ipc package does not import internal/event // so the wire shape stays independent of the in-process type. diff --git a/internal/ipc/client.go b/internal/ipc/client.go index ccba692..946ed37 100644 --- a/internal/ipc/client.go +++ b/internal/ipc/client.go @@ -649,11 +649,11 @@ func (c *Client) ModelStatus(ctx context.Context) (ModelStatusResp, error) { } func (c *Client) DismissProposedRoutine(ctx context.Context, id int64) error { - return c.call(ctx, MethodDismissProposedRoutine, dismissProposedRoutineReq{ID: id}, nil) + return c.call(ctx, MethodDismissProposedRoutine, idReq{ID: id}, nil) } func (c *Client) AcceptProposedRoutine(ctx context.Context, id int64) error { - return c.call(ctx, MethodAcceptProposedRoutine, acceptProposedRoutineReq{ID: id}, nil) + return c.call(ctx, MethodAcceptProposedRoutine, idReq{ID: id}, nil) } func (c *Client) Chat(ctx context.Context, conversation, text string) (ChatReply, error) { @@ -713,13 +713,11 @@ func (c *Client) DayPlan(ctx context.Context) (DayPlan, error) { } func (c *Client) RevertFact(ctx context.Context, key string) (int64, error) { - var result struct { - NewID int64 `json:"new_id"` - } - if err := c.call(ctx, MethodRevertFact, map[string]string{"key": key}, &result); err != nil { + var r revertResp + if err := c.call(ctx, MethodRevertFact, revertReq{Key: key}, &r); err != nil { return 0, err } - return result.NewID, nil + return r.NewID, nil } // Ping asks whether the daemon is there, and whether it is locked. It is not a diff --git a/internal/ipc/server.go b/internal/ipc/server.go index b421aed..6e7d590 100644 --- a/internal/ipc/server.go +++ b/internal/ipc/server.go @@ -335,6 +335,22 @@ func withoutParams[R any](fn func(ctx context.Context, api CoreAPI) (R, error)) } } +// withParamsSlice is withParams for a list read. It replaces a nil slice with +// an empty one so the wire carries [] rather than null, which every reader of +// these methods relies on. +func withParamsSlice[P any, E any](fn func(ctx context.Context, api CoreAPI, p P) ([]E, error)) handlerFunc { + return withParams(func(ctx context.Context, api CoreAPI, p P) ([]E, error) { + out, err := fn(ctx, api, p) + if err != nil { + return nil, err + } + if out == nil { + out = []E{} + } + return out, nil + }) +} + // methodTable — one entry per CoreAPI-backed method. Built once at package // init, not per-Server and not per-dispatch: entries close over nothing but // the CoreAPI method being called, and dispatch passes in the *current* @@ -373,15 +389,8 @@ var methodTable = map[Method]handlerFunc{ MethodMarkReminder: withParamsVoid(func(ctx context.Context, api CoreAPI, p markReminderReq) error { return api.MarkReminder(ctx, p.ID, p.Status) }), - MethodListReminders: withParams(func(ctx context.Context, api CoreAPI, p nReq) ([]Reminder, error) { - out, err := api.ListReminders(ctx, p.N) - if err != nil { - return nil, err - } - if out == nil { - out = []Reminder{} - } - return out, nil + MethodListReminders: withParamsSlice(func(ctx context.Context, api CoreAPI, p nReq) ([]Reminder, error) { + return api.ListReminders(ctx, p.N) }), MethodRecordNudge: withParams(func(ctx context.Context, api CoreAPI, p recordNudgeReq) (idResp, error) { id, err := api.RecordNudge(ctx, p.Rule, p.Channel, p.Message, p.Ts) @@ -390,109 +399,39 @@ var methodTable = map[Method]handlerFunc{ MethodResolveNudge: withParamsVoid(func(ctx context.Context, api CoreAPI, p resolveNudgeReq) error { return api.ResolveNudge(ctx, p.ID, p.Outcome, p.Ts) }), - MethodRecentOutcomes: withParams(func(ctx context.Context, api CoreAPI, p outcomesReq) ([]string, error) { - out, err := api.RecentOutcomes(ctx, p.Rule, p.N) - if err != nil { - return nil, err - } - if out == nil { - out = []string{} // stable non-null on the wire - } - return out, nil + MethodRecentOutcomes: withParamsSlice(func(ctx context.Context, api CoreAPI, p outcomesReq) ([]string, error) { + return api.RecentOutcomes(ctx, p.Rule, p.N) }), - MethodRecentFacts: withParams(func(ctx context.Context, api CoreAPI, p nReq) ([]Fact, error) { - out, err := api.RecentFacts(ctx, p.N) - if err != nil { - return nil, err - } - if out == nil { - out = []Fact{} - } - return out, nil + MethodRecentFacts: withParamsSlice(func(ctx context.Context, api CoreAPI, p nReq) ([]Fact, error) { + return api.RecentFacts(ctx, p.N) }), - MethodRecentActiveFacts: withParams(func(ctx context.Context, api CoreAPI, p kindNReq) ([]Fact, error) { - out, err := api.RecentActiveFactsByKind(ctx, p.Kind, p.N) - if err != nil { - return nil, err - } - if out == nil { - out = []Fact{} - } - return out, nil + MethodRecentActiveFacts: withParamsSlice(func(ctx context.Context, api CoreAPI, p kindNReq) ([]Fact, error) { + return api.RecentActiveFactsByKind(ctx, p.Kind, p.N) }), - MethodCalendarEvents: withParams(func(ctx context.Context, api CoreAPI, p calendarEventsReq) ([]Fact, error) { - out, err := api.CalendarEvents(ctx, p.From, p.To) - if err != nil { - return nil, err - } - if out == nil { - out = []Fact{} - } - return out, nil + MethodCalendarEvents: withParamsSlice(func(ctx context.Context, api CoreAPI, p calendarEventsReq) ([]Fact, error) { + return api.CalendarEvents(ctx, p.From, p.To) }), - MethodRecentEcoTraces: withParams(func(ctx context.Context, api CoreAPI, p nReq) ([]EcosystemTrace, error) { - out, err := api.RecentEcosystemTraces(ctx, p.N) - if err != nil { - return nil, err - } - if out == nil { - out = []EcosystemTrace{} - } - return out, nil + MethodRecentEcoTraces: withParamsSlice(func(ctx context.Context, api CoreAPI, p nReq) ([]EcosystemTrace, error) { + return api.RecentEcosystemTraces(ctx, p.N) }), - MethodDeliveryAttempts: withParams(func(ctx context.Context, api CoreAPI, p deliveryAttemptsReq) ([]DeliveryAttempt, error) { - out, err := api.DeliveryAttempts(ctx, p.Status, p.N) - if err != nil { - return nil, err - } - if out == nil { - out = []DeliveryAttempt{} - } - return out, nil + MethodDeliveryAttempts: withParamsSlice(func(ctx context.Context, api CoreAPI, p deliveryAttemptsReq) ([]DeliveryAttempt, error) { + return api.DeliveryAttempts(ctx, p.Status, p.N) }), - MethodRecentNudges: withParams(func(ctx context.Context, api CoreAPI, p nReq) ([]Nudge, error) { - out, err := api.RecentNudges(ctx, p.N) - if err != nil { - return nil, err - } - if out == nil { - out = []Nudge{} - } - return out, nil + MethodRecentNudges: withParamsSlice(func(ctx context.Context, api CoreAPI, p nReq) ([]Nudge, error) { + return api.RecentNudges(ctx, p.N) }), MethodWriteNote: withParams(func(ctx context.Context, api CoreAPI, p writeNoteReq) (idResp, error) { id, err := api.WriteNote(ctx, p.Ts, p.Text, p.Embedding, p.Source) return idResp{ID: id}, err }), - MethodQueryNotes: withParams(func(ctx context.Context, api CoreAPI, p queryNotesReq) ([]Note, error) { - out, err := api.QueryNotes(ctx, p.Embedding, p.K) - if err != nil { - return nil, err - } - if out == nil { - out = []Note{} - } - return out, nil + MethodQueryNotes: withParamsSlice(func(ctx context.Context, api CoreAPI, p queryNotesReq) ([]Note, error) { + return api.QueryNotes(ctx, p.Embedding, p.K) }), - MethodRecentNotesFromSource: withParams(func(ctx context.Context, api CoreAPI, p sourceNReq) ([]Note, error) { - out, err := api.RecentNotesFromSource(ctx, p.Prefix, p.N) - if err != nil { - return nil, err - } - if out == nil { - out = []Note{} - } - return out, nil + MethodRecentNotesFromSource: withParamsSlice(func(ctx context.Context, api CoreAPI, p sourceNReq) ([]Note, error) { + return api.RecentNotesFromSource(ctx, p.Prefix, p.N) }), - MethodRecentNotes: withParams(func(ctx context.Context, api CoreAPI, p nReq) ([]Note, error) { - out, err := api.RecentNotes(ctx, p.N) - if err != nil { - return nil, err - } - if out == nil { - out = []Note{} - } - return out, nil + MethodRecentNotes: withParamsSlice(func(ctx context.Context, api CoreAPI, p nReq) ([]Note, error) { + return api.RecentNotes(ctx, p.N) }), MethodProposeTool: withParams(func(ctx context.Context, api CoreAPI, p proposeToolReq) (proposeToolResp, error) { ok, err := api.ProposeTool(ctx, p.Name, p.Utterance, p.Scope, p.Ts) @@ -563,18 +502,15 @@ var methodTable = map[Method]handlerFunc{ } return listProposedRoutinesResp{Routines: out}, nil }), - MethodDismissProposedRoutine: withParamsVoid(func(ctx context.Context, api CoreAPI, p dismissProposedRoutineReq) error { + MethodDismissProposedRoutine: withParamsVoid(func(ctx context.Context, api CoreAPI, p idReq) error { return api.DismissProposedRoutine(ctx, p.ID) }), - MethodAcceptProposedRoutine: withParamsVoid(func(ctx context.Context, api CoreAPI, p acceptProposedRoutineReq) error { + MethodAcceptProposedRoutine: withParamsVoid(func(ctx context.Context, api CoreAPI, p idReq) error { return api.AcceptProposedRoutine(ctx, p.ID) }), - MethodRevertFact: withParams(func(ctx context.Context, api CoreAPI, p revertReq) (map[string]int64, error) { + MethodRevertFact: withParams(func(ctx context.Context, api CoreAPI, p revertReq) (revertResp, error) { newID, err := api.RevertFact(ctx, p.Key) - if err != nil { - return nil, err - } - return map[string]int64{"new_id": newID}, nil + return revertResp{NewID: newID}, err }), MethodChat: withParams(func(ctx context.Context, api CoreAPI, p chatReq) (chatResp, error) { reply, err := api.Chat(ctx, p.Conversation, p.Text) @@ -599,15 +535,8 @@ var methodTable = map[Method]handlerFunc{ MethodMorningStatus: withoutParams(func(ctx context.Context, api CoreAPI) ([]MorningRoutineStatus, error) { return api.MorningStatus(ctx) }), - MethodRecentEvents: withParams(func(ctx context.Context, api CoreAPI, p nReq) ([]IntakeEvent, error) { - out, err := api.RecentEvents(ctx, p.N) - if err != nil { - return nil, err - } - if out == nil { - out = []IntakeEvent{} - } - return out, nil + MethodRecentEvents: withParamsSlice(func(ctx context.Context, api CoreAPI, p nReq) ([]IntakeEvent, error) { + return api.RecentEvents(ctx, p.N) }), MethodMCPServers: withoutParams(func(ctx context.Context, api CoreAPI) ([]MCPServerStatus, error) { out, err := api.MCPServers(ctx)