Module: Clacky::Agent::TimeMachine
- Included in:
- Clacky::Agent
- Defined in:
- lib/clacky/agent/time_machine.rb
Overview
Time Machine module for task history management with undo/redo support Stores complete file snapshots (AFTER state) to support message compression
Instance Method Summary collapse
-
#active_messages ⇒ Object
Filter messages to only show tasks up to active_task_id.
-
#get_child_tasks(task_id) ⇒ Object
Get children of a task (for branch detection).
-
#get_task_history(limit: 10) ⇒ Array<Hash>
Get task history with summaries for UI display.
-
#restore_to_task_state(task_id) ⇒ Object
Restore files to the state at given task Made public for testing.
-
#save_modified_files_snapshot(modified_files) ⇒ Object
Save snapshots of modified files (AFTER state) Made public for testing.
-
#start_new_task ⇒ Object
Start a new task and establish parent relationship Made public for testing.
-
#switch_to_task(target_task_id) ⇒ Object
Switch to specific task (for redo or branch switching).
-
#undo_last_task ⇒ Object
Undo to parent task.
Instance Method Details
#active_messages ⇒ Object
Filter messages to only show tasks up to active_task_id. This hides “future” messages when user has undone. Returns API-ready array (strips internal fields + handles orphaned tool_calls). Made public for testing
97 98 99 100 101 102 103 |
# File 'lib/clacky/agent/time_machine.rb', line 97 def return @history.to_api if @active_task_id == @current_task_id @history.for_task(@active_task_id).map do |msg| msg.reject { |k, _| MessageHistory::INTERNAL_FIELDS.include?(k) } end end |
#get_child_tasks(task_id) ⇒ Object
Get children of a task (for branch detection)
137 138 139 |
# File 'lib/clacky/agent/time_machine.rb', line 137 def get_child_tasks(task_id) @task_parents.select { |_, parent| parent == task_id }.keys end |
#get_task_history(limit: 10) ⇒ Array<Hash>
Get task history with summaries for UI display
144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 |
# File 'lib/clacky/agent/time_machine.rb', line 144 def get_task_history(limit: 10) return [] if @current_task_id == 0 tasks = [] (1..@current_task_id).to_a.reverse.take(limit).reverse.each do |task_id| # Find first user message for this task first_user_msg = @history.to_a.find do |msg| msg[:task_id] == task_id && msg[:role] == "user" end summary = if first_user_msg content = (first_user_msg[:content]) # Truncate to 60 characters (including "...") content.length > 60 ? "#{content[0...57]}..." : content else "Task #{task_id}" end # Determine task status status = if task_id == @active_task_id :current elsif task_id < @active_task_id :past else :future end # Check if task has branches (multiple children) children = get_child_tasks(task_id) has_branches = children.length > 1 tasks << { task_id: task_id, summary: summary, status: status, has_branches: has_branches } end tasks end |
#restore_to_task_state(task_id) ⇒ Object
Restore files to the state at given task Made public for testing
59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 |
# File 'lib/clacky/agent/time_machine.rb', line 59 def restore_to_task_state(task_id) # Collect all modified files from task 1 to target task files_to_restore = {} (1..task_id).each do |tid| snapshot_dir = File.join( Dir.home, ".clacky", "snapshots", @session_id, "task-#{tid}" ) next unless Dir.exist?(snapshot_dir) Dir.glob(File.join(snapshot_dir, "**", "*")).each do |snapshot_file| next if File.directory?(snapshot_file) relative_path = snapshot_file.sub(snapshot_dir + "/", "") files_to_restore[relative_path] = snapshot_file end end # Restore files files_to_restore.each do |relative_path, snapshot_file| target_file = File.join(@working_dir, relative_path) FileUtils.mkdir_p(File.dirname(target_file)) FileUtils.cp(snapshot_file, target_file) end rescue StandardError => e # Silently handle errors in tests raise end |
#save_modified_files_snapshot(modified_files) ⇒ Object
Save snapshots of modified files (AFTER state) Made public for testing
29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 |
# File 'lib/clacky/agent/time_machine.rb', line 29 def save_modified_files_snapshot(modified_files) return if modified_files.nil? || modified_files.empty? snapshot_dir = File.join( Dir.home, ".clacky", "snapshots", @session_id, "task-#{@current_task_id}" ) FileUtils.mkdir_p(snapshot_dir) modified_files.each do |file_path| next unless File.exist?(file_path) # Save file content to snapshot relative_path = file_path.start_with?(@working_dir) ? file_path.sub(@working_dir + "/", "") : File.basename(file_path) snapshot_file = File.join(snapshot_dir, relative_path) FileUtils.mkdir_p(File.dirname(snapshot_file)) FileUtils.cp(file_path, snapshot_file) end rescue StandardError => e # Silently handle errors in tests end |
#start_new_task ⇒ Object
Start a new task and establish parent relationship Made public for testing
17 18 19 20 21 22 23 24 |
# File 'lib/clacky/agent/time_machine.rb', line 17 def start_new_task parent_id = @active_task_id @current_task_id += 1 @active_task_id = @current_task_id @task_parents[@current_task_id] = parent_id @current_task_id end |
#switch_to_task(target_task_id) ⇒ Object
Switch to specific task (for redo or branch switching)
121 122 123 124 125 126 127 128 129 130 131 132 133 134 |
# File 'lib/clacky/agent/time_machine.rb', line 121 def switch_to_task(target_task_id) if target_task_id > @current_task_id || target_task_id < 1 return { success: false, message: "Invalid task ID: #{target_task_id}" } end restore_to_task_state(target_task_id) @active_task_id = target_task_id { success: true, message: "⏩ Switched to task #{target_task_id}", task_id: target_task_id } end |
#undo_last_task ⇒ Object
Undo to parent task
106 107 108 109 110 111 112 113 114 115 116 117 118 |
# File 'lib/clacky/agent/time_machine.rb', line 106 def undo_last_task parent_id = @task_parents[@active_task_id] return { success: false, message: "Already at root task" } if parent_id.nil? || parent_id == 0 restore_to_task_state(parent_id) @active_task_id = parent_id { success: true, message: "⏪ Undone to task #{parent_id}", task_id: parent_id } end |