
    Xj(                       U d Z ddlmZ ddlZddlZddlZddlZddlZddlmZm	Z	 ddl
mZ  ee      j                         j                  j                  Zedz  dz  ZdZd	Zh d
Z ej(                         Zg aded<   dad dZd!dZd!dZd"dZd#d$dZd%dZd&dZd'dZ d(dZ!d)dZ"d*dZ#d%dZ$d+d,dZ%d"dZ&d-d.dZ'd/dZ(d/dZ)d0d1dZ*y)2u  Durable task ledger — the trail every background task leaves behind.

WHY THIS EXISTS (2026-07-16, Ahmed's call): a background worker used to live
only in RAM. If Jarvis crashed or restarted mid-task, the task — and any
finding it had produced — vanished silently. And when Ahmed later asked "did
you do X?" / "what happened to X?", the master had nothing durable to consult,
so it either guessed or shrugged. Both are wrong.

This ledger records EVERY dispatched task to disk the moment it starts, updates
it tool-by-tool as it runs, and finalizes it on done/fail/kill — so:

  1. RECOVERY — on boot, any task still marked "running" is stale (the process
     that owned it died); we relabel it "interrupted" so the trail shows
     exactly where things stopped.
  2. RECALL — the master can query this ledger ("did you send that email?")
     and answer the TRUE state (done at 4:12 / still running / failed because
     the Gmail call errored / interrupted by a restart) instead of guessing.
  3. CROSS-DEVICE MEMORY — on completion the pool also drops a milestone
     checkpoint into jarvis-memory (fire_save), so "what happened to X?" works
     days later from any device, not just this process's lifetime.

Storage is a single JSON file (control/task-ledger.json) rewritten atomically
on every change — the task volume is a handful per hour, so this is trivially
cheap and dead simple to reason about. Kept to the last MAX_TASKS records.

The module is self-contained: it imports nothing from the rest of the voice
package at import time (memory_control is lazy-imported inside the checkpoint
helper), so it can be used from the pool, the engine, or a plain thread.
    )annotationsN)datetimetimezone)Pathcontrolztask-ledger.json      >   donefailedkilledinterrupted
list[dict]_tasksFc                 h    t        j                  t        j                        j	                  d      S )Nseconds)timespec)r   nowr   utc	isoformat     ?/Users/ahmed/devFolder/Ultron/claude-voice/voice/task_ledger.py_nowr   7   s#    <<%///CCr   c                 l   	 t         j                  j                  d       t         j                  d      } | j	                  t        j                  t        t         d dd             t        j                  | t                y# t        $ r%}t        dt        |      dd	  d
       Y d}~yd}~ww xY w)z0Write the ledger atomically. Caller holds _lock.T)exist_okz	.json.tmpNFr   )ensure_asciiindentz#       [task-ledger: write failed: x   ])LEDGERparentmkdirwith_suffix
write_textjsondumpsr   	MAX_TASKSosreplace	Exceptionprintstr)tmpes     r   _persist_lockedr/   ;   s    ET*  -tzz&)"5E)*, 	-


3 E3CF4CL>CDDEs   BB 	B3B..B3c                 x   t         5  t        r
	 ddd       y	 t        j                         rEt	        j
                  t        j                         xs d      } t        | t              r
| t         d a
daddd       y# t        $ r'}t        dt        |      dd  d       g a
Y d}~6d}~ww xY w# 1 sw Y   yxY w)z9Read the ledger from disk into memory. Call once at boot.Nz[]z"       [task-ledger: load failed: r   r   T)_lock_loadedr    is_filer%   loads	read_text
isinstancelistr'   r   r*   r+   r,   )datar.   s     r   loadr9   G   s     
 
	~~zz&"2"2"4"<=dD)!9*+.F  
  	6s1vds|nAFGF	 
s5   B0AA=2B0=	B-B(#B0(B--B00B9c                     g } t         5  t        D ]G  }|j                  d      dv sd|d<   t               |d<   d|d<   | j	                  t        |             I | r
t                ddd       | S # 1 sw Y   | S xY w)zBoot recovery: any task still 'running'/'queued' from a PRIOR process
    can't be alive now (we just started), so mark it 'interrupted'. Returns the
    records that were interrupted so the caller can proactively tell Ahmed.status)runningqueuedr   endedz1interrupted by a restart/crash before it finishederrorN)r1   r   getr   appenddictr/   )r   ts     r   recover_stalerD   X   s|     !K	AuuX"77+(!V'
P'
""47+   
  
 s   A0?A00A:c                   dt        j                         j                  dd z   }|| |xs dj                         dd |xs dj                         dd |dg ddt	               t	               ddd	}t
        5  t        j                  |       t        t              t        kD  rt        dt         = t                ddd       |S # 1 sw Y   |S xY w)
zBRegister a freshly-dispatched task. Returns its durable ledger id.t_N
    r      r<   F)idworkertitletasktierr;   stepsresultr?   createdstartedr>   checkpointed)uuiduuid4hexstripr   r1   r   rA   lenr'   r/   )rK   rL   rM   rN   tidrecs         r   creater[   i   s    
!!#2&
&C+2$$&t,""$Tc*66C 
cv;"{
{# 
 J 
 Js   5ACCc                \    t        t              D ]  }|j                  d      | k(  s|c S  y )NrJ   )reversedr   r@   rY   rC   s     r   _find_lockedr_      s+    f55;#H  r   c                   | r|syt         5  t        |       }||j                  d      t        v r
	 ddd       y|j	                  dg       j                  t               |dd d       t        |d         t        kD  r|d   dd |d   t        dz
   d z   |d<   |dd |d<   t                ddd       y# 1 sw Y   yxY w)zBAppend a live tool-step note (what the worker is doing right now).Nr;   rO      )rC   note   	last_step)
r1   r_   r@   	_TERMINAL
setdefaultrA   r   rX   	MAX_STEPSr/   )rY   rb   rC   s      r   steprh      s    d	9h94 
 	
Wb!((tvtDSz)JKqz?Y&7BQ!G*y1}5E5F*GGAgJds+ 
s   &B6A2B66B?c                    t         5  t        |       }|
	 ddd       yd|d<   |xs dj                         dd |d<   t               |d<   t	                t        |      cddd       S # 1 sw Y   yxY w)zIMark a task done with its result. Returns the record (for checkpointing).Nr
   r;   rH   i  rP   r>   r1   r_   rW   r   r/   rB   )rY   rP   rC   s      r   finishrk      sh    	9 
 (|**,Ud3(V'
Aw 
   A)A A))A2c                    t         5  t        |       }|
	 ddd       yd|d<   |xs dj                         dd |d<   t               |d<   t	                t        |      cddd       S # 1 sw Y   yxY w)z7Mark a task failed with the reason. Returns the record.Nr   r;   rH   rI   r?   r>   rj   )rY   r?   rC   s      r   failrn      sh    	9 
 (kr((*4C0'
V'
Aw 
rl   c                (   ddddddj                  ||      }t        5  t        |       }||j                  d      t        v r
	 ddd       y||d<   t	               |d<   |dk(  rd	|d
<   n
|dk(  rd|d
<   t                ddd       y# 1 sw Y   yxY w)u   Set a NON-result terminal status (killed/interrupted). Never downgrades a
    task that already finished (done/failed) — those keep their real outcome.
    Accepts the pool's status words and normalizes them.r   r   r
   r   )r   stoppedr   	completedr   Nr;   r>   zkilled on requestr?   zinterrupted before it finished)r@   r1   r_   re   r   r/   )rY   r;   normrC   s       r   markrs      s     =(v  #FF 3 	 
9h94 
 (V'
8,AgJ]"9AgJ 
s   &B1BBc                r    t         5  t        |       }|d|d<   t                d d d        y # 1 sw Y   y xY w)NTrS   )r1   r_   r/   r^   s     r   mark_checkpointedru      s.    	= $An	 
s   -6c                n    t         5  t        |       }|rt        |      nd cd d d        S # 1 sw Y   y xY w)N)r1   r_   rB   r^   s     r   r@   r@      s$    	tAw 
s   +4c                    t         5  t        |  d  D cg c]  }t        |       c}d d d   cd d d        S c c}w # 1 sw Y   y xY w)N)r1   r   rB   )nrC   s     r   recentrz      s>    	!'-AQ-dd3 
- 
s   =8==Ac                     t         5  t        D  cg c]"  } | j                  d      dk(  st        |       $ c} d d d   cd d d        S c c} w # 1 sw Y   y xY w)Nr;   r<   rx   )r1   r   r@   rB   )rC   s    r   
open_tasksr|      sE    	!'HA155?i+GQH2N 
H 
s    	AA	A	A	AAc                   | xs dj                         j                         }t        5  t        t              }ddd       |s#| d D cg c]  }t        |       c}ddd   S g }t              D ]  }dj                  |j                  dd      |j                  dd      |j                  dd      |j                  dd      dj                  d	 |j                  d
g       D              g      j                         }||v r|j                  t        |             t        |      |k\  s |S  |S # 1 sw Y   xY wc c}w )z|Case-insensitive substring search over title/task/result/steps, newest
    first. Empty query returns the most recent tasks.rH   Nrx    rL   rM   rP   r?   c              3  @   K   | ]  }|j                  d d        yw)rb   rH   N)r@   ).0ss     r   	<genexpr>zfind.<locals>.<genexpr>   s     C0B1QUU62&0Bs   rO   )rW   lowerr1   r7   r   rB   r]   joinr@   rA   rX   )querylimitqpoolrC   hitshays          r   findr      s"    
"##%A	F| 
!%ufg/AQ/"55Dd^hhEE'2fb 115523FEE'2HHCgr0BCC
  57	 	
 8KKQ t9K  K 
 0s   D3D?3D<c                   	 t        j                  | j                  d      xs | j                  d            }| j                  d      rt        j                  | d         n"t        j                  t        j
                        }t        ||z
  j                               }|dk\  r|dz   d|dz  ddS | dS # t        $ r Y yw xY w)	NrR   rQ   r>   <   m02dr   ?)	r   fromisoformatr@   r   r   r   inttotal_secondsr*   )rZ   startendr   s       r   _ager      s    &&swwy'9'OSWWY=OP7:www7Gx%%c'l3LL. 	u++-.-."W!r'!AF3<q)AQCq'A s   B2B: 5B: :	CCc           	     b   | j                  dd      }| j                  dd      }| j                  d      xs | j                  d      xs ddd	 j                  d
d      }|dk(  r^| j                  d      r4| j                  dd      j                         j                         d   dd nd}d| dt	        |        d| d| S |dk(  r%d| dt	        |        d| j                  dd       dS |dk(  rd| d| d| j                  dd       S |dk(  r	d| d | dS |d!k(  r	d| d"| dS d| d#| S )$uH   One human line describing a task's outcome — for recall + checkpoints.r;   r   rL   rM   r>   rR   rH   N   Tr~   r
   rP   r   r   finished'u   ' — DONE (z, ended z): r<   u   ' — STILL RUNNING (z so far; last step: rd   u   …)r   u   ' — FAILED (r?   zunknown errorr   u   ' — KILLED on request (r   u1   ' — INTERRUPTED by a restart before finishing (u   ' — )r@   r)   rW   
splitlinesr   )rZ   strL   whenbodys        r   	summarizer     sq   	3	BGGGV$EGGG8	 28b#2>FFsCPD	V|FIgghFWswwx$**,779!<TcB]g5'd3i[c$HH	YE7/S	{:N77;./q2 	3	X~5'vS/1R0STT	X~5'24&::	]5'J4&PQRRugVB4  r   c                n    t        | |      }|sd| rd|  dz   S dz   S dj                  d |D              S )z@A ready-to-read block answering 'did you do X?' from the ledger.)r   z$No matching task found in the ledgerz for 'z'..
c              3  8   K   | ]  }d t        |      z     yw)z- N)r   )r   hs     r   r   zrecall_text.<locals>.<genexpr>   s     7$QTIaL($s   )r   r   )r   r   r   s      r   recall_textr     sN    U#D6).VE7"%9 	:479 	:997$777r   )returnr,   )r   None)r   r   )rK   )
rK   r,   rL   r,   rM   r,   rN   r,   r   r,   )rY   r,   r   dict | None)rY   r,   rb   r,   r   r   )rY   r,   rP   r,   r   r   )rY   r,   r?   r,   r   r   )rY   r,   r;   r,   r   r   )rY   r,   r   r   )   )ry   r   r   r   )   )r   r,   r   r   r   r   )rZ   rB   r   r,   )rH   r   )r   r,   r   r   r   r,   )+__doc__
__future__r   r%   r(   	threadingtimerT   r   r   pathlibr   __file__resolver!   PROJECTr    r'   rg   re   Lockr1   r   __annotations__r2   r   r/   r9   rD   r[   r_   rh   rk   rn   rs   ru   r@   rz   r|   r   r   r   r   r   r   r   <module>r      s   < #  	    ' 
x.
 
 
"
)
)
0
0	9	1	1		 8		
 
D	E""6 

(&4
O
,!(8r   