Address #122 review: pageable read_journal, service-clock removal
Build image / build-and-push (push) Successful in 20s

- 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) <[email protected]>
Claude-Session: https://claude.ai/code/session_01H3zbym8Doka2d7D48maSgZ
This commit is contained in:
2026-07-22 21:39:20 -04:00
co-authored by Claude Opus 4.8
parent 887a3c2cc6
commit 7b150275ae
2 changed files with 17 additions and 7 deletions
+6 -7
View File
@@ -2,7 +2,6 @@ package agent
import ( import (
"context" "context"
"time"
"gitea.stevedudenhoeffer.com/steve/majordomo/llm" "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"` 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"` From string `json:"from" description:"optional earliest observed date, YYYY-MM-DD"`
To string `json:"to" description:"optional latest 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) { }) (any, error) {
q := service.JournalQuery{ObjectID: args.ObjectID, Limit: 50} q := service.JournalQuery{ObjectID: args.ObjectID, Limit: 50, Offset: args.Offset}
if args.From != "" { if args.From != "" {
q.From = &args.From q.From = &args.From
} }
@@ -230,6 +230,7 @@ func (a *adapter) readJournal(ctx context.Context, args struct {
if err != nil { if err != nil {
return nil, err return nil, err
} }
// hasMore is actionable now: re-call with offset += len(entries) to page.
return map[string]any{"entries": entries, "hasMore": hasMore}, nil 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)"` 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)"` Version int64 `json:"version" description:"the plop's current version (from describe_garden)"`
}) (any, error) { }) (any, error) {
// Soft-remove: set removed_at to today, mirroring clear_object, so the plop // Soft-remove via the service, so removed_at is stamped from the same
// stays in the planting history and the change is undoable. // (injectable) clock clear_object uses rather than the adapter's wall clock.
today := time.Now().UTC().Format("2006-01-02") return a.svc.RemovePlanting(ctx, a.actor, args.PlantingID, args.Version)
return a.svc.UpdatePlanting(ctx, a.actor, args.PlantingID,
service.PlantingPatch{SetRemovedAt: true, RemovedAt: &today}, args.Version)
} }
func (a *adapter) listSeedLots(ctx context.Context, args struct { func (a *adapter) listSeedLots(ctx context.Context, args struct {
+11
View File
@@ -178,6 +178,17 @@ func (s *Service) UpdatePlanting(ctx context.Context, actorID, plantingID int64,
return updated, nil 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 // plantingEditSummary describes a plop edit for the history list. Soft-removal
// ("clear bed", harvested) is the one edit worth naming specifically — it reads // ("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. // as a removal to the person who did it, not as an edit.