X-Git-Url: https://git.giacomolli.uk/onechat/blobdiff_plain/98d137c3e945a09f76e85bd18238558c668fdca4..9346d20fe4297dce1aa16171749a7ef3e41ce7bd:/notify.go?ds=inline diff --git a/notify.go b/notify.go index 1036312..d4e4e7a 100644 --- a/notify.go +++ b/notify.go @@ -3,6 +3,7 @@ package main import ( "context" "crypto/sha256" + "encoding/hex" "encoding/json" "errors" "fmt" @@ -36,33 +37,40 @@ const ( // time. notifyTimeout = 10 * time.Second - notifyUsage = `Usage: onechat notify --source SOURCE --id ID --text TEXT + notifyUsage = `Usage: onechat notify [--agent-id ID] --source SOURCE --id ID --text TEXT -Send untrusted external data to the root agent at its next turn boundary. +Send untrusted external data at the selected agent's next turn boundary. +Omit --agent-id for root. An explicit ID must identify the root or a permanent +child, not a task worker. Names are not accepted. Success means durably accepted, not processed. Retry with the same source, -ID, and text after an uncertain result. Changed text for an existing key fails. +ID, target identity, and text after an uncertain result. A changed target or +text for an existing key fails. Text is limited to 32768 bytes. Source and ID are 1 to 128 ASCII letters, digits, dots, underscores, or hyphens. The local test interface keeps up to 1024 receipts without expiry and rejects new keys when full. -Child targets, receipt expiry, and processing status are not supported. +Receipt expiry and processing status are not supported. ` ) // NotifyInput is caller-supplied data. Its source label proves no identity or -// authority. Source and ID together identify a retry of the same text. +// authority. Source and ID together identify a retry of the same text. AgentID +// selects a permanent agent. An empty AgentID selects the root. type NotifyInput struct { - Source string `json:"source"` - ID string `json:"id"` - Text string `json:"text"` + AgentID string `json:"agent_id,omitempty"` + Source string `json:"source"` + ID string `json:"id"` + Text string `json:"text"` } // NotifyReceipt keeps a retry key after its queued delivery finishes. Digest -// binds the original text without retaining another copy of that text. +// binds the original text without retaining another copy of that text. TargetID +// binds the permanent agent identity. An empty TargetID selects the root. type NotifyReceipt struct { - Source string `json:"source"` - ID string `json:"id"` - Digest string `json:"digest"` - Number int `json:"number"` + TargetID string `json:"target_id,omitempty"` + Source string `json:"source"` + ID string `json:"id"` + Digest string `json:"digest"` + Number int `json:"number"` } // notify submits a local event and prints only its acceptance receipt. The @@ -166,9 +174,10 @@ func notifyParse(args []string) (NotifyInput, error) { var input NotifyInput fields := map[string]*string{ - "--source": &input.Source, - "--id": &input.ID, - "--text": &input.Text, + "--agent-id": &input.AgentID, + "--source": &input.Source, + "--id": &input.ID, + "--text": &input.Text, } seen := make(map[string]bool) @@ -192,6 +201,10 @@ func notifyParse(args []string) (NotifyInput, error) { *field = value } + if seen["--agent-id"] && input.AgentID == "" { + return input, errors.New("notification agent ID must not be empty") + } + err := notifyValidate(input) if err != nil { return input, fmt.Errorf("validate notification: %w", err) @@ -202,6 +215,13 @@ func notifyParse(args []string) (NotifyInput, error) { // notifyValidate checks both CLI and direct socket inputs without echoing data. func notifyValidate(input NotifyInput) error { + if input.AgentID != "" { + id, err := hex.DecodeString(input.AgentID) + if err != nil || len(id) != agentIDBytes { + return errors.New("notification agent ID is invalid") + } + } + if !notifyIdentifier(input.Source) || !notifyIdentifier(input.ID) { return errors.New("notification source and ID must be 1 to 128 ASCII letters, digits, dots, underscores, or hyphens") }