From 7b150275ae68d259c80aad7b9caa68208846751a Mon Sep 17 00:00:00 2001 From: Steve Dudenhoeffer Date: Wed, 22 Jul 2026 21:39:20 -0400 Subject: [PATCH] Address #122 review: pageable read_journal, service-clock removal MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - read_journal now takes an offset, so the hasMore it returns is actionable — an agent can page a journal longer than 50 entries. - remove_planting goes through a new service RemovePlanting that stamps removed_at from s.now() (the injectable clock ClearObject and the fill path use), instead of the adapter computing the date off the wall clock. It delegates to UpdatePlanting, so the role check, version guard and history record are unchanged. Drops the now-unused time import. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01H3zbym8Doka2d7D48maSgZ --- internal/agent/tools.go | 13 ++++++------- internal/service/plantings.go | 11 +++++++++++ 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/internal/agent/tools.go b/internal/agent/tools.go index 0dae664..43571dc 100644 --- a/internal/agent/tools.go +++ b/internal/agent/tools.go @@ -2,7 +2,6 @@ package agent import ( "context" - "time" "gitea.stevedudenhoeffer.com/steve/majordomo/llm" @@ -218,8 +217,9 @@ func (a *adapter) readJournal(ctx context.Context, args struct { ObjectID *int64 `json:"objectId" description:"optional bed to narrow to; omit for the whole garden"` From string `json:"from" description:"optional earliest observed date, YYYY-MM-DD"` To string `json:"to" description:"optional latest observed date, YYYY-MM-DD"` + Offset int `json:"offset" description:"how many entries to skip; pass the count you've already seen to page when hasMore is true"` }) (any, error) { - q := service.JournalQuery{ObjectID: args.ObjectID, Limit: 50} + q := service.JournalQuery{ObjectID: args.ObjectID, Limit: 50, Offset: args.Offset} if args.From != "" { q.From = &args.From } @@ -230,6 +230,7 @@ func (a *adapter) readJournal(ctx context.Context, args struct { if err != nil { return nil, err } + // hasMore is actionable now: re-call with offset += len(entries) to page. return map[string]any{"entries": entries, "hasMore": hasMore}, nil } @@ -261,11 +262,9 @@ func (a *adapter) removePlanting(ctx context.Context, args struct { PlantingID int64 `json:"plantingId" description:"plop to remove (its id from describe_garden)"` Version int64 `json:"version" description:"the plop's current version (from describe_garden)"` }) (any, error) { - // Soft-remove: set removed_at to today, mirroring clear_object, so the plop - // stays in the planting history and the change is undoable. - today := time.Now().UTC().Format("2006-01-02") - return a.svc.UpdatePlanting(ctx, a.actor, args.PlantingID, - service.PlantingPatch{SetRemovedAt: true, RemovedAt: &today}, args.Version) + // Soft-remove via the service, so removed_at is stamped from the same + // (injectable) clock clear_object uses rather than the adapter's wall clock. + return a.svc.RemovePlanting(ctx, a.actor, args.PlantingID, args.Version) } func (a *adapter) listSeedLots(ctx context.Context, args struct { diff --git a/internal/service/plantings.go b/internal/service/plantings.go index 246f749..879feff 100644 --- a/internal/service/plantings.go +++ b/internal/service/plantings.go @@ -178,6 +178,17 @@ func (s *Service) UpdatePlanting(ctx context.Context, actorID, plantingID int64, return updated, nil } +// RemovePlanting soft-removes a single plop — the one-plop counterpart to +// ClearObject, used by the agent's remove_planting tool. It stamps removed_at +// from the service clock (s.now()), same as ClearObject and the fill path, so the +// removal date can't diverge by which caller set it; then delegates to +// UpdatePlanting for the editor-role check, version guard and history record. +func (s *Service) RemovePlanting(ctx context.Context, actorID, plantingID, version int64) (*domain.Planting, error) { + today := s.now().UTC().Format(dateLayout) + return s.UpdatePlanting(ctx, actorID, plantingID, + PlantingPatch{SetRemovedAt: true, RemovedAt: &today}, version) +} + // plantingEditSummary describes a plop edit for the history list. Soft-removal // ("clear bed", harvested) is the one edit worth naming specifically — it reads // as a removal to the person who did it, not as an edit.