package ipc import ( "encoding/json" "errors" "fmt" ) // Method — one RPC verb. The set is intentionally small: it mirrors exactly // what a module legitimately needs from core state, and nothing more. Adding // a method is a core-authority change (every method is a new thing a module // can ask for); do it deliberately. type Method string const ( MethodWriteFact Method = "write_fact" MethodLatestFact Method = "latest_fact" MethodLatestFactBySource Method = "latest_fact_by_source" MethodSince Method = "since" MethodPresence Method = "presence" MethodCreateReminder Method = "create_reminder" MethodMarkReminder Method = "mark_reminder" MethodListReminders Method = "list_reminders" MethodRecordNudge Method = "record_nudge" MethodResolveNudge Method = "resolve_nudge" MethodRecentOutcomes Method = "recent_outcomes" MethodRecentFacts Method = "recent_facts" MethodRecentActiveFacts Method = "recent_active_facts_by_kind" MethodCalendarEvents Method = "calendar_events" MethodRecentNudges Method = "recent_nudges" MethodDeliveryAttempts Method = "delivery_attempts" MethodRecentEcoTraces Method = "recent_ecosystem_traces" MethodWriteNote Method = "write_note" MethodQueryNotes Method = "query_notes" MethodRecentNotes Method = "recent_notes" MethodRecentNotesFromSource Method = "recent_notes_source" MethodProposeTool Method = "propose_tool" MethodEnableTool Method = "enable_tool" MethodDisableTool Method = "disable_tool" MethodAssertStepUp Method = "assert_stepup" MethodStoreEncryptionKey Method = "store_encryption_key" MethodUnlock Method = "unlock" MethodLookupTool Method = "lookup_tool" MethodListTools Method = "list_tools" MethodDeleteTool Method = "delete_tool" MethodListProposedRoutines Method = "list_proposed_routines" MethodDismissProposedRoutine Method = "dismiss_proposed_routine" MethodAcceptProposedRoutine Method = "accept_proposed_routine" MethodRevertFact Method = "revert_fact" MethodTickTrace Method = "tick_trace" MethodMorningStatus Method = "morning_status" MethodMCPServers Method = "mcp_servers" MethodDayPlan Method = "day_plan" MethodChat Method = "chat" MethodCaptureTask Method = "capture_task" MethodListTasks Method = "list_tasks" MethodSetTaskStatus Method = "set_task_status" MethodIngestMail Method = "ingest_mail" MethodSwapModel Method = "swap_model" MethodModelStatus Method = "model_status" MethodDescribeImage Method = "describe_image" MethodCaptureStart Method = "capture_start" MethodCaptureAppend Method = "capture_append" MethodCaptureStop Method = "capture_stop" MethodCaptureStatus Method = "capture_status" MethodEnrollSpeaker Method = "enroll_speaker" MethodListSpeakers Method = "list_speakers" MethodForgetSpeaker Method = "forget_speaker" MethodRecentEvents Method = "recent_events" MethodSeedEvent Method = "seed_event" // MethodPing — liveness, and the only method that answers in locked mode // without a passkey assertion. It reaches no store, takes no arguments and // returns whether the daemon is locked, so an operator tool can tell "she is // up and waiting for a passkey" apart from "she is not there at all". // Everything else about her state needs the store, and the store needs the // key. MethodPing Method = "ping" ) // Request — one frame from module to core. Params is the JSON-encoded argument // struct for Method (see api.go for the per-method shapes). The server // unmarshals Params based on Method; an unknown Method ⇒ ErrUnknownMethod. type Request struct { Method Method `json:"m"` Params json.RawMessage `json:"p,omitempty"` } // Response — one frame from core back to module. Exactly one of Result/Error // is set. Result is the JSON-encoded return value of the method (might be a // scalar, a struct, or null for void methods). type Response struct { Result json.RawMessage `json:"r,omitempty"` Error *RpcError `json:"e,omitempty"` } // RpcError — a typed wire error. Code is one of the sentinel codes below; // the client rehydrates it into the matching package sentinel so callers can // use errors.Is like they would in-process (core's contract is the same on // both sides of the wire — the boundary shouldn't change error semantics). type RpcError struct { Code string `json:"c"` Message string `json:"m,omitempty"` } func (e *RpcError) Error() string { if e.Message != "" { return fmt.Sprintf("ipc: %s: %s", e.Code, e.Message) } return fmt.Sprintf("ipc: %s", e.Code) } // Sentinel codes. Stable over the wire — do not rename. Mirror the package // sentinels in api.go 1:1. The string is the contract. const ( codeNoFact = "no_fact" codeConfidence = "confidence" codeVoidsMissing = "voids_missing" codeNudgeNotFound = "nudge_not_found" codeNudgeOutcome = "nudge_outcome" codeReminderMissing = "reminder_not_found" codeReminderState = "reminder_state" codeToolNotFound = "tool_not_found" codeUnknownMethod = "unknown_method" codeBadParams = "bad_params" codeForbidden = "forbidden" codeInternal = "internal" ) // codeOf maps a server-side sentinel to its wire code. Anything not matched // is codeInternal — we never leak internal Go error text to a module; it // gets a generic "internal" and the daemon logs the real error server-side. func codeOf(err error) string { switch { case err == nil: return "" case errors.Is(err, ErrNoFact): return codeNoFact case errors.Is(err, ErrConfidence): return codeConfidence case errors.Is(err, ErrVoidsMissing): return codeVoidsMissing case errors.Is(err, ErrNudgeNotFound): return codeNudgeNotFound case errors.Is(err, ErrNudgeOutcome): return codeNudgeOutcome case errors.Is(err, ErrReminderNotFound): return codeReminderMissing case errors.Is(err, ErrReminderState): return codeReminderState case errors.Is(err, ErrToolNotFound): return codeToolNotFound case errors.Is(err, ErrUnknownMethod): return codeUnknownMethod case errors.Is(err, ErrBadParams): return codeBadParams case errors.Is(err, ErrForbidden): return codeForbidden default: return codeInternal } } // rpcErr builds the wire error for a server-side error. message is omitted // for sentinel codes (the Code carries the meaning; no need to echo text the // caller can re-derive from errors.Is) and included for internal/bad-params // where the text is the actual diagnostic. func rpcErr(err error) *RpcError { c := codeOf(err) if c == codeInternal || c == codeBadParams { return &RpcError{Code: c, Message: err.Error()} } return &RpcError{Code: c} }