From b6d85847f8555bb2ee7d69834636d6391ef074f3 Mon Sep 17 00:00:00 2001 From: jochen Date: Fri, 6 Nov 2026 21:51:44 +0100 Subject: [PATCH] operator-channel.asks answers the open asks within a default bound, and says what it cut (issue 1974) Why: with no argument the verb answered every ask of the month, 737,934 characters, more than a session reads. Claude-Session: https://claude.ai/code/session_01NcbgfFSnn24M3GcqttoU7i --- modules/messenger/cmd/messenger/asks.go | 26 +++++++++++++++++--- modules/messenger/cmd/messenger/held_test.go | 17 ++++++------- modules/messenger/cmd/messenger/main.go | 14 +++++------ 3 files changed, 36 insertions(+), 21 deletions(-) diff --git a/modules/messenger/cmd/messenger/asks.go b/modules/messenger/cmd/messenger/asks.go index 86ac84332..b8211aacd 100644 --- a/modules/messenger/cmd/messenger/asks.go +++ b/modules/messenger/cmd/messenger/asks.go @@ -1418,27 +1418,35 @@ func (h *Holder) AsksNow() []AskView { } // AsksQuery narrows what the tool `operator-channel.asks` answers (issue 595): Key one ask, State open (open and held) or -// ended, Since the asks opened at or after that time, Bound the most bytes of the answer's asks. The zero query keeps the -// whole answer, as before. +// ended (all takes both), Since the asks opened at or after that time, Bound the most bytes of the answer's asks. With no +// argument at all the query is the open asks within DefaultAsksBound (issue 1974), and the answer says so. type AsksQuery struct { Key, State string Since time.Time Bound int + // Defaulted is set by AsksQueryOf when no key, state or since was given: the open asks only. + Defaulted bool } +// DefaultAsksBound is the most bytes of asks an answer holds when the caller gives no bound (issue 1974: the whole of a +// month's asks was 737,934 characters, more than a session reads). +const DefaultAsksBound = 32768 + // AsksAnswer is the answer of a narrowed query: the asks kept, how many the filters matched, and, when the bound cut the // answer short, that it did and how. type AsksAnswer struct { Asks []AskView `json:"asks"` Matched int `json:"matched"` Cut string `json:"cut,omitempty"` + // Default says that no key, state or since was given, so only the open asks are answered, and how to ask for more. + Default string `json:"default,omitempty"` } // AsksQueryOf reads the arguments of the tool; an empty set is the zero query. func AsksQueryOf(a map[string]any) (AsksQuery, error) { q := AsksQuery{Key: strArg(a, "key"), State: strArg(a, "state")} - if q.State != "" && q.State != "open" && q.State != "ended" { - return q, errorf("state is open or ended, not %q", q.State) + if q.State != "" && q.State != "open" && q.State != "ended" && q.State != "all" { + return q, errorf("state is open, ended or all, not %q", q.State) } if since := strArg(a, "since"); since != "" { t, err := time.Parse(time.RFC3339, since) @@ -1453,6 +1461,12 @@ func AsksQueryOf(a map[string]any) (AsksQuery, error) { } q.Bound = int(v) } + if q.Key == "" && q.State == "" && q.Since.IsZero() { + q.State, q.Defaulted = "open", true + } + if q.Bound == 0 { + q.Bound = DefaultAsksBound + } return q, nil } @@ -1461,6 +1475,10 @@ func AsksQueryOf(a map[string]any) (AsksQuery, error) { func (q AsksQuery) Narrow(all []AskView) AsksAnswer { var out AsksAnswer out.Asks = []AskView{} + if q.Defaulted { + out.Default = "no key, state or since was given, so only the open asks are shown: state ended or all for the ended ones, " + + "key for one ask, since for the asks opened from a time" + } var kept []AskView for _, v := range all { open := v.State == StateOpen || v.State == StateHeld diff --git a/modules/messenger/cmd/messenger/held_test.go b/modules/messenger/cmd/messenger/held_test.go index 2d6c74c1e..dd545eee7 100644 --- a/modules/messenger/cmd/messenger/held_test.go +++ b/modules/messenger/cmd/messenger/held_test.go @@ -143,7 +143,7 @@ func TestAHeldAskNamesEveryKindAndWhyWhenNoChannelIsReady(t *testing.T) { } // Issue 595: operator-channel.asks answers one ask, the open or the ended, those opened from a time, and no more bytes than a -// bound, said when it cut the answer short; with no argument it keeps the whole answer. +// bound, said when it cut the answer short; with no argument it answers the open asks only, within a default bound, and says so (issue 1974). func TestTheAsksVerbNarrowsItsAnswer(t *testing.T) { view := func(key, state, opened string) AskView { return AskView{Key: key, Asker: "mesh-delivery", Headline: "Approve " + key, State: state, Opened: opened} @@ -159,13 +159,15 @@ func TestTheAsksVerbNarrowsItsAnswer(t *testing.T) { cut bool err bool }{ - {"no argument is the zero query", map[string]any{}, nil, 0, false, false}, + {"no argument is the open asks, said", map[string]any{}, []string{"a.3", "a.2"}, 2, false, false}, + {"state all is every ask", map[string]any{"state": "all"}, []string{"a.3", "a.2", "a.1", "a.0"}, 4, false, false}, + {"a key finds an ended ask whatever its state", map[string]any{"key": "a.0"}, []string{"a.0"}, 1, false, false}, {"key", map[string]any{"key": "a.1"}, []string{"a.1"}, 1, false, false}, {"state open takes open and held", map[string]any{"state": "open"}, []string{"a.3", "a.2"}, 2, false, false}, {"state ended", map[string]any{"state": "ended"}, []string{"a.1", "a.0"}, 2, false, false}, {"since", map[string]any{"since": "2026-11-02T12:00:00Z"}, []string{"a.3", "a.2"}, 2, false, false}, {"bound cuts and says so", map[string]any{"bound": float64(2*len(one) + 10), "state": "open"}, []string{"a.3", "a.2"}, 2, false, false}, - {"a bound under the asks cuts", map[string]any{"bound": float64(len(one) + 5)}, []string{"a.3"}, 4, true, false}, + {"a bound under the asks cuts", map[string]any{"bound": float64(len(one) + 5)}, []string{"a.3"}, 2, true, false}, {"a state that is neither", map[string]any{"state": "done"}, nil, 0, false, true}, {"a time that is not one", map[string]any{"since": "yesterday"}, nil, 0, false, true}, } { @@ -177,13 +179,10 @@ func TestTheAsksVerbNarrowsItsAnswer(t *testing.T) { if tc.err { continue } - if tc.want == nil { - if q != (AsksQuery{}) { - t.Errorf("%s: %+v is not the zero query, which keeps today's answer", tc.name, q) - } - continue - } got := q.Narrow(all) + if (got.Default != "") != (tc.args["key"] == nil && tc.args["state"] == nil && tc.args["since"] == nil) || q.Bound == 0 { + t.Errorf("%s: default %q, bound %d: only no argument is defaulted, and every query is bounded", tc.name, got.Default, q.Bound) + } var keys []string for _, v := range got.Asks { keys = append(keys, v.Key) diff --git a/modules/messenger/cmd/messenger/main.go b/modules/messenger/cmd/messenger/main.go index d1346ef97..ad4946aa8 100644 --- a/modules/messenger/cmd/messenger/main.go +++ b/modules/messenger/cmd/messenger/main.go @@ -734,12 +734,13 @@ func tools(h *Holder, l *listening) []stdio.Tool { {Name: "operator-channel.asks", Description: "The asks this router holds (novox/hq ADR 0259): open (or held, with since when and why no channel can carry it yet) first, then the last month's — each " + "one's asker, headline, state, when it opened and expires, the channels it was offered on, how it " + - "ended (the warrant: who chose what, through which channel), and whether its asker was told. With no argument every " + - "ask, which is large; filter it: key for one ask, state open or ended, since (a time, RFC 3339) for the asks opened " + - "from then, and bound, the most bytes of the answer, which says when it cut it short.", + "ended (the warrant: who chose what, through which channel), and whether its asker was told. With no argument " + + "only the open asks, within 32768 bytes; the answer says what it cut and how to ask for more: key for one ask " + + "(any state), state open, ended or all, since (a time, RFC 3339) for the asks opened from then, and bound, the " + + "most bytes of the answer.", Input: map[string]any{ - "key": str("one ask, by its key (asker.id); none for every ask"), - "state": str("open (open and held) or ended; none for both"), + "key": str("one ask, by its key (asker.id); none for the open asks"), + "state": str("open (open and held), ended or all; none is open when no key or since is given, else both"), "since": str("only the asks opened at or after this time, RFC 3339"), "bound": map[string]any{"type": "integer", "description": "the most bytes of the asks in the answer; said when it cuts the answer short"}}, Run: func(a map[string]any) (any, error) { @@ -747,9 +748,6 @@ func tools(h *Holder, l *listening) []stdio.Tool { if err != nil { return nil, err } - if q == (AsksQuery{}) { - return h.AsksNow(), nil - } return q.Narrow(h.AsksNow()), nil }}, {Name: "messenger_identities", -- 2.54.0