Class: Ask::Agent::TodoList

Inherits:
Object
  • Object
show all
Defined in:
lib/ask/agent/todo_list.rb

Overview

Session-scoped task list maintained by the model through the TodoWrite tool.

The list is the externalized plan: the model writes it once and checks it against each step, humans see progress live (via the TodoUpdated event), and checkpoints carry it across rollbacks and forks.

Defined Under Namespace

Classes: Entry

Constant Summary collapse

STATUSES =
%w[pending in_progress completed blocked].freeze

Instance Method Summary collapse

Constructor Details

#initializeTodoList

Returns a new instance of TodoList.



18
19
20
21
22
23
# File 'lib/ask/agent/todo_list.rb', line 18

def initialize
  @entries = []
  @next_id = 1
  @mutex = Mutex.new
  @listeners = []
end

Instance Method Details

#add(title, status: "pending") ⇒ Entry

Parameters:

  • title (String)
  • status (String) (defaults to: "pending")

    pending, in_progress, completed, or blocked

Returns:

Raises:

  • (ArgumentError)

    on empty title or invalid status



43
44
45
46
47
48
49
50
51
52
53
54
# File 'lib/ask/agent/todo_list.rb', line 43

def add(title, status: "pending")
  raise ArgumentError, "title is required" if title.to_s.strip.empty?

  entry = @mutex.synchronize do
    e = Entry.new(id: "todo_#{@next_id}", title: title.to_s, status: validate_status(status || "pending"))
    @next_id += 1
    @entries << e
    e
  end
  notify
  entry
end

#allArray<Entry>

Returns snapshot of the current entries.

Returns:

  • (Array<Entry>)

    snapshot of the current entries



26
27
28
# File 'lib/ask/agent/todo_list.rb', line 26

def all
  @mutex.synchronize { @entries.dup }
end

#clearvoid

This method returns an undefined value.



78
79
80
81
82
# File 'lib/ask/agent/todo_list.rb', line 78

def clear
  @mutex.synchronize { @entries.clear }
  notify
  nil
end

#restore(data) ⇒ void

This method returns an undefined value.

Rebuild from a serialized snapshot (rollback, fork, load). Fires no events.

Parameters:

  • data (Hash, nil)


94
95
96
97
98
99
100
101
102
103
104
105
106
107
# File 'lib/ask/agent/todo_list.rb', line 94

def restore(data)
  @mutex.synchronize do
    raw = Array(data&.dig(:entries) || data&.dig("entries") || [])
    @entries = raw.map do |e|
      Entry.new(
        id: (e[:id] || e["id"]).to_s,
        title: (e[:title] || e["title"]).to_s,
        status: (e[:status] || e["status"]).to_s
      )
    end
    @next_id = @entries.size + 1
  end
  nil
end

#subscribe(&block) ⇒ self

Register a listener called with the full entry list after every change (used to emit TodoUpdated events for live rendering).

Returns:

  • (self)


34
35
36
37
# File 'lib/ask/agent/todo_list.rb', line 34

def subscribe(&block)
  @listeners << block
  self
end

#to_hHash

Returns serialized form for persistence (checkpoints).

Returns:

  • (Hash)

    serialized form for persistence (checkpoints)



85
86
87
# File 'lib/ask/agent/todo_list.rb', line 85

def to_h
  { entries: all.map(&:to_h) }
end

#to_sString

Returns human-readable list.

Returns:

  • (String)

    human-readable list



110
111
112
113
114
115
# File 'lib/ask/agent/todo_list.rb', line 110

def to_s
  entries = all
  return "(no todos)" if entries.empty?

  entries.map { |e| "[#{e.status}] #{e.title} (#{e.id})" }.join("\n")
end

#update(id, status: nil, title: nil) ⇒ Entry

Returns the updated entry.

Parameters:

Returns:

  • (Entry)

    the updated entry

Raises:

  • (ArgumentError)

    on unknown id or invalid status



61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
# File 'lib/ask/agent/todo_list.rb', line 61

def update(id, status: nil, title: nil)
  entry = @mutex.synchronize do
    index = @entries.index { |e| e.id == id }
    raise ArgumentError, "no todo with id #{id.inspect}" unless index

    @entries[index] = Entry.new(
      id: id,
      title: title.nil? ? @entries[index].title : title.to_s,
      status: status.nil? ? @entries[index].status : validate_status(status)
    )
    @entries[index]
  end
  notify
  entry
end