25357bf267
- Extend IPC wire protocol: add ListReminders, ListEvents, ListProposedRoutines, DismissProposedRoutine, AcceptProposedRoutine IPC methods with request/response types. Update wire.go with new message kinds. - Add ack_sends table (migration #5): tracks sev4 telegram repeat-til-ack delivery state with rule name + timestamp, indexed for dedup. - Add Store.DeleteTool: permanently removes a tool row (for dismissing proposed tools), idempotent on missing tool. - Update tick.go: wire new IPC handlers into daemon tick.
143 lines
5.1 KiB
Go
143 lines
5.1 KiB
Go
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"
|
|
MethodCalendarEvents Method = "calendar_events"
|
|
MethodRecentNudges Method = "recent_nudges"
|
|
MethodWriteNote Method = "write_note"
|
|
MethodQueryNotes Method = "query_notes"
|
|
MethodRecentNotes Method = "recent_notes"
|
|
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"
|
|
MethodRevertFact Method = "revert_fact"
|
|
MethodTickTrace Method = "tick_trace"
|
|
MethodChat Method = "chat"
|
|
)
|
|
|
|
// 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}
|
|
}
|